commit a5220643de584032664f1e526e2e92c176260740 Author: Salvatore Giordano Date: Mon Feb 1 10:07:54 2021 +0100 add stream_chat_v1 example diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bad42c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# flutter-samples diff --git a/stream_chat_v1/example/.gitignore b/stream_chat_v1/example/.gitignore new file mode 100644 index 0000000..8011d84 --- /dev/null +++ b/stream_chat_v1/example/.gitignore @@ -0,0 +1,39 @@ +# 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 + +fvm \ No newline at end of file diff --git a/stream_chat_v1/example/.metadata b/stream_chat_v1/example/.metadata new file mode 100644 index 0000000..01d2dcb --- /dev/null +++ b/stream_chat_v1/example/.metadata @@ -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 diff --git a/stream_chat_v1/example/README.md b/stream_chat_v1/example/README.md new file mode 100644 index 0000000..9e8af88 --- /dev/null +++ b/stream_chat_v1/example/README.md @@ -0,0 +1,7 @@ +# Flutter Chat Example Application + +An example chat application using Flutter and Stream Chat. + +## Getting Started + +Make sure to follow the steps from the [tutorial](https://getstream.io/chat/flutter/tutorial/). diff --git a/stream_chat_v1/example/android/.gitignore b/stream_chat_v1/example/android/.gitignore new file mode 100644 index 0000000..bc2100d --- /dev/null +++ b/stream_chat_v1/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/stream_chat_v1/example/android/app/build.gradle b/stream_chat_v1/example/android/app/build.gradle new file mode 100644 index 0000000..c5ff1b9 --- /dev/null +++ b/stream_chat_v1/example/android/app/build.gradle @@ -0,0 +1,71 @@ +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 29 + ndkVersion '21.3.6528147' + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + lintOptions { + disable 'InvalidPackage' + checkReleaseBuilds false + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + minSdkVersion 21 + targetSdkVersion 29 + 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' + implementation 'com.google.firebase:firebase-messaging:20.1.2' +} + +apply plugin: 'com.google.gms.google-services' diff --git a/stream_chat_v1/example/android/app/google-services.json b/stream_chat_v1/example/android/app/google-services.json new file mode 100644 index 0000000..2611383 --- /dev/null +++ b/stream_chat_v1/example/android/app/google-services.json @@ -0,0 +1,40 @@ +{ + "project_info": { + "project_number": "1004276287628", + "firebase_url": "https://test-a0490.firebaseio.com", + "project_id": "test-a0490", + "storage_bucket": "test-a0490.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:1004276287628:android:7fff771d5e464c6a5f93a9", + "android_client_info": { + "package_name": "com.example.example" + } + }, + "oauth_client": [ + { + "client_id": "1004276287628-43j2uub5rd0968ois7h963i0np373t6k.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyC2ESbd8qWtIUWwGn1L_Dbxzh2W6yfGqiU" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "1004276287628-43j2uub5rd0968ois7h963i0np373t6k.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/stream_chat_v1/example/android/app/src/debug/AndroidManifest.xml b/stream_chat_v1/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..c208884 --- /dev/null +++ b/stream_chat_v1/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/stream_chat_v1/example/android/app/src/main/AndroidManifest.xml b/stream_chat_v1/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0492c6d --- /dev/null +++ b/stream_chat_v1/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/stream_chat_v1/example/android/app/src/main/kotlin/com/example/example/Application.kt b/stream_chat_v1/example/android/app/src/main/kotlin/com/example/example/Application.kt new file mode 100644 index 0000000..dd47a03 --- /dev/null +++ b/stream_chat_v1/example/android/app/src/main/kotlin/com/example/example/Application.kt @@ -0,0 +1,27 @@ +package com.example.example + +import com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin +import io.flutter.app.FlutterApplication +import io.flutter.plugin.common.PluginRegistry +import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback +import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin +import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService +import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin +import io.flutter.plugins.pathprovider.PathProviderPlugin + +class Application : FlutterApplication(), PluginRegistrantCallback { + override fun onCreate() { + super.onCreate() + FlutterFirebaseMessagingService.setPluginRegistrant(this) + } + + override fun registerWith(registry: PluginRegistry?) { + PathProviderPlugin.registerWith(registry?.registrarFor( + "io.flutter.plugins.pathprovider.PathProviderPlugin")) + SharedPreferencesPlugin.registerWith(registry?.registrarFor( + "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")) + FlutterLocalNotificationsPlugin.registerWith(registry?.registrarFor( + "com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin")) + FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) + } +} \ No newline at end of file diff --git a/stream_chat_v1/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/stream_chat_v1/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..1656503 --- /dev/null +++ b/stream_chat_v1/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -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); + } +} diff --git a/stream_chat_v1/example/android/app/src/main/res/drawable/launch_background.xml b/stream_chat_v1/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/stream_chat_v1/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/stream_chat_v1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/stream_chat_v1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..9179767 Binary files /dev/null and b/stream_chat_v1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/stream_chat_v1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/stream_chat_v1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..103da98 Binary files /dev/null and b/stream_chat_v1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/stream_chat_v1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/stream_chat_v1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..1718335 Binary files /dev/null and b/stream_chat_v1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/stream_chat_v1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/stream_chat_v1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..a111f50 Binary files /dev/null and b/stream_chat_v1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/stream_chat_v1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/stream_chat_v1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..fc254c2 Binary files /dev/null and b/stream_chat_v1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/stream_chat_v1/example/android/app/src/main/res/values/styles.xml b/stream_chat_v1/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/stream_chat_v1/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/stream_chat_v1/example/android/app/src/profile/AndroidManifest.xml b/stream_chat_v1/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..c208884 --- /dev/null +++ b/stream_chat_v1/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/stream_chat_v1/example/android/build.gradle b/stream_chat_v1/example/android/build.gradle new file mode 100644 index 0000000..8c57e18 --- /dev/null +++ b/stream_chat_v1/example/android/build.gradle @@ -0,0 +1,32 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.6.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:4.3.2' + } +} + +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 +} diff --git a/stream_chat_v1/example/android/gradle.properties b/stream_chat_v1/example/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/stream_chat_v1/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/stream_chat_v1/example/android/gradle/wrapper/gradle-wrapper.properties b/stream_chat_v1/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4a4c204 --- /dev/null +++ b/stream_chat_v1/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Oct 22 11:03:39 CEST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/stream_chat_v1/example/android/settings.gradle b/stream_chat_v1/example/android/settings.gradle new file mode 100644 index 0000000..5a2f14f --- /dev/null +++ b/stream_chat_v1/example/android/settings.gradle @@ -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 +} diff --git a/stream_chat_v1/example/android/settings_aar.gradle b/stream_chat_v1/example/android/settings_aar.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/stream_chat_v1/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/stream_chat_v1/example/assets/android_icon.png b/stream_chat_v1/example/assets/android_icon.png new file mode 100644 index 0000000..3b85d6c Binary files /dev/null and b/stream_chat_v1/example/assets/android_icon.png differ diff --git a/stream_chat_v1/example/assets/icon_arrow_right.svg b/stream_chat_v1/example/assets/icon_arrow_right.svg new file mode 100644 index 0000000..7cfa0c5 --- /dev/null +++ b/stream_chat_v1/example/assets/icon_arrow_right.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/stream_chat_v1/example/assets/ios_icon.png b/stream_chat_v1/example/assets/ios_icon.png new file mode 100644 index 0000000..314eb1d Binary files /dev/null and b/stream_chat_v1/example/assets/ios_icon.png differ diff --git a/stream_chat_v1/example/assets/logo.svg b/stream_chat_v1/example/assets/logo.svg new file mode 100644 index 0000000..49eecf7 --- /dev/null +++ b/stream_chat_v1/example/assets/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/stream_chat_v1/example/ios/.gitignore b/stream_chat_v1/example/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/stream_chat_v1/example/ios/.gitignore @@ -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 diff --git a/stream_chat_v1/example/ios/Flutter/AppFrameworkInfo.plist b/stream_chat_v1/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/stream_chat_v1/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/stream_chat_v1/example/ios/Flutter/Debug.xcconfig b/stream_chat_v1/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/stream_chat_v1/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/stream_chat_v1/example/ios/Flutter/Release.xcconfig b/stream_chat_v1/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/stream_chat_v1/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/stream_chat_v1/example/ios/Gemfile b/stream_chat_v1/example/ios/Gemfile new file mode 100644 index 0000000..cdd3a6b --- /dev/null +++ b/stream_chat_v1/example/ios/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/stream_chat_v1/example/ios/Gemfile.lock b/stream_chat_v1/example/ios/Gemfile.lock new file mode 100644 index 0000000..dc0ee04 --- /dev/null +++ b/stream_chat_v1/example/ios/Gemfile.lock @@ -0,0 +1,180 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + atomos (0.1.3) + aws-eventstream (1.1.0) + aws-partitions (1.380.0) + aws-sdk-core (3.109.1) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.83.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.3) + claide (1.0.3) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.20) + declarative-option (0.1.0) + digest-crc (0.6.1) + rake (~> 13.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + emoji_regex (3.0.0) + excon (0.76.0) + faraday (1.0.1) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday_middleware (1.0.0) + faraday (~> 1.0) + fastimage (2.2.0) + fastlane (2.162.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.37.0, < 0.39.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-firebase_app_distribution (0.2.3) + gh_inspector (1.1.3) + google-api-client (0.38.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.3.3) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.0.1) + google-cloud-storage (1.29.1) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.13.1) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.14) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.4.0) + json (2.3.1) + jwt (2.2.2) + memoist (0.16.2) + mini_magick (4.10.1) + mini_mime (1.0.2) + multi_json (1.15.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + naturally (2.2.0) + os (1.1.1) + plist (3.5.0) + public_suffix (4.0.6) + rake (13.0.1) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + rubyzip (2.3.0) + security (0.1.3) + signet (0.14.0) + addressable (~> 2.3) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + slack-notifier (2.3.2) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) + word_wrap (1.0.0) + xcodeproj (1.18.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane + fastlane-plugin-firebase_app_distribution + +BUNDLED WITH + 2.0.2 diff --git a/stream_chat_v1/example/ios/Notifications/Info.plist b/stream_chat_v1/example/ios/Notifications/Info.plist new file mode 100644 index 0000000..a225b5c --- /dev/null +++ b/stream_chat_v1/example/ios/Notifications/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Notifications + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/stream_chat_v1/example/ios/Notifications/NotificationService.swift b/stream_chat_v1/example/ios/Notifications/NotificationService.swift new file mode 100644 index 0000000..976e8d6 --- /dev/null +++ b/stream_chat_v1/example/ios/Notifications/NotificationService.swift @@ -0,0 +1,182 @@ +// +// NotificationService.swift +// Notifications +// +// Created by Salvatore Giordano on 25/03/2020. +// Copyright © 2020 The Chromium Authors. All rights reserved. +// + +import UserNotifications +//import StreamChatClient + +final class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + guard let sharedDefaults = UserDefaults(suiteName: "group.io.getstream.flutter"), + let apiKey = sharedDefaults.string(forKey: "KEY_API_KEY"), + let userId = sharedDefaults.string(forKey: "KEY_USER_ID"), + let token = sharedDefaults.string(forKey: "KEY_TOKEN"), + let messageId = bestAttemptContent?.userInfo["message_id"] as? String else { + 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 ?? "")" +// contentHandler(self?.bestAttemptContent ?? request.content) +// } +// Client.shared.disconnect() +// } +// } +// } + } + + override func serviceExtensionTimeWillExpire() { + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } +} + +//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() +// /// An extra data for the channel. +// public let extraData: Codable? +//} diff --git a/stream_chat_v1/example/ios/Notifications/Notifications.entitlements b/stream_chat_v1/example/ios/Notifications/Notifications.entitlements new file mode 100644 index 0000000..e5b7189 --- /dev/null +++ b/stream_chat_v1/example/ios/Notifications/Notifications.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.io.getstream.flutter + + + diff --git a/stream_chat_v1/example/ios/Podfile b/stream_chat_v1/example/ios/Podfile new file mode 100644 index 0000000..63861e2 --- /dev/null +++ b/stream_chat_v1/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '11.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 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 + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + 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_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +pod 'StreamChatClient' + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/stream_chat_v1/example/ios/Podfile.lock b/stream_chat_v1/example/ios/Podfile.lock new file mode 100644 index 0000000..f266b65 --- /dev/null +++ b/stream_chat_v1/example/ios/Podfile.lock @@ -0,0 +1,299 @@ +PODS: + - DKImagePickerController/Core (4.3.2): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.2) + - DKImagePickerController/PhotoGallery (4.3.2): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.2) + - DKPhotoGallery (0.0.17): + - DKPhotoGallery/Core (= 0.0.17) + - DKPhotoGallery/Model (= 0.0.17) + - DKPhotoGallery/Preview (= 0.0.17) + - DKPhotoGallery/Resource (= 0.0.17) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.17): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.17): + - SDWebImage + - SwiftyGif + - esys_flutter_share (0.0.1): + - Flutter + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter + - Firebase/CoreOnly (6.33.0): + - FirebaseCore (= 6.10.3) + - Firebase/Messaging (6.33.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 4.7.0) + - firebase_core (0.5.3): + - Firebase/CoreOnly (~> 6.33.0) + - Flutter + - firebase_messaging (7.0.3): + - Firebase/CoreOnly (~> 6.33.0) + - Firebase/Messaging (~> 6.33.0) + - firebase_core + - Flutter + - FirebaseCore (6.10.3): + - FirebaseCoreDiagnostics (~> 1.6) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Logger (~> 6.7) + - FirebaseCoreDiagnostics (1.7.0): + - GoogleDataTransport (~> 7.4) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Logger (~> 6.7) + - nanopb (~> 1.30906.0) + - FirebaseInstallations (1.7.0): + - FirebaseCore (~> 6.10) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.8.0): + - FirebaseCore (~> 6.10) + - FirebaseInstallations (~> 1.6) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) + - FirebaseMessaging (4.7.1): + - FirebaseCore (~> 6.10) + - FirebaseInstanceID (~> 4.7) + - GoogleUtilities/AppDelegateSwizzler (~> 6.7) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Reachability (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) + - Protobuf (>= 3.9.2, ~> 3.9) + - Flutter (1.0.0) + - flutter_apns (0.0.1): + - Flutter + - flutter_app_badger (0.0.1): + - Flutter + - flutter_keyboard_visibility (0.0.1): + - Flutter + - flutter_local_notifications (0.0.1): + - Flutter + - flutter_secure_storage (3.3.1): + - Flutter + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - GoogleDataTransport (7.5.1): + - nanopb (~> 1.30906.0) + - GoogleUtilities/AppDelegateSwizzler (6.7.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.7.2): + - PromisesObjC (~> 1.2) + - GoogleUtilities/Logger (6.7.2): + - GoogleUtilities/Environment + - GoogleUtilities/Network (6.7.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.7.2)" + - GoogleUtilities/Reachability (6.7.2): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.7.2): + - GoogleUtilities/Logger + - image_gallery_saver (1.5.0): + - Flutter + - image_picker (0.0.1): + - Flutter + - 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 + - photo_manager (0.0.1): + - Flutter + - PromisesObjC (1.2.11) + - Protobuf (3.13.0) + - SDWebImage (5.10.0): + - SDWebImage/Core (= 5.10.0) + - SDWebImage/Core (5.10.0) + - shared_preferences (0.0.1): + - Flutter + - sqflite (0.0.2): + - Flutter + - FMDB (>= 2.7.5) + - 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.2): + - Starscream (~> 4.0) + - SwiftyGif (5.3.0) + - url_launcher (0.0.1): + - Flutter + - video_compress (0.3.0): + - Flutter + - video_player (0.0.1): + - Flutter + - wakelock (0.0.1): + - Flutter + +DEPENDENCIES: + - esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`) + - 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_app_badger (from `.symlinks/plugins/flutter_app_badger/ios`) + - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) + - path_provider (from `.symlinks/plugins/path_provider/ios`) + - photo_manager (from `.symlinks/plugins/photo_manager/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) + - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) + - StreamChatClient + - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - video_compress (from `.symlinks/plugins/video_compress/ios`) + - video_player (from `.symlinks/plugins/video_player/ios`) + - wakelock (from `.symlinks/plugins/wakelock/ios`) + +SPEC REPOS: + trunk: + - DKImagePickerController + - DKPhotoGallery + - Firebase + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseInstallations + - FirebaseInstanceID + - FirebaseMessaging + - FMDB + - GoogleDataTransport + - GoogleUtilities + - nanopb + - PromisesObjC + - Protobuf + - SDWebImage + - sqlite3 + - Starscream + - StreamChatClient + - SwiftyGif + +EXTERNAL SOURCES: + esys_flutter_share: + :path: ".symlinks/plugins/esys_flutter_share/ios" + 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: + :path: Flutter + flutter_apns: + :path: ".symlinks/plugins/flutter_apns/ios" + flutter_app_badger: + :path: ".symlinks/plugins/flutter_app_badger/ios" + flutter_keyboard_visibility: + :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" + image_gallery_saver: + :path: ".symlinks/plugins/image_gallery_saver/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" + path_provider: + :path: ".symlinks/plugins/path_provider/ios" + photo_manager: + :path: ".symlinks/plugins/photo_manager/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/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" + video_compress: + :path: ".symlinks/plugins/video_compress/ios" + video_player: + :path: ".symlinks/plugins/video_player/ios" + wakelock: + :path: ".symlinks/plugins/wakelock/ios" + +SPEC CHECKSUMS: + DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d + DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 + esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4 + file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 + Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 + firebase_core: 5d6a02f3d85acd5f8321c2d6d62877626a670659 + firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 + FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd + FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 + FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2 + FirebaseInstanceID: bd3ffc24367f901a43c063b36c640b345a4a5dd1 + FirebaseMessaging: 5eca4ef173de76253352511aafef774caa1cba2a + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_apns: ddc629f26016140bf52165040b0a8e8869f9ce32 + flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68 + flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 + flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 + flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 + GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 + image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2 + image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 + nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc + path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67 + PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f + Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748 + SDWebImage: 9169792e9eec3e45bba2a0c02f74bf8bd922d1ee + shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d + sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + sqlite3: 8f7d2078ae27778699a622a94b853285793422a2 + sqlite3_flutter_libs: 5651f8ff48e3b44d910863c4ea5916085b1b245f + Starscream: 5178aed56b316f13fa3bc55694e583d35dd414d9 + StreamChatClient: fc6419fa7ceda9c048188bb7c8e9f07755ced05f + SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 + url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef + video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe + video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e + wakelock: bfc7955c418d0db797614075aabbc58a39ab5107 + +PODFILE CHECKSUM: eb001256612a59f8f9e4d083ad8b9671e69dd184 + +COCOAPODS: 1.10.0 diff --git a/stream_chat_v1/example/ios/Runner.xcodeproj/project.pbxproj b/stream_chat_v1/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1e7527e --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,845 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0BC14C50242B5A7A0028DE94 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC14C4F242B5A7A0028DE94 /* NotificationService.swift */; }; + 0BC14C54242B5A7A0028DE94 /* Notifications.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7DEC2743BD66C91B700A3B97 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BB2E5E4E236267EDF0D8817 /* Pods_Runner.framework */; }; + 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 PBXContainerItemProxy section */ + 0BC14C52242B5A7A0028DE94 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0BC14C4C242B5A7A0028DE94; + remoteInfo = Notifications; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 0BC14C55242B5A7A0028DE94 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 0BC14C54242B5A7A0028DE94 /* Notifications.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Notifications.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0BC14C4F242B5A7A0028DE94 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 0BC14C51242B5A7A0028DE94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0BC14C5A242B5ED90028DE94 /* Notifications.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Notifications.entitlements; sourceTree = ""; }; + 0BC14C5B242B5FF50028DE94 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2452A9E77396497EB4CF3072 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 68F846A6DB42D92393F5F7E0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7BF51EE28C89025F73A5211F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 8BB2E5E4E236267EDF0D8817 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0BC14C4A242B5A7A0028DE94 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7DEC2743BD66C91B700A3B97 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0BC14C4E242B5A7A0028DE94 /* Notifications */ = { + isa = PBXGroup; + children = ( + 0BC14C5A242B5ED90028DE94 /* Notifications.entitlements */, + 0BC14C4F242B5A7A0028DE94 /* NotificationService.swift */, + 0BC14C51242B5A7A0028DE94 /* Info.plist */, + ); + path = Notifications; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 97C146F01CF9000F007C117D /* Runner */, + 9740EEB11CF90186004384FC /* Flutter */, + 0BC14C4E242B5A7A0028DE94 /* Notifications */, + 97C146EF1CF9000F007C117D /* Products */, + CF168B61BAB91958681C7C21 /* Pods */, + BC09A38346C8B2CD72199469 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 0BC14C5B242B5FF50028DE94 /* Runner.entitlements */, + 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 = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; + BC09A38346C8B2CD72199469 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8BB2E5E4E236267EDF0D8817 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CF168B61BAB91958681C7C21 /* Pods */ = { + isa = PBXGroup; + children = ( + 2452A9E77396497EB4CF3072 /* Pods-Runner.debug.xcconfig */, + 7BF51EE28C89025F73A5211F /* Pods-Runner.release.xcconfig */, + 68F846A6DB42D92393F5F7E0 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0BC14C4C242B5A7A0028DE94 /* Notifications */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0BC14C59242B5A7A0028DE94 /* Build configuration list for PBXNativeTarget "Notifications" */; + buildPhases = ( + 0BC14C49242B5A7A0028DE94 /* Sources */, + 0BC14C4A242B5A7A0028DE94 /* Frameworks */, + 0BC14C4B242B5A7A0028DE94 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Notifications; + productName = Notifications; + productReference = 0BC14C4D242B5A7A0028DE94 /* Notifications.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9E02B5C38D6CC2455D9E48E9 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 5702861DACEDB848A3E454E8 /* [CP] Embed Pods Frameworks */, + 0BC14C55242B5A7A0028DE94 /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 0BC14C53242B5A7A0028DE94 /* PBXTargetDependency */, + ); + 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 = { + LastSwiftUpdateCheck = 1140; + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 0BC14C4C242B5A7A0028DE94 = { + CreatedOnToolsVersion = 11.4; + DevelopmentTeam = EHV7XZLAHA; + ProvisioningStyle = Manual; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = EHV7XZLAHA; + LastSwiftMigration = 1100; + ProvisioningStyle = Manual; + }; + }; + }; + 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 */, + 0BC14C4C242B5A7A0028DE94 /* Notifications */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0BC14C4B242B5A7A0028DE94 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 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\" embed_and_thin"; + }; + 5702861DACEDB848A3E454E8 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + 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}/esys_flutter_share/esys_flutter_share.framework", + "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", + "${BUILT_PRODUCTS_DIR}/flutter_apns/flutter_apns.framework", + "${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework", + "${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework", + "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", + "${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework", + "${BUILT_PRODUCTS_DIR}/image_gallery_saver/image_gallery_saver.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}/photo_manager/photo_manager.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_compress/video_compress.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}/esys_flutter_share.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_app_badger.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}/flutter_secure_storage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_gallery_saver.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}/photo_manager.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_compress.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + 9E02B5C38D6CC2455D9E48E9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0BC14C49242B5A7A0028DE94 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0BC14C50242B5A7A0028DE94 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0BC14C53242B5A7A0028DE94 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0BC14C4C242B5A7A0028DE94 /* Notifications */; + targetProxy = 0BC14C52242B5A7A0028DE94 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 0BC14C56242B5A7A0028DE94 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Notifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter.Notifications"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0BC14C57242B5A7A0028DE94 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Notifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter.Notifications"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0BC14C58242B5A7A0028DE94 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Notifications/Notifications.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Notifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter.Notifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter.Notifications"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + 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 = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + 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; + 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 = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter"; + 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 = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = EHV7XZLAHA; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.getstream.flutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "match AdHoc io.getstream.flutter"; + 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 */ + 0BC14C59242B5A7A0028DE94 /* Build configuration list for PBXNativeTarget "Notifications" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0BC14C56242B5A7A0028DE94 /* Debug */, + 0BC14C57242B5A7A0028DE94 /* Release */, + 0BC14C58242B5A7A0028DE94 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 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 */; +} diff --git a/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/stream_chat_v1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Notifications.xcscheme b/stream_chat_v1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Notifications.xcscheme new file mode 100644 index 0000000..9387f0d --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Notifications.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stream_chat_v1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/stream_chat_v1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..fb2dffc --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stream_chat_v1/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/stream_chat_v1/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/stream_chat_v1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/stream_chat_v1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/stream_chat_v1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/stream_chat_v1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/stream_chat_v1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/stream_chat_v1/example/ios/Runner/AppDelegate.swift b/stream_chat_v1/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..e024c1e --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,45 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + let sharedDefaults = UserDefaults(suiteName: "group.io.getstream.flutter") + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + if let messageQueue = sharedDefaults?.stringArray(forKey: "messageQueue") { + UserDefaults.standard.setValue(messageQueue, forKey: "flutter.messageQueue") + sharedDefaults?.removeObject(forKey: "messageQueue") + } + + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self + } + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func applicationDidEnterBackground(_ application: UIApplication) { + if let apiKey = UserDefaults.standard.string(forKey: "flutter.KEY_API_KEY") { + sharedDefaults?.setValue(apiKey, forKey: "KEY_API_KEY") + } + + if let token = UserDefaults.standard.string(forKey: "flutter.KEY_TOKEN") { + sharedDefaults?.setValue(token, forKey: "KEY_TOKEN") + } + + if let userId = UserDefaults.standard.string(forKey: "flutter.KEY_USER_ID") { + sharedDefaults?.setValue(userId, forKey: "KEY_USER_ID") + } + } + + override func applicationWillEnterForeground(_ application: UIApplication) { + if let messageQueue = sharedDefaults?.stringArray(forKey: "messageQueue") { + UserDefaults.standard.setValue(messageQueue, forKey: "flutter.messageQueue") + sharedDefaults?.removeObject(forKey: "messageQueue") + } + } +} diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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" + } +} diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..6286cc9 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..95d13de Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..5ad8018 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6e6c85b Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..6f26f22 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..7fe870d Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..3ab319a Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..5ad8018 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..0a94384 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..f049762 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..f049762 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..b51e00a Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..8c49730 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..c59c573 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..b29e2f0 Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -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" + } +} diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -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. \ No newline at end of file diff --git a/stream_chat_v1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/stream_chat_v1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stream_chat_v1/example/ios/Runner/Base.lproj/Main.storyboard b/stream_chat_v1/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stream_chat_v1/example/ios/Runner/Info.plist b/stream_chat_v1/example/ios/Runner/Info.plist new file mode 100644 index 0000000..7e63f46 --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ChatSample + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSAppleMusicUsageDescription + Used to send message attachments + NSCameraUsageDescription + Used to send message attachments + NSMicrophoneUsageDescription + Used to send message attachments + NSPhotoLibraryUsageDescription + Used to send message attachments + UIBackgroundModes + + fetch + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UIBackgroundModes + + remote-notification + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/stream_chat_v1/example/ios/Runner/Runner-Bridging-Header.h b/stream_chat_v1/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..7335fdf --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/stream_chat_v1/example/ios/Runner/Runner.entitlements b/stream_chat_v1/example/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..433bdfe --- /dev/null +++ b/stream_chat_v1/example/ios/Runner/Runner.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.security.application-groups + + group.io.getstream.flutter + + + diff --git a/stream_chat_v1/example/ios/fastlane/Appfile b/stream_chat_v1/example/ios/fastlane/Appfile new file mode 100644 index 0000000..1803063 --- /dev/null +++ b/stream_chat_v1/example/ios/fastlane/Appfile @@ -0,0 +1,6 @@ +# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app +# apple_id("[[APPLE_ID]]") # Your Apple email address + + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/stream_chat_v1/example/ios/fastlane/Fastfile b/stream_chat_v1/example/ios/fastlane/Fastfile new file mode 100644 index 0000000..ec2adc8 --- /dev/null +++ b/stream_chat_v1/example/ios/fastlane/Fastfile @@ -0,0 +1,46 @@ +fastlane_version "2.162.0" +default_platform :ios + +before_all do + if is_ci + setup_ci() + end +end + +desc "Installs all Certs and Profiles necessary for development and ad-hoc" +lane :match_me do + match( + type: "adhoc", + app_identifier: [ + "io.getstream.flutter", + "io.getstream.flutter.Notifications" + ], + readonly: is_ci, + force_for_new_devices: true + ) +end + +platform :ios do + desc "Deploy build to Firebase" + lane :deploy_to_firebase do + match_me + + gym( + workspace: "./Runner.xcworkspace", + scheme: "Runner", + export_method: "ad-hoc", + export_options: "./fastlane/beta_gym_export_options.plist", + silent: true, + clean: true, + include_symbols: true, + output_directory: "./dist" + ) + + message = changelog_from_git_commits(commits_count: 10) + + firebase_app_distribution( + app: "1:674907137625:ios:cafb9fb076a453c4d7f348", + groups: "ios-stream-testers" + ) + end +end \ No newline at end of file diff --git a/stream_chat_v1/example/ios/fastlane/Matchfile b/stream_chat_v1/example/ios/fastlane/Matchfile new file mode 100644 index 0000000..cc976de --- /dev/null +++ b/stream_chat_v1/example/ios/fastlane/Matchfile @@ -0,0 +1,16 @@ +git_url("https://github.com/GetStream/ios-certificates") + +storage_mode("git") + +username("salvatore@getstream.io") + +team_id("EHV7XZLAHA") + +# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"]) + +# username("user@fastlane.tools") # Your Apple Developer Portal username + +# For all available options run `fastlane match --help` +# Remove the # in the beginning of the line to enable the other options + +# The docs are available on https://docs.fastlane.tools/actions/match diff --git a/stream_chat_v1/example/ios/fastlane/Pluginfile b/stream_chat_v1/example/ios/fastlane/Pluginfile new file mode 100644 index 0000000..b18539b --- /dev/null +++ b/stream_chat_v1/example/ios/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-firebase_app_distribution' diff --git a/stream_chat_v1/example/ios/fastlane/README.md b/stream_chat_v1/example/ios/fastlane/README.md new file mode 100644 index 0000000..e464ef2 --- /dev/null +++ b/stream_chat_v1/example/ios/fastlane/README.md @@ -0,0 +1,37 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew install fastlane` + +# Available Actions +### match_me +``` +fastlane match_me +``` +Installs all Certs and Profiles necessary for development and ad-hoc + +---- + +## iOS +### ios deploy_to_firebase +``` +fastlane ios deploy_to_firebase +``` +Deploy build to Firebase + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/stream_chat_v1/example/ios/fastlane/beta_gym_export_options.plist b/stream_chat_v1/example/ios/fastlane/beta_gym_export_options.plist new file mode 100644 index 0000000..53c7409 --- /dev/null +++ b/stream_chat_v1/example/ios/fastlane/beta_gym_export_options.plist @@ -0,0 +1,15 @@ + + + + + iCloudContainerEnvironment + Development + provisioningProfiles + + io.getstream.flutter + match AdHoc io.getstream.flutter + io.getstream.flutter.Notifications + match AdHoc io.getstream.flutter.Notifications + + + \ No newline at end of file diff --git a/stream_chat_v1/example/ios/fastlane/report.xml b/stream_chat_v1/example/ios/fastlane/report.xml new file mode 100644 index 0000000..69eca3e --- /dev/null +++ b/stream_chat_v1/example/ios/fastlane/report.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stream_chat_v1/example/lib/advanced_options_page.dart b/stream_chat_v1/example/lib/advanced_options_page.dart new file mode 100644 index 0000000..35e2af7 --- /dev/null +++ b/stream_chat_v1/example/lib/advanced_options_page.dart @@ -0,0 +1,328 @@ +import 'package:example/routes/routes.dart'; +import 'package:example/stream_version.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'choose_user_page.dart'; +import 'main.dart'; + +class AdvancedOptionsPage extends StatefulWidget { + @override + _AdvancedOptionsPageState createState() => _AdvancedOptionsPageState(); +} + +class _AdvancedOptionsPageState extends State { + final _formKey = GlobalKey(); + + final TextEditingController _apiKeyController = TextEditingController(); + String _apiKeyError; + + final TextEditingController _userIdController = TextEditingController(); + String _userIdError; + + final TextEditingController _userTokenController = TextEditingController(); + String _userTokenError; + + final TextEditingController _usernameController = TextEditingController(); + + bool loading = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + resizeToAvoidBottomPadding: false, + appBar: AppBar( + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + elevation: 1, + centerTitle: true, + brightness: Theme.of(context).brightness, + title: Text( + 'Advanced Options', + style: StreamChatTheme.of(context) + .textTheme + .headlineBold + .copyWith(color: StreamChatTheme.of(context).colorTheme.black), + ), + leading: IconButton( + icon: StreamSvgIcon.left( + color: StreamChatTheme.of(context).colorTheme.black, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + body: Builder( + builder: (context) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + controller: _apiKeyController, + onChanged: (_) { + if (_apiKeyError != null) { + setState(() { + _apiKeyError = null; + }); + } + }, + validator: (value) { + if (value.isEmpty) { + setState(() { + _apiKeyError = 'Please enter the Chat API Key'; + }); + return _apiKeyError; + } + return null; + }, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.black, + ), + decoration: InputDecoration( + errorStyle: TextStyle(height: 0, fontSize: 0), + labelStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: _apiKeyError != null + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context).colorTheme.grey, + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + filled: true, + labelText: + 'Chat API Key ${_apiKeyError != null ? ': $_apiKeyError' : ''}', + ), + textInputAction: TextInputAction.next, + ), + SizedBox(height: 8), + TextFormField( + controller: _userIdController, + onChanged: (_) { + if (_userIdError != null) { + setState(() { + _userIdError = null; + }); + } + }, + validator: (value) { + if (value.isEmpty) { + setState(() { + _userIdError = 'Please enter the User ID'; + }); + return _userIdError; + } + return null; + }, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.black, + ), + textInputAction: TextInputAction.next, + decoration: InputDecoration( + errorStyle: TextStyle(height: 0, fontSize: 0), + labelStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: _userIdError != null + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context).colorTheme.grey, + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + filled: true, + labelText: + 'User ID ${_userIdError != null ? ': $_userIdError' : ''}', + ), + ), + SizedBox(height: 8), + TextFormField( + onChanged: (_) { + if (_userTokenError != null) { + setState(() { + _userTokenError = null; + }); + } + }, + controller: _userTokenController, + validator: (value) { + if (value.isEmpty) { + setState(() { + _userTokenError = 'Please enter the user token'; + }); + return _userTokenError; + } + return null; + }, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.black, + ), + textInputAction: TextInputAction.next, + decoration: InputDecoration( + errorStyle: TextStyle(height: 0, fontSize: 0), + labelStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: _userTokenError != null + ? StreamChatTheme.of(context).colorTheme.accentRed + : StreamChatTheme.of(context).colorTheme.grey, + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + filled: true, + labelText: + 'User Token ${_userTokenError != null ? ': $_userTokenError' : ''}', + ), + ), + SizedBox(height: 8), + TextFormField( + controller: _usernameController, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + fillColor: + StreamChatTheme.of(context).colorTheme.whiteSmoke, + filled: true, + labelText: 'Username (optional)', + ), + ), + Spacer(), + RaisedButton( + color: Theme.of(context).brightness == Brightness.light + ? StreamChatTheme.of(context).colorTheme.accentBlue + : Colors.white, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + child: Text( + 'Login', + style: TextStyle( + fontSize: 16, + color: Theme.of(context).brightness != Brightness.light + ? StreamChatTheme.of(context).colorTheme.accentBlue + : Colors.white, + ), + ), + onPressed: () async { + if (loading) { + return; + } + if (_formKey.currentState.validate()) { + final apiKey = _apiKeyController.text; + final userId = _userIdController.text; + final userToken = _userTokenController.text; + final username = _usernameController.text; + + loading = true; + showDialog( + barrierDismissible: false, + context: context, + barrierColor: + StreamChatTheme.of(context).colorTheme.overlay, + builder: (context) => Center( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: StreamChatTheme.of(context) + .colorTheme + .white, + ), + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), + ), + ), + ); + + final client = Client( + apiKey, + logLevel: Level.INFO, + )..chatPersistenceClient = chatPersistentClient; + + try { + await client.setUser( + User(id: userId, extraData: { + 'name': username, + }), + userToken, + ); + + final secureStorage = FlutterSecureStorage(); + secureStorage.write( + key: kStreamApiKey, + value: apiKey, + ); + secureStorage.write( + key: kStreamUserId, + value: userId, + ); + secureStorage.write( + key: kStreamToken, + value: userToken, + ); + } catch (e) { + var errorText = 'Error connecting, retry'; + if (e is Map) { + errorText = e['message'] ?? errorText; + } + Navigator.pop(context); + setState(() { + _apiKeyError = errorText; + }); + loading = false; + await client.disconnect(); + return; + } + loading = false; + await Navigator.pushNamedAndRemoveUntil( + context, + Routes.APP, + ModalRoute.withName(Routes.APP), + arguments: client, + ); + } + }, + ), + StreamVersion(), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/chat_info_screen.dart b/stream_chat_v1/example/lib/chat_info_screen.dart new file mode 100644 index 0000000..a6b14de --- /dev/null +++ b/stream_chat_v1/example/lib/chat_info_screen.dart @@ -0,0 +1,600 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; + +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'main.dart'; +import 'routes/routes.dart'; + +/// Detail screen for a 1:1 chat correspondence +class ChatInfoScreen extends StatefulWidget { + /// User in consideration + final User user; + + const ChatInfoScreen({Key key, this.user}) : super(key: key); + + @override + _ChatInfoScreenState createState() => _ChatInfoScreenState(); +} + +class _ChatInfoScreenState extends State { + @override + Widget build(BuildContext context) { + final channel = StreamChannel.of(context).channel; + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + body: ListView( + children: [ + _buildUserHeader(), + Container( + height: 8.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + _buildOptionListTiles(), + Container( + height: 8.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + if ([ + 'admin', + 'owner', + ].contains(channel.state.members + .firstWhere((m) => m.userId == channel.client.state.user.id, + orElse: () => null) + ?.role)) + _buildDeleteListTile(), + ], + ), + ); + } + + Widget _buildUserHeader() { + return Material( + color: StreamChatTheme.of(context).colorTheme.whiteSnow, + child: SafeArea( + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: UserAvatar( + user: widget.user, + constraints: BoxConstraints( + maxWidth: 72.0, + maxHeight: 72.0, + ), + borderRadius: BorderRadius.circular(36.0), + showOnlineStatus: false, + ), + ), + //SizedBox(height: 4.0), + Text( + widget.user.name, + style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), + ), + SizedBox(height: 7.0), + _buildConnectedTitleState(), + SizedBox(height: 15.0), + OptionListTile( + title: '@${widget.user.id}', + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + trailing: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + widget.user.name, + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + fontSize: 16.0), + ), + ), + onTap: () {}, + ), + ], + ), + Positioned( + top: 21, + left: 16, + child: InkWell( + child: StreamSvgIcon.left( + color: StreamChatTheme.of(context).colorTheme.black, + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildOptionListTiles() { + var channel = StreamChannel.of(context); + + return Column( + children: [ + // _OptionListTile( + // title: 'Notifications', + // leading: StreamSvgIcon.Icon_notification( + // size: 24.0, + // color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + // ), + // trailing: CupertinoSwitch( + // value: true, + // onChanged: (val) {}, + // ), + // onTap: () {}, + // ), + StreamBuilder( + stream: StreamChannel.of(context).channel.isMutedStream, + builder: (context, snapshot) { + return OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + title: 'Mute user', + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 22.0), + child: StreamSvgIcon.mute( + size: 24.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), + ), + trailing: snapshot.data == null + ? CircularProgressIndicator() + : CupertinoSwitch( + value: snapshot.data, + onChanged: (val) { + if (snapshot.data) { + channel.channel.unmute(); + } else { + channel.channel.mute(); + } + }, + ), + onTap: () {}, + ); + }), + // _OptionListTile( + // title: 'Block User', + // leading: StreamSvgIcon.Icon_user_delete( + // size: 24.0, + // color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + // ), + // trailing: CupertinoSwitch( + // value: widget.user.banned, + // onChanged: (val) { + // if (widget.user.banned) { + // channel.channel.shadowBan(widget.user.id, {}); + // } else { + // channel.channel.unbanUser(widget.user.id); + // } + // }, + // ), + // onTap: () {}, + // ), + OptionListTile( + title: 'Photos & Videos', + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.pictures( + size: 36.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + final channel = StreamChannel.of(context).channel; + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelMediaDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + onShowMessage: (m, c) async { + final client = StreamChat.of(context).client; + final message = m; + final channel = client.channel( + c.type, + id: c.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, + ), + ), + ), + ), + ); + }, + ), + OptionListTile( + title: 'Files', + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0), + child: StreamSvgIcon.files( + size: 32.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + final channel = StreamChannel.of(context).channel; + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelFileDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + ), + ), + ), + ), + ); + }, + ), + OptionListTile( + title: 'Shared groups', + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 22.0), + child: StreamSvgIcon.Icon_group( + size: 24.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => _SharedGroupsScreen( + StreamChat.of(context).user, widget.user))); + }, + ), + ], + ); + } + + Widget _buildDeleteListTile() { + return OptionListTile( + title: 'Delete Conversation', + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentRed, + ), + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 22.0), + child: StreamSvgIcon.delete( + color: StreamChatTheme.of(context).colorTheme.accentRed, + size: 24.0, + ), + ), + onTap: () { + _showDeleteDialog(); + }, + titleColor: StreamChatTheme.of(context).colorTheme.accentRed, + ); + } + + void _showDeleteDialog() async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: StreamChatTheme.of(context).colorTheme.accentRed, + ), + ); + var channel = StreamChannel.of(context).channel; + if (res == true) { + await channel.delete().then((value) { + Navigator.pop(context); + }); + } + } + + Widget _buildConnectedTitleState() { + var alternativeWidget; + + final otherMember = widget.user; + + if (otherMember != null) { + if (otherMember.online) { + alternativeWidget = Text( + 'Online', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ); + } else { + alternativeWidget = Text( + 'Last seen ${Jiffy(otherMember.lastActive).fromNow()}', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ); + } + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.user.online) + Material( + type: MaterialType.circle, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + constraints: BoxConstraints.tightFor( + width: 24, + height: 12, + ), + child: Material( + shape: CircleBorder(), + color: StreamChatTheme.of(context).colorTheme.accentGreen, + ), + ), + color: StreamChatTheme.of(context).colorTheme.white, + ), + alternativeWidget, + if (widget.user.online) + SizedBox( + width: 24.0, + ), + ], + ); + } +} + +class _SharedGroupsScreen extends StatefulWidget { + final User mainUser; + final User otherUser; + + _SharedGroupsScreen(this.mainUser, this.otherUser); + + @override + __SharedGroupsScreenState createState() => __SharedGroupsScreenState(); +} + +class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { + @override + Widget build(BuildContext context) { + var chat = StreamChat.of(context); + + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + centerTitle: true, + title: Text( + 'Shared Groups', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16.0), + ), + leading: Center( + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + child: StreamSvgIcon.left( + color: StreamChatTheme.of(context).colorTheme.black, + size: 24.0, + ), + width: 24.0, + height: 24.0, + ), + ), + ), + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + ), + body: FutureBuilder>( + future: chat.client.queryChannels( + filter: { + r'$and': [ + { + 'members': { + r'$in': [widget.otherUser.id], + }, + }, + { + 'members': { + r'$in': [widget.mainUser.id], + }, + } + ], + }, + ), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), + ); + } + + if (snapshot.data.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamSvgIcon.message( + size: 136.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + SizedBox(height: 16.0), + Text( + 'No Shared Groups', + style: TextStyle( + fontSize: 14.0, + color: StreamChatTheme.of(context).colorTheme.black, + ), + ), + SizedBox(height: 8.0), + Text( + 'Group shared with User will appear here.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), + ), + ], + ), + ); + } + + return ListView.builder( + itemCount: snapshot.data.length, + itemBuilder: (context, position) { + return StreamChannel( + channel: snapshot.data[position], + child: _buildListTile(snapshot.data[position]), + ); + }, + ); + }, + ), + ); + } + + Widget _buildListTile(Channel channel) { + var extraData = channel.extraData; + var members = channel.state.members; + + var textStyle = TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold); + + return Container( + height: 64.0, + child: LayoutBuilder(builder: (context, constraints) { + String title; + if (extraData['name'] == null) { + final otherMembers = members.where( + (member) => member.userId != StreamChat.of(context).user.id); + if (otherMembers.isNotEmpty) { + final maxWidth = constraints.maxWidth; + final maxChars = maxWidth / textStyle.fontSize; + var currentChars = 0; + final currentMembers = []; + otherMembers.forEach((element) { + final newLength = currentChars + element.user.name.length; + if (newLength < maxChars) { + currentChars = newLength; + currentMembers.add(element); + } + }); + + final exceedingMembers = + otherMembers.length - currentMembers.length; + title = + '${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } else { + title = 'No title'; + } + } else { + title = extraData['name']; + } + + return Column( + children: [ + Expanded( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: ChannelImage( + channel: channel, + constraints: + BoxConstraints(maxWidth: 40.0, maxHeight: 40.0), + ), + ), + Expanded( + child: Text( + title, + style: textStyle, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${channel.memberCount} members', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ), + ) + ], + ), + ), + Container( + height: 1.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), + ), + ], + ); + }), + ); + } +} diff --git a/stream_chat_v1/example/lib/chips_input_text_field.dart b/stream_chat_v1/example/lib/chips_input_text_field.dart new file mode 100644 index 0000000..7291ef0 --- /dev/null +++ b/stream_chat_v1/example/lib/chips_input_text_field.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +typedef ChipBuilder = Widget Function(BuildContext context, T chip); +typedef OnChipAdded = void Function(T chip); +typedef OnChipRemoved = void Function(T chip); + +class ChipsInputTextField extends StatefulWidget { + final TextEditingController controller; + final FocusNode focusNode; + final ValueChanged onInputChanged; + final ChipBuilder chipBuilder; + final OnChipAdded onChipAdded; + final OnChipRemoved onChipRemoved; + final String hint; + + const ChipsInputTextField({ + Key key, + @required this.chipBuilder, + @required this.controller, + this.onInputChanged, + this.focusNode, + this.onChipAdded, + this.onChipRemoved, + this.hint = 'Type a name', + }) : super(key: key); + + @override + ChipInputTextFieldState createState() => ChipInputTextFieldState(); +} + +class ChipInputTextFieldState extends State> { + final _chips = {}; + bool _pauseItemAddition = false; + + void addItem(T item) { + setState(() => _chips.add(item)); + if (widget.onChipAdded != null) widget.onChipAdded(item); + } + + void removeItem(T item) { + setState(() { + _chips.remove(item); + if (_chips.isEmpty) resumeItemAddition(); + }); + if (widget.onChipRemoved != null) widget.onChipRemoved(item); + } + + void pauseItemAddition() { + if (!_pauseItemAddition) { + setState(() => _pauseItemAddition = true); + } + widget.focusNode?.unfocus(); + } + + void resumeItemAddition() { + if (_pauseItemAddition) { + setState(() => _pauseItemAddition = false); + } + widget.focusNode?.requestFocus(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _pauseItemAddition ? resumeItemAddition : null, + child: Material( + elevation: 1, + color: StreamChatTheme.of(context).colorTheme.white, + child: Container( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Text( + 'TO:', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Wrap( + spacing: 8.0, + runSpacing: 4.0, + children: _chips.map((item) { + return widget.chipBuilder(context, item); + }).toList(), + ), + if (!_pauseItemAddition) + TextField( + controller: widget.controller, + onChanged: widget.onInputChanged, + focusNode: widget.focusNode, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + contentPadding: const EdgeInsets.only(top: 4.0), + hintText: widget.hint, + hintStyle: StreamChatTheme.of(context) + .textTheme + .body + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ), + ], + ), + ), + SizedBox(width: 12), + Align( + alignment: Alignment.bottomCenter, + child: IconButton( + icon: _chips.isEmpty + ? StreamSvgIcon.user( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + size: 24, + ) + : StreamSvgIcon.userAdd( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + size: 24, + ), + onPressed: resumeItemAddition, + alignment: Alignment.topRight, + visualDensity: VisualDensity.compact, + padding: const EdgeInsets.all(0), + splashRadius: 24, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/choose_user_page.dart b/stream_chat_v1/example/lib/choose_user_page.dart new file mode 100644 index 0000000..02852c6 --- /dev/null +++ b/stream_chat_v1/example/lib/choose_user_page.dart @@ -0,0 +1,267 @@ +import 'package:example/stream_version.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'routes/routes.dart'; + +const kStreamApiKey = 'STREAM_API_KEY'; +const kStreamUserId = 'STREAM_USER_ID'; +const kStreamToken = 'STREAM_TOKEN'; +const kDefaultStreamApiKey = 'kv7mcsxr24p8'; + +class ChooseUserPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final users = { + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8': + User( + id: 'salvatore', + extraData: { + 'name': 'Salvatore Giordano', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-USKK9FFRT-30c415e207a9-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FoaWwifQ.WnIUoB5gR2kcAsFhiDvkiD6zdHXZ-VSU2aQWWkhsvfo': + User( + id: 'sahil', + extraData: { + 'name': 'Sahil Kumar', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01EYU51M89-bbc152b40321-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmVuIn0.nAz2sNFGQwY7rl2Og2z3TGHUsdpnN53tOsUglJFvLmg': + User( + id: 'ben', + extraData: { + 'name': 'Ben Golden', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01AXAF23MG-f57403a3cb0d-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.lEq6TrZtHzjoNtf7HHRufUPyGo_pa8vg4_XhEBp4ckY': + User( + id: 'thierry', + extraData: { + 'name': 'Thierry Schellenbach', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U02RM6X6D-g28a1278a98e-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GLSI0ESshERMo2WjUpysD709NEtn1zmGimUN2an7g9o': + User( + id: 'tommaso', + extraData: { + 'name': 'Tommaso Barbugli', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U02U7SJP4-0f65a5997877-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2ZW4ifQ.z3zI4PqJnNhc-1o-VKcmb6BnnQ0oxFNCRHwEulHqcWc': + User( + id: 'deven', + extraData: { + 'name': 'Deven Joshi', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01AM7ELPTL-8a60da32704c-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVldmFzaCJ9.3EdHegTxibrz3A9cTiKmpEyawwcCVB8FXnoFzr4eKvw': + User( + id: 'neevash', + extraData: { + 'name': 'Neevash Ramdial', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01DZ046DS8-b00d321d2880-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.fnelU7HcP7QoEEsCGteNlF1fppofzNlrnpDQuIgeKCU': + User( + id: 'qatest1', + extraData: { + 'name': 'QA test 1', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.vSCqAEbs2WVmMWsOsa7065Fsjq-rsTih6qsHPynl7XM': + User( + id: 'qatest2', + extraData: { + 'name': 'QA test 2', + }, + ), + }; + + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + body: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 34, + bottom: 20, + ), + child: Center( + child: SvgPicture.asset( + 'assets/logo.svg', + height: 40, + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 13.0), + child: Text( + 'Welcome to Stream Chat', + style: StreamChatTheme.of(context).textTheme.title, + ), + ), + Text( + 'Select a user to try the Flutter SDK:', + style: StreamChatTheme.of(context).textTheme.body, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 32), + child: ListView.separated( + separatorBuilder: (context, i) { + return Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ); + }, + itemCount: users.length + 1, + itemBuilder: (context, i) { + return [ + ...users.entries.map((entry) { + final token = entry.key; + final user = entry.value; + return ListTile( + onTap: () async { + showDialog( + barrierDismissible: false, + context: context, + barrierColor: StreamChatTheme.of(context) + .colorTheme + .overlay, + builder: (context) => Center( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: StreamChatTheme.of(context) + .colorTheme + .white, + ), + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), + ), + ), + ); + + final secureStorage = FlutterSecureStorage(); + final client = StreamChat.of(context).client; + client.apiKey = kDefaultStreamApiKey; + await client.setUser( + user, + token, + ); + + secureStorage.write( + key: kStreamApiKey, + value: kDefaultStreamApiKey, + ); + secureStorage.write( + key: kStreamUserId, + value: user.id, + ); + secureStorage.write( + key: kStreamToken, + value: token, + ); + Navigator.pushNamedAndRemoveUntil( + context, + Routes.HOME, + ModalRoute.withName(Routes.HOME), + ); + }, + leading: UserAvatar( + user: user, + constraints: BoxConstraints.tight( + Size.fromRadius(20), + ), + ), + title: Text( + user.name, + style: + StreamChatTheme.of(context).textTheme.bodyBold, + ), + subtitle: Text( + 'Stream test account', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + trailing: StreamSvgIcon.arrow_right( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), + ); + }), + ListTile( + onTap: () { + Navigator.pushNamed(context, Routes.ADVANCED_OPTIONS); + }, + leading: CircleAvatar( + child: StreamSvgIcon.settings( + color: StreamChatTheme.of(context).colorTheme.black, + ), + backgroundColor: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, + ), + title: Text( + 'Advanced Options', + style: StreamChatTheme.of(context).textTheme.bodyBold, + ), + subtitle: Text( + 'Custom settings', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + trailing: SvgPicture.asset( + 'assets/icon_arrow_right.svg', + height: 24, + width: 24, + clipBehavior: Clip.none, + ), + ), + ][i]; + }, + ), + ), + ), + StreamVersion(), + ], + ), + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/custom_message.dart b/stream_chat_v1/example/lib/custom_message.dart new file mode 100644 index 0000000..5da4541 --- /dev/null +++ b/stream_chat_v1/example/lib/custom_message.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Fifth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// Customizing how messages are rendered is another very common use-case that the SDK supports easily. +/// +/// Replace the built-in message component with your own is done by passing it as a builder function to the [MessageListView] widget. +/// +/// The message builder function will get the usual [BuildContext] argument as well as the [Message] object and its position inside the list. +/// +/// If you look at the code you can see that we use [StreamChat.of] to retrieve the current user so that we can style messages own messages in a different way. +/// +/// Since custom widgets and builders are always children of [StreamChat] or part of a [Channel], +/// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly +/// or to retrieve outer scope needed such as messages from the [Channel.state]. +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) => StreamChat( + child: child, + client: client, + ), + home: ChannelListPage(), + ); + } +} + +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: 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( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + messageBuilder: _messageBuilder, + ), + ), + MessageInput(), + ], + ), + ); + } + + Widget _messageBuilder( + BuildContext context, + MessageDetails details, + List messages, + ) { + final message = details.message; + final isCurrentUser = StreamChat.of(context).user.id == message.user.id; + final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left; + final color = isCurrentUser ? Colors.blueGrey : Colors.blue; + + return Padding( + padding: EdgeInsets.all(5.0), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: color, width: 1), + borderRadius: BorderRadius.all( + Radius.circular(5.0), + ), + ), + child: ListTile( + title: Text( + message.text, + textAlign: textAlign, + ), + subtitle: Text( + message.user.extraData['name'], + textAlign: textAlign, + ), + ), + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/custom_theme.dart b/stream_chat_v1/example/lib/custom_theme.dart new file mode 100644 index 0000000..817d2e6 --- /dev/null +++ b/stream_chat_v1/example/lib/custom_theme.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Sixth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// The Flutter SDK comes with a fully designed set of widgets which you can customize to fit with your application style and typography. +/// Changing the theme of Chat widgets works in a very similar way that [MaterialApp] and [Theme] do. +/// +/// Out of the box all chat widgets use their own default styling, there are two ways to change the styling: +/// +/// 1. Initialize the [StreamChatTheme] from your existing [MaterialApp] style +/// 2. Construct a custom theme and provide all the customizations needed +/// +/// First we create a new Material [Theme] and pick [Colors.green] as swatch color. The theme is then passed to [MaterialApp] as usual. +/// +/// Then we create a new [StreamChatTheme] from the green theme we just created. +/// After saving the app you will see the UI will update several widgets to match with the new color. +/// +/// We also change the message color posted by the current user. +/// You can perform these more granular style changes using [StreamChatTheme.copyWith]. +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + final themeData = ThemeData(primarySwatch: Colors.green); + final defaultTheme = StreamChatThemeData.fromTheme(themeData); + final colorTheme = defaultTheme.colorTheme; + final customTheme = defaultTheme.merge(StreamChatThemeData( + ownMessageTheme: MessageTheme( + messageBackgroundColor: colorTheme.black, + messageText: TextStyle( + color: colorTheme.white, + ), + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(8), + ), + ), + )); + + return MaterialApp( + theme: themeData, + builder: (context, child) { + return StreamChat( + child: child, + client: client, + streamChatThemeData: customTheme, + ); + }, + home: ChannelListPage(), + ); + } +} + +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: 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( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + threadBuilder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + ); + }, + ), + ), + MessageInput(), + ], + ), + ); + } +} + +class ThreadPage extends StatelessWidget { + final Message parent; + + ThreadPage({ + Key key, + this.parent, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ThreadHeader( + parent: parent, + ), + body: Column( + children: [ + Expanded( + child: MessageListView( + parentMessage: parent, + ), + ), + MessageInput( + parentMessage: parent, + ), + ], + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/customize_channel_preview.dart b/stream_chat_v1/example/lib/customize_channel_preview.dart new file mode 100644 index 0000000..9b7deb6 --- /dev/null +++ b/stream_chat_v1/example/lib/customize_channel_preview.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Third step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// So far you’ve learned how to use the default widgets. +/// The library has been designed with composition in mind and to allow all common customizations to be very easy. +/// This means that you can change any component in your application by swapping the default widgets with the ones you build yourself. +/// +/// Let’s see how we can make some changes to the SDK’s UI components. +/// We start by changing how channel previews are shown in the channel list and include the number of unread messages for each. +/// +/// We're passing a custom widget to [ChannelListView.channelPreviewBuilder], this will override the default [ChannelPreview] and allows you to create one yourself. +/// +/// There are a couple interesting things we do in this widget: +/// +/// - Instead of creating a whole new style for the channel name, we inherit the text style from the parent theme ([StreamChatTheme.of]) and only change the color attribute +/// +/// - We loop over the list of channel messages to search for the first not deleted message ([Channel.state.messages]) +/// +/// - We retrieve the count of unread messages from [Channel.state] +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) => StreamChat( + child: child, + client: client, + ), + home: ChannelListPage(), + ); + } +} + +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: ChannelListView( + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + channelPreviewBuilder: _channelPreviewBuilder, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + ), + ), + ); + } + + Widget _channelPreviewBuilder(BuildContext context, Channel channel) { + final lastMessage = channel.state.messages.reversed + .firstWhere((message) => !message.isDeleted); + + final subtitle = (lastMessage == null ? "nothing yet" : lastMessage.text); + final opacity = channel.state.unreadCount > .0 ? 1.0 : 0.5; + + return ListTile( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => StreamChannel( + child: ChannelPage(), + channel: channel, + ), + ), + ); + }, + leading: ChannelImage( + channel: channel, + ), + title: ChannelName( + textStyle: + StreamChatTheme.of(context).channelPreviewTheme.title.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(opacity), + ), + ), + subtitle: Text(subtitle), + trailing: channel.state.unreadCount > 0 + ? CircleAvatar( + radius: 10, + child: Text(channel.state.unreadCount.toString()), + ) + : SizedBox(), + ); + } +} + +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/customize_message_widget.dart b/stream_chat_v1/example/lib/customize_message_widget.dart new file mode 100644 index 0000000..14233dd --- /dev/null +++ b/stream_chat_v1/example/lib/customize_message_widget.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Fifth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// Customizing how messages are rendered is another very common use-case that the SDK supports easily. +/// +/// Replace the built-in message component with your own is done by passing it as a builder function to the [MessageListView] widget. +/// +/// The message builder function will get the usual [BuildContext] argument as well as the [Message] object and its position inside the list. +/// +/// If you look at the code you can see that we use [StreamChat.of] to retrieve the current user so that we can style messages own messages in a different way. +/// +/// Since custom widgets and builders are always children of [StreamChat] or part of a [Channel], +/// you can use [StreamChat.of], [StreamChannel.of] and [StreamChatTheme.of] to use the API client directly +/// or to retrieve outer scope needed such as messages from the [Channel.state]. +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) => StreamChat( + child: child, + client: client, + ), + home: Container( + child: ChannelListPage(), + ), + ); + } +} + +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: 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( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + messageBuilder: _messageBuilder, + ), + ), + MessageInput(), + ], + ), + ); + } + + Widget _messageBuilder( + BuildContext context, + MessageDetails details, + List messages, + ) { + final message = details.message; + final color = details.isMyMessage ? Colors.red : Colors.blue; + if (message.isSystem) { + return SizedBox(); + } + return MessageWidget( + message: message, + messageTheme: details.isMyMessage + ? StreamChatTheme.of(context).ownMessageTheme + : StreamChatTheme.of(context).otherMessageTheme, + borderSide: BorderSide( + color: color, + width: 2, + ), + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 4, + ), + attachmentBorderSide: BorderSide( + color: color, + width: 2, + ), + attachmentPadding: EdgeInsets.all(8), + borderRadiusGeometry: BorderRadius.vertical( + top: !details.isLastUser ? Radius.circular(16) : Radius.zero, + bottom: !details.isNextUser ? Radius.circular(16) : Radius.zero, + ), + showSendingIndicator: false, + reverse: false, + showUserAvatar: + details.isNextUser ? DisplayWidget.hide : DisplayWidget.show, + showTimestamp: !details.isNextUser, + showUsername: !details.isNextUser, + showReactions: false, + ); + } +} diff --git a/stream_chat_v1/example/lib/group_chat_details_screen.dart b/stream_chat_v1/example/lib/group_chat_details_screen.dart new file mode 100644 index 0000000..afc9d12 --- /dev/null +++ b/stream_chat_v1/example/lib/group_chat_details_screen.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:uuid/uuid.dart'; + +import 'main.dart'; +import 'routes/routes.dart'; + +class GroupChatDetailsScreen extends StatefulWidget { + final List selectedUsers; + + const GroupChatDetailsScreen({ + Key key, + @required this.selectedUsers, + }) : super(key: key); + + @override + _GroupChatDetailsScreenState createState() => _GroupChatDetailsScreenState(); +} + +class _GroupChatDetailsScreenState extends State { + final _selectedUsers = []; + + TextEditingController _groupNameController; + + bool _isGroupNameEmpty = true; + + int get _totalUsers => _selectedUsers.length; + + void _groupNameListener() { + final name = _groupNameController.text; + if (mounted) { + setState(() { + _isGroupNameEmpty = name.isEmpty; + }); + } + } + + @override + void initState() { + super.initState(); + _selectedUsers.addAll(widget.selectedUsers); + _groupNameController = TextEditingController() + ..addListener(_groupNameListener); + } + + @override + void dispose() { + _groupNameController?.removeListener(_groupNameListener); + _groupNameController?.clear(); + _groupNameController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + Navigator.pop(context, _selectedUsers); + return false; + }, + child: Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + leading: const StreamBackButton(), + title: Text( + 'Name of Group Chat', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16, + ), + ), + centerTitle: true, + bottom: PreferredSize( + preferredSize: Size.fromHeight(kToolbarHeight), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 16), + child: Row( + children: [ + Text( + 'NAME', + style: TextStyle( + fontSize: 12, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + ), + SizedBox(width: 16), + Expanded( + child: TextField( + controller: _groupNameController, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + contentPadding: const EdgeInsets.all(0), + hintText: 'Choose a group chat name', + hintStyle: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ), + ], + ), + ), + ), + actions: [ + StreamNeumorphicButton( + child: IconButton( + padding: const EdgeInsets.all(0), + icon: StreamSvgIcon.check( + size: 24, + color: _isGroupNameEmpty + ? StreamChatTheme.of(context).colorTheme.grey + : StreamChatTheme.of(context).colorTheme.accentBlue, + ), + onPressed: _isGroupNameEmpty + ? null + : () async { + try { + final groupName = _groupNameController.text; + final client = StreamChat.of(context).client; + final channel = client.channel('messaging', + id: Uuid().v4(), + extraData: { + 'members': [ + client.state.user.id, + ..._selectedUsers.map((e) => e.id), + ], + 'name': groupName, + }); + await channel.watch(); + Navigator.pushNamedAndRemoveUntil( + context, + Routes.CHANNEL_PAGE, + ModalRoute.withName(Routes.HOME), + arguments: ChannelPageArgs(channel: channel), + ); + } catch (err) { + _showErrorAlert(); + } + }, + ), + ), + ], + ), + body: ConnectionStatusBuilder( + statusBuilder: (context, status) { + String statusString = ''; + bool showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } + return InfoTile( + showMessage: showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: Column( + children: [ + Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Text( + '$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ), + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: ListView.separated( + itemCount: _selectedUsers.length + 1, + separatorBuilder: (_, __) => Container( + height: 1, + color: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, + ), + itemBuilder: (_, index) { + if (index == _selectedUsers.length) { + return Container( + height: 1, + color: StreamChatTheme.of(context) + .colorTheme + .greyWhisper, + ); + } + final user = _selectedUsers[index]; + return ListTile( + key: ObjectKey(user), + leading: UserAvatar( + user: user, + constraints: BoxConstraints.tightFor( + width: 40, + height: 40, + ), + ), + title: Text( + user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + trailing: IconButton( + icon: Icon( + Icons.clear_rounded, + color: StreamChatTheme.of(context) + .colorTheme + .black, + ), + padding: const EdgeInsets.all(0), + splashRadius: 24, + onPressed: () { + setState(() { + _selectedUsers.remove(user); + }); + if (_selectedUsers.isEmpty) { + Navigator.pop(context, _selectedUsers); + } + }, + ), + ); + }, + ), + ), + ), + ], + ), + ); + }, + ), + ), + ); + } + + void _showErrorAlert() { + showModalBottomSheet( + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + )), + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 26.0, + ), + StreamSvgIcon.error( + color: StreamChatTheme.of(context).colorTheme.accentRed, + size: 24.0, + ), + SizedBox( + height: 26.0, + ), + Text( + 'Something went wrong', + style: StreamChatTheme.of(context).textTheme.headlineBold, + ), + SizedBox( + height: 7.0, + ), + Text('The operation couldn\'t be completed.'), + SizedBox( + height: 36.0, + ), + Container( + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), + height: 1.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FlatButton( + child: Text( + 'OK', + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/stream_chat_v1/example/lib/group_info_screen.dart b/stream_chat_v1/example/lib/group_info_screen.dart new file mode 100644 index 0000000..0f200c8 --- /dev/null +++ b/stream_chat_v1/example/lib/group_info_screen.dart @@ -0,0 +1,1023 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/option_list_tile.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'chat_info_screen.dart'; +import 'main.dart'; +import 'routes/routes.dart'; + +class GroupInfoScreen extends StatefulWidget { + @override + _GroupInfoScreenState createState() => _GroupInfoScreenState(); +} + +class _GroupInfoScreenState extends State { + TextEditingController _nameController; + + TextEditingController _searchController; + String _userNameQuery = ''; + + Timer _debounce; + Function modalSetStateCallback; + + final FocusNode _focusNode = FocusNode(); + + bool listExpanded = false; + + void _userNameListener() { + if (_searchController.text == _userNameQuery) { + return; + } + if (_debounce?.isActive ?? false) _debounce.cancel(); + _debounce = Timer(const Duration(milliseconds: 350), () { + if (mounted && modalSetStateCallback != null) { + modalSetStateCallback(() { + _userNameQuery = _searchController.text; + }); + } + }); + } + + @override + void initState() { + super.initState(); + var channel = StreamChannel.of(context); + _nameController = TextEditingController.fromValue( + TextEditingValue(text: channel.channel.extraData['name'] ?? '')); + _searchController = TextEditingController()..addListener(_userNameListener); + + _nameController.addListener(() { + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + var channel = StreamChannel.of(context); + + return StreamBuilder>( + stream: channel.channel.state.membersStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Container( + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + child: Center(child: CircularProgressIndicator()), + ); + } + + var userMember = snapshot.data + .firstWhere((e) => e.user.id == StreamChat.of(context).user.id); + var isOwner = userMember.role == 'owner'; + + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: AppBar( + brightness: Theme.of(context).brightness, + elevation: 1.0, + toolbarHeight: 56.0, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + leading: InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.only(left: 12.0), + child: StreamSvgIcon.left( + color: StreamChatTheme.of(context).colorTheme.black, + ), + ), + ), + leadingWidth: 36.0, + title: Column( + children: [ + StreamBuilder( + stream: channel.channelStateStream, + builder: (context, state) { + if (!state.hasData) { + return Text( + 'Loading...', + style: TextStyle( + color: + StreamChatTheme.of(context).colorTheme.black, + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + + return Text( + _getChannelName( + 2 * MediaQuery.of(context).size.width / 3, + members: snapshot.data, + extraData: state.data.channel.extraData, + maxFontSize: 16.0), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }), + SizedBox( + height: 3.0, + ), + Text( + '${channel.channel.memberCount} Members, ${snapshot?.data?.where((e) => e.user.online)?.length ?? 0} Online', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + fontSize: 12.0, + ), + ), + ], + ), + centerTitle: true, + actions: [ + if (!channel.channel.isDistinct) + StreamNeumorphicButton( + child: InkWell( + onTap: () { + _buildAddUserModal(context); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon.userAdd( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue), + ), + ), + ), + ], + ), + body: ListView( + children: [ + _buildMembers(snapshot.data), + Container( + height: 8.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + if (isOwner) _buildNameTile(), + _buildOptionListTiles(), + ], + ), + ); + }); + } + + Widget _buildMembers(List members) { + final groupMembers = members + ..sort((prev, curr) { + if (curr.role == 'owner') return 1; + return 0; + }); + + int groupMembersLength; + + if (listExpanded) { + groupMembersLength = groupMembers.length; + } else { + groupMembersLength = groupMembers.length > 6 ? 6 : groupMembers.length; + } + + return Column( + children: [ + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: groupMembersLength, + itemBuilder: (context, index) { + final member = groupMembers[index]; + return Material( + child: InkWell( + onTap: () { + var userMember = groupMembers.firstWhere( + (e) => e.user.id == StreamChat.of(context).user.id); + _showUserInfoModal(member.user, userMember.role == 'owner'); + }, + child: Container( + height: 65.0, + child: Column( + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 12.0), + child: UserAvatar( + user: member.user, + constraints: BoxConstraints( + maxHeight: 40.0, maxWidth: 40.0), + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + member.user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + SizedBox( + height: 1.0, + ), + Text( + _getLastSeen(member.user), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + member.role == 'owner' ? 'Owner' : '', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ), + ), + ], + ), + Container( + height: 1.0, + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + ), + ], + ), + ), + ), + color: StreamChatTheme.of(context).colorTheme.whiteSnow, + ); + }, + ), + if (groupMembersLength != groupMembers.length) + InkWell( + onTap: () { + setState(() { + listExpanded = true; + }); + }, + child: Material( + color: StreamChatTheme.of(context).colorTheme.whiteSnow, + child: Container( + height: 65.0, + child: Column( + children: [ + Expanded( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 21.0, vertical: 12.0), + child: StreamSvgIcon.down( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${members.length - groupMembersLength} more', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .grey), + ), + ], + ), + ), + ], + ), + ), + Container( + height: 1.0, + color: + StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + ], + ), + ), + ), + ), + ], + ); + } + + Widget _buildNameTile() { + var channel = StreamChannel.of(context).channel; + var channelName = channel.extraData['name'] ?? ''; + + return Material( + color: StreamChatTheme.of(context).colorTheme.whiteSnow, + child: Container( + height: 56.0, + alignment: Alignment.center, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(7.0), + child: Text( + 'NAME', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ), + ), + SizedBox( + width: 7.0, + ), + Expanded( + child: TextField( + focusNode: _focusNode, + controller: _nameController, + cursorColor: StreamChatTheme.of(context).colorTheme.black, + decoration: InputDecoration.collapsed( + hintText: 'Add a group name', + hintStyle: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5))), + style: TextStyle( + fontWeight: FontWeight.bold, + height: 0.82, + ), + ), + ), + if ((channelName == null) || + (channelName != _nameController.text.trim())) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + child: StreamSvgIcon.close_small(), + onTap: () { + setState(() { + _nameController.text = + _getChannelName(MediaQuery.of(context).size.width); + _focusNode.unfocus(); + }); + }, + ), + Padding( + padding: const EdgeInsets.only(right: 16.0, left: 8.0), + child: InkWell( + child: StreamSvgIcon.check( + color: + StreamChatTheme.of(context).colorTheme.accentBlue, + size: 24.0, + ), + onTap: () { + StreamChannel.of(context).channel.update({ + 'name': _nameController.text.trim(), + }).catchError((err) { + setState(() { + _nameController.text = channelName; + _focusNode.unfocus(); + }); + }); + }, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildOptionListTiles() { + var channel = StreamChannel.of(context); + + return Column( + children: [ + // OptionListTile( + // title: 'Notifications', + // leading: StreamSvgIcon.Icon_notification( + // size: 24.0, + // color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + // ), + // trailing: CupertinoSwitch( + // value: true, + // onChanged: (val) {}, + // ), + // onTap: () {}, + // ), + StreamBuilder( + stream: StreamChannel.of(context).channel.isMutedStream, + builder: (context, snapshot) { + return OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + separatorColor: + StreamChatTheme.of(context).colorTheme.greyGainsboro, + title: 'Mute group', + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.mute( + size: 24.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), + ), + trailing: snapshot.data == null + ? CircularProgressIndicator() + : CupertinoSwitch( + value: snapshot.data, + onChanged: (val) { + if (snapshot.data) { + channel.channel.unmute(); + } else { + channel.channel.mute(); + } + }, + ), + onTap: () {}, + ); + }), + OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, + title: 'Photos & Videos', + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: StreamSvgIcon.pictures( + size: 32.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + var channel = StreamChannel.of(context).channel; + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelMediaDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + onShowMessage: (m, c) async { + final client = StreamChat.of(context).client; + final message = m; + final channel = client.channel( + c.type, + id: c.id, + ); + if (channel.state == null) { + await channel.watch(); + } + await Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, + ), + ), + ), + ), + ); + }, + ), + OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, + title: 'Files', + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: StreamSvgIcon.files( + size: 32.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + onTap: () { + var channel = StreamChannel.of(context).channel; + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelFileDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + ), + ), + ), + ), + ); + }, + ), + if (!channel.channel.isDistinct) + OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + separatorColor: + StreamChatTheme.of(context).colorTheme.greyGainsboro, + title: 'Leave Group', + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.userRemove( + size: 24.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), + ), + trailing: Container( + height: 24.0, + width: 24.0, + ), + onTap: () async { + await channel.channel + .removeMembers([StreamChat.of(context).user.id]); + Navigator.pop(context); + Navigator.pop(context); + }, + ), + ], + ); + } + + void _buildAddUserModal(context) { + var channel = StreamChannel.of(context).channel; + + showDialog( + context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, + builder: (context) { + return StatefulBuilder(builder: (context, modalSetState) { + modalSetStateCallback = modalSetState; + return Padding( + padding: EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0), + child: Material( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + clipBehavior: Clip.antiAlias, + child: Scaffold( + body: UsersBloc( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: _buildTextInputSection(modalSetState), + ), + Expanded( + child: UserListView( + selectedUsers: {}, + onUserTap: (user, _) async { + _searchController.clear(); + + await channel.addMembers([user.id]); + Navigator.pop(context); + setState(() {}); + }, + crossAxisCount: 4, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_searchController.text.isNotEmpty) + 'name': { + r'$autocomplete': _userNameQuery, + }, + 'id': { + r'$nin': [ + StreamChat.of(context).user.id, + ...channel.state.members.map((e) => e.userId), + ], + }, + }, + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + Text( + 'No user matches these keywords...'), + ], + ), + ), + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + }); + }, + ); + } + + Widget _buildTextInputSection(modalSetState) { + final theme = StreamChatTheme.of(context); + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + height: 36, + child: TextField( + controller: _searchController, + cursorColor: theme.colorTheme.black, + autofocus: true, + decoration: InputDecoration( + hintText: 'Search', + hintStyle: theme.textTheme.body.copyWith( + color: theme.colorTheme.grey, + ), + prefixIconConstraints: BoxConstraints.tight(Size(40, 24)), + prefixIcon: StreamSvgIcon.search( + color: theme.colorTheme.black, + size: 24, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24.0), + borderSide: BorderSide( + color: theme.colorTheme.greyWhisper, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24.0), + borderSide: BorderSide( + color: theme.colorTheme.greyWhisper, + )), + contentPadding: const EdgeInsets.all(0), + ), + ), + ), + ), + SizedBox(width: 16.0), + IconButton( + icon: StreamSvgIcon.close_small( + color: theme.colorTheme.grey, + ), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + padding: EdgeInsets.zero, + splashRadius: 24, + onPressed: () => Navigator.pop(context), + ) + ], + ), + ], + ); + } + + void _showUserInfoModal(User user, bool isUserAdmin) { + var channel = StreamChannel.of(context).channel; + + showModalBottomSheet( + context: context, + clipBehavior: Clip.antiAlias, + isScrollControlled: true, + builder: (context) { + return StreamChannel( + channel: channel, + child: Material( + color: StreamChatTheme.of(context).colorTheme.white, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 24.0, + ), + Center( + child: Text( + user.name, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox( + height: 5.0, + ), + _buildConnectedTitleState(user), + Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: UserAvatar( + user: user, + constraints: BoxConstraints( + maxHeight: 64.0, + minHeight: 64.0, + ), + borderRadius: BorderRadius.circular(32.0), + ), + ), + ), + if (StreamChat.of(context).user.id != user.id) + _buildModalListTile( + context, + StreamSvgIcon.user( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 24.0, + ), + 'View info', + () async { + var client = StreamChat.of(context).client; + + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); + + await c.watch(); + + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: c, + child: ChatInfoScreen( + user: user, + ), + ), + ), + ); + }, + ), + if (StreamChat.of(context).user.id != user.id) + _buildModalListTile( + context, + StreamSvgIcon.message( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 24.0, + ), + 'Message', + () async { + var client = StreamChat.of(context).client; + + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); + + await c.watch(); + + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: c, + child: ChannelPage(), + ), + ), + ); + }, + ), + if (!channel.isDistinct && + StreamChat.of(context).user.id != user.id && + isUserAdmin) + _buildModalListTile( + context, + StreamSvgIcon.Icon_user_settings( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 24.0, + ), + 'Make Owner', () { + // TODO: Add make owner implementation (Remaining from backend) + }), + if (!channel.isDistinct && + StreamChat.of(context).user.id != user.id && + isUserAdmin) + _buildModalListTile( + context, + StreamSvgIcon.userRemove( + color: StreamChatTheme.of(context).colorTheme.accentRed, + size: 24.0, + ), + 'Remove From Group', () async { + await channel.removeMembers([user.id]); + Navigator.pop(context); + }, color: StreamChatTheme.of(context).colorTheme.accentRed), + _buildModalListTile( + context, + StreamSvgIcon.close_small( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 24.0, + ), + 'Cancel', () { + Navigator.pop(context); + }), + ], + ), + ), + ); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + ), + ); + } + + Widget _buildConnectedTitleState(User user) { + var alternativeWidget; + + final otherMember = user; + + if (otherMember != null) { + if (otherMember.online) { + alternativeWidget = Text( + 'Online', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ); + } else { + alternativeWidget = Text( + 'Last seen ${Jiffy(otherMember.lastActive).fromNow()}', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ); + } + } + + return alternativeWidget; + } + + Widget _buildModalListTile( + BuildContext context, Widget leading, String title, VoidCallback onTap, + {Color color}) { + color ??= StreamChatTheme.of(context).colorTheme.black; + + return Material( + color: StreamChatTheme.of(context).colorTheme.white, + child: InkWell( + onTap: onTap, + child: Column( + children: [ + Container( + height: 1.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + Container( + height: 64.0, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: leading, + ), + Expanded( + child: Text( + title, + style: + TextStyle(color: color, fontWeight: FontWeight.bold), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + + String _getChannelName(double width, + {List members, Map extraData, double maxFontSize}) { + String title; + var client = StreamChat.of(context); + if (extraData['name'] == null) { + final otherMembers = + members.where((member) => member.user.id != client.user.id); + if (otherMembers.isNotEmpty) { + final maxWidth = width; + final maxChars = maxWidth / maxFontSize; + var currentChars = 0; + final currentMembers = []; + otherMembers.forEach((element) { + final newLength = currentChars + element.user.name.length; + if (newLength < maxChars) { + currentChars = newLength; + currentMembers.add(element); + } + }); + + final exceedingMembers = otherMembers.length - currentMembers.length; + title = + '${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } else { + title = 'No title'; + } + } else { + title = extraData['name']; + } + return title; + } + + String _getLastSeen(User user) { + if (user.online) { + return 'Online'; + } else { + return 'Last seen ${Jiffy(user.lastActive).fromNow()}'; + } + } +} diff --git a/stream_chat_v1/example/lib/main.dart b/stream_chat_v1/example/lib/main.dart new file mode 100644 index 0000000..93c7b08 --- /dev/null +++ b/stream_chat_v1/example/lib/main.dart @@ -0,0 +1,834 @@ +import 'dart:async'; + +import 'package:example/chat_info_screen.dart'; +import 'package:example/choose_user_page.dart'; +import 'package:example/group_info_screen.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_persistence/stream_chat_persistence.dart'; +import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; + +import 'notifications_service.dart'; +import 'routes/app_routes.dart'; +import 'routes/routes.dart'; +import 'search_text_field.dart'; + +final chatPersistentClient = StreamChatPersistenceClient( + logLevel: Level.INFO, + connectionMode: ConnectionMode.background, +); + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + final secureStorage = FlutterSecureStorage(); + + final apiKey = await secureStorage.read(key: kStreamApiKey); + final userId = await secureStorage.read(key: kStreamUserId); + + final client = Client( + apiKey ?? kDefaultStreamApiKey, + logLevel: Level.INFO, + )..chatPersistenceClient = chatPersistentClient; + + if (userId != null) { + final token = await secureStorage.read(key: kStreamToken); + await client.setUser( + User(id: userId), + token, + ); + } + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: StreamingSharedPreferences.instance, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SizedBox(); + } + return PreferenceBuilder( + preference: snapshot.data.getInt( + 'theme', + defaultValue: 0, + ), + builder: (context, snapshot) => MaterialApp( + builder: (context, child) { + return StreamChat( + client: client, + onBackgroundEventReceived: showLocalNotification, + child: Builder( + builder: (context) => AnnotatedRegion( + child: child, + value: SystemUiOverlayStyle( + systemNavigationBarColor: + StreamChatTheme.of(context).colorTheme.white, + systemNavigationBarIconBrightness: + Theme.of(context).brightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + ), + ), + ), + ); + }, + debugShowCheckedModeBanner: false, + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + themeMode: { + -1: ThemeMode.dark, + 0: ThemeMode.system, + 1: ThemeMode.light, + }[snapshot], + onGenerateRoute: AppRoutes.generateRoute, + initialRoute: + client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, + ), + ); + }, + ); + } +} + +class HomePage extends StatefulWidget { + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _currentIndex = 0; + + bool _isSelected(int index) => _currentIndex == index; + + List get _navBarItems { + return [ + BottomNavigationBarItem( + icon: Stack( + clipBehavior: Clip.none, + children: [ + StreamSvgIcon.message( + color: _isSelected(0) + ? StreamChatTheme.of(context).colorTheme.black + : Colors.grey, + ), + Positioned( + top: -3, + right: -16, + child: UnreadIndicator(), + ), + ], + ), + label: 'Chats', + ), + BottomNavigationBarItem( + icon: Stack( + clipBehavior: Clip.none, + children: [ + StreamSvgIcon.mentions( + color: _isSelected(1) + ? StreamChatTheme.of(context).colorTheme.black + : Colors.grey, + ), + ], + ), + label: 'Mentions', + ), + ]; + } + + @override + Widget build(BuildContext context) { + final user = StreamChat.of(context).user; + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: ChannelListHeader( + onNewChatButtonTap: () { + Navigator.pushNamed(context, Routes.NEW_CHAT); + }, + preNavigationCallback: () { + FocusScope.of(context).requestFocus(FocusNode()); + }, + ), + drawer: _buildDrawer(context, user), + drawerEdgeDragWidth: 50, + bottomNavigationBar: BottomNavigationBar( + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + currentIndex: _currentIndex, + items: _navBarItems, + selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, + unselectedLabelStyle: + StreamChatTheme.of(context).textTheme.footnoteBold, + type: BottomNavigationBarType.fixed, + selectedItemColor: StreamChatTheme.of(context).colorTheme.black, + unselectedItemColor: Colors.grey, + onTap: (index) { + setState(() => _currentIndex = index); + }, + ), + body: IndexedStack( + index: _currentIndex, + children: [ + ChannelListPage(), + UserMentionPage(), + ], + ), + ); + } + + Drawer _buildDrawer(BuildContext context, User user) { + return Drawer( + child: Container( + color: StreamChatTheme.of(context).colorTheme.white, + child: SafeArea( + child: Padding( + padding: EdgeInsets.only( + top: MediaQuery.of(context).viewPadding.top + 8, + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 20.0, + left: 8, + ), + child: Row( + children: [ + UserAvatar( + user: user, + showOnlineStatus: false, + constraints: BoxConstraints.tight(Size.fromRadius(20)), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text( + user.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ListTile( + leading: StreamSvgIcon.penWrite( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + onTap: () { + Navigator.popAndPushNamed( + context, + Routes.NEW_CHAT, + ); + }, + title: Text( + 'New direct message', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + ListTile( + leading: StreamSvgIcon.contacts( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + onTap: () { + Navigator.popAndPushNamed( + context, + Routes.NEW_GROUP_CHAT, + ); + }, + title: Text( + 'New group', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + child: ListTile( + onTap: () async { + Navigator.pop(context); + + final secureStorage = FlutterSecureStorage(); + await secureStorage.deleteAll(); + + StreamChat.of(context).client.disconnect( + clearUser: true, + ); + + await Navigator.pushReplacementNamed( + context, + Routes.CHOOSE_USER, + ); + }, + leading: StreamSvgIcon.user( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + title: Text( + 'Sign out', + style: TextStyle( + fontSize: 14.5, + ), + ), + trailing: IconButton( + icon: StreamSvgIcon.Icon_moon( + size: 24, + ), + color: StreamChatTheme.of(context).colorTheme.grey, + onPressed: () async { + final sp = await StreamingSharedPreferences.instance; + sp.setInt( + 'theme', + Theme.of(context).brightness == Brightness.dark + ? 1 + : -1, + ); + }, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class UserMentionPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final user = StreamChat.of(context).user; + return MessageSearchBloc( + child: MessageSearchListView( + filters: { + 'members': { + r'$in': [user.id], + }, + }, + messageFilters: { + 'mentioned_users.id': { + r'$contains': user.id, + }, + }, + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + showResultCount: false, + emptyBuilder: (_, __) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.mentions( + size: 96, + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + ), + ), + Text( + 'No mentions exist yet...', + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + onItemTap: (messageResponse) async { + final client = StreamChat.of(context).client; + final message = messageResponse.message; + final channel = client.channel( + messageResponse.channel.type, + id: messageResponse.channel.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, + ), + ); + } +} + +class ChannelListPage extends StatefulWidget { + @override + _ChannelListPageState createState() => _ChannelListPageState(); +} + +class _ChannelListPageState extends State { + TextEditingController _controller; + + String _channelQuery = ''; + + bool _isSearchActive = false; + + Timer _debounce; + + void _channelQueryListener() { + if (_debounce?.isActive ?? false) _debounce.cancel(); + _debounce = Timer(const Duration(milliseconds: 350), () { + if (mounted) { + setState(() { + _channelQuery = _controller.text; + _isSearchActive = _channelQuery.isNotEmpty; + }); + } + }); + } + + @override + void initState() { + super.initState(); + _controller = TextEditingController()..addListener(_channelQueryListener); + } + + @override + void dispose() { + _controller?.removeListener(_channelQueryListener); + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final user = StreamChat.of(context).user; + return WillPopScope( + onWillPop: () async { + if (_isSearchActive) { + _controller.clear(); + setState(() => _isSearchActive = false); + return false; + } + return true; + }, + child: ChannelsBloc( + child: MessageSearchBloc( + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (_, __) => [ + SliverToBoxAdapter( + child: SearchTextField( + controller: _controller, + showCloseButton: _isSearchActive, + ), + ), + ], + body: AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: _isSearchActive + ? MessageSearchListView( + messageQuery: _channelQuery, + filters: { + 'members': { + r'$in': [user.id] + } + }, + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + pullToRefresh: false, + paginationParams: PaginationParams(limit: 20), + emptyBuilder: (_, query) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: Colors.grey, + ), + ), + Text( + 'No results for \"$query\"...', + ), + ], + ), + ), + ), + ); + }, + ); + }, + onItemTap: (messageResponse) async { + FocusScope.of(context).requestFocus(FocusNode()); + final client = StreamChat.of(context).client; + final message = messageResponse.message; + final channel = client.channel( + messageResponse.channel.type, + id: messageResponse.channel.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, + ) + : ChannelListView( + onStartChatPressed: () { + Navigator.pushNamed(context, Routes.NEW_CHAT); + }, + swipeToAction: true, + filter: { + 'members': { + r'$in': [user.id], + }, + }, + options: { + 'presence': true, + }, + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + onViewInfoTap: (channel) { + if (channel.memberCount == 2 && channel.isDistinct) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + user: channel.state.members.first.user, + ), + ), + ), + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: GroupInfoScreen(), + ), + ), + ); + } + }, + ), + ), + ), + ), + ), + ), + ); + } +} + +class ChannelPageArgs { + final Channel channel; + final Message initialMessage; + + const ChannelPageArgs({ + this.channel, + this.initialMessage, + }); +} + +class ChannelPage extends StatefulWidget { + final int initialScrollIndex; + final double initialAlignment; + final bool highlightInitialMessage; + + const ChannelPage({ + Key key, + this.initialScrollIndex, + this.initialAlignment, + this.highlightInitialMessage = false, + }) : super(key: key); + + @override + _ChannelPageState createState() => _ChannelPageState(); +} + +class _ChannelPageState extends State { + Message _quotedMessage; + FocusNode _focusNode; + + @override + void initState() { + _focusNode = FocusNode(); + super.initState(); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } + + void _reply(Message message) { + setState(() => _quotedMessage = message); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _focusNode.requestFocus(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: ChannelHeader( + showTypingIndicator: false, + onImageTap: () async { + var channel = StreamChannel.of(context).channel; + + if (channel.memberCount == 2 && channel.isDistinct) { + final currentUser = StreamChat.of(context).user; + final otherUser = channel.state.members.firstWhere( + (element) => element.user.id != currentUser.id, + orElse: () => null, + ); + if (otherUser != null) { + final pop = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + child: ChatInfoScreen( + user: otherUser.user, + ), + channel: channel, + ), + ), + ); + + if (pop == true) { + Navigator.pop(context); + } + } + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + child: GroupInfoScreen(), + channel: channel, + ), + ), + ); + } + }, + ), + body: Column( + children: [ + Expanded( + child: Stack( + children: [ + MessageListView( + initialScrollIndex: widget.initialScrollIndex, + initialAlignment: widget.initialAlignment, + highlightInitialMessage: widget.highlightInitialMessage, + onMessageSwiped: _reply, + onReplyTap: _reply, + threadBuilder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + ); + }, + onShowMessage: (m, c) async { + final client = StreamChat.of(context).client; + final message = m; + final channel = client.channel( + c.type, + id: c.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pushReplacementNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + alignment: Alignment.centerLeft, + color: StreamChatTheme.of(context) + .colorTheme + .whiteSnow + .withOpacity(.9), + child: TypingIndicator( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.grey), + ), + ), + ), + ], + ), + ), + MessageInput( + focusNode: _focusNode, + quotedMessage: _quotedMessage, + onQuotedMessageCleared: () { + setState(() => _quotedMessage = null); + _focusNode.unfocus(); + }, + ), + ], + ), + ); + } +} + +class ThreadPage extends StatefulWidget { + final Message parent; + final int initialScrollIndex; + final double initialAlignment; + + ThreadPage({ + Key key, + this.parent, + this.initialScrollIndex, + this.initialAlignment, + }) : super(key: key); + + @override + _ThreadPageState createState() => _ThreadPageState(); +} + +class _ThreadPageState extends State { + Message _quotedMessage; + FocusNode _focusNode = FocusNode(); + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } + + void _reply(Message message) { + setState(() => _quotedMessage = message); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _focusNode.requestFocus(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: ThreadHeader( + parent: widget.parent, + ), + body: Column( + children: [ + Expanded( + child: MessageListView( + parentMessage: widget.parent, + initialScrollIndex: widget.initialScrollIndex, + initialAlignment: widget.initialAlignment, + onMessageSwiped: _reply, + onReplyTap: _reply, + ), + ), + if (widget.parent.type != 'deleted') + MessageInput( + parentMessage: widget.parent, + focusNode: _focusNode, + quotedMessage: _quotedMessage, + onQuotedMessageCleared: () { + setState(() => _quotedMessage = null); + _focusNode.unfocus(); + }, + ), + ], + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/multiple_conversation.dart b/stream_chat_v1/example/lib/multiple_conversation.dart new file mode 100644 index 0000000..b5fdd5a --- /dev/null +++ b/stream_chat_v1/example/lib/multiple_conversation.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Second step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// Most chat applications handle more than just one single conversation. +/// Apps like Facebook Messenger, Whatsapp and Telegram allows you to have multiple one to one and group conversations. +/// +/// Let’s find out how we can change our application chat screen to display the list of conversations and navigate between them. +/// +/// > Note: the SDK uses Flutter’s [Navigator] to move from one route to another, this allows us to avoid any boiler-plate code. +/// > Of course you can take total control of how navigation works by customizing widgets like [Channel] and [ChannelList]. +/// +/// If you run the application, you will see that the first screen shows a list of conversations, you can open each by tapping and go back to the list. +/// +/// Every single widget involved in this UI can be customized or swapped with your own. +/// +/// The [ChannelListPage] widget retrieves the list of channels based on a custom query and ordering. +/// In this case we are showing the list of channels the current user is a member and we order them based on the time they had a new message. +/// [ChannelListView] handles pagination and updates automatically out of the box when new channels are created or when a new message is added to a channel. +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) => StreamChat( + client: client, + child: child, + ), + home: ChannelListPage(), + ); + } +} + +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: 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( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/new_chat_screen.dart b/stream_chat_v1/example/lib/new_chat_screen.dart new file mode 100644 index 0000000..7f47e87 --- /dev/null +++ b/stream_chat_v1/example/lib/new_chat_screen.dart @@ -0,0 +1,417 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'chips_input_text_field.dart'; +import 'main.dart'; +import 'routes/routes.dart'; + +class NewChatScreen extends StatefulWidget { + @override + _NewChatScreenState createState() => _NewChatScreenState(); +} + +class _NewChatScreenState extends State { + final _chipInputTextFieldStateKey = + GlobalKey>(); + + TextEditingController _controller; + + ChipInputTextFieldState get _chipInputTextFieldState => + _chipInputTextFieldStateKey.currentState; + + String _userNameQuery = ''; + + final _selectedUsers = {}; + + final _searchFocusNode = FocusNode(); + final _messageInputFocusNode = FocusNode(); + + bool _isSearchActive = false; + + Channel channel; + + Timer _debounce; + + bool _showUserList = true; + + void _userNameListener() { + if (_debounce?.isActive ?? false) _debounce.cancel(); + _debounce = Timer(const Duration(milliseconds: 350), () { + if (mounted) + setState(() { + _userNameQuery = _controller.text; + _isSearchActive = _userNameQuery.isNotEmpty; + }); + }); + } + + @override + void initState() { + super.initState(); + channel = StreamChat.of(context).client.channel('messaging'); + _controller = TextEditingController()..addListener(_userNameListener); + + _searchFocusNode.addListener(() async { + if (_searchFocusNode.hasFocus && !_showUserList) { + setState(() { + _showUserList = true; + }); + } + }); + + _messageInputFocusNode.addListener(() async { + if (_messageInputFocusNode.hasFocus && _selectedUsers.isNotEmpty) { + final chatState = StreamChat.of(context); + + final res = await chatState.client.queryChannels( + options: { + 'state': false, + 'watch': false, + }, + filter: { + 'members': [ + ..._selectedUsers.map((e) => e.id), + chatState.user.id, + ], + 'distinct': true, + }, + messageLimit: 0, + paginationParams: PaginationParams( + limit: 1, + ), + ); + + final _channelExisted = res.length == 1; + if (_channelExisted) { + channel = res.first; + await channel.watch(); + } else { + channel = chatState.client.channel( + 'messaging', + extraData: { + 'members': [ + ..._selectedUsers.map((e) => e.id), + chatState.user.id, + ], + }, + ); + } + + setState(() { + _showUserList = false; + }); + } + }); + } + + @override + void dispose() { + _searchFocusNode.dispose(); + _messageInputFocusNode.dispose(); + _controller?.clear(); + _controller?.removeListener(_userNameListener); + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: AppBar( + brightness: Theme.of(context).brightness, + elevation: 0, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + leading: const StreamBackButton(), + title: Text( + 'New Chat', + style: StreamChatTheme.of(context) + .textTheme + .headlineBold + .copyWith(color: StreamChatTheme.of(context).colorTheme.black), + ), + centerTitle: true, + ), + body: ConnectionStatusBuilder( + statusBuilder: (context, status) { + String statusString = ''; + bool showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } + return InfoTile( + showMessage: showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: StreamChannel( + showLoading: false, + channel: channel, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ChipsInputTextField( + key: _chipInputTextFieldStateKey, + controller: _controller, + focusNode: _searchFocusNode, + chipBuilder: (context, user) { + return GestureDetector( + onTap: () { + _chipInputTextFieldState.removeItem(user); + _searchFocusNode.requestFocus(); + }, + child: Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + Container( + decoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.only(left: 24), + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 4, 12, 4), + child: Text( + user.name, + maxLines: 1, + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black, + ), + ), + ), + ), + Container( + foregroundDecoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .overlay, + shape: BoxShape.circle, + ), + child: UserAvatar( + showOnlineStatus: false, + user: user, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + ), + ), + StreamSvgIcon.close(), + ], + ), + ); + }, + onChipAdded: (user) { + setState(() => _selectedUsers.add(user)); + }, + onChipRemoved: (user) { + setState(() => _selectedUsers.remove(user)); + }, + ), + if (!_isSearchActive && !_selectedUsers.isNotEmpty) + Container( + child: InkWell( + onTap: () { + Navigator.pushNamed( + context, + Routes.NEW_GROUP_CHAT, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: [ + StreamNeumorphicButton( + child: Center( + child: StreamSvgIcon.contacts( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + size: 24, + ), + ), + ), + SizedBox(width: 8), + Text( + 'Create a Group', + style: StreamChatTheme.of(context) + .textTheme + .bodyBold, + ), + ], + ), + ), + ), + ), + if (_showUserList) + Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Text( + _isSearchActive + ? "Matches for \"$_userNameQuery\"" + : 'On the platform', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5))), + ), + ), + Expanded( + child: _showUserList + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: UsersBloc( + child: UserListView( + selectedUsers: _selectedUsers, + groupAlphabetically: + _isSearchActive ? false : true, + onUserTap: (user, _) { + _controller.clear(); + if (!_selectedUsers.contains(user)) { + _chipInputTextFieldState + ..addItem(user) + ..pauseItemAddition(); + } else { + _chipInputTextFieldState.removeItem(user); + } + }, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_userNameQuery.isNotEmpty) + 'name': { + r'$autocomplete': _userNameQuery, + }, + 'id': { + r'$ne': StreamChat.of(context).user.id, + }, + }, + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: + AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: + viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: + const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: Colors.grey, + ), + ), + Text( + 'No user matches these keywords...', + style: StreamChatTheme.of( + context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme + .of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ), + ), + ) + : FutureBuilder( + future: channel.initialized, + builder: (context, snapshot) { + if (snapshot.data == true) { + return MessageListView(); + } + + return Center( + child: Text( + 'No chats here yet...', + style: TextStyle( + fontSize: 12, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + ), + ); + }, + ), + ), + MessageInput( + focusNode: _messageInputFocusNode, + preMessageSending: (message) async { + await channel.watch(); + return message; + }, + onMessageSent: (m) { + Navigator.pushNamedAndRemoveUntil( + context, + Routes.CHANNEL_PAGE, + ModalRoute.withName(Routes.HOME), + arguments: ChannelPageArgs(channel: channel), + ); + }, + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/new_group_chat_screen.dart b/stream_chat_v1/example/lib/new_group_chat_screen.dart new file mode 100644 index 0000000..4e924d8 --- /dev/null +++ b/stream_chat_v1/example/lib/new_group_chat_screen.dart @@ -0,0 +1,339 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'routes/routes.dart'; +import 'search_text_field.dart'; + +class NewGroupChatScreen extends StatefulWidget { + @override + _NewGroupChatScreenState createState() => _NewGroupChatScreenState(); +} + +class _NewGroupChatScreenState extends State { + TextEditingController _controller; + + String _userNameQuery = ''; + + final _selectedUsers = {}; + + bool _isSearchActive = false; + + Timer _debounce; + + void _userNameListener() { + if (_debounce?.isActive ?? false) _debounce.cancel(); + _debounce = Timer(const Duration(milliseconds: 350), () { + if (mounted) { + setState(() { + _userNameQuery = _controller.text; + _isSearchActive = _userNameQuery.isNotEmpty; + }); + } + }); + } + + @override + void initState() { + super.initState(); + _controller = TextEditingController()..addListener(_userNameListener); + } + + @override + void dispose() { + _controller?.clear(); + _controller?.removeListener(_userNameListener); + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: AppBar( + elevation: 1, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + leading: const StreamBackButton(), + title: Text( + 'Add Group Members', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16, + ), + ), + centerTitle: true, + actions: [ + if (_selectedUsers.isNotEmpty) + IconButton( + icon: StreamSvgIcon.arrow_right( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + ), + onPressed: () async { + final updatedList = await Navigator.pushNamed( + context, + Routes.NEW_GROUP_CHAT_DETAILS, + arguments: _selectedUsers.toList(growable: false), + ); + if (updatedList != null) { + setState(() { + _selectedUsers + ..clear() + ..addAll(updatedList); + }); + } + }, + ) + ], + ), + body: ConnectionStatusBuilder( + statusBuilder: (context, status) { + String statusString = ''; + bool showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } + return InfoTile( + showMessage: showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverToBoxAdapter( + child: SearchTextField( + controller: _controller, + ), + ), + if (_selectedUsers.isNotEmpty) + SliverToBoxAdapter( + child: Container( + height: 104, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: _selectedUsers.length, + padding: const EdgeInsets.all(8), + separatorBuilder: (_, __) => SizedBox(width: 16), + itemBuilder: (_, index) { + final user = _selectedUsers.elementAt(index); + return Column( + children: [ + Stack( + children: [ + UserAvatar( + onlineIndicatorAlignment: + Alignment(0.9, 0.9), + user: user, + showOnlineStatus: true, + borderRadius: BorderRadius.circular(32), + constraints: BoxConstraints.tightFor( + height: 64, + width: 64, + ), + ), + Positioned( + top: -4, + right: -4, + child: GestureDetector( + onTap: () { + if (_selectedUsers.contains(user)) { + setState(() => + _selectedUsers.remove(user)); + } + }, + child: Container( + decoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .white, + shape: BoxShape.circle, + border: Border.all( + color: StreamChatTheme.of(context) + .colorTheme + .whiteSnow, + ), + ), + child: StreamSvgIcon.close( + color: StreamChatTheme.of(context) + .colorTheme + .black, + size: 24, + ), + ), + ), + ) + ], + ), + SizedBox(height: 4), + Text( + user.name.split(' ')[0], + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ], + ); + }, + ), + ), + ), + SliverPersistentHeader( + pinned: true, + delegate: _HeaderDelegate( + height: 30, + child: Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Text( + _isSearchActive + ? 'Matches for \"$_userNameQuery\"' + : 'On the platform', + style: TextStyle( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ), + ), + ), + ]; + }, + body: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: UsersBloc( + child: UserListView( + selectedUsers: _selectedUsers, + pullToRefresh: false, + groupAlphabetically: _isSearchActive ? false : true, + onUserTap: (user, _) { + if (!_selectedUsers.contains(user)) { + setState(() { + _selectedUsers.add(user); + }); + } else { + setState(() { + _selectedUsers.remove(user); + }); + } + }, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_userNameQuery.isNotEmpty) + 'name': { + r'$autocomplete': _userNameQuery, + }, + 'id': { + r'$ne': StreamChat.of(context).user.id, + } + }, + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + Text( + 'No user matches these keywords...', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .grey, + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ), + ), + ), + ), + ); + }, + ), + ); + } +} + +class _HeaderDelegate extends SliverPersistentHeaderDelegate { + final Widget child; + final double height; + + const _HeaderDelegate({ + @required this.child, + @required this.height, + }); + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return Container( + color: StreamChatTheme.of(context).colorTheme.white, + child: child, + ); + } + + @override + double get maxExtent => height; + + @override + double get minExtent => height; + + @override + bool shouldRebuild(_HeaderDelegate oldDelegate) => true; +} diff --git a/stream_chat_v1/example/lib/notifications_service.dart b/stream_chat_v1/example/lib/notifications_service.dart new file mode 100644 index 0000000..246923d --- /dev/null +++ b/stream_chat_v1/example/lib/notifications_service.dart @@ -0,0 +1,31 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart' + hide Message; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +void showLocalNotification(Event event) async { + if (event.message == null) return; + final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + final initializationSettingsAndroid = + AndroidInitializationSettings('launch_background'); + final initializationSettingsIOS = IOSInitializationSettings(); + final initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + await flutterLocalNotificationsPlugin.show( + event.message.id.hashCode, + event.message.user.name, + event.message.text, + NotificationDetails( + android: AndroidNotificationDetails( + 'message channel', + 'Message channel', + 'Channel used for showing messages', + priority: Priority.high, + importance: Importance.high, + ), + iOS: IOSNotificationDetails(), + ), + ); +} diff --git a/stream_chat_v1/example/lib/routes/app_routes.dart b/stream_chat_v1/example/lib/routes/app_routes.dart new file mode 100644 index 0000000..e7c7e31 --- /dev/null +++ b/stream_chat_v1/example/lib/routes/app_routes.dart @@ -0,0 +1,93 @@ +import 'routes.dart'; +import 'package:flutter/material.dart'; +import '../choose_user_page.dart'; +import '../advanced_options_page.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import '../main.dart'; +import '../group_chat_details_screen.dart'; +import '../new_group_chat_screen.dart'; +import '../new_chat_screen.dart'; +import '../chat_info_screen.dart'; +import '../group_info_screen.dart'; + +class AppRoutes { + /// Add entry for new route here + static Route generateRoute(RouteSettings settings) { + final args = settings.arguments; + switch (settings.name) { + case Routes.APP: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.APP), + builder: (_) { + return MyApp(args); + }); + case Routes.HOME: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.HOME), + builder: (_) { + return HomePage(); + }); + case Routes.CHOOSE_USER: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.CHOOSE_USER), + builder: (_) { + return ChooseUserPage(); + }); + case Routes.ADVANCED_OPTIONS: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.ADVANCED_OPTIONS), + builder: (_) => AdvancedOptionsPage(), + ); + case Routes.CHANNEL_PAGE: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.CHANNEL_PAGE), + builder: (_) { + final arg = args as ChannelPageArgs; + return StreamChannel( + channel: arg.channel, + initialMessageId: arg.initialMessage?.id, + child: ChannelPage( + highlightInitialMessage: arg.initialMessage != null, + ), + ); + }); + case Routes.NEW_CHAT: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.NEW_CHAT), + builder: (_) { + return NewChatScreen(); + }); + case Routes.NEW_GROUP_CHAT: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT), + builder: (_) { + return NewGroupChatScreen(); + }); + case Routes.NEW_GROUP_CHAT_DETAILS: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT_DETAILS), + builder: (_) { + return GroupChatDetailsScreen( + selectedUsers: args, + ); + }); + case Routes.CHAT_INFO_SCREEN: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN), + builder: (_) { + return ChatInfoScreen( + user: args, + ); + }); + case Routes.GROUP_INFO_SCREEN: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN), + builder: (_) { + return GroupInfoScreen(); + }); + // Default case, should not reach here. + default: + return null; + } + } +} diff --git a/stream_chat_v1/example/lib/routes/routes.dart b/stream_chat_v1/example/lib/routes/routes.dart new file mode 100644 index 0000000..2280ca3 --- /dev/null +++ b/stream_chat_v1/example/lib/routes/routes.dart @@ -0,0 +1,13 @@ +/// Define all the route names here +class Routes { + static const String APP = '/app'; + static const String HOME = '/home'; + static const String CHOOSE_USER = '/choose_user'; + static const String ADVANCED_OPTIONS = '/advance_options'; + static const String CHANNEL_PAGE = '/channel_page'; + static const String NEW_CHAT = '/new_chat'; + static const String NEW_GROUP_CHAT = '/new_group_chat'; + static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details'; + static const String CHAT_INFO_SCREEN = '/chat_info_screen'; + static const String GROUP_INFO_SCREEN = '/group_info_screen'; +} diff --git a/stream_chat_v1/example/lib/search_text_field.dart b/stream_chat_v1/example/lib/search_text_field.dart new file mode 100644 index 0000000..ff34e8a --- /dev/null +++ b/stream_chat_v1/example/lib/search_text_field.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class SearchTextField extends StatelessWidget { + final TextEditingController controller; + final ValueChanged onChanged; + final String hintText; + final VoidCallback onTap; + final bool showCloseButton; + + const SearchTextField({ + Key key, + @required this.controller, + this.onChanged, + this.onTap, + this.hintText = 'Search', + this.showCloseButton = true, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 36, + decoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.white, + border: Border.all( + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ), + borderRadius: BorderRadius.circular(24), + ), + margin: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Row( + children: [ + Expanded( + child: TextField( + onTap: onTap, + controller: controller, + onChanged: onChanged, + decoration: InputDecoration( + prefixText: ' ', + prefixIconConstraints: BoxConstraints.tight(Size(40, 24)), + prefixIcon: Padding( + padding: const EdgeInsets.only( + left: 8, + right: 8, + ), + child: StreamSvgIcon.search( + color: StreamChatTheme.of(context).colorTheme.black, + size: 24, + ), + ), + hintText: hintText, + hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + contentPadding: const EdgeInsets.all(0), + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(24), + ), + ), + ), + ), + if (showCloseButton) + Material( + color: Colors.transparent, + child: IconButton( + padding: const EdgeInsets.all(0), + icon: StreamSvgIcon.close_small( + color: Colors.grey, + ), + splashRadius: 24, + onPressed: () { + if (controller.text.isNotEmpty) { + Future.microtask( + () => [ + controller.clear(), + if (onChanged != null) onChanged(''), + ], + ); + } + }, + ), + ), + ], + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/single_conversation.dart b/stream_chat_v1/example/lib/single_conversation.dart new file mode 100644 index 0000000..2093d38 --- /dev/null +++ b/stream_chat_v1/example/lib/single_conversation.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// First step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// There are three important things to notice that are common to all Flutter application using StreamChat: +/// +/// 1. The Dart API [Client] is initialized with your API Key +/// 2. The current user is set by calling [Client.setUser] +/// 3. The client is then passed to the top-level [StreamChat] widget +/// [StreamChat] is an inherited widget and must be the parent of all Chat related widgets. +/// +/// Please note that while Flutter can be used to build both mobile and web applications; +/// in this tutorial we focus on mobile, make sure when running the app you use a mobile device. +/// +/// Let's have a look at what we've built: +/// +/// - We set up the Chat [Client] with the API key +/// +/// - We set the the current user for Chat with [Client.setUser] and a pre-generated user token +/// +/// - We make [StreamChat] the root Widget of our application +/// +/// - We create a single [ChannelPage] widget under [StreamChat] with three widgets: [ChannelHeader], [MessageListView] and [MessageInput] +/// +/// If you now run the simulator you will see a single channel UI. +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + final channel = client.channel('messaging', id: 'godevs'); + + // ignore: unawaited_futures + channel.watch(); + + runApp(MyApp(client, channel)); +} + +class MyApp extends StatelessWidget { + final Client client; + final Channel channel; + + MyApp(this.client, this.channel); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, widget) { + return StreamChat( + child: widget, + client: client, + ); + }, + home: StreamChannel( + channel: channel, + child: ChannelPage(), + ), + ); + } +} + +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/split_view.dart b/stream_chat_v1/example/lib/split_view.dart new file mode 100644 index 0000000..f99e69f --- /dev/null +++ b/stream_chat_v1/example/lib/split_view.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) => StreamChat( + child: child, + client: client, + ), + home: SplitView(), + ); + } +} + +class SplitView extends StatefulWidget { + @override + _SplitViewState createState() => _SplitViewState(); +} + +class _SplitViewState extends State { + Channel selectedChannel; + + @override + Widget build(BuildContext context) { + return Flex( + direction: Axis.horizontal, + children: [ + Flexible( + child: ChannelListPage( + onTap: (channel) { + setState(() { + selectedChannel = channel; + }); + }, + ), + flex: 1, + ), + Flexible( + child: Scaffold( + body: selectedChannel != null + ? StreamChannel( + key: ValueKey(selectedChannel.cid), + child: ChannelPage(), + channel: selectedChannel, + ) + : Center( + child: Text( + 'Pick a channel to show the messages 💬', + style: Theme.of(context).textTheme.headline5, + ), + ), + ), + flex: 2, + ), + ], + ); + } +} + +class ChannelListPage extends StatelessWidget { + final void Function(Channel) onTap; + + ChannelListPage({this.onTap}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: ChannelListView( + onChannelTap: onTap != null + ? (channel, _) { + onTap(channel); + } + : null, + filter: { + 'members': { + '\$in': [StreamChat.of(context).user.id], + } + }, + sort: [SortOption('last_message_at')], + pagination: PaginationParams( + limit: 20, + ), + ), + ), + ); + } +} + +class ChannelPage extends StatelessWidget { + const ChannelPage({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Navigator( + onGenerateRoute: (settings) { + return MaterialPageRoute( + builder: (context) { + return Scaffold( + appBar: ChannelHeader( + showBackButton: false, + ), + body: Column( + children: [ + Expanded( + child: MessageListView(), + ), + MessageInput(), + ], + ), + ); + }, + ); + }, + ); + } +} diff --git a/stream_chat_v1/example/lib/stream_version.dart b/stream_chat_v1/example/lib/stream_version.dart new file mode 100644 index 0000000..b3f926b --- /dev/null +++ b/stream_chat_v1/example/lib/stream_version.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:yaml/yaml.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class StreamVersion extends StatelessWidget { + const StreamVersion({ + Key key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 16), + alignment: Alignment.bottomCenter, + child: FutureBuilder( + future: rootBundle.loadString('pubspec.lock'), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SizedBox(); + } + + final pubspec = snapshot.data; + final yaml = loadYaml(pubspec); + final streamChatDep = + yaml['packages']['stream_chat_flutter']['version']; + + return Text( + 'Stream SDK v ${streamChatDep}', + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + ); + }, + ), + ); + } +} diff --git a/stream_chat_v1/example/lib/threads.dart b/stream_chat_v1/example/lib/threads.dart new file mode 100644 index 0000000..f89e6fc --- /dev/null +++ b/stream_chat_v1/example/lib/threads.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Fourth step of the [tutorial](https://getstream.io/chat/flutter/tutorial/) +/// +/// Stream Chat supports message threads out of the box. Threads allows users to create sub-conversations inside the same channel. +/// +/// Using threaded conversations is very simple and mostly a matter of plugging the [MessageListView] to another widget that renders the widget. +/// To make this simple, such a widget only needs to build [MessageListView] with the parent attribute set to the thread’s root message. +/// +/// Now we can open threads and create new ones as well, if you long press a message you can tap on Reply and it will open the same [ThreadPage]. +void main() async { + final client = Client( + 's2dxdhpxd94g', + logLevel: Level.INFO, + ); + + await client.setUser( + User(id: 'super-band-9'), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A', + ); + + runApp(MyApp(client)); +} + +class MyApp extends StatelessWidget { + final Client client; + + MyApp(this.client); + + @override + Widget build(BuildContext context) { + return MaterialApp( + builder: (context, child) => StreamChat( + child: child, + client: client, + ), + home: Container( + child: ChannelListPage(), + ), + ); + } +} + +class ChannelListPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ChannelsBloc( + child: 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( + appBar: ChannelHeader(), + body: Column( + children: [ + Expanded( + child: MessageListView( + threadBuilder: (_, parentMessage) { + return ThreadPage( + parent: parentMessage, + ); + }, + ), + ), + MessageInput(), + ], + ), + ); + } +} + +class ThreadPage extends StatelessWidget { + final Message parent; + + ThreadPage({ + Key key, + this.parent, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ThreadHeader( + parent: parent, + ), + body: Column( + children: [ + Expanded( + child: MessageListView( + parentMessage: parent, + ), + ), + MessageInput( + parentMessage: parent, + ), + ], + ), + ); + } +} diff --git a/stream_chat_v1/example/pubspec.lock b/stream_chat_v1/example/pubspec.lock new file mode 100644 index 0000000..503a536 --- /dev/null +++ b/stream_chat_v1/example/pubspec.lock @@ -0,0 +1,1142 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "12.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.40.6" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.0-nullsafety.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0-nullsafety.1" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.0" + carousel_slider: + dependency: transitive + description: + name: carousel_slider + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.3" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0-nullsafety.1" + chewie: + dependency: transitive + description: + name: chewie + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + clipboard: + dependency: transitive + description: + name: clipboard + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2+8" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0-nullsafety.3" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.2" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + dio: + dependency: transitive + description: + name: dio + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.10" + emojis: + dependency: transitive + description: + name: emojis + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.3" + esys_flutter_share: + dependency: transitive + description: + name: esys_flutter_share + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + ezanimation: + dependency: transitive + description: + name: ezanimation + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0-nullsafety.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0-nullsafety.2" + file_picker: + dependency: transitive + description: + name: file_picker + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5+1" + firebase_core: + dependency: transitive + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.3" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1+1" + firebase_messaging: + dependency: transitive + description: + name: firebase_messaging + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_apns: + dependency: "direct main" + description: + name: flutter_apns + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.1" + flutter_app_badger: + dependency: transitive + description: + name: flutter_app_badger + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + flutter_driver: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.2" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.1" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+1" + flutter_markdown: + dependency: transitive + description: + name: flutter_markdown + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.2" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.11" + flutter_portal: + dependency: transitive + description: + name: flutter_portal + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.5" + flutter_slidable: + dependency: transitive + description: + name: flutter_slidable + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.7" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "0.19.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+4" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.19" + image_gallery_saver: + dependency: transitive + description: + name: image_gallery_saver + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.7" + image_picker: + dependency: transitive + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.7+21" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + import_js_library: + dependency: transitive + description: + name: import_js_library + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + jiffy: + dependency: transitive + description: + name: jiffy + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3-nullsafety.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" + lottie: + dependency: transitive + description: + name: lottie + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0+1" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10-nullsafety.1" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0-nullsafety.3" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7" + moor: + dependency: transitive + description: + name: moor + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.0" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.13" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0-nullsafety.1" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1+1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.27" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+8" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0-nullsafety.2" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + photo_manager: + dependency: transitive + description: + name: photo_manager + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.8" + photo_view: + dependency: transitive + description: + name: photo_view + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.3" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0-nullsafety.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0-nullsafety.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0-nullsafety.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.24.1" + scrollable_positioned_list: + dependency: transitive + description: + name: scrollable_positioned_list + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.9" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.12+4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2+4" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+11" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2+7" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2+2" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.9" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.9+2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" + shimmer: + dependency: transitive + description: + name: shimmer + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0-nullsafety.3" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.10-nullsafety.2" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0-nullsafety.2" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3+1" + sqlite3: + dependency: transitive + description: + name: sqlite3 + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.8" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0-nullsafety.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0-nullsafety.1" + stream_chat: + dependency: transitive + description: + path: "../../dart_client" + relative: true + source: path + version: "0.2.24+2" + stream_chat_flutter: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "1.0.0-rc" + stream_chat_flutter_core: + dependency: transitive + description: + path: "../../stream_chat_flutter_core" + relative: true + source: path + version: "1.0.0-rc" + stream_chat_persistence: + dependency: "direct main" + description: + path: "../../stream_chat_persistence" + relative: true + source: path + version: "1.0.0-rc" + streaming_shared_preferences: + dependency: "direct main" + description: + name: streaming_shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0-nullsafety.1" + substring_highlight: + dependency: transitive + description: + name: substring_highlight + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0+2" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0-nullsafety.1" + test: + dependency: "direct dev" + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0-nullsafety.5" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.19-nullsafety.2" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.12-nullsafety.5" + timezone: + dependency: transitive + description: + name: timezone + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.9" + transparent_image: + dependency: transitive + description: + name: transparent_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0-nullsafety.3" + url_launcher: + dependency: transitive + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "5.7.10" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+4" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+9" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.9" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5+1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+3" + uuid: + dependency: "direct main" + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0-nullsafety.3" + video_compress: + dependency: transitive + description: + name: video_compress + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + video_player: + dependency: transitive + description: + name: video_player + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4+1" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "5.5.0" + vm_service_client: + dependency: transitive + description: + name: vm_service_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.6+2" + wakelock: + dependency: transitive + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1+1" + wakelock_platform_interface: + dependency: transitive + description: + name: wakelock_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" + wakelock_web: + dependency: transitive + description: + name: wakelock_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+3" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+15" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.4" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.4" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.1" + yaml: + dependency: "direct main" + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" +sdks: + dart: ">=2.10.2 <2.11.0" + flutter: ">=1.22.2 <2.0.0" diff --git a/stream_chat_v1/example/pubspec.yaml b/stream_chat_v1/example/pubspec.yaml new file mode 100644 index 0000000..73d1c2f --- /dev/null +++ b/stream_chat_v1/example/pubspec.yaml @@ -0,0 +1,42 @@ +name: example +description: A new Flutter project. +publish_to: 'none' +version: 1.2.0 + +environment: + sdk: ">=2.2.2 <3.0.0" + +dependencies: + flutter: + sdk: flutter + stream_chat_flutter: + path: ../ + stream_chat_persistence: + path: ../../stream_chat_persistence + flutter_apns: ^1.4.1 + flutter_local_notifications: ^2.0.2 + flutter_svg: ^0.19.1 + flutter_secure_storage: ^3.3.5 + yaml: ^2.2.1 + uuid: ^2.2.2 + streaming_shared_preferences: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + flutter_launcher_icons: ^0.8.1 + test: any + +flutter: + assets: + - assets/ + - pubspec.lock + uses-material-design: true + +flutter_icons: + android: true + ios: true + image_path_android: "assets/android_icon.png" + image_path_ios: "assets/ios_icon.png" diff --git a/stream_chat_v1/example/test_driver/single_conversation.dart b/stream_chat_v1/example/test_driver/single_conversation.dart new file mode 100644 index 0000000..28ec8e5 --- /dev/null +++ b/stream_chat_v1/example/test_driver/single_conversation.dart @@ -0,0 +1,9 @@ +import 'package:flutter_driver/driver_extension.dart'; + +import '../lib/single_conversation.dart' as app; + +void main() async { + enableFlutterDriverExtension(); + + await app.main(); +} diff --git a/stream_chat_v1/example/test_driver/single_conversation_test.dart b/stream_chat_v1/example/test_driver/single_conversation_test.dart new file mode 100644 index 0000000..c4f7b99 --- /dev/null +++ b/stream_chat_v1/example/test_driver/single_conversation_test.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart'; + +void main() { + test('Single conversation', () async { + final inputFinder = find.byValueKey('messageInputText'); + final sendButtonFinder = find.byValueKey('sendButton'); + final messageListViewFinder = find.byValueKey('messageListView'); + + FlutterDriver driver = await FlutterDriver.connect(); + // Connect to the Flutter driver before running any tests. + + sleep(Duration(seconds: 5)); + + await driver.waitFor(inputFinder); + + await driver.tap(inputFinder); + + sleep(Duration(seconds: 1)); + + await driver.enterText('hey'); + + sleep(Duration(seconds: 1)); + + await driver.tap(sendButtonFinder); + + sleep(Duration(seconds: 1)); + + await driver.scroll( + messageListViewFinder, + 0, + 2000, + Duration(seconds: 1), + ); + + sleep(Duration(seconds: 1)); + + // Close the connection to the driver after the tests have completed. + sleep(Duration(seconds: 5)); + if (driver != null) { + await driver.close(); + } + }); +} diff --git a/stream_chat_v1/example/web/favicon.png b/stream_chat_v1/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/stream_chat_v1/example/web/favicon.png differ diff --git a/stream_chat_v1/example/web/icons/Icon-192.png b/stream_chat_v1/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/stream_chat_v1/example/web/icons/Icon-192.png differ diff --git a/stream_chat_v1/example/web/icons/Icon-512.png b/stream_chat_v1/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/stream_chat_v1/example/web/icons/Icon-512.png differ diff --git a/stream_chat_v1/example/web/index.html b/stream_chat_v1/example/web/index.html new file mode 100644 index 0000000..c9fb727 --- /dev/null +++ b/stream_chat_v1/example/web/index.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + example + + + + + + + + + diff --git a/stream_chat_v1/example/web/manifest.json b/stream_chat_v1/example/web/manifest.json new file mode 100644 index 0000000..8c01291 --- /dev/null +++ b/stream_chat_v1/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/stream_chat_v1/example/web/sql-wasm.js b/stream_chat_v1/example/web/sql-wasm.js new file mode 100644 index 0000000..9ef1837 --- /dev/null +++ b/stream_chat_v1/example/web/sql-wasm.js @@ -0,0 +1,207 @@ +// We are modularizing this manually because the current modularize setting in Emscripten has some issues: +// https://github.com/kripken/emscripten/issues/5820 +// In addition, When you use emcc's modularization, it still expects to export a global object called `Module`, +// which is able to be used/called before the WASM is loaded. +// The modularization below exports a promise that loads and resolves to the actual sql.js module. +// That way, this module can't be used before the WASM is finished loading. + +// We are going to define a function that a user will call to start loading initializing our Sql.js library +// However, that function might be called multiple times, and on subsequent calls, we don't actually want it to instantiate a new instance of the Module +// Instead, we want to return the previously loaded module + +// TODO: Make this not declare a global if used in the browser +var initSqlJsPromise = undefined; + +var initSqlJs = function (moduleConfig) { + +if (initSqlJsPromise){ +return initSqlJsPromise; +} +// If we're here, we've never called this function before +initSqlJsPromise = new Promise((resolveModule, reject) => { + +// We are modularizing this manually because the current modularize setting in Emscripten has some issues: +// https://github.com/kripken/emscripten/issues/5820 + +// The way to affect the loading of emcc compiled modules is to create a variable called `Module` and add +// properties to it, like `preRun`, `postRun`, etc +// We are using that to get notified when the WASM has finished loading. +// Only then will we return our promise + +// If they passed in a moduleConfig object, use that +// Otherwise, initialize Module to the empty object +var Module = typeof moduleConfig !== 'undefined' ? moduleConfig : {}; + +// EMCC only allows for a single onAbort function (not an array of functions) +// So if the user defined their own onAbort function, we remember it and call it +var originalOnAbortFunction = Module['onAbort']; +Module['onAbort'] = function (errorThatCausedAbort) { +reject(new Error(errorThatCausedAbort)); +if (originalOnAbortFunction){ +originalOnAbortFunction(errorThatCausedAbort); +} +}; + +Module['postRun'] = Module['postRun'] || []; +Module['postRun'].push(function () { +// When Emscripted calls postRun, this promise resolves with the built Module +resolveModule(Module); +}); + +// There is a section of code in the emcc-generated code below that looks like this: +// (Note that this is lowercase `module`) +// if (typeof module !== 'undefined') { +// module['exports'] = Module; +// } +// When that runs, it's going to overwrite our own modularization export efforts in shell-post.js! +// The only way to tell emcc not to emit it is to pass the MODULARIZE=1 or MODULARIZE_INSTANCE=1 flags, +// but that carries with it additional unnecessary baggage/bugs we don't want either. +// So, we have three options: +// 1) We undefine `module` +// 2) We remember what `module['exports']` was at the beginning of this function and we restore it later +// 3) We write a script to remove those lines of code as part of the Make process. +// +// Since those are the only lines of code that care about module, we will undefine it. It's the most straightforward +// of the options, and has the side effect of reducing emcc's efforts to modify the module if its output were to change in the future. +// That's a nice side effect since we're handling the modularization efforts ourselves +module = undefined; + +// The emcc-generated code and shell-post.js code goes below, +// meaning that all of it runs inside of this promise. If anything throws an exception, our promise will abort +var aa;var f;f||(f=typeof Module !== 'undefined' ? Module : {}); +var va=function(){var a;var b=h(4);var c={};var d=function(){function a(a,b){this.fb=a;this.db=b;this.nb=1;this.Eb=[]}a.prototype.bind=function(a){if(!this.fb)throw"Statement closed";this.reset();return Array.isArray(a)?this.lc(a):this.mc(a)};a.prototype.step=function(){var a;if(!this.fb)throw"Statement closed";this.nb=1;switch(a=Tb(this.fb)){case c.hc:return!0;case c.DONE:return!1;default:return this.db.handleError(a)}};a.prototype.sc=function(a){null==a&&(a=this.nb++);return Ub(this.fb,a)};a.prototype.tc= +function(a){null==a&&(a=this.nb++);return Vb(this.fb,a)};a.prototype.getBlob=function(a){var b;null==a&&(a=this.nb++);var c=Wb(this.fb,a);var d=Xb(this.fb,a);var e=new Uint8Array(c);for(a=b=0;0<=c?bc;a=0<=c?++b:--b)e[a]=l[d+a];return e};a.prototype.get=function(a){var b,d;null!=a&&this.bind(a)&&this.step();var e=[];a=b=0;for(d=ib(this.fb);0<=d?bd;a=0<=d?++b:--b)switch(Yb(this.fb,a)){case c.fc:case c.FLOAT:e.push(this.sc(a));break;case c.ic:e.push(this.tc(a));break;case c.Zb:e.push(this.getBlob(a)); +break;default:e.push(null)}return e};a.prototype.getColumnNames=function(){var a,b;var c=[];var d=a=0;for(b=ib(this.fb);0<=b?ab;d=0<=b?++a:--a)c.push(Zb(this.fb,d));return c};a.prototype.getAsObject=function(a){var b,c;var d=this.get(a);var e=this.getColumnNames();var g={};a=b=0;for(c=e.length;b>>0);if(null!=a){var c=this.filename,d=c?n("/",c):"/";c=ia(!0,!0);d=ja(d,(void 0!==c?c:438)&4095|32768,0);if(a){if("string"===typeof a){for(var e=Array(a.length),k=0,m=a.length;kc;e=0<=c?++g:--g){var m=q(d+4*e,"i32");var z=jc(m);e=function(){switch(!1){case 1!==z:return kc; +case 2!==z:return lc;case 3!==z:return mc;case 4!==z:return function(a){var b,c;var d=nc(a);var e=oc(a);a=new Uint8Array(d);for(b=c=0;0<=d?cd;b=0<=d?++c:--c)a[b]=l[e+b];return a};default:return function(){return null}}}();e=e(m);k.push(e)}if(c=b.apply(null,k))switch(typeof c){case "number":return pc(a,c);case "string":return qc(a,c,-1,-1)}else return rc(a)});this.handleError(sc(this.db,a,b.length,c.jc,0,d,0,0,0));return this};return a}();var g=f.cwrap("sqlite3_open","number",["string","number"]); +var k=f.cwrap("sqlite3_close_v2","number",["number"]);var m=f.cwrap("sqlite3_exec","number",["number","string","number","number","number"]);f.cwrap("sqlite3_free","",["number"]);var y=f.cwrap("sqlite3_changes","number",["number"]);var z=f.cwrap("sqlite3_prepare_v2","number",["number","string","number","number","number"]);var fa=f.cwrap("sqlite3_prepare_v2","number",["number","number","number","number","number"]);var ca=f.cwrap("sqlite3_bind_text","number",["number","number","number","number","number"]); +var Ia=f.cwrap("sqlite3_bind_blob","number",["number","number","number","number","number"]);var ac=f.cwrap("sqlite3_bind_double","number",["number","number","number"]);var $b=f.cwrap("sqlite3_bind_int","number",["number","number","number"]);var bc=f.cwrap("sqlite3_bind_parameter_index","number",["number","string"]);var Tb=f.cwrap("sqlite3_step","number",["number"]);var hc=f.cwrap("sqlite3_errmsg","string",["number"]);var ib=f.cwrap("sqlite3_data_count","number",["number"]);var Ub=f.cwrap("sqlite3_column_double", +"number",["number","number"]);var Vb=f.cwrap("sqlite3_column_text","string",["number","number"]);var Xb=f.cwrap("sqlite3_column_blob","number",["number","number"]);var Wb=f.cwrap("sqlite3_column_bytes","number",["number","number"]);var Yb=f.cwrap("sqlite3_column_type","number",["number","number"]);var Zb=f.cwrap("sqlite3_column_name","string",["number","number"]);var dc=f.cwrap("sqlite3_reset","number",["number"]);var cc=f.cwrap("sqlite3_clear_bindings","number",["number"]);var ec=f.cwrap("sqlite3_finalize", +"number",["number"]);var sc=f.cwrap("sqlite3_create_function_v2","number","number string number number number number number number number".split(" "));var jc=f.cwrap("sqlite3_value_type","number",["number"]);var nc=f.cwrap("sqlite3_value_bytes","number",["number"]);var mc=f.cwrap("sqlite3_value_text","string",["number"]);var kc=f.cwrap("sqlite3_value_int","number",["number"]);var oc=f.cwrap("sqlite3_value_blob","number",["number"]);var lc=f.cwrap("sqlite3_value_double","number",["number"]);var pc= +f.cwrap("sqlite3_result_double","",["number","number"]);var rc=f.cwrap("sqlite3_result_null","",["number"]);var qc=f.cwrap("sqlite3_result_text","",["number","string","number","number"]);var fc=f.cwrap("RegisterExtensionFunctions","number",["number"]);this.SQL={Database:e};for(a in this.SQL)f[a]=this.SQL[a];var da=0;c.xb=0;c.we=1;c.Pe=2;c.Ze=3;c.Cc=4;c.Ec=5;c.Se=6;c.NOMEM=7;c.bf=8;c.Qe=9;c.Re=10;c.Hc=11;c.NOTFOUND=12;c.Oe=13;c.Fc=14;c.$e=15;c.EMPTY=16;c.cf=17;c.df=18;c.Gc=19;c.Te=20;c.Ue=21;c.Ve= +22;c.Dc=23;c.Ne=24;c.af=25;c.We=26;c.Xe=27;c.ef=28;c.hc=100;c.DONE=101;c.fc=1;c.FLOAT=2;c.ic=3;c.Zb=4;c.Ye=5;c.jc=1}.bind(this);f.preRun=f.preRun||[];f.preRun.push(va);var wa={},u;for(u in f)f.hasOwnProperty(u)&&(wa[u]=f[u]);f.arguments=[];f.thisProgram="./this.program";f.quit=function(a,b){throw b;};f.preRun=[];f.postRun=[];var v=!1,w=!1,x=!1,xa=!1;v="object"===typeof window;w="function"===typeof importScripts;x="object"===typeof process&&"function"===typeof require&&!v&&!w;xa=!v&&!x&&!w;var A=""; +if(x){A=__dirname+"/";var ya,za;f.read=function(a,b){ya||(ya=require("fs"));za||(za=require("path"));a=za.normalize(a);a=ya.readFileSync(a);return b?a:a.toString()};f.readBinary=function(a){a=f.read(a,!0);a.buffer||(a=new Uint8Array(a));assert(a.buffer);return a};1>2];a=b+a+15&-16;if(a<=Da())D[Ca>>2]=a;else if(!Ea(a))return 0;return b} +var Fa={"f64-rem":function(a,b){return a%b},"debugger":function(){debugger}},Ga=1,E=Array(64);function ua(a){for(var b=0;64>b;b++)if(!E[b])return E[b]=a,Ga+b;throw"Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.";}"object"!==typeof WebAssembly&&C("no native wasm support detected"); +function q(a,b){b=b||"i8";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":return l[a>>0];case "i8":return l[a>>0];case "i16":return Ha[a>>1];case "i32":return D[a>>2];case "i64":return D[a>>2];case "float":return Ja[a>>2];case "double":return Ka[a>>3];default:B("invalid type for getValue: "+b)}return null}var La,Ma=!1;function assert(a,b){a||B("Assertion failed: "+b)}function Na(a){var b=f["_"+a];assert(b,"Cannot call unknown function "+a+", make sure it is exported");return b} +function Oa(a,b,c,d){var e={string:function(a){var b=0;if(null!==a&&void 0!==a&&0!==a){var c=(a.length<<2)+1;b=h(c);r(a,F,b,c)}return b},array:function(a){var b=h(a.length);l.set(a,b);return b}},g=Na(a),k=[];a=0;if(d)for(var m=0;m>0]=0;break;case "i8":l[a>>0]=0;break;case "i16":Ha[a>>1]=0;break;case "i32":D[a>>2]=0;break;case "i64":aa=[0,1<=+Pa(0)?~~+Qa(0)>>>0:0];D[a>>2]=aa[0];D[a+4>>2]=aa[1];break;case "float":Ja[a>>2]=0;break;case "double":Ka[a>>3]=0;break;default:B("invalid type for setValue: "+b)}}var Ra=0,Sa=3; +function ea(a){var b=Ra;if("number"===typeof a){var c=!0;var d=a}else c=!1,d=a.length;b=b==Sa?e:[Ta,h,Ba][b](Math.max(d,1));if(c){var e=b;assert(0==(b&3));for(a=b+(d&-4);e>2]=0;for(a=b+d;e>0]=0;return b}a.subarray||a.slice?F.set(a,b):F.set(new Uint8Array(a),b);return b}var Ua="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0; +function t(a,b,c){var d=b+c;for(c=b;a[c]&&!(c>=d);)++c;if(16e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d}function G(a){return a?t(F,a,void 0):""} +function r(a,b,c,d){if(!(0=k){var m=a.charCodeAt(++g);k=65536+((k&1023)<<10)|m&1023}if(127>=k){if(c>=d)break;b[c++]=k}else{if(2047>=k){if(c+1>=d)break;b[c++]=192|k>>6}else{if(65535>=k){if(c+2>=d)break;b[c++]=224|k>>12}else{if(c+3>=d)break;b[c++]=240|k>>18;b[c++]=128|k>>12&63}b[c++]=128|k>>6&63}b[c++]=128|k&63}}b[c]=0;return c-e} +function oa(a){for(var b=0,c=0;c=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}"undefined"!==typeof TextDecoder&&new TextDecoder("utf-16le");function Va(a){return a.replace(/__Z[\w\d_]+/g,function(a){return a===a?a:a+" ["+a+"]"})}function Wa(a){0Ya&&C("TOTAL_MEMORY should be larger than TOTAL_STACK, was "+Ya+"! (TOTAL_STACK=5242880)"); +f.buffer?buffer=f.buffer:"object"===typeof WebAssembly&&"function"===typeof WebAssembly.Memory?(La=new WebAssembly.Memory({initial:Ya/65536}),buffer=La.buffer):buffer=new ArrayBuffer(Ya);Xa();D[Ca>>2]=5303264;function Za(a){for(;0>2];var c=D[b>>2]}else ob.rb=!0,J.USER=J.LOGNAME="web_user",J.PATH="/",J.PWD="/",J.HOME="/home/web_user",J.LANG="C.UTF-8",J._=f.thisProgram,c=db?Ta(1024):Ba(1024),b=db?Ta(256):Ba(256),D[b>>2]=c,D[a>>2]=b;a=[];var d=0,e;for(e in J)if("string"===typeof J[e]){var g=e+"="+J[e];a.push(g);d+=g.length}if(1024>0]=d.charCodeAt(m);l[k>>0]=0;D[b+ +4*e>>2]=c;c+=g.length+1}D[b+4*a.length>>2]=0}function pb(a){f.___errno_location&&(D[f.___errno_location()>>2]=a);return a}function qb(a,b){for(var c=0,d=a.length-1;0<=d;d--){var e=a[d];"."===e?a.splice(d,1):".."===e?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a}function rb(a){var b="/"===a.charAt(0),c="/"===a.substr(-1);(a=qb(a.split("/").filter(function(a){return!!a}),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a} +function sb(a){var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&(b=b.substr(0,b.length-1));return a+b}function tb(a){if("/"===a)return"/";var b=a.lastIndexOf("/");return-1===b?a:a.substr(b+1)}function ub(){var a=Array.prototype.slice.call(arguments,0);return rb(a.join("/"))}function n(a,b){return rb(a+"/"+b)} +function vb(){for(var a="",b=!1,c=arguments.length-1;-1<=c&&!b;c--){b=0<=c?arguments[c]:"/";if("string"!==typeof b)throw new TypeError("Arguments to path.resolve must be strings");if(!b)return"";a=b+"/"+a;b="/"===b.charAt(0)}a=qb(a.split("/").filter(function(a){return!!a}),!b).join("/");return(b?"/":"")+a||"."}var wb=[];function xb(a,b){wb[a]={input:[],output:[],ub:b};yb(a,zb)} +var zb={open:function(a){var b=wb[a.node.rdev];if(!b)throw new K(L.Cb);a.tty=b;a.seekable=!1},close:function(a){a.tty.ub.flush(a.tty)},flush:function(a){a.tty.ub.flush(a.tty)},read:function(a,b,c,d){if(!a.tty||!a.tty.ub.Xb)throw new K(L.Ob);for(var e=0,g=0;g=b||(b=Math.max(b,c*(1048576>c?2:1.125)|0),0!=c&&(b=Math.max(b,256)),c=a.bb,a.bb=new Uint8Array(b),0b)a.bb.length=b;else for(;a.bb.length=a.node.gb)return 0;a=Math.min(a.node.gb-e,d);if(8b)throw new K(L.ib);return b},Pb:function(a,b,c){M.Tb(a.node,b+c);a.node.gb=Math.max(a.node.gb,b+c)},zb:function(a,b,c,d,e,g,k){if(32768!== +(a.node.mode&61440))throw new K(L.Cb);c=a.node.bb;if(k&2||c.buffer!==b&&c.buffer!==b.buffer){if(0>2)}catch(c){if(!c.code)throw c; +throw new K(L[c.code]);}return b.mode},kb:function(a){for(var b=[];a.parent!==a;)b.push(a.name),a=a.parent;b.push(a.jb.Hb.root);b.reverse();return ub.apply(null,b)},qc:function(a){a&=-2656257;var b=0,c;for(c in P.Ub)a&c&&(b|=P.Ub[c],a^=c);if(a)throw new K(L.ib);return b},ab:{lb:function(a){a=P.kb(a);try{var b=fs.lstatSync(a)}catch(c){if(!c.code)throw c;throw new K(L[c.code]);}P.yb&&!b.pb&&(b.pb=4096);P.yb&&!b.blocks&&(b.blocks=(b.size+b.pb-1)/b.pb|0);return{dev:b.dev,ino:b.ino,mode:b.mode,nlink:b.nlink, +uid:b.uid,gid:b.gid,rdev:b.rdev,size:b.size,atime:b.atime,mtime:b.mtime,ctime:b.ctime,pb:b.pb,blocks:b.blocks}},hb:function(a,b){var c=P.kb(a);try{void 0!==b.mode&&(fs.chmodSync(c,b.mode),a.mode=b.mode),void 0!==b.size&&fs.truncateSync(c,b.size)}catch(d){if(!d.code)throw d;throw new K(L[d.code]);}},lookup:function(a,b){var c=n(P.kb(a),b);c=P.Wb(c);return P.createNode(a,b,c)},vb:function(a,b,c,d){a=P.createNode(a,b,c,d);b=P.kb(a);try{N(a.mode)?fs.mkdirSync(b,a.mode):fs.writeFileSync(b,"",{mode:a.mode})}catch(e){if(!e.code)throw e; +throw new K(L[e.code]);}return a},rename:function(a,b,c){a=P.kb(a);b=n(P.kb(b),c);try{fs.renameSync(a,b)}catch(d){if(!d.code)throw d;throw new K(L[d.code]);}},unlink:function(a,b){a=n(P.kb(a),b);try{fs.unlinkSync(a)}catch(c){if(!c.code)throw c;throw new K(L[c.code]);}},rmdir:function(a,b){a=n(P.kb(a),b);try{fs.rmdirSync(a)}catch(c){if(!c.code)throw c;throw new K(L[c.code]);}},readdir:function(a){a=P.kb(a);try{return fs.readdirSync(a)}catch(b){if(!b.code)throw b;throw new K(L[b.code]);}},symlink:function(a, +b,c){a=n(P.kb(a),b);try{fs.symlinkSync(c,a)}catch(d){if(!d.code)throw d;throw new K(L[d.code]);}},readlink:function(a){var b=P.kb(a);try{return b=fs.readlinkSync(b),b=Fb.relative(Fb.resolve(a.jb.Hb.root),b)}catch(c){if(!c.code)throw c;throw new K(L[c.code]);}}},cb:{open:function(a){var b=P.kb(a.node);try{32768===(a.node.mode&61440)&&(a.wb=fs.openSync(b,P.qc(a.flags)))}catch(c){if(!c.code)throw c;throw new K(L[c.code]);}},close:function(a){try{32768===(a.node.mode&61440)&&a.wb&&fs.closeSync(a.wb)}catch(b){if(!b.code)throw b; +throw new K(L[b.code]);}},read:function(a,b,c,d,e){if(0===d)return 0;try{return fs.readSync(a.wb,P.Rb(b.buffer),c,d,e)}catch(g){throw new K(L[g.code]);}},write:function(a,b,c,d,e){try{return fs.writeSync(a.wb,P.Rb(b.buffer),c,d,e)}catch(g){throw new K(L[g.code]);}},ob:function(a,b,c){if(1===c)b+=a.position;else if(2===c&&32768===(a.node.mode&61440))try{b+=fs.fstatSync(a.wb).size}catch(d){throw new K(L[d.code]);}if(0>b)throw new K(L.ib);return b}}},Gb=null,Hb={},Q=[],Ib=1,R=null,Jb=!0,S={},K=null, +Eb={};function T(a,b){a=vb("/",a);b=b||{};if(!a)return{path:"",node:null};var c={Vb:!0,Jb:0},d;for(d in c)void 0===b[d]&&(b[d]=c[d]);if(8>>0)%R.length}function Nb(a){var b=Mb(a.parent.id,a.name);a.tb=R[b];R[b]=a}function Ob(a){var b=Mb(a.parent.id,a.name);if(R[b]===a)R[b]=a.tb;else for(b=R[b];b;){if(b.tb===a){b.tb=a.tb;break}b=b.tb}} +function O(a,b){var c;if(c=(c=Pb(a,"x"))?c:a.ab.lookup?0:13)throw new K(c,a);for(c=R[Mb(a.id,b)];c;c=c.tb){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.ab.lookup(a,b)} +function Db(a,b,c,d){Qb||(Qb=function(a,b,c,d){a||(a=this);this.parent=a;this.jb=a.jb;this.sb=null;this.id=Ib++;this.name=b;this.mode=c;this.ab={};this.cb={};this.rdev=d},Qb.prototype={},Object.defineProperties(Qb.prototype,{read:{get:function(){return 365===(this.mode&365)},set:function(a){a?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146===(this.mode&146)},set:function(a){a?this.mode|=146:this.mode&=-147}}}));a=new Qb(a,b,c,d);Nb(a);return a} +function N(a){return 16384===(a&61440)}var Rb={r:0,rs:1052672,"r+":2,w:577,wx:705,xw:705,"w+":578,"wx+":706,"xw+":706,a:1089,ax:1217,xa:1217,"a+":1090,"ax+":1218,"xa+":1218};function ic(a){var b=["r","w","rw"][a&3];a&512&&(b+="w");return b}function Pb(a,b){if(Jb)return 0;if(-1===b.indexOf("r")||a.mode&292){if(-1!==b.indexOf("w")&&!(a.mode&146)||-1!==b.indexOf("x")&&!(a.mode&73))return 13}else return 13;return 0}function tc(a,b){try{return O(a,b),17}catch(c){}return Pb(a,"wx")} +function uc(a,b,c){try{var d=O(a,b)}catch(e){return e.eb}if(a=Pb(a,"wx"))return a;if(c){if(!N(d.mode))return 20;if(d===d.parent||"/"===Lb(d))return 16}else if(N(d.mode))return 21;return 0}function vc(a){var b=4096;for(a=a||0;a<=b;a++)if(!Q[a])return a;throw new K(24);} +function wc(a,b){xc||(xc=function(){},xc.prototype={},Object.defineProperties(xc.prototype,{object:{get:function(){return this.node},set:function(a){this.node=a}}}));var c=new xc,d;for(d in a)c[d]=a[d];a=c;b=vc(b);a.fd=b;return Q[b]=a}var Cb={open:function(a){a.cb=Hb[a.node.rdev].cb;a.cb.open&&a.cb.open(a)},ob:function(){throw new K(29);}};function yb(a,b){Hb[a]={cb:b}} +function yc(a,b){var c="/"===b,d=!b;if(c&&Gb)throw new K(16);if(!c&&!d){var e=T(b,{Vb:!1});b=e.path;e=e.node;if(e.sb)throw new K(16);if(!N(e.mode))throw new K(20);}b={type:a,Hb:{},Yb:b,wc:[]};a=a.jb(b);a.jb=b;b.root=a;c?Gb=a:e&&(e.sb=b,e.jb&&e.jb.wc.push(b))}function ja(a,b,c){var d=T(a,{parent:!0}).node;a=tb(a);if(!a||"."===a||".."===a)throw new K(22);var e=tc(d,a);if(e)throw new K(e);if(!d.ab.vb)throw new K(1);return d.ab.vb(d,a,b,c)}function U(a,b){ja(a,(void 0!==b?b:511)&1023|16384,0)} +function zc(a,b,c){"undefined"===typeof c&&(c=b,b=438);ja(a,b|8192,c)}function Ac(a,b){if(!vb(a))throw new K(2);var c=T(b,{parent:!0}).node;if(!c)throw new K(2);b=tb(b);var d=tc(c,b);if(d)throw new K(d);if(!c.ab.symlink)throw new K(1);c.ab.symlink(c,b,a)} +function ta(a){var b=T(a,{parent:!0}).node,c=tb(a),d=O(b,c),e=uc(b,c,!1);if(e)throw new K(e);if(!b.ab.unlink)throw new K(1);if(d.sb)throw new K(16);try{S.willDeletePath&&S.willDeletePath(a)}catch(g){console.log("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+g.message)}b.ab.unlink(b,c);Ob(d);try{if(S.onDeletePath)S.onDeletePath(a)}catch(g){console.log("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}} +function Kb(a){a=T(a).node;if(!a)throw new K(2);if(!a.ab.readlink)throw new K(22);return vb(Lb(a.parent),a.ab.readlink(a))}function ra(a,b){a=T(a,{qb:!b}).node;if(!a)throw new K(2);if(!a.ab.lb)throw new K(1);return a.ab.lb(a)}function Bc(a){return ra(a,!0)}function ka(a,b){var c;"string"===typeof a?c=T(a,{qb:!0}).node:c=a;if(!c.ab.hb)throw new K(1);c.ab.hb(c,{mode:b&4095|c.mode&-4096,timestamp:Date.now()})} +function Cc(a){var b;"string"===typeof a?b=T(a,{qb:!0}).node:b=a;if(!b.ab.hb)throw new K(1);b.ab.hb(b,{timestamp:Date.now()})}function Dc(a,b){if(0>b)throw new K(22);var c;"string"===typeof a?c=T(a,{qb:!0}).node:c=a;if(!c.ab.hb)throw new K(1);if(N(c.mode))throw new K(21);if(32768!==(c.mode&61440))throw new K(22);if(a=Pb(c,"w"))throw new K(a);c.ab.hb(c,{size:b,timestamp:Date.now()})} +function p(a,b,c,d){if(""===a)throw new K(2);if("string"===typeof b){var e=Rb[b];if("undefined"===typeof e)throw Error("Unknown file open mode: "+b);b=e}c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var g=a;else{a=rb(a);try{g=T(a,{qb:!(b&131072)}).node}catch(k){}}e=!1;if(b&64)if(g){if(b&128)throw new K(17);}else g=ja(a,c,0),e=!0;if(!g)throw new K(2);8192===(g.mode&61440)&&(b&=-513);if(b&65536&&!N(g.mode))throw new K(20);if(!e&&(c=g?40960===(g.mode&61440)?40:N(g.mode)&& +("r"!==ic(b)||b&512)?21:Pb(g,ic(b)):2))throw new K(c);b&512&&Dc(g,0);b&=-641;d=wc({node:g,path:Lb(g),flags:b,seekable:!0,position:0,cb:g.cb,Bc:[],error:!1},d);d.cb.open&&d.cb.open(d);!f.logReadFiles||b&1||(Ec||(Ec={}),a in Ec||(Ec[a]=1,console.log("FS.trackingDelegate error on read file: "+a)));try{S.onOpenFile&&(g=0,1!==(b&2097155)&&(g|=1),0!==(b&2097155)&&(g|=2),S.onOpenFile(a,g))}catch(k){console.log("FS.trackingDelegate['onOpenFile']('"+a+"', flags) threw an exception: "+k.message)}return d} +function ma(a){if(null===a.fd)throw new K(9);a.Gb&&(a.Gb=null);try{a.cb.close&&a.cb.close(a)}catch(b){throw b;}finally{Q[a.fd]=null}a.fd=null}function Fc(a,b,c){if(null===a.fd)throw new K(9);if(!a.seekable||!a.cb.ob)throw new K(29);if(0!=c&&1!=c&&2!=c)throw new K(22);a.position=a.cb.ob(a,b,c);a.Bc=[]} +function sa(a,b,c,d,e){if(0>d||0>e)throw new K(22);if(null===a.fd)throw new K(9);if(1===(a.flags&2097155))throw new K(9);if(N(a.node.mode))throw new K(21);if(!a.cb.read)throw new K(22);var g="undefined"!==typeof e;if(!g)e=a.position;else if(!a.seekable)throw new K(29);b=a.cb.read(a,b,c,d,e);g||(a.position+=b);return b} +function la(a,b,c,d,e,g){if(0>d||0>e)throw new K(22);if(null===a.fd)throw new K(9);if(0===(a.flags&2097155))throw new K(9);if(N(a.node.mode))throw new K(21);if(!a.cb.write)throw new K(22);a.flags&1024&&Fc(a,0,2);var k="undefined"!==typeof e;if(!k)e=a.position;else if(!a.seekable)throw new K(29);b=a.cb.write(a,b,c,d,e,g);k||(a.position+=b);try{if(a.path&&S.onWriteToFile)S.onWriteToFile(a.path)}catch(m){console.log("FS.trackingDelegate['onWriteToFile']('"+a.path+"') threw an exception: "+m.message)}return b} +function Gc(){K||(K=function(a,b){this.node=b;this.zc=function(a){this.eb=a};this.zc(a);this.message="FS error";this.stack&&Object.defineProperty(this,"stack",{value:Error().stack,writable:!0})},K.prototype=Error(),K.prototype.constructor=K,[2].forEach(function(a){Eb[a]=new K(a);Eb[a].stack=""}))}var Hc;function ia(a,b){var c=0;a&&(c|=365);b&&(c|=146);return c} +function Ic(a,b,c){a=n("/dev",a);var d=ia(!!b,!!c);Jc||(Jc=64);var e=Jc++<<8|0;yb(e,{open:function(a){a.seekable=!1},close:function(){c&&c.buffer&&c.buffer.length&&c(10)},read:function(a,c,d,e){for(var g=0,k=0;k>2]=d.dev;D[c+4>>2]=0;D[c+8>>2]=d.ino;D[c+12>>2]=d.mode;D[c+16>>2]=d.nlink;D[c+20>>2]=d.uid;D[c+24>>2]=d.gid;D[c+28>>2]=d.rdev;D[c+32>>2]=0;D[c+36>>2]=d.size;D[c+40>>2]=4096;D[c+44>>2]=d.blocks;D[c+48>>2]=d.atime.getTime()/1E3|0;D[c+52>>2]=0;D[c+56>>2]=d.mtime.getTime()/1E3|0;D[c+60>>2]=0;D[c+64>>2]=d.ctime.getTime()/1E3|0;D[c+68>>2]=0;D[c+72>>2]=d.ino;return 0}var W=0; +function X(){W+=4;return D[W-4>>2]}function Y(){return G(X())}function Z(){var a=Q[X()];if(!a)throw new K(L.Kb);return a}function Da(){return l.length}function Ea(a){if(2147418112=b?b=Wa(2*b):b=Math.min(Wa((3*b+2147483648)/4),2147418112);a=Wa(b);var c=buffer.byteLength;try{var d=-1!==La.grow((a-c)/65536)?buffer=La.buffer:null}catch(e){d=null}if(!d||d.byteLength!=b)return!1;Xa();return!0} +function Mc(a){if(0===a)return 0;a=G(a);if(!J.hasOwnProperty(a))return 0;Mc.rb&&ha(Mc.rb);a=J[a];var b=oa(a)+1,c=Ta(b);c&&r(a,l,c,b);Mc.rb=c;return Mc.rb}r("GMT",F,60272,4); +function Nc(){function a(a){return(a=a.toTimeString().match(/\(([A-Za-z ]+)\)$/))?a[1]:"GMT"}if(!Oc){Oc=!0;D[Pc()>>2]=60*(new Date).getTimezoneOffset();var b=new Date(2E3,0,1),c=new Date(2E3,6,1);D[Qc()>>2]=Number(b.getTimezoneOffset()!=c.getTimezoneOffset());var d=a(b),e=a(c);d=ea(ba(d));e=ea(ba(e));c.getTimezoneOffset()>2]=d,D[Rc()+4>>2]=e):(D[Rc()>>2]=e,D[Rc()+4>>2]=d)}}var Oc; +function Sc(a){a/=1E3;if((v||w)&&self.performance&&self.performance.now)for(var b=self.performance.now();self.performance.now()-b>2]=c.position;c.Gb&&0===d&&0===g&&(c.Gb=null);return 0}catch(k){return"undefined"!==typeof V&&k instanceof K||B(k),-k.eb}},ca:function(a,b){W=b;try{var c=Y(),d=X();ka(c,d);return 0}catch(e){return"undefined"!==typeof V&&e instanceof K||B(e),-e.eb}},ba:function(a,b){W=b;try{var c=X(),d=X();if(0===d)return-L.ib;if(dd?-L.ib:p(c.path,c.flags,0,d).fd;case 1:case 2:return 0; +case 3:return c.flags;case 4:return d=X(),c.flags|=d,0;case 12:return d=X(),Ha[d+0>>1]=2,0;case 13:case 14:return 0;case 16:case 8:return-L.ib;case 9:return pb(L.ib),-1;default:return-L.ib}}catch(e){return"undefined"!==typeof V&&e instanceof K||B(e),-e.eb}},U:function(a,b){W=b;try{var c=Z(),d=X(),e=X();return sa(c,l,d,e)}catch(g){return"undefined"!==typeof V&&g instanceof K||B(g),-g.eb}},T:function(a,b){W=b;try{var c=Y();var d=X();if(d&-8)var e=-L.ib;else{var g=T(c,{qb:!0}).node;a="";d&4&&(a+="r"); +d&2&&(a+="w");d&1&&(a+="x");e=a&&Pb(g,a)?-L.$b:0}return e}catch(k){return"undefined"!==typeof V&&k instanceof K||B(k),-k.eb}},S:function(a,b){W=b;try{var c=Y(),d=X();a=c;a=rb(a);"/"===a[a.length-1]&&(a=a.substr(0,a.length-1));U(a,d);return 0}catch(e){return"undefined"!==typeof V&&e instanceof K||B(e),-e.eb}},R:function(a,b){W=b;try{var c=Z(),d=X(),e=X();return la(c,l,d,e)}catch(g){return"undefined"!==typeof V&&g instanceof K||B(g),-g.eb}},Q:function(a,b){W=b;try{var c=Y(),d=T(c,{parent:!0}).node, +e=tb(c),g=O(d,e),k=uc(d,e,!0);if(k)throw new K(k);if(!d.ab.rmdir)throw new K(1);if(g.sb)throw new K(16);try{S.willDeletePath&&S.willDeletePath(c)}catch(m){console.log("FS.trackingDelegate['willDeletePath']('"+c+"') threw an exception: "+m.message)}d.ab.rmdir(d,e);Ob(g);try{if(S.onDeletePath)S.onDeletePath(c)}catch(m){console.log("FS.trackingDelegate['onDeletePath']('"+c+"') threw an exception: "+m.message)}return 0}catch(m){return"undefined"!==typeof V&&m instanceof K||B(m),-m.eb}},P:function(a,b){W= +b;try{var c=Y(),d=X(),e=X();return p(c,d,e).fd}catch(g){return"undefined"!==typeof V&&g instanceof K||B(g),-g.eb}},s:function(a,b){W=b;try{var c=Z();ma(c);return 0}catch(d){return"undefined"!==typeof V&&d instanceof K||B(d),-d.eb}},O:function(a,b){W=b;try{var c=Y(),d=X();var e=X();if(0>=e)var g=-L.ib;else{var k=Kb(c),m=Math.min(e,oa(k)),y=l[d+m];r(k,F,d,e+1);l[d+m]=y;g=m}return g}catch(z){return"undefined"!==typeof V&&z instanceof K||B(z),-z.eb}},N:function(a,b){W=b;try{var c=X(),d=X(),e=Kc[c];if(!e)return 0; +if(d===e.uc){var g=Q[e.fd],k=e.flags,m=new Uint8Array(F.subarray(c,c+d));g&&g.cb.Ab&&g.cb.Ab(g,m,0,d,k);Kc[c]=null;e.Db&&ha(e.vc)}return 0}catch(y){return"undefined"!==typeof V&&y instanceof K||B(y),-y.eb}},M:function(a,b){W=b;try{var c=X(),d=X(),e=Q[c];if(!e)throw new K(9);ka(e.node,d);return 0}catch(g){return"undefined"!==typeof V&&g instanceof K||B(g),-g.eb}},L:Da,K:function(a,b,c){F.set(F.subarray(b,b+c),a)},J:Ea,r:Mc,q:function(a){var b=Date.now();D[a>>2]=b/1E3|0;D[a+4>>2]=b%1E3*1E3|0;return 0}, +I:function(a){return Math.log(a)/Math.LN10},p:function(){B("trap!")},H:function(a){Nc();a=new Date(1E3*D[a>>2]);D[15056]=a.getSeconds();D[15057]=a.getMinutes();D[15058]=a.getHours();D[15059]=a.getDate();D[15060]=a.getMonth();D[15061]=a.getFullYear()-1900;D[15062]=a.getDay();var b=new Date(a.getFullYear(),0,1);D[15063]=(a.getTime()-b.getTime())/864E5|0;D[15065]=-(60*a.getTimezoneOffset());var c=(new Date(2E3,6,1)).getTimezoneOffset();b=b.getTimezoneOffset();a=(c!=b&&a.getTimezoneOffset()==Math.min(b, +c))|0;D[15064]=a;a=D[Rc()+(a?4:0)>>2];D[15066]=a;return 60224},G:function(a,b){var c=D[a>>2];a=D[a+4>>2];0!==b&&(D[b>>2]=0,D[b+4>>2]=0);return Sc(1E6*c+a/1E3)},F:function(a){switch(a){case 30:return 16384;case 85:return 131068;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809; +case 79:return 0;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32; +case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===typeof navigator?navigator.hardwareConcurrency||1:1}pb(22);return-1}, +E:function(a){var b=Date.now()/1E3|0;a&&(D[a>>2]=b);return b},D:function(a,b){if(b){var c=1E3*D[b+8>>2];c+=D[b+12>>2]/1E3}else c=Date.now();a=G(a);try{b=c;var d=T(a,{qb:!0}).node;d.ab.hb(d,{timestamp:Math.max(b,c)});return 0}catch(e){a=e;if(!(a instanceof K)){a+=" : ";a:{d=Error();if(!d.stack){try{throw Error(0);}catch(g){d=g}if(!d.stack){d="(no stack trace available)";break a}}d=d.stack.toString()}f.extraStackTrace&&(d+="\n"+f.extraStackTrace());d=Va(d);throw a+d;}pb(a.eb);return-1}},C:function(){B("OOM")}, +a:Ca},buffer);f.asm=Vc;f._RegisterExtensionFunctions=function(){return f.asm.ha.apply(null,arguments)};var nb=f.___emscripten_environ_constructor=function(){return f.asm.ia.apply(null,arguments)};f.___errno_location=function(){return f.asm.ja.apply(null,arguments)}; +var Qc=f.__get_daylight=function(){return f.asm.ka.apply(null,arguments)},Pc=f.__get_timezone=function(){return f.asm.la.apply(null,arguments)},Rc=f.__get_tzname=function(){return f.asm.ma.apply(null,arguments)},ha=f._free=function(){return f.asm.na.apply(null,arguments)},Ta=f._malloc=function(){return f.asm.oa.apply(null,arguments)},Tc=f._memalign=function(){return f.asm.pa.apply(null,arguments)},Uc=f._memset=function(){return f.asm.qa.apply(null,arguments)}; +f._sqlite3_bind_blob=function(){return f.asm.ra.apply(null,arguments)};f._sqlite3_bind_double=function(){return f.asm.sa.apply(null,arguments)};f._sqlite3_bind_int=function(){return f.asm.ta.apply(null,arguments)};f._sqlite3_bind_parameter_index=function(){return f.asm.ua.apply(null,arguments)};f._sqlite3_bind_text=function(){return f.asm.va.apply(null,arguments)};f._sqlite3_changes=function(){return f.asm.wa.apply(null,arguments)};f._sqlite3_clear_bindings=function(){return f.asm.xa.apply(null,arguments)}; +f._sqlite3_close_v2=function(){return f.asm.ya.apply(null,arguments)};f._sqlite3_column_blob=function(){return f.asm.za.apply(null,arguments)};f._sqlite3_column_bytes=function(){return f.asm.Aa.apply(null,arguments)};f._sqlite3_column_double=function(){return f.asm.Ba.apply(null,arguments)};f._sqlite3_column_name=function(){return f.asm.Ca.apply(null,arguments)};f._sqlite3_column_text=function(){return f.asm.Da.apply(null,arguments)};f._sqlite3_column_type=function(){return f.asm.Ea.apply(null,arguments)}; +f._sqlite3_create_function_v2=function(){return f.asm.Fa.apply(null,arguments)};f._sqlite3_data_count=function(){return f.asm.Ga.apply(null,arguments)};f._sqlite3_errmsg=function(){return f.asm.Ha.apply(null,arguments)};f._sqlite3_exec=function(){return f.asm.Ia.apply(null,arguments)};f._sqlite3_finalize=function(){return f.asm.Ja.apply(null,arguments)};f._sqlite3_free=function(){return f.asm.Ka.apply(null,arguments)};f._sqlite3_open=function(){return f.asm.La.apply(null,arguments)}; +f._sqlite3_prepare_v2=function(){return f.asm.Ma.apply(null,arguments)};f._sqlite3_reset=function(){return f.asm.Na.apply(null,arguments)};f._sqlite3_result_double=function(){return f.asm.Oa.apply(null,arguments)};f._sqlite3_result_null=function(){return f.asm.Pa.apply(null,arguments)};f._sqlite3_result_text=function(){return f.asm.Qa.apply(null,arguments)};f._sqlite3_step=function(){return f.asm.Ra.apply(null,arguments)};f._sqlite3_value_blob=function(){return f.asm.Sa.apply(null,arguments)}; +f._sqlite3_value_bytes=function(){return f.asm.Ta.apply(null,arguments)};f._sqlite3_value_double=function(){return f.asm.Ua.apply(null,arguments)};f._sqlite3_value_int=function(){return f.asm.Va.apply(null,arguments)};f._sqlite3_value_text=function(){return f.asm.Wa.apply(null,arguments)};f._sqlite3_value_type=function(){return f.asm.Xa.apply(null,arguments)}; +var h=f.stackAlloc=function(){return f.asm.Za.apply(null,arguments)},qa=f.stackRestore=function(){return f.asm._a.apply(null,arguments)},na=f.stackSave=function(){return f.asm.$a.apply(null,arguments)};f.dynCall_vi=function(){return f.asm.Ya.apply(null,arguments)};f.asm=Vc;f.cwrap=function(a,b,c,d){c=c||[];var e=c.every(function(a){return"number"===a});return"string"!==b&&e&&!d?Na(a):function(){return Oa(a,b,c,arguments)}};f.stackSave=na;f.stackRestore=qa;f.stackAlloc=h; +function Wc(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}Wc.prototype=Error();Wc.prototype.constructor=Wc;gb=function Xc(){f.calledRun||Yc();f.calledRun||(gb=Xc)}; +function Yc(){function a(){if(!f.calledRun&&(f.calledRun=!0,!Ma)){db||(db=!0,f.noFSInit||Hc||(Hc=!0,Gc(),f.stdin=f.stdin,f.stdout=f.stdout,f.stderr=f.stderr,f.stdin?Ic("stdin",f.stdin):Ac("/dev/tty","/dev/stdin"),f.stdout?Ic("stdout",null,f.stdout):Ac("/dev/tty","/dev/stdout"),f.stderr?Ic("stderr",null,f.stderr):Ac("/dev/tty1","/dev/stderr"),p("/dev/stdin","r"),p("/dev/stdout","w"),p("/dev/stderr","w")),Za(ab));Jb=!1;Za(bb);if(f.onRuntimeInitialized)f.onRuntimeInitialized();if(f.postRun)for("function"== +typeof f.postRun&&(f.postRun=[f.postRun]);f.postRun.length;){var a=f.postRun.shift();cb.unshift(a)}Za(cb)}}if(!(0