Merge branch 'master' into feature/new-ui

This commit is contained in:
Salvatore Giordano
2020-10-05 10:42:01 +02:00
23 changed files with 886 additions and 567 deletions
+21 -26
View File
@@ -6,9 +6,6 @@ on:
- master
- feature/new-ui
env:
flutter_version: "1.20.2"
jobs:
build_and_deploy_ios:
runs-on: [macos-latest]
@@ -28,7 +25,7 @@ jobs:
bundle install
- uses: subosito/flutter-action@v1.3.2
with:
flutter-version: ${{ env.flutter_version }}
channel: 'stable'
- name: Install firebase-tools
run: npm install -g firebase-tools
- name: Flutter setup
@@ -47,25 +44,23 @@ jobs:
build_and_deploy_android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v1
- uses: actions/setup-java@v1
with:
java-version: '12.x'
- uses: subosito/flutter-action@v1.3.2
with:
flutter-version: ${{ env.flutter_version }}
- run: |
cd example
flutter pub get
- name: Build
run: |
cd example
flutter build apk
- name: Deploy
uses: wzieba/Firebase-Distribution-Github-Action@v1.2.1
with:
appId: ${{secrets.FIREBASE_ANDROID_APPID}}
token: ${{secrets.FIREBASE_TOKEN}}
groups: stream-testers
file: example/build/app/outputs/apk/release/app-release.apk
- uses: actions/checkout@v2
- name: Flutter action
uses: subosito/flutter-action@v1.3.2
with:
channel: 'stable'
- name: Get dependencies
run: flutter pub get
- name: Coverage fix
run: |
file=test/coverage_helper_test.dart
echo "// Helper file to make coverage work for all dart files\n" > $file
echo "// ignore_for_file: unused_import" >> $file
find lib -name '*.dart' | grep -e '[^g]\.dart' | grep -v '_html.dart' | cut -c4- | awk -v package=stream_chat_flutter '{printf "import '\''package:%s%s'\'';\n", package, $1}' >> $file
echo "" >> $file
echo "void main(){}" >> $file
cat $file
- name: Run tests
run: flutter test --coverage
- name: Codecov
run: bash <(curl -s https://codecov.io/bash) -c -t ${{ secrets.CODECOV_TOKEN }} -f coverage/lcov.info -F flutter_tool
+42
View File
@@ -1,3 +1,45 @@
## 0.2.8+3
- Add simple example of channel creation in sample app
- Add back button to the full-screen video view
- Update llc version
## 0.2.8+2
- Add back button to the full-screen view
## 0.2.8+1
- Update LLC dependency
- Update file_picker dependency
## 0.2.8
- Update LLC dependency
## 0.2.7+2
- Fix channellistview loading when client is not initialized
- Update LLC dependency
## 0.2.7
- Update llc dependency
- Fixed a bug that made the SDK crash if it went to background while not connected
## 0.2.6+1
- Update llc dependency
## 0.2.6
- Add `pullToRefresh` property to `ChannelListView`
- Add `onLinkTap` to `MessageWidget`
## 0.2.5
- Implement `didUpdateWidget` in `ChannelListView` to react to setState
## 0.2.4
- Update llc dependency
+2
View File
@@ -10,6 +10,8 @@
[![Pub](https://img.shields.io/pub/v/stream_chat_flutter.svg)](https://pub.dartlang.org/packages/stream_chat_flutter)
![](https://img.shields.io/badge/platform-flutter%20%7C%20flutter%20web-ff69b4.svg?style=flat-square)
[![Gitter](https://badges.gitter.im/GetStream/stream-chat-flutter.svg)](https://gitter.im/GetStream/stream-chat-flutter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![CI](https://github.com/GetStream/stream-chat-flutter/workflows/CI/badge.svg?branch=master)
[![codecov](https://codecov.io/gh/GetStream/stream-chat-flutter/branch/master/graph/badge.svg)](https://codecov.io/gh/GetStream/stream-chat-flutter)
<img align="right" src="https://getstream.imgix.net/images/ios-chat-tutorial/iphone_chat_art@1x.png?auto=format,enhance" width="50%" />
+1
View File
@@ -0,0 +1 @@
13ecb9b157bb3e3d1ca2efb15c813a88
@@ -7,7 +7,7 @@
//
import UserNotifications
import StreamChatClient
//import StreamChatClient
final class NotificationService: UNNotificationServiceExtension {
@@ -26,29 +26,29 @@ final class NotificationService: UNNotificationServiceExtension {
return
}
Client.config = .init(apiKey: apiKey, logOptions: .error)
Client.shared.set(user: User(id: userId), token: token) { res in
guard res.isConnected else {
return
}
Client.shared.message(withId: messageId) { [weak self] res in
if let message = res.value?.message,
let channel = res.value?.channel {
let messageWrapper = MessageWrapper(channel: channel, message: message)
if let encodedData = try? JSONEncoder.stream.encode(messageWrapper),
let encodedString = String(data: encodedData, encoding: .utf8) {
let storedMessages = sharedDefaults.stringArray(forKey: "messageQueue") ?? []
sharedDefaults.setValue(storedMessages + [encodedString], forKey: "messageQueue")
// Modify the notification content here...
self?.bestAttemptContent?.title = "[modified] \(self?.bestAttemptContent?.title ?? "<NoContent>")"
contentHandler(self?.bestAttemptContent ?? request.content)
}
Client.shared.disconnect()
}
}
}
// Client.config = .init(apiKey: apiKey, logOptions: .error)
// Client.shared.set(user: User(id: userId), token: token) { res in
// guard res.isConnected else {
// return
// }
//
// Client.shared.message(withId: messageId) { [weak self] res in
// if let message = res.value?.message,
// let channel = res.value?.channel {
// let messageWrapper = MessageWrapper(channel: channel, message: message)
// if let encodedData = try? JSONEncoder.stream.encode(messageWrapper),
// let encodedString = String(data: encodedData, encoding: .utf8) {
// let storedMessages = sharedDefaults.stringArray(forKey: "messageQueue") ?? []
// sharedDefaults.setValue(storedMessages + [encodedString], forKey: "messageQueue")
//
// // Modify the notification content here...
// self?.bestAttemptContent?.title = "[modified] \(self?.bestAttemptContent?.title ?? "<NoContent>")"
// contentHandler(self?.bestAttemptContent ?? request.content)
// }
// Client.shared.disconnect()
// }
// }
// }
}
override func serviceExtensionTimeWillExpire() {
@@ -58,125 +58,125 @@ final class NotificationService: UNNotificationServiceExtension {
}
}
public struct MessageWrapper: Encodable {
private enum CodingKeys: String, CodingKey {
case id
case channel
case type
case user
case created = "created_at"
case updated = "updated_at"
case text
case command
case args
case attachments
case parentId = "parent_id"
case showReplyInChannel = "show_in_channel"
case mentionedUsers = "mentioned_users"
}
init(channel: Channel, message: Message) {
id = message.id
type = message.type
user = message.user
created = message.created
updated = message.updated
text = message.text
command = message.command
args = message.args
attachments = message.attachments
parentId = message.parentId
showReplyInChannel = message.showReplyInChannel
mentionedUsers = message.mentionedUsers
extraData = message.extraData
self.channel = ChannelWrapper(channel: channel)
}
/// A message id.
public let id: String
/// The channel cid.
public let channel: ChannelWrapper?
/// A message type (see `MessageType`).
public let type: MessageType
/// A user (see `User`).
public let user: User
/// A created date.
public let created: Date
/// A updated date.
public let updated: Date
/// A text.
public let text: String
/// A used command name.
public let command: String?
/// A used command args.
public let args: String?
/// Attachments (see `Attachment`).
public let attachments: [Attachment]
/// A parent message id.
public let parentId: String?
/// Check if this reply message needs to show in the channel.
public let showReplyInChannel: Bool
/// Mentioned users (see `User`).
public let mentionedUsers: [User]
/// An extra data for the message.
public let extraData: Codable?
}
public struct ChannelWrapper: Encodable {
/// Coding keys for the encoding.
private enum CodingKeys: String, CodingKey {
case id
case cid
case type
case name
case imageURL = "image"
case members
case lastMessageDate = "last_message_at"
case createdBy = "created_by"
case created = "created_at"
case deleted = "deleted_at"
case frozen
}
init(channel: Channel) {
id = channel.id
cid = channel.cid
type = channel.type
name = channel.name
imageURL = channel.imageURL
lastMessageDate = channel.lastMessageDate
created = channel.created
deleted = channel.deleted
createdBy = channel.createdBy
config = channel.config
frozen = channel.frozen
extraData = channel.extraData
}
/// A channel id.
public let id: String
/// A channel type + id.
public let cid: ChannelId
/// A channel type.
public let type: ChannelType
/// A channel name.
public let name: String?
/// An image of the channel.
public let imageURL: URL?
/// The last message date.
public let lastMessageDate: Date?
/// A channel created date.
public let created: Date
/// A channel deleted date.
public let deleted: Date?
/// A creator of the channel.
public let createdBy: User?
/// A config.
public let config: Channel.Config
/// Checks if the channel is frozen.
public let frozen: Bool
/// A list of user ids of the channel members.
public let members = Set<Member>()
/// An extra data for the channel.
public let extraData: Codable?
}
//public struct MessageWrapper: Encodable {
// private enum CodingKeys: String, CodingKey {
// case id
// case channel
// case type
// case user
// case created = "created_at"
// case updated = "updated_at"
// case text
// case command
// case args
// case attachments
// case parentId = "parent_id"
// case showReplyInChannel = "show_in_channel"
// case mentionedUsers = "mentioned_users"
// }
//
// init(channel: Channel, message: Message) {
// id = message.id
// type = message.type
// user = message.user
// created = message.created
// updated = message.updated
// text = message.text
// command = message.command
// args = message.args
// attachments = message.attachments
// parentId = message.parentId
// showReplyInChannel = message.showReplyInChannel
// mentionedUsers = message.mentionedUsers
// extraData = message.extraData
// self.channel = ChannelWrapper(channel: channel)
// }
//
// /// A message id.
// public let id: String
// /// The channel cid.
// public let channel: ChannelWrapper?
// /// A message type (see `MessageType`).
// public let type: MessageType
// /// A user (see `User`).
// public let user: User
// /// A created date.
// public let created: Date
// /// A updated date.
// public let updated: Date
// /// A text.
// public let text: String
// /// A used command name.
// public let command: String?
// /// A used command args.
// public let args: String?
// /// Attachments (see `Attachment`).
// public let attachments: [Attachment]
// /// A parent message id.
// public let parentId: String?
// /// Check if this reply message needs to show in the channel.
// public let showReplyInChannel: Bool
// /// Mentioned users (see `User`).
// public let mentionedUsers: [User]
// /// An extra data for the message.
// public let extraData: Codable?
//}
//
//public struct ChannelWrapper: Encodable {
// /// Coding keys for the encoding.
// private enum CodingKeys: String, CodingKey {
// case id
// case cid
// case type
// case name
// case imageURL = "image"
// case members
// case lastMessageDate = "last_message_at"
// case createdBy = "created_by"
// case created = "created_at"
// case deleted = "deleted_at"
// case frozen
// }
//
// init(channel: Channel) {
// id = channel.id
// cid = channel.cid
// type = channel.type
// name = channel.name
// imageURL = channel.imageURL
// lastMessageDate = channel.lastMessageDate
// created = channel.created
// deleted = channel.deleted
// createdBy = channel.createdBy
// config = channel.config
// frozen = channel.frozen
// extraData = channel.extraData
// }
//
// /// A channel id.
// public let id: String
// /// A channel type + id.
// public let cid: ChannelId
// /// A channel type.
// public let type: ChannelType
// /// A channel name.
// public let name: String?
// /// An image of the channel.
// public let imageURL: URL?
// /// The last message date.
// public let lastMessageDate: Date?
// /// A channel created date.
// public let created: Date
// /// A channel deleted date.
// public let deleted: Date?
// /// A creator of the channel.
// public let createdBy: User?
// /// A config.
// public let config: Channel.Config
// /// Checks if the channel is frozen.
// public let frozen: Bool
// /// A list of user ids of the channel members.
// public let members = Set<Member>()
// /// An extra data for the channel.
// public let extraData: Codable?
//}
+16 -63
View File
@@ -10,81 +10,34 @@ project 'Runner', {
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
generated_key_values
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
pod 'StreamChatClient', '~> 2.0.0'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
install! 'cocoapods', :disable_input_output_paths => true
pod 'StreamChatClient'
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
flutter_additional_ios_build_settings(target)
end
end
+106 -164
View File
@@ -33,53 +33,49 @@ PODS:
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Firebase/Core (6.28.1):
- Firebase/CoreOnly (6.26.0):
- FirebaseCore (= 6.7.2)
- Firebase/Messaging (6.26.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 6.6.2)
- Firebase/CoreOnly (6.28.1):
- FirebaseCore (= 6.9.1)
- Firebase/Messaging (6.28.1):
- Firebase/CoreOnly
- FirebaseMessaging (~> 4.6.0)
- firebase_messaging (0.0.1):
- Firebase/Core
- Firebase/Messaging
- FirebaseMessaging (~> 4.4.1)
- firebase_core (0.5.0):
- Firebase/CoreOnly (~> 6.26.0)
- Flutter
- FirebaseAnalytics (6.6.2):
- FirebaseCore (~> 6.8)
- FirebaseInstallations (~> 1.4)
- GoogleAppMeasurement (= 6.6.2)
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 1.30905.0)
- FirebaseCore (6.9.1):
- firebase_messaging (7.0.2):
- Firebase/CoreOnly (~> 6.26.0)
- Firebase/Messaging (~> 6.26.0)
- firebase_core
- Flutter
- FirebaseAnalyticsInterop (1.5.0)
- FirebaseCore (6.7.2):
- FirebaseCoreDiagnostics (~> 1.3)
- FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- FirebaseCoreDiagnostics (1.7.0):
- GoogleDataTransport (~> 7.4)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- FirebaseCoreDiagnostics (1.5.0):
- GoogleDataTransport (~> 7.0)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- nanopb (~> 1.30905.0)
- FirebaseInstallations (1.5.0):
- FirebaseCore (~> 6.8)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/UserDefaults (~> 6.7)
- nanopb (~> 1.30906.0)
- FirebaseCoreDiagnosticsInterop (1.2.0)
- FirebaseInstallations (1.3.0):
- FirebaseCore (~> 6.6)
- GoogleUtilities/Environment (~> 6.6)
- GoogleUtilities/UserDefaults (~> 6.6)
- PromisesObjC (~> 1.2)
- FirebaseInstanceID (4.5.0):
- FirebaseCore (~> 6.8)
- FirebaseInstanceID (4.3.4):
- FirebaseCore (~> 6.6)
- FirebaseInstallations (~> 1.0)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/UserDefaults (~> 6.7)
- FirebaseMessaging (4.6.0):
- FirebaseCore (~> 6.8)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/UserDefaults (~> 6.5)
- FirebaseMessaging (4.4.1):
- FirebaseAnalyticsInterop (~> 1.5)
- FirebaseCore (~> 6.6)
- FirebaseInstanceID (~> 4.3)
- GoogleUtilities/AppDelegateSwizzler (~> 6.7)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Reachability (~> 6.7)
- GoogleUtilities/UserDefaults (~> 6.7)
- GoogleUtilities/AppDelegateSwizzler (~> 6.5)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Reachability (~> 6.5)
- GoogleUtilities/UserDefaults (~> 6.5)
- Protobuf (>= 3.9.2, ~> 3.9)
- Flutter (1.0.0)
- flutter_apns (0.0.1):
@@ -88,117 +84,92 @@ PODS:
- Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_plugin_android_lifecycle (0.0.1):
- Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- GoogleAppMeasurement (6.6.2):
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 1.30905.0)
- GoogleDataTransport (7.0.0):
- nanopb (~> 1.30905.0)
- GoogleUtilities/AppDelegateSwizzler (6.7.1):
- GoogleDataTransport (7.4.0):
- nanopb (~> 1.30906.0)
- GoogleUtilities/AppDelegateSwizzler (6.7.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (6.7.1):
- GoogleUtilities/Environment (6.7.2):
- PromisesObjC (~> 1.2)
- GoogleUtilities/Logger (6.7.1):
- GoogleUtilities/Logger (6.7.2):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (6.7.1):
- GoogleUtilities/Logger
- GoogleUtilities/Network (6.7.1):
- GoogleUtilities/Network (6.7.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (6.7.1)"
- GoogleUtilities/Reachability (6.7.1):
- "GoogleUtilities/NSData+zlib (6.7.2)"
- GoogleUtilities/Reachability (6.7.2):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (6.7.1):
- GoogleUtilities/UserDefaults (6.7.2):
- GoogleUtilities/Logger
- GzipSwift (5.1.1)
- image_picker (0.0.1):
- Flutter
- moor_ffi (0.0.1):
- Flutter
- nanopb (1.30905.0):
- nanopb/decode (= 1.30905.0)
- nanopb/encode (= 1.30905.0)
- nanopb/decode (1.30905.0)
- nanopb/encode (1.30905.0)
- nanopb (1.30906.0):
- nanopb/decode (= 1.30906.0)
- nanopb/encode (= 1.30906.0)
- nanopb/decode (1.30906.0)
- nanopb/encode (1.30906.0)
- path_provider (0.0.1):
- Flutter
- path_provider_linux (0.0.1):
- Flutter
- path_provider_macos (0.0.1):
- Flutter
- PromisesObjC (1.2.9)
- Protobuf (3.12.0)
- ReachabilitySwift (5.0.0)
- SDWebImage (5.8.4):
- SDWebImage/Core (= 5.8.4)
- SDWebImage/Core (5.8.4)
- PromisesObjC (1.2.10)
- Protobuf (3.13.0)
- SDWebImage (5.9.2):
- SDWebImage/Core (= 5.9.2)
- SDWebImage/Core (5.9.2)
- shared_preferences (0.0.1):
- Flutter
- shared_preferences_linux (0.0.1):
- Flutter
- shared_preferences_macos (0.0.1):
- Flutter
- shared_preferences_web (0.0.1):
- Flutter
- sqflite (0.0.1):
- Flutter
- FMDB (~> 2.7.2)
- Starscream (3.1.1)
- StreamChatClient (2.0.1):
- GzipSwift (~> 5.1)
- ReachabilitySwift (~> 5.0)
- Starscream (~> 3.1)
- sqlite3 (3.32.3):
- sqlite3/common (= 3.32.3)
- sqlite3/common (3.32.3)
- sqlite3/fts5 (3.32.3):
- sqlite3/common
- sqlite3/json1 (3.32.3):
- sqlite3/common
- sqlite3/perf-threadsafe (3.32.3):
- sqlite3/common
- sqlite3/rtree (3.32.3):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- sqlite3 (~> 3.32.3)
- sqlite3/fts5
- sqlite3/json1
- sqlite3/perf-threadsafe
- sqlite3/rtree
- Starscream (4.0.4)
- StreamChatClient (2.4.0):
- Starscream (~> 4.0)
- SwiftyGif (5.3.0)
- url_launcher (0.0.1):
- Flutter
- url_launcher_linux (0.0.1):
- Flutter
- url_launcher_macos (0.0.1):
- Flutter
- url_launcher_web (0.0.1):
- Flutter
- video_player (0.0.1):
- Flutter
- video_player_web (0.0.1):
- Flutter
- wakelock (0.0.1):
- Flutter
DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
- flutter_apns (from `.symlinks/plugins/flutter_apns/ios`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- moor_ffi (from `.symlinks/plugins/moor_ffi/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`)
- path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`)
- shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
- shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- StreamChatClient (~> 2.0.0)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- StreamChatClient
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- url_launcher_linux (from `.symlinks/plugins/url_launcher_linux/ios`)
- url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`)
- url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`)
- video_player (from `.symlinks/plugins/video_player/ios`)
- video_player_web (from `.symlinks/plugins/video_player_web/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
SPEC REPOS:
@@ -206,22 +177,21 @@ SPEC REPOS:
- DKImagePickerController
- DKPhotoGallery
- Firebase
- FirebaseAnalytics
- FirebaseAnalyticsInterop
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseCoreDiagnosticsInterop
- FirebaseInstallations
- FirebaseInstanceID
- FirebaseMessaging
- FMDB
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
- GzipSwift
- nanopb
- PromisesObjC
- Protobuf
- ReachabilitySwift
- SDWebImage
- sqlite3
- Starscream
- StreamChatClient
- SwiftyGif
@@ -229,6 +199,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_messaging:
:path: ".symlinks/plugins/firebase_messaging/ios"
Flutter:
@@ -239,40 +211,20 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_plugin_android_lifecycle:
:path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
moor_ffi:
:path: ".symlinks/plugins/moor_ffi/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
path_provider_linux:
:path: ".symlinks/plugins/path_provider_linux/ios"
path_provider_macos:
:path: ".symlinks/plugins/path_provider_macos/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
shared_preferences_linux:
:path: ".symlinks/plugins/shared_preferences_linux/ios"
shared_preferences_macos:
:path: ".symlinks/plugins/shared_preferences_macos/ios"
shared_preferences_web:
:path: ".symlinks/plugins/shared_preferences_web/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
url_launcher_linux:
:path: ".symlinks/plugins/url_launcher_linux/ios"
url_launcher_macos:
:path: ".symlinks/plugins/url_launcher_macos/ios"
url_launcher_web:
:path: ".symlinks/plugins/url_launcher_web/ios"
video_player:
:path: ".symlinks/plugins/video_player/ios"
video_player_web:
:path: ".symlinks/plugins/video_player_web/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
@@ -280,50 +232,40 @@ SPEC CHECKSUMS:
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Firebase: ed042590caa0029392257529a8003c25ee82bc18
firebase_messaging: 21344b3b3a7d9d325d63a70e3750c0c798fe1e03
FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4
FirebaseCore: 687b8e6a0a4337b898a6326d68254c2f80c143af
FirebaseCoreDiagnostics: 7535fe695737f8c5b350584292a70b7f8ff0357b
FirebaseInstallations: 3c520c951305cbf9ca54eb891ff9e6d1fd384881
FirebaseInstanceID: 358d5cb393d2750a745569ede06827c35aea530b
FirebaseMessaging: bdd4d573eab37ebee29bad4e7c4b0ef18fa1a952
Firebase: 7cf5f9c67f03cb3b606d1d6535286e1080e57eb6
firebase_core: 3134fe79d257d430f163b558caf52a10a87efe8a
firebase_messaging: 2844c37f9ce87c0904b38fe435223161b1a71528
FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae
FirebaseCore: f42e5e5f382cdcf6b617ed737bf6c871a6947b17
FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1
FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850
FirebaseInstallations: 6f5f680e65dc374397a483c32d1799ba822a395b
FirebaseInstanceID: cef67c4967c7cecb56ea65d8acbb4834825c587b
FirebaseMessaging: 29543feb343b09546ab3aa04d008ee8595b43c44
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_apns: f516b118e423fe7c0a38771180549c4d6cb67c2f
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186
flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
GoogleAppMeasurement: 8cd1f289d60e629cf16ab03363b9e89c776b9651
GoogleDataTransport: 8a40cb194ad242b6f6dfe72c14fe40fc67c4dcd7
GoogleUtilities: e121a3867449ce16b0e35ddf1797ea7a389ffdf2
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
GoogleDataTransport: b7f406340a291370045a270c599e53c6fa6ec20f
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09
moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
PromisesObjC: b48e0338dbbac2207e611750777895f7a5811b75
Protobuf: 2793fcd0622a00b546c60e7cbbcc493e043e9bb9
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9
PromisesObjC: b14b1c6b68e306650688599de8a45e49fae81151
Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748
SDWebImage: 0b42b8719ab0c5257177d5894306e8a336b21cbb
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
shared_preferences_linux: afefbfe8d921e207f01ede8b60373d9e3b566b78
shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9
sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0
Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0
StreamChatClient: 91b0f585e7dc92ade58e657daffafb16d485b2a6
sqlite3: 8f7d2078ae27778699a622a94b853285793422a2
sqlite3_flutter_libs: 5651f8ff48e3b44d910863c4ea5916085b1b245f
Starscream: 5178aed56b316f13fa3bc55694e583d35dd414d9
StreamChatClient: 8c83a141e753e45fa096ff56d4b782d59e46f251
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
url_launcher_linux: ac237cb7a8058736e4aae38bdbcc748a4b394cc0
url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313
url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c
video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e
video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7
wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4
PODFILE CHECKSUM: aaaddd91b568e9a010ff3278737eb06bc017bd6e
PODFILE CHECKSUM: eb001256612a59f8f9e4d083ad8b9671e69dd184
COCOAPODS: 1.8.4
+51 -3
View File
@@ -308,9 +308,60 @@
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/Starscream-framework/Starscream.framework",
"${BUILT_PRODUCTS_DIR}/StreamChatClient-framework/StreamChatClient.framework",
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
"${PODS_ROOT}/../Flutter/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework",
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
"${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/flutter_apns/flutter_apns.framework",
"${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework",
"${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework",
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework",
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
"${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework",
"${BUILT_PRODUCTS_DIR}/sqlite3_flutter_libs/sqlite3_flutter_libs.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework",
"${BUILT_PRODUCTS_DIR}/video_player/video_player.framework",
"${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Starscream.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/StreamChatClient.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_apns.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_keyboard_visibility.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3_flutter_libs.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -489,7 +540,6 @@
};
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -573,7 +623,6 @@
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -629,7 +678,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
+194
View File
@@ -110,6 +110,14 @@ class ChannelListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return CreateChannelPage();
}));
},
),
body: ChannelsBloc(
child: ChannelListView(
swipeToAction: true,
@@ -204,3 +212,189 @@ class ThreadPage extends StatelessWidget {
);
}
}
class CreateChannelPage extends StatefulWidget {
@override
_CreateChannelPageState createState() => _CreateChannelPageState();
}
class _CreateChannelPageState extends State<CreateChannelPage> {
final ScrollController _scrollController = ScrollController();
Client client;
List<User> users = [];
List<User> selectedUsers = [];
int offset = 0;
bool loading = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
title: Text(
'Create a channel',
style: Theme.of(context).textTheme.headline6,
),
),
floatingActionButton:
selectedUsers.isNotEmpty ? _buildFAB(context) : SizedBox(),
body: _buildListView(),
);
}
ListView _buildListView() {
return ListView.builder(
controller: _scrollController,
itemBuilder: _itemBuilder,
itemCount: users.length,
);
}
Widget _itemBuilder(context, i) {
final user = users[i];
return ListTile(
onLongPress: () {
_selectUser(user);
},
selected: selectedUsers.contains(user),
onTap: () {
if (selectedUsers.isNotEmpty) {
return _selectUser(user);
}
_createChannel(context, [user]);
},
leading: UserAvatar(
user: user,
),
title: Text(user.name),
);
}
Widget _buildFAB(BuildContext context) {
return FloatingActionButton(
child: Icon(Icons.done),
onPressed: () async {
String name;
if (selectedUsers.length > 1) {
name = await _showEnterNameDialog(context);
if (name?.isNotEmpty != true) {
return;
}
}
_createChannel(context, selectedUsers, name);
},
);
}
Future<String> _showEnterNameDialog(BuildContext context) {
final controller = TextEditingController();
return showDialog(
context: context,
builder: (context) => SimpleDialog(
contentPadding: const EdgeInsets.all(16),
title: Text('Enter a name for the channel'),
children: [
TextField(
controller: controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
ButtonBar(
children: [
FlatButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'),
),
FlatButton(
onPressed: () => Navigator.pop(context, controller.text),
child: Text('Ok'),
),
],
),
],
),
);
}
Future _createChannel(
BuildContext context,
List<User> users, [
String name,
]) async {
final channel = client.channel('messaging', extraData: {
'members': [
client.state.user.id,
...users.map((e) => e.id),
],
if (name != null) 'name': name,
});
await channel.watch();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: ChannelPage(),
channel: channel,
);
},
),
);
}
void _selectUser(User user) {
if (!selectedUsers.contains(user)) {
setState(() {
selectedUsers.add(user);
});
} else {
setState(() {
selectedUsers.remove(user);
});
}
}
@override
void initState() {
super.initState();
client = StreamChat.of(context).client;
_scrollController.addListener(() async {
if (!loading &&
_scrollController.offset >=
_scrollController.position.maxScrollExtent - 100) {
offset += 25;
await _queryUsers();
}
});
_queryUsers();
}
Future<void> _queryUsers() {
loading = true;
return client.queryUsers(
pagination: PaginationParams(
limit: 25,
offset: offset,
),
sort: [
SortOption(
'name',
direction: SortOption.ASC,
),
],
).then((value) {
setState(() {
users = [
...users,
...value.users,
];
});
}).whenComplete(() => loading = false);
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
name: example
description: A new Flutter project.
version: 1.0.13+15
version: 1.0.14+16
environment:
sdk: ">=2.2.2 <3.0.0"
+1 -1
View File
@@ -78,7 +78,7 @@ class ChannelImage extends StatelessWidget {
String image;
if (snapshot.data?.containsKey('image') == true) {
image = snapshot.data['image'];
} else if (channel.state.members.length == 2) {
} else if (channel.state.members?.length == 2) {
final otherMember = channel.state.members
.firstWhere((member) => member.user.id != streamChat.user.id);
return StreamBuilder<User>(
+128 -83
View File
@@ -66,6 +66,7 @@ class ChannelListView extends StatefulWidget {
this.errorBuilder,
this.onImageTap,
this.swipeToAction = false,
this.pullToRefresh = true,
}) : super(key: key);
/// The builder that will be used in case of error
@@ -114,6 +115,9 @@ class ChannelListView extends StatefulWidget {
/// The function called when the image is tapped
final Function(Channel) onImageTap;
/// Set it to false to disable the pull-to-refresh widget
final bool pullToRefresh;
@override
_ChannelListViewState createState() => _ChannelListViewState();
}
@@ -124,105 +128,146 @@ class _ChannelListViewState extends State<ChannelListView>
@override
Widget build(BuildContext context) {
final channelsProvider = ChannelsBloc.of(context);
final channelsBloc = ChannelsBloc.of(context);
if (!widget.pullToRefresh) {
return _buildListView(channelsBloc);
}
return RefreshIndicator(
onRefresh: () async {
return channelsProvider.queryChannels(
return channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: StreamBuilder<List<Channel>>(
stream: channelsProvider.channelsStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
child: _buildListView(channelsBloc),
);
}
if (widget.errorBuilder != null) {
return widget.errorBuilder(snapshot.error);
}
StreamBuilder<List<Channel>> _buildListView(
ChannelsBlocState channelsBlocState,
) {
return StreamBuilder<List<Channel>>(
stream: channelsBlocState.channelsStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
if (widget.errorBuilder != null) {
return widget.errorBuilder(snapshot.error);
}
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(
right: 2.0,
),
child: Icon(Icons.error_outline),
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(
right: 2.0,
),
child: Icon(Icons.error_outline),
),
TextSpan(text: 'Error loading channels'),
],
),
style: Theme.of(context).textTheme.headline6,
),
TextSpan(text: 'Error loading channels'),
],
),
Padding(
padding: const EdgeInsets.only(
top: 16.0,
),
child: Text(message),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(
top: 16.0,
),
FlatButton(
onPressed: () {
channelsProvider.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: Text('Retry'),
),
],
),
);
}
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final channels = snapshot.data;
return ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
return _itemBuilder(context, i, channels);
},
childCount: (channels.length * 2) + 1,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = channels.indexWhere(
(channel) => 'CHANNEL-${channel.id}' == valueKey.value);
return index != -1 ? (index * 2) : null;
},
child: Text(message),
),
FlatButton(
onPressed: () {
channelsBlocState.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: Text('Retry'),
),
],
),
);
}),
);
}
if (!snapshot.hasData) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: CircularProgressIndicator(),
),
),
);
},
);
}
final channels = snapshot.data;
if (channels.isEmpty) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Text('You have no channels currently'),
),
),
);
},
);
}
return ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
return _itemBuilder(context, i, channels);
},
childCount: (channels.length * 2) + 1,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = channels.indexWhere(
(channel) => 'CHANNEL-${channel.id}' == valueKey.value);
return index != -1 ? (index * 2) : null;
},
),
);
});
}
Widget _itemBuilder(context, int i, List<Channel> channels) {
@@ -430,7 +475,7 @@ class _ChannelListViewState extends State<ChannelListView>
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith(
offset: channelsProvider.channels.length,
offset: channelsProvider.channels?.length ?? 0,
),
options: widget.options,
);
@@ -482,7 +527,7 @@ class _ChannelListViewState extends State<ChannelListView>
void didUpdateWidget(ChannelListView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.filter != oldWidget.filter ||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
widget.sort != oldWidget.sort ||
widget.pagination != oldWidget.pagination ||
widget.options != oldWidget.options) {
+25 -19
View File
@@ -11,7 +11,7 @@ class ChannelsBloc extends StatefulWidget {
/// The widget child
final Widget child;
/// Set this to false to prevent channels to be brought to the top of the list when a new message arrives
/// Set this to true to prevent channels to be brought to the top of the list when a new message arrives
final bool lockChannelsOrder;
/// Instantiate a new ChannelsBloc
@@ -56,8 +56,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
final BehaviorSubject<bool> _queryChannelsLoadingController =
BehaviorSubject.seeded(false);
final BehaviorSubject<List<Channel>> _channelsController =
BehaviorSubject.seeded([]);
final BehaviorSubject<List<Channel>> _channelsController = BehaviorSubject();
/// The stream notifying the state of queryChannel call
Stream<bool> get queryChannelsLoading =>
@@ -73,7 +72,10 @@ class ChannelsBlocState extends State<ChannelsBloc>
Map<String, dynamic> options,
bool onlyOffline = false,
}) async {
if (_queryChannelsLoadingController.value == true) {
final client = StreamChat.of(context).client;
if (client.state?.user == null ||
_queryChannelsLoadingController.value == true) {
return;
}
_queryChannelsLoadingController.sink.add(true);
@@ -82,16 +84,15 @@ class ChannelsBlocState extends State<ChannelsBloc>
final clear = paginationParams == null ||
paginationParams.offset == null ||
paginationParams.offset == 0;
final oldChannels = List<Channel>.from(channels);
StreamChat.of(context)
.client
final oldChannels = List<Channel>.from(channels ?? []);
client
.queryChannels(
filter: filter,
sort: sortOptions,
options: options,
paginationParams: paginationParams,
onlyOffline: onlyOffline,
)
filter: filter,
sort: sortOptions,
options: options,
paginationParams: paginationParams,
onlyOffline: onlyOffline,
)
.listen((channels) {
if (clear) {
_channelsController.add(channels);
@@ -123,23 +124,28 @@ class ChannelsBlocState extends State<ChannelsBloc>
_subscriptions.add(client.on(EventType.messageNew).listen((e) {
final newChannels = List<Channel>.from(channels ?? []);
final index = newChannels.indexWhere((c) => c.cid == e.cid);
if (index > 0) {
final channel = newChannels.removeAt(index);
newChannels.insert(0, channel);
_channelsController.add(newChannels);
if (index > -1) {
if (index > 0) {
final channel = newChannels.removeAt(index);
newChannels.insert(0, channel);
}
} else {
final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid);
if (hiddenIndex > -1) {
newChannels.insert(0, _hiddenChannels[hiddenIndex]);
_hiddenChannels.removeAt(hiddenIndex);
_channelsController.add(newChannels);
} else {
if (client.state.channels[e.cid] != null) {
newChannels.insert(0, client.state.channels[e.cid]);
}
}
}
_channelsController.add(newChannels);
}));
}
_subscriptions.add(client.on(EventType.channelHidden).listen((event) async {
final newChannels = List<Channel>.from(channels);
final newChannels = List<Channel>.from(channels ?? []);
final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid);
if (channelIndex > -1) {
final channel = newChannels.removeAt(channelIndex);
+14 -6
View File
@@ -15,12 +15,20 @@ class FullScreenImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PhotoView(
imageProvider: CachedNetworkImageProvider(url),
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: url,
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
iconTheme: IconThemeData(
color: Colors.white,
),
),
body: PhotoView(
imageProvider: CachedNetworkImageProvider(url),
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: url,
),
),
);
}
+6
View File
@@ -26,6 +26,12 @@ class _FullScreenVideoState extends State<FullScreenVideo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
iconTheme: IconThemeData(
color: Colors.white,
),
),
body: Builder(
key: _scaffoldKey,
builder: (context) {
+7 -4
View File
@@ -182,7 +182,7 @@ class MessageInputState extends State<MessageInput> {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
overflow: Overflow.visible,
clipBehavior: Clip.none,
children: <Widget>[
_buildBorder(context),
Column(
@@ -738,7 +738,10 @@ class MessageInputState extends State<MessageInput> {
} else if (fileType == DefaultAttachmentTypes.file) {
type = FileType.any;
}
file = await FilePicker.getFile(type: type);
final res = await FilePicker.platform.pickFiles(type: type);
if (res?.files?.isNotEmpty == true) {
file = File(res.files.first.path);
}
}
setState(() {
@@ -909,9 +912,9 @@ class MessageInputState extends State<MessageInput> {
);
}
return sendingFuture.whenComplete(() {
return sendingFuture.then((resp) {
if (widget.onMessageSent != null) {
widget.onMessageSent(message);
widget.onMessageSent(resp.message);
} else {
if (widget.editMessage != null) {
Navigator.pop(context);
+5
View File
@@ -104,6 +104,7 @@ class MessageListView extends StatefulWidget {
this.onThreadTap,
this.dateDividerBuilder,
this.scrollPhysics = const AlwaysScrollableScrollPhysics(),
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : super(key: key);
/// Function used to build a custom message widget
@@ -128,6 +129,9 @@ class MessageListView extends StatefulWidget {
/// The ScrollPhysics used by the ListView
final ScrollPhysics scrollPhysics;
/// The [ScrollViewKeyboardDismissBehavior] used by the ListView
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
@override
_MessageListViewState createState() => _MessageListViewState();
}
@@ -159,6 +163,7 @@ class _MessageListViewState extends State<MessageListView> {
child: ListView.custom(
key: Key('messageListView'),
physics: widget.scrollPhysics,
keyboardDismissBehavior: widget.keyboardDismissBehavior,
controller: _scrollController,
reverse: true,
childrenDelegate: SliverChildBuilderDelegate(
+7 -1
View File
@@ -11,10 +11,12 @@ class MessageText extends StatelessWidget {
@required this.message,
@required this.messageTheme,
this.onMentionTap,
this.onLinkTap,
}) : super(key: key);
final Message message;
final void Function(User) onMentionTap;
final void Function(String) onLinkTap;
final MessageTheme messageTheme;
@override
@@ -35,7 +37,11 @@ class MessageText extends StatelessWidget {
print('tap on ${mentionedUser.name}');
}
} else {
launchURL(context, link);
if (onLinkTap != null) {
onLinkTap(link);
} else {
launchURL(context, link);
}
}
},
styleSheet: MarkdownStyleSheet.fromTheme(
+5
View File
@@ -101,6 +101,9 @@ class MessageWidget extends StatefulWidget {
/// The function called when tapping on UserAvatar
final void Function(User) onUserAvatarTap;
/// The function called when tapping on a link
final void Function(String) onLinkTap;
final List<Read> readList;
/// If true show the users username next to the timestamp of the message
@@ -132,6 +135,7 @@ class MessageWidget extends StatefulWidget {
this.showDeleteMessage = true,
this.showEditMessage = true,
this.onUserAvatarTap,
this.onLinkTap,
this.onMessageActions,
this.editMessageInputBuilder,
this.textBuilder,
@@ -814,6 +818,7 @@ class _MessageWidgetState extends State<MessageWidget> {
return widget.textBuilder != null
? widget.textBuilder(context, widget.message)
: MessageText(
onLinkTap: widget.onLinkTap,
message: widget.message,
onMentionTap: widget.onMentionTap,
messageTheme: widget.messageTheme,
+49 -46
View File
@@ -176,55 +176,58 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
if (client.showLocalNotification != null) {
_newMessageSubscription = client
.on(EventType.messageNew)
.where((e) => e.user?.id != user.id)
.where((e) => e.message.silent != true)
.listen((event) async {
var channel = client.state.channels[event.cid];
if (client.state?.user != null) {
if (state == AppLifecycleState.paused) {
if (client.showLocalNotification != null) {
_newMessageSubscription = client
.on(EventType.messageNew)
.where((e) => e.user?.id != user.id)
.where((e) => e.message.silent != true)
.listen((event) async {
var channel = client.state.channels[event.cid];
if (channel == null) {
channel = client.channel(
event.type,
id: event.cid.split(':')[1],
if (channel == null) {
channel = client.channel(
event.type,
id: event.cid.split(':')[1],
);
await channel.query();
}
client.showLocalNotification(
event.message,
ChannelModel(
id: channel.id,
createdAt: channel.createdAt,
extraData: channel.extraData,
type: channel.type,
memberCount: channel.memberCount,
frozen: channel.frozen,
cid: channel.cid,
deletedAt: channel.deletedAt,
config: channel.config,
createdBy: channel.createdBy,
updatedAt: channel.updatedAt,
lastMessageAt: channel.lastMessageAt,
),
);
await channel.query();
}
client.showLocalNotification(
event.message,
ChannelModel(
id: channel.id,
createdAt: channel.createdAt,
extraData: channel.extraData,
type: channel.type,
memberCount: channel.memberCount,
frozen: channel.frozen,
cid: channel.cid,
deletedAt: channel.deletedAt,
config: channel.config,
createdBy: channel.createdBy,
updatedAt: channel.updatedAt,
lastMessageAt: channel.lastMessageAt,
),
);
});
_disconnectTimer = Timer(client.backgroundKeepAlive, () {
});
_disconnectTimer = Timer(client.backgroundKeepAlive, () {
client.disconnect();
});
} else {
client.disconnect();
});
} else {
client.disconnect();
}
} else if (state == AppLifecycleState.resumed) {
_newMessageSubscription?.cancel();
if (_disconnectTimer?.isActive == true) {
_disconnectTimer.cancel();
} else {
if (client.wsConnectionStatus.value == ConnectionStatus.disconnected) {
NotificationService.handleIosMessageQueue(client);
client.connect();
}
} else if (state == AppLifecycleState.resumed) {
_newMessageSubscription?.cancel();
if (_disconnectTimer?.isActive == true) {
_disconnectTimer.cancel();
} else {
if (client.wsConnectionStatus.value ==
ConnectionStatus.disconnected) {
NotificationService.handleIosMessageQueue(client);
client.connect();
}
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ class TypingIndicator extends StatelessWidget {
builder: (context, snapshot) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: snapshot.data.isNotEmpty
child: snapshot.data?.isNotEmpty == true
? Align(
key: Key('typings'),
alignment: alignment,
+6 -3
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 0.2.4
version: 0.2.8+3
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -20,11 +20,11 @@ dependencies:
url_launcher: ^5.4.11
video_player: ^0.10.11+1
chewie: ^0.9.10
file_picker: ^1.12.0
file_picker: ^2.0.0
image_picker: ^0.6.7+2
flutter_keyboard_visibility: ^3.2.1
flutter_svg: ^0.18.0
stream_chat: ^0.2.3+3
stream_chat: ^0.2.5+2
mime: ^0.9.6+3
visibility_detector: ^0.1.5
line_awesome_icons: ^1.0.4+2
@@ -38,3 +38,6 @@ flutter:
dev_dependencies:
pedantic: ^1.9.0
flutter_test:
sdk: flutter
mockito: ^4.1.1
+52
View File
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class MockClient extends Mock implements Client {}
class MockChannel extends Mock implements Channel {}
class MockChannelState extends Mock implements ChannelClientState {}
void main() {
testWidgets(
'it should show basic channel information',
(WidgetTester tester) async {
final client = MockClient();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.extraData).thenReturn({
'name': 'test name',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.lastMessage).thenReturn(Message(
text: 'hello',
));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelPreview(
channel: channel,
),
),
),
),
));
expect(find.text('22/06/2020'), findsOneWidget);
expect(find.text('test name'), findsOneWidget);
expect(find.text('1'), findsOneWidget);
expect(find.text('hello'), findsOneWidget);
expect(find.byType(ChannelImage), findsOneWidget);
},
);
}