Merge pull request #2 from imtoori/master

Example: single and multiple conversations
This commit is contained in:
Tommaso Barbugli
2020-02-24 22:24:49 +01:00
committed by GitHub
76 changed files with 2964 additions and 3 deletions
+9
View File
@@ -0,0 +1,9 @@
# This is a generated file; do not edit or check into version control.
path_provider=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.1/
screen=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/screen-0.0.5/
sqflite=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.2.1/
url_launcher=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.4.2/
url_launcher_macos=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.0.1+4/
url_launcher_web=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_web-0.1.1+1/
video_player=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player-0.10.8+1/
video_player_web=/Users/salvatoregiordano/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player_web-0.1.2+1/
+1
View File
@@ -0,0 +1 @@
{"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"path_provider","dependencies":[]},{"name":"screen","dependencies":[]},{"name":"sqflite","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_web","url_launcher_macos"]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"video_player","dependencies":["video_player_web"]},{"name":"video_player_web","dependencies":[]}]}
+39
View File
@@ -1,3 +1,42 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
coverage/
coverage_helper_test.dart
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
+2
View File
@@ -1 +1,3 @@
## 0.0.1
- First release
+1 -1
View File
@@ -28,7 +28,7 @@ TODO
```yaml
dependencies:
stream_chat_flutter: ^0.1.0
stream_chat_flutter: ^0.0.1
```
You should then run `flutter packages get`
+2 -2
View File
@@ -39,12 +39,12 @@ linter:
# - one_member_abstracts
# - only_throw_errors
# - overridden_fields
- package_api_docs
# - package_api_docs
- package_names
- package_prefixed_library_names
- prefer_is_not_empty
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
- public_member_api_docs
# - public_member_api_docs
- slash_for_doc_comments
# - sort_constructors_first
# - sort_unnamed_constructors_first
+37
View File
@@ -0,0 +1,37 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+10
View File
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 0b8abb4724aa590dd0f429683339b1e045a1594d
channel: stable
project_type: app
+16
View File
@@ -0,0 +1,16 @@
# example
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
+7
View File
@@ -0,0 +1,7 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
+67
View File
@@ -0,0 +1,67 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
@@ -0,0 +1,12 @@
package com.example.example
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+31
View File
@@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+4
View File
@@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
+15
View File
@@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
+32
View File
@@ -0,0 +1,32 @@
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
+2
View File
@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
+2
View File
@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
+90
View File
@@ -0,0 +1,90 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
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
end
generated_key_values
end
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'
# 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
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
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
@@ -0,0 +1,518 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
+13
View File
@@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
+60
View File
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
void main() async {
final client = Client('qk4nn7rpcn75');
await client.setUser(
User(id: "wild-breeze-7"),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo',
);
runApp(
StreamChat(
client: client,
child: MaterialApp(
home: ChannelListPage(),
),
),
);
}
class ChannelListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ChannelListView(
filter: {
'members': {
'\$in': [StreamChat.of(context).user.id],
}
},
sort: [SortOption("last_message_at")],
pagination: PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
),
);
}
}
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
);
}
}
+47
View File
@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
void main() async {
final client = Client('qk4nn7rpcn75');
await client.setUser(
User(id: "wild-breeze-7"),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo',
);
final channelClient = client.channel('messaging', id: 'godevs');
channelClient.watch();
runApp(
StreamChat(
client: client,
child: MaterialApp(
home: StreamChannel(
channelClient: channelClient,
child: ChannelPage(),
),
),
),
);
}
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
);
}
}
+21
View File
@@ -0,0 +1,21 @@
name: example
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
stream_chat_flutter:
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
+25
View File
@@ -0,0 +1,25 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class ChannelImage extends StatelessWidget {
const ChannelImage({
Key key,
@required this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: 20,
backgroundImage: channel.extraData.containsKey('image')
? CachedNetworkImageProvider(channel.extraData['image'] as String)
: null,
child: channel.extraData.containsKey('image')
? null
: Text(channel.config.name[0]),
);
}
}
+208
View File
@@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'channel_preview.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
typedef ChannelTapCallback = void Function(ChannelClient, Widget);
class ChannelListView extends StatefulWidget {
ChannelListView({
Key key,
this.filter,
this.options,
this.sort,
this.pagination,
this.onChannelTap,
this.channelWidget,
this.channelPreview,
}) : assert(channelWidget != null || onChannelTap != null),
super(key: key);
final Map<String, dynamic> filter;
final Map<String, dynamic> options;
final List<SortOption> sort;
final PaginationParams pagination;
final ScrollController _scrollController = ScrollController();
final ChannelTapCallback onChannelTap;
final Widget channelWidget;
final Widget channelPreview;
@override
_ChannelListViewState createState() => _ChannelListViewState();
}
class _ChannelListViewState extends State<ChannelListView> {
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
return RefreshIndicator(
onRefresh: () async {
streamChat.clearChannels();
return streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: StreamBuilder<List<ChannelState>>(
stream: streamChat.channelsStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
print((snapshot.error as Error).stackTrace);
return Center(
child: Text(snapshot.error.toString()),
);
}
final channelsStates = snapshot.data;
return ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: widget._scrollController,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
return _itemBuilder(context, i, channelsStates);
},
childCount: (channelsStates.length * 2) + 1,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = channelsStates.indexWhere(
(cs) => 'CHANNEL-${cs.channel.id}' == valueKey.value);
return index != -1 ? (index * 2) : null;
},
),
);
}),
);
}
Widget _itemBuilder(context, int i, List<ChannelState> channelsStates) {
if (i % 2 != 0) {
return _separatorBuilder(context, i);
}
i = i ~/ 2;
final streamChat = StreamChat.of(context);
if (i < channelsStates.length) {
final channelState = channelsStates[i];
final channelClient =
streamChat.client.channelClients[channelState.channel.id];
ChannelTapCallback onTap;
if (widget.onChannelTap != null) {
onTap = widget.onChannelTap;
} else {
onTap = (client, _) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: widget.channelWidget,
channelClient: client,
);
},
),
);
};
}
Widget child;
if (widget.channelPreview != null) {
child = Stack(
children: [
widget.channelPreview,
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
onTap(channelClient, widget.channelWidget);
},
),
),
),
],
);
} else {
child = ChannelPreview(
onTap: (channelClient) {
onTap(channelClient, widget.channelWidget);
},
);
}
return StreamChannel(
key: ValueKey<String>('CHANNEL-${channelClient.id}'),
child: child,
channelClient: channelClient,
);
} else {
return _buildQueryProgressIndicator(context, streamChat);
}
}
Widget _buildQueryProgressIndicator(context, StreamChat streamChat) {
return StreamBuilder<bool>(
stream: streamChat.queryChannelsLoading,
initialData: false,
builder: (context, snapshot) {
return Container(
height: 100,
padding: EdgeInsets.all(32),
child: Center(
child: snapshot.data ? CircularProgressIndicator() : Container(),
),
);
});
}
Widget _separatorBuilder(context, i) {
return Container(
height: 1,
color: Colors.black.withOpacity(0.1),
margin: EdgeInsets.symmetric(horizontal: 16),
);
}
void _listenChannelPagination(StreamChat streamChat) {
if (widget._scrollController.position.maxScrollExtent ==
widget._scrollController.position.pixels) {
streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith(
offset: streamChat.channels.length,
),
options: widget.options,
);
}
}
@override
void initState() {
super.initState();
final streamChat = StreamChat.of(context);
streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
widget._scrollController.addListener(() {
_listenChannelPagination(streamChat);
});
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class ChannelNameText extends StatelessWidget {
const ChannelNameText({
Key key,
this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return Text(
channel.extraData['name'] as String ?? channel.config.name,
style: Theme.of(context).textTheme.body2,
);
}
}
+147
View File
@@ -0,0 +1,147 @@
import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_chat/stream_chat.dart';
import 'channel_image.dart';
import 'channel_name_text.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
class ChannelPreview extends StatelessWidget {
final void Function(ChannelClient) onTap;
const ChannelPreview({
Key key,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context);
return StreamBuilder<ChannelState>(
stream: streamChannel.channelClient.state.channelStateStream,
initialData: streamChannel.channelState,
builder: (context, snapshot) {
final channelState = snapshot.data;
return _buildChannelPreview(
context,
channelState,
streamChannel,
);
});
}
StreamChannel _buildChannelPreview(
BuildContext context,
ChannelState channelState,
StreamChannelState streamChannel,
) {
final channelClient =
StreamChat.of(context).client.channelClients[channelState.channel.id];
return StreamChannel(
channelClient: channelClient,
child: ListTile(
onTap: () {
onTap(channelClient);
},
leading: ChannelImage(
channel: channelState.channel,
),
title: ChannelNameText(
channel: channelState.channel,
),
subtitle: _buildSubtitle(
streamChannel,
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
_buildDate(context, channelState.channel.lastMessageAt),
],
),
),
);
}
Text _buildDate(BuildContext context, DateTime lastMessageAt) {
String stringDate;
final now = DateTime.now();
if (now.year != lastMessageAt.year ||
now.month != lastMessageAt.month ||
now.day != lastMessageAt.day) {
stringDate =
'${lastMessageAt.day}/${lastMessageAt.month}/${lastMessageAt.year}';
stringDate = formatDate(lastMessageAt, [dd, '/', mm, '/', yyyy]);
} else {
stringDate = '${lastMessageAt.hour}:${lastMessageAt.minute}';
stringDate = formatDate(lastMessageAt, [HH, ':', nn]);
}
return Text(
stringDate,
style: Theme.of(context).textTheme.caption,
);
}
Widget _buildSubtitle(
StreamChannelState streamChannel,
) {
return StreamBuilder<List<User>>(
stream: streamChannel.channelClient.state.typingEventsStream,
initialData: [],
builder: (context, snapshot) {
final typings = snapshot.data;
final opacity =
streamChannel.channelClient.state.unreadCount > .0 ? 1.0 : 0.5;
return typings.isNotEmpty
? _buildTypings(typings, context, opacity)
: _buildLastMessage(context, streamChannel, opacity);
});
}
Widget _buildLastMessage(
BuildContext context, StreamChannelState streamChannel, double opacity) {
final lastMessage = streamChannel.channelState.messages.isNotEmpty
? streamChannel.channelState.messages.last
: null;
if (lastMessage == null) {
return SizedBox.fromSize(
size: Size.zero,
);
}
final prefix = lastMessage.attachments
.map((e) {
if (e.type == 'image') {
return '📷';
} else if (e.type == 'video') {
return '🎬';
}
return null;
})
.where((e) => e != null)
.join(' ');
return Text(
'$prefix ${lastMessage.text ?? ''}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black.withOpacity(opacity),
),
);
}
Text _buildTypings(List<User> typings, BuildContext context, double opacity) {
return Text(
'${typings.map((u) => u.extraData.containsKey('name') ? u.extraData['name'] : u.id).join(',')} ${typings.length == 1 ? 'is' : 'are'} typing...',
maxLines: 1,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black.withOpacity(opacity),
),
);
}
}
+137
View File
@@ -0,0 +1,137 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'stream_channel.dart';
class MessageInput extends StatefulWidget {
MessageInput({
Key key,
this.onMessageSent,
this.parent,
}) : super(key: key);
final void Function(Message) onMessageSent;
final Message parent;
@override
_MessageInputState createState() => _MessageInputState();
}
class _MessageInputState extends State<MessageInput> {
final _textController = TextEditingController();
bool _messageIsPresent = false;
bool _typingStarted = false;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
gradient: _typingStarted
? LinearGradient(colors: [Color(0xFF00AEFF), Color(0xFF0076FF)])
: null,
),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
borderRadius: BorderRadius.circular(10.0),
),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(5),
borderRadius: BorderRadius.circular(10.0),
border: Border.all(color: Colors.black.withOpacity(.2)),
),
child: Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
child: TextField(
minLines: null,
maxLines: null,
onSubmitted: (_) {
_sendMessage(context);
},
controller: _textController,
onChanged: (s) {
StreamChannel.of(context).channelClient.keyStroke();
setState(() {
_messageIsPresent = s.trim().isNotEmpty;
});
},
onTap: () {
setState(() {
_typingStarted = true;
});
},
style: Theme.of(context).textTheme.body1,
autofocus: false,
decoration: InputDecoration(
hintText: 'Write a message',
prefixText: ' ',
border: InputBorder.none,
),
),
),
AnimatedCrossFade(
crossFadeState: _messageIsPresent
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: _buildSendButton(context),
secondChild: Container(),
duration: Duration(milliseconds: 300),
alignment: Alignment.center,
),
],
),
),
),
),
);
}
IconButton _buildSendButton(BuildContext context) {
return IconButton(
onPressed: () {
_sendMessage(context);
},
icon: Icon(
Icons.send,
),
);
}
void _sendMessage(BuildContext context) {
final text = _textController.text.trim();
if (text.isEmpty) {
return;
}
_textController.clear();
setState(() {
_messageIsPresent = false;
_typingStarted = false;
});
FocusScope.of(context).unfocus();
StreamChannel.of(context)
.channelClient
.sendMessage(
Message(
parentId: widget.parent?.id,
text: text,
),
)
.then((_) {
if (widget.onMessageSent != null) {
widget.onMessageSent(Message(text: text));
}
});
}
}
+282
View File
@@ -0,0 +1,282 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_widgets/flutter_widgets.dart';
import 'package:stream_chat/stream_chat.dart';
import 'message_widget.dart';
import 'stream_channel.dart';
typedef MessageBuilder = Widget Function(BuildContext, Message, int index);
typedef ParentMessageBuilder = Widget Function(BuildContext, Message);
typedef OnThreadSelectCallback = void Function(Message parent);
class MessageListView extends StatefulWidget {
MessageListView({
Key key,
MessageBuilder messageBuilder,
this.parentMessageBuilder,
this.parentMessage,
this.onThreadSelect,
}) : _messageBuilder = messageBuilder,
super(key: key);
final MessageBuilder _messageBuilder;
final ParentMessageBuilder parentMessageBuilder;
final OnThreadSelectCallback onThreadSelect;
final Message parentMessage;
@override
_MessageListViewState createState() => _MessageListViewState();
}
class _MessageListViewState extends State<MessageListView> {
static const _newMessageLoadingOffset = 100;
final ScrollController _scrollController = ScrollController();
bool _isBottom = true;
bool _topWasVisible = false;
List<Message> _messages = [];
List<Message> _newMessageList = [];
@override
Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context);
/// TODO: find a better solution when (https://github.com/flutter/flutter/issues/21023) is fixed
return NotificationListener<ScrollNotification>(
onNotification: (_) {
if (_scrollController.offset < 150 && _newMessageList.isNotEmpty) {
setState(() {
_messages.insertAll(0, _newMessageList);
_newMessageList.clear();
});
}
return true;
},
child: ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
reverse: true,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
if (i == _messages.length + 1) {
if (widget.parentMessage != null) {
if (widget.parentMessageBuilder != null) {
return widget.parentMessageBuilder(
context, widget.parentMessage);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessageWidget(
key: ValueKey<String>(
'PARENT-MESSAGE-${widget.parentMessage.id}'),
previousMessage: null,
message: widget.parentMessage.copyWith(replyCount: 0),
nextMessage: null,
onThreadSelect: widget.onThreadSelect,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Container(
padding: const EdgeInsets.all(8),
child: Text(
'Start of a new thread',
textAlign: TextAlign.center,
),
color: Theme.of(context).primaryColorLight,
),
),
],
);
}
} else {
return SizedBox.fromSize(
size: Size.zero,
);
}
}
if (i == _messages.length) {
return _buildLoadingIndicator(streamChannel);
}
final message = _messages[i];
if (widget._messageBuilder != null) {
return widget._messageBuilder(context, message, i);
}
final previousMessage =
i < _messages.length - 1 ? _messages[i + 1] : null;
final nextMessage = i > 0 ? _messages[i - 1] : null;
if (i == 0) {
return _buildBottomMessage(
streamChannel,
previousMessage,
message,
context,
);
}
if (i == _messages.length - 1) {
return _buildTopMessage(
message,
nextMessage,
streamChannel,
context,
);
}
return MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'),
previousMessage: previousMessage,
message: message,
nextMessage: nextMessage,
onThreadSelect: widget.onThreadSelect,
);
},
childCount: _messages.length + 2,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = _messages
.indexWhere((m) => 'MESSAGE-${m.id}' == valueKey.value);
return index != -1 ? index : null;
},
),
),
);
}
Container _buildLoadingIndicator(StreamChannelState streamChannel) {
return Container(
height: 50,
child: StreamBuilder<bool>(
stream: streamChannel.queryMessage,
initialData: false,
builder: (context, snapshot) {
if (snapshot.hasError) {
print((snapshot.error as Error).stackTrace.toString());
return Center(
child: Text(snapshot.error.toString()),
);
}
if (!snapshot.data) {
return Container();
}
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}),
);
}
Widget _buildTopMessage(
Message message,
Message nextMessage,
StreamChannelState streamChannelState,
BuildContext context,
) {
return VisibilityDetector(
key: ValueKey<String>('TOP-MESSAGE'),
child: MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'),
previousMessage: null,
message: message,
nextMessage: nextMessage,
onThreadSelect: widget.onThreadSelect,
),
onVisibilityChanged: (visibility) {
final topIsVisible = visibility.visibleBounds != Rect.zero;
if (topIsVisible && !_topWasVisible) {
streamChannelState.queryMessages();
}
_topWasVisible = topIsVisible;
},
);
}
Widget _buildBottomMessage(
StreamChannelState channelBloc,
Message previousMessage,
Message message,
BuildContext context,
) {
return VisibilityDetector(
key: ValueKey<String>('BOTTOM-MESSAGE'),
onVisibilityChanged: (visibility) {
_isBottom = visibility.visibleBounds != Rect.zero;
if (_isBottom) {
if (channelBloc.channelClient.state.unreadCount > 0) {
channelBloc.channelClient.markRead();
}
}
},
child: MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'),
previousMessage: previousMessage,
message: message,
nextMessage: null,
onThreadSelect: widget.onThreadSelect,
),
);
}
StreamSubscription _streamListener;
@override
void initState() {
super.initState();
final streamChannel = StreamChannel.of(context);
if (streamChannel.channelClient.state.unreadCount > 0) {
streamChannel.channelClient.markRead();
}
Stream<List<Message>> stream;
if (widget.parentMessage == null) {
stream = streamChannel.channelStateStream.map((c) => c.messages);
} else {
streamChannel.getReplies(widget.parentMessage.id);
stream = streamChannel.channelClient.state.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage.id))
.map((threads) => threads[widget.parentMessage.id]);
}
_streamListener = stream.listen((newMessages) {
newMessages = newMessages.reversed.toList();
if (_messages.isEmpty || newMessages.first.id != _messages.first.id) {
if (!_scrollController.hasClients ||
_scrollController.offset < _newMessageLoadingOffset) {
setState(() {
_messages = newMessages;
});
} else if (newMessages.first.user.id ==
streamChannel.channelClient.client.state.user.id) {
_scrollController.jumpTo(0);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_messages = newMessages;
});
});
} else {
_newMessageList = newMessages;
}
} else {
setState(() {
_messages = newMessages;
});
}
});
}
@override
void dispose() {
_streamListener.cancel();
super.dispose();
}
}
+353
View File
@@ -0,0 +1,353 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:date_format/date_format.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:video_player/video_player.dart';
import 'message_list_view.dart';
import 'stream_chat.dart';
import 'user_avatar.dart';
class MessageWidget extends StatefulWidget {
const MessageWidget({
Key key,
@required this.previousMessage,
@required this.message,
@required this.nextMessage,
this.onThreadSelect,
}) : super(key: key);
final Message previousMessage;
final Message message;
final Message nextMessage;
final OnThreadSelectCallback onThreadSelect;
@override
_MessageWidgetState createState() => _MessageWidgetState();
}
class _MessageWidgetState extends State<MessageWidget>
with AutomaticKeepAliveClientMixin {
final Map<String, ChangeNotifier> _videoControllers = {};
final Map<String, ChangeNotifier> _chuwieControllers = {};
@override
Widget build(BuildContext context) {
super.build(context);
final streamChat = StreamChat.of(context);
final currentUserId = streamChat.user.id;
final messageUserId = widget.message.user.id;
final previousUserId = widget.previousMessage?.user?.id;
final nextUserId = widget.nextMessage?.user?.id;
final isMyMessage = messageUserId == currentUserId;
final isLastUser = previousUserId == messageUserId;
final isNextUser = nextUserId == messageUserId;
final alignment =
isMyMessage ? Alignment.centerRight : Alignment.centerLeft;
var row = <Widget>[
Column(
crossAxisAlignment:
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
_buildBubble(context, isMyMessage, isLastUser),
widget.message.replyCount > 0
? GestureDetector(
onTap: () {
if (widget.onThreadSelect != null) {
widget.onThreadSelect(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
children: <Widget>[
Text(
'Replies: ${widget.message.replyCount}',
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.blue),
),
Icon(
Icons.subdirectory_arrow_left,
color: Colors.black12,
),
],
),
),
)
: Container(),
isNextUser ? Container() : _buildTimestamp(isMyMessage, alignment),
],
),
isNextUser
? Container(
width: 40,
)
: Padding(
padding: EdgeInsets.only(
left: isMyMessage ? 8.0 : 0,
right: isMyMessage ? 0 : 8.0,
),
child: UserAvatar(user: widget.message.user),
),
];
if (!isMyMessage) {
row = row.reversed.toList();
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
margin: EdgeInsets.only(
top: isLastUser ? 5 : 24,
bottom: widget.nextMessage == null ? 30 : 0,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment:
isMyMessage ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: row,
),
);
}
Widget _buildBubble(
BuildContext context,
bool isMyMessage,
bool isLastUser,
) {
var nOfAttachmentWidgets = 0;
final column = Column(
crossAxisAlignment:
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: widget.message.attachments.map((attachment) {
nOfAttachmentWidgets++;
Widget attachmentWidget;
if (attachment.type == 'video') {
attachmentWidget = _buildVideo(attachment, isMyMessage, isLastUser);
} else if (attachment.type == 'image' || attachment.type == 'giphy') {
attachmentWidget = _buildImage(isMyMessage, isLastUser, attachment);
}
if (attachmentWidget != null) {
final boxDecoration = _buildBoxDecoration(isMyMessage, isLastUser)
.copyWith(color: Color(0xffebebeb));
return ClipRRect(
borderRadius: boxDecoration.borderRadius,
child: Container(
decoration: boxDecoration,
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
attachmentWidget,
attachment.title != null
? Container(
constraints:
BoxConstraints.loose(Size.fromHeight(70)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
attachment.title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.blue),
),
Text(
Uri.parse(attachment.thumbUrl)
.authority
.split('.')
.reversed
.take(2)
.toList()
.reversed
.join('.'),
overflow: TextOverflow.ellipsis,
style:
Theme.of(context).textTheme.caption,
),
],
),
),
color: Color(0xffebebeb),
)
: Container(),
],
),
attachment.type == 'image' && attachment.titleLink != null
? Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _launchURL(attachment.titleLink),
),
),
)
: SizedBox.fromSize(
size: Size.zero,
),
],
),
margin: EdgeInsets.only(
top: nOfAttachmentWidgets > 1 ? 5 : 0,
),
),
);
}
nOfAttachmentWidgets--;
return Container();
}).toList(),
);
if (widget.message.text.trim().isNotEmpty) {
column.children.add(Container(
margin: EdgeInsets.only(
top: nOfAttachmentWidgets > 0 ? 5 : 0,
),
decoration: _buildBoxDecoration(
isMyMessage, isLastUser || nOfAttachmentWidgets > 0),
padding: EdgeInsets.all(10),
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: MarkdownBody(
data: '${widget.message.text}',
onTapLink: (link) {
_launchURL(link);
},
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)),
),
));
}
return column;
}
Widget _buildImage(
bool isMyMessage,
bool isLastUser,
Attachment attachment,
) {
return CachedNetworkImage(
imageUrl: attachment.thumbUrl ?? attachment.imageUrl,
fit: BoxFit.cover,
);
}
Widget _buildVideo(
Attachment attachment,
bool isMyMessage,
bool isLastUser,
) {
VideoPlayerController videoController;
if (_videoControllers.containsKey(attachment.assetUrl)) {
videoController = _videoControllers[attachment.assetUrl];
} else {
videoController = VideoPlayerController.network(attachment.assetUrl);
_videoControllers[attachment.assetUrl] = videoController;
}
ChewieController chewieController;
if (_chuwieControllers.containsKey(attachment.assetUrl)) {
chewieController = _chuwieControllers[attachment.assetUrl];
} else {
chewieController = ChewieController(
videoPlayerController: videoController,
autoInitialize: true,
errorBuilder: (_, e) {
return Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
attachment.thumbUrl,
),
),
),
),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _launchURL(attachment.titleLink),
),
),
],
);
});
_chuwieControllers[attachment.assetUrl] = chewieController;
}
return Chewie(
key: ValueKey<String>(
'ATTACHMENT-${attachment.title}-${widget.message.id}'),
controller: chewieController,
);
}
Future<void> _launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Cannot launch the url'),
),
);
}
}
@override
void dispose() {
_videoControllers.values.forEach((element) {
element.dispose();
});
super.dispose();
}
Widget _buildTimestamp(bool isMyMessage, Alignment alignment) {
return Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Text(
formatDate(widget.message.createdAt.toLocal(), [HH, ':', nn]),
),
);
}
BoxDecoration _buildBoxDecoration(bool isMyMessage, bool isLastUser) {
return BoxDecoration(
border: isMyMessage ? null : Border.all(color: Colors.black.withAlpha(8)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular((isMyMessage || !isLastUser) ? 16 : 2),
bottomLeft: Radius.circular(isMyMessage ? 16 : 2),
topRight: Radius.circular((isMyMessage && isLastUser) ? 2 : 16),
bottomRight: Radius.circular(isMyMessage ? 2 : 16),
),
color: isMyMessage ? Color(0xffebebeb) : Colors.white,
);
}
@override
bool get wantKeepAlive {
return widget.message.attachments.isNotEmpty;
}
}
+128
View File
@@ -0,0 +1,128 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart';
class StreamChannel extends StatefulWidget {
StreamChannel({
Key key,
@required this.child,
@required this.channelClient,
}) : super(
key: key,
);
final Widget child;
final ChannelClient channelClient;
static StreamChannelState of(BuildContext context) {
StreamChannelState streamChannelState;
streamChannelState = context.findAncestorStateOfType<StreamChannelState>();
if (streamChannelState == null) {
throw Exception(
'You must have a StreamChannel widget at the top of your widget tree');
}
return streamChannelState;
}
@override
StreamChannelState createState() => StreamChannelState();
}
class StreamChannelState extends State<StreamChannel> {
StreamChannelState();
ChannelClient get channelClient => widget.channelClient;
ChannelState get channelState => widget.channelClient.state.channelState;
Stream<ChannelState> get channelStateStream =>
widget.channelClient.state.channelStateStream;
final BehaviorSubject<bool> _queryMessageController = BehaviorSubject();
Stream<bool> get queryMessage => _queryMessageController.stream;
void queryMessages() {
_queryMessageController.add(true);
String firstId;
if (channelState.messages.isNotEmpty) {
firstId = channelState.messages.first.id;
}
widget.channelClient
.query(
messagesPagination: PaginationParams(
lessThan: firstId,
limit: 100,
),
)
.then((res) {
_queryMessageController.add(false);
}).catchError((e, stack) {
_queryMessageController.addError(e, stack);
});
}
Future<void> getReplies(String parentId) async {
_queryMessageController.add(true);
String firstId;
if (widget.channelClient.state.threads.containsKey(parentId)) {
firstId = widget.channelClient.state.threads[parentId].first.id;
}
return widget.channelClient
.getReplies(
parentId,
PaginationParams(
lessThan: firstId,
limit: 100,
),
)
.then((res) {
_queryMessageController.add(false);
}).catchError((e, stack) {
_queryMessageController.addError(e, stack);
});
}
@override
void dispose() {
_queryMessageController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.channelClient == null) {
return Center(
child: CircularProgressIndicator(),
);
}
return FutureBuilder<bool>(
future: widget.channelClient.initialized,
initialData: widget.channelClient.state != null,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (snapshot.hasError) {
return Center(
child: Text(snapshot.error),
);
} else {
return widget.child;
}
},
);
}
}
+103
View File
@@ -0,0 +1,103 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart';
class StreamChat extends InheritedWidget {
final Client client;
final List<StreamSubscription> _subscriptions = [];
StreamChat({
Key key,
@required this.client,
@required Widget child,
}) : super(
key: key,
child: child,
) {
_subscriptions.add(client.on('message.new').listen((Event e) {
final index = channels.indexWhere((c) => c.channel.cid == e.cid);
if (index > 0) {
final channel = channels.removeAt(index);
channels.insert(0, channel);
_channelsController.add(channels);
}
}));
}
User get user => client.state.user;
Stream<User> get userStream => client.state.userStream;
Stream<List<ChannelState>> get channelsStream => _channelsController.stream;
final BehaviorSubject<List<ChannelState>> _channelsController =
BehaviorSubject();
final List<ChannelState> channels = [];
final BehaviorSubject<bool> _queryChannelsLoadingController =
BehaviorSubject.seeded(false);
Stream<bool> get queryChannelsLoading =>
_queryChannelsLoadingController.stream;
Future<void> queryChannels({
Map<String, dynamic> filter,
List<SortOption> sortOptions,
PaginationParams paginationParams,
Map<String, dynamic> options,
}) async {
if (_queryChannelsLoadingController.value) {
return;
}
_queryChannelsLoadingController.sink.add(true);
try {
final res = await client.queryChannels(
filter: filter,
sort: sortOptions,
options: options,
paginationParams: paginationParams,
);
channels.addAll(res.map((c) => c.state.channelState));
_channelsController.sink.add(channels);
_queryChannelsLoadingController.sink.add(false);
} catch (e) {
_channelsController.sink.addError(e);
}
}
void clearChannels() {
channels.clear();
}
void dispose() {
client.dispose();
_subscriptions.forEach((s) => s.cancel());
_queryChannelsLoadingController.close();
_channelsController.close();
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
static StreamChat of(BuildContext context, [bool listen = false]) {
StreamChat streamChat;
if (listen) {
streamChat = context.dependOnInheritedWidgetOfExactType<StreamChat>();
} else {
streamChat = context.findAncestorWidgetOfExactType<StreamChat>();
}
if (streamChat == null) {
throw Exception(
'You must have a StreamChat widget at the top of your widget tree');
}
return streamChat;
}
}
+29
View File
@@ -0,0 +1,29 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class UserAvatar extends StatelessWidget {
const UserAvatar({
Key key,
@required this.user,
this.radius = 16,
}) : super(key: key);
final User user;
final double radius;
@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: radius,
backgroundImage: user.extraData.containsKey('image')
? CachedNetworkImageProvider(user.extraData['image'] as String)
: null,
child: user.extraData.containsKey('image')
? null
: Text(user?.extraData?.containsKey('name') ?? false
? user.extraData['name'][0]
: ''),
);
}
}
+10
View File
@@ -0,0 +1,10 @@
export 'package:stream_chat/stream_chat.dart';
export 'src/channel_list_view.dart';
export 'src/channel_preview.dart';
export 'src/message_input.dart';
export 'src/message_list_view.dart';
export 'src/message_widget.dart';
export 'src/stream_channel.dart';
export 'src/stream_chat.dart';
export 'src/user_avatar.dart';
+25
View File
@@ -0,0 +1,25 @@
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.0.1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
rxdart: ^0.23.1
flutter_widgets: ^0.1.10
timeago: ^2.0.26
date_format: ^1.0.8
cached_network_image: ^2.0.0
flutter_markdown: ^0.3.3
url_launcher: ^5.4.1
video_player: ^0.10.7
chewie: ^0.9.8+1
animations: ^1.0.0+3
stream_chat: ^0.1.6
dev_dependencies:
pedantic: ^1.8.0+1