Merge branch 'feature/ui-split' of https://github.com/GetStream/stream-chat-flutter into feature/ui-split

This commit is contained in:
Deven Joshi
2021-01-25 12:55:22 +05:30
87 changed files with 1321 additions and 251 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
flutter pub global activate melos
flutter pub global activate melos 0.4.0-dev.2
echo "::add-path::$HOME/.pub-cache/bin"
echo "::add-path::$GITHUB_WORKSPACE/_flutter/.pub-cache/bin"
echo "::add-path::$GITHUB_WORKSPACE/_flutter/bin/cache/dart-sdk/bin"
+2 -2
View File
@@ -55,5 +55,5 @@ dev_dependencies:
pedantic: 1.9.2
environment:
sdk: ">=2.1.0 <3.0.0"
flutter: ">=1.12.13+hotfix.5 <2.0.0"
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.22.4 <2.0.0"
+5
View File
@@ -1,3 +1,8 @@
## 0.2.24
- Create enum for push providers
- Add merge helper functions in `Message` and `ChannelModel` for easier data manipulation
## 0.2.23+3
- Remove + notation from userAgent
+41
View File
@@ -0,0 +1,41 @@
# 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/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
+10
View File
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 78910062997c3a836feee883712c241a5fd22983
channel: stable
project_type: app
+2
View File
@@ -0,0 +1,2 @@
# Stream Chat Dart Example
Please see `lib/` for example code.
@@ -0,0 +1,11 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
@@ -0,0 +1,63 @@
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
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 16
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
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"
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
@@ -0,0 +1,6 @@
package com.example.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
@@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
@@ -0,0 +1,32 @@
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "[email protected]",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

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

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
+248
View File
@@ -0,0 +1,248 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
Future<void> main() async {
/// Create a new instance of [Client] passing the apikey obtained from your
/// project dashboard.
final client = Client('b67pax5b2wdq');
/// Set the current user. In a production scenario, this should be done using
/// a backend to generate a user token using our server SDK.
/// Please see the following for more information:
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
await client.setUser(
User(
id: 'cool-shadow-7',
extraData: {
'image':
'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
},
),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo',
);
/// Creates a channel using the type `messaging` and `godevs`.
/// Channels are containers for holding messages between different members. To
/// learn more about channels and some of our predefined types, checkout our
/// our channel docs: https://getstream.io/chat/docs/initialize_channel/?language=dart
final channel = client.channel('messaging', id: 'godevs');
/// `.watch()` is used to create and listen to the channel for updates. If the
/// channel already exists, it will simply listen for new events.
await channel.watch();
runApp(
StreamExample(
client: client,
channel: channel,
),
);
}
/// Example using Stream's Low Level Dart client.
class StreamExample extends StatelessWidget {
/// To initialize this example, an instance of [client] and [channel] is required.
const StreamExample({
Key key,
@required this.client,
@required this.channel,
}) : super(key: key);
/// Instance of [Client] we created earlier. This contains information about
/// our application and connection state.
final Client client;
/// The channel we'd like to observe and participate.
final Channel channel;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Stream Chat Dart Example',
home: HomeScreen(channel: channel),
);
}
}
/// Main screen of our application. The layout is comprised of an [AppBar]
/// containing the channel name and a [MessageView] displaying recent messages.
class HomeScreen extends StatelessWidget {
/// [HomeScreen] is constructed using the [Channel] we defined earlier.
const HomeScreen({Key key, @required this.channel}) : super(key: key);
/// Channel object containing the [Channel.id] we'd like to observe.
final Channel channel;
@override
Widget build(BuildContext context) {
final messages = channel.state.channelStateStream;
return Scaffold(
appBar: AppBar(
title: Text('Channel: ${channel.id}'),
),
body: SafeArea(
child: StreamBuilder<ChannelState>(
stream: messages,
builder: (
BuildContext context,
AsyncSnapshot<ChannelState> snapshot,
) {
if (snapshot.hasData && snapshot.data != null) {
return MessageView(
messages: snapshot.data.messages.reversed.toList(),
channel: channel,
);
} else if (snapshot.hasError) {
return const Center(
child: Text(
'There was an error loading messages. Please see logs.',
),
);
}
return const Center(
child: SizedBox(
width: 100.0,
height: 100.0,
child: CircularProgressIndicator(),
),
);
},
),
),
);
}
}
/// UI used to display a list of recent messages and a [TextField] for sending
/// new messages.
class MessageView extends StatefulWidget {
/// Message takes the latest list of messages and the current channel.
const MessageView({
Key key,
@required this.messages,
@required this.channel,
}) : super(key: key);
/// List of messages sent in the given channel.
final List<Message> messages;
/// Current channel being observed.
final Channel channel;
@override
_MessageViewState createState() => _MessageViewState();
}
class _MessageViewState extends State<MessageView> {
TextEditingController _controller;
ScrollController _scrollController;
List<Message> get _messages => widget.messages;
@override
void initState() {
super.initState();
_controller = TextEditingController();
_scrollController = ScrollController();
}
@override
void dispose() {
_controller.dispose();
_scrollController.dispose();
super.dispose();
}
/// Convenience method for scrolling the list view when a new message is sent.
void _updateList() {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _messages.length,
reverse: true,
itemBuilder: (BuildContext context, int index) {
final item = _messages[index];
if (item.user.id == widget.channel.client.uid) {
return Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.text),
),
);
} else {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.text),
),
);
}
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Enter your message',
),
),
),
Material(
type: MaterialType.circle,
color: Colors.blue,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () async {
// We can send a new message by calling `sendMessage` on
// the current channel. After sending a message, the
// TextField is cleared and the list view is scrolled
// to show the new item.
if (_controller.value.text.isNotEmpty) {
await widget.channel.sendMessage(
Message(text: _controller.value.text),
);
_controller.clear();
_updateList();
}
},
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: Icon(
Icons.send,
color: Colors.white,
),
),
),
),
)
],
),
)
],
);
}
}
/// Helper extension for quickly retrieving the current user id from a [Client].
extension on Client {
String get uid => state.user.id;
}
+21
View File
@@ -0,0 +1,21 @@
name: example
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.0
stream_chat:
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
+2 -38
View File
@@ -1108,30 +1108,7 @@ class ChannelClientState {
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
if (oldIndex != -1) {
newMessages[oldIndex] = newMessages[oldIndex].copyWith(
text: message.text,
type: message.type,
attachments: message.attachments,
mentionedUsers: message.mentionedUsers,
reactionCounts: message.reactionCounts,
reactionScores: message.reactionScores,
latestReactions: message.latestReactions,
ownReactions: message.ownReactions,
parentId: message.parentId,
quotedMessageId: message.quotedMessageId,
quotedMessage: message.quotedMessage,
replyCount: message.replyCount,
showInChannel: message.showInChannel,
command: message.command,
silent: message.silent,
createdAt: message.createdAt,
updatedAt: message.updatedAt,
deletedAt: message.deletedAt,
user: message.user,
status: message.status,
threadParticipants: message.threadParticipants,
extraData: message.extraData,
);
newMessages[oldIndex] = newMessages[oldIndex].merge(message);
} else {
newMessages.add(message);
}
@@ -1395,20 +1372,7 @@ class ChannelClientState {
_channelState = _channelState.copyWith(
messages: newMessages,
channel: _channelState.channel?.copyWith(
lastMessageAt: updatedState.channel?.lastMessageAt,
createdAt: updatedState.channel?.createdAt,
type: updatedState.channel?.type,
extraData: updatedState.channel?.extraData,
updatedAt: updatedState.channel?.updatedAt,
id: updatedState.channel?.id,
createdBy: updatedState.channel?.createdBy,
config: updatedState.channel?.config,
deletedAt: updatedState.channel?.deletedAt,
cid: updatedState.channel?.cid,
frozen: updatedState.channel?.frozen,
memberCount: updatedState.channel?.memberCount,
),
channel: _channelState.channel?.merge(updatedState.channel),
watchers: newWatchers,
watcherCount: updatedState.watcherCount,
members: newMembers,
+22 -2
View File
@@ -46,6 +46,26 @@ const String KEY_TOKEN = 'KEY_TOKEN';
/// The key used to save the apiKey to sharedPreferences
const String KEY_API_KEY = 'KEY_API_KEY';
/// Provider used to send push notifications.
enum PushProvider {
/// Send notifications using Google's Firebase Cloud Messaging
firebase,
/// Send notifications using Apple's Push Notification service
apn
}
extension on PushProvider {
/// Returns the string notion for [PushProvider].
String get name {
if (this == PushProvider.apn) {
return 'apn';
} else {
return 'firebase';
}
}
}
/// The official Dart client for Stream Chat,
/// a service for building chat applications.
/// This library can be used on any Dart project and on both mobile and web apps with Flutter.
@@ -1002,10 +1022,10 @@ class Client {
}
/// Add a device for Push Notifications.
Future<EmptyResponse> addDevice(String id, String pushProvider) async {
Future<EmptyResponse> addDevice(String id, PushProvider pushProvider) async {
final response = await post('/devices', data: {
'id': id,
'push_provider': pushProvider,
'push_provider': pushProvider.name,
});
return decode<EmptyResponse>(response.data, EmptyResponse.fromJson);
}
@@ -142,4 +142,25 @@ class ChannelModel {
extraData: extraData ?? this.extraData,
team: team ?? this.team,
);
/// Returns a new [ChannelModel] that is a combination of this channelModel and the given
/// [other] channelModel.
ChannelModel merge(ChannelModel other) {
if (other == null) return this;
return copyWith(
id: other.id,
type: other.type,
cid: other.cid,
config: other.config,
createdBy: other.createdBy,
frozen: other.frozen,
lastMessageAt: other.lastMessageAt,
createdAt: other.createdAt,
updatedAt: other.updatedAt,
deletedAt: other.deletedAt,
memberCount: other.memberCount,
extraData: other.extraData,
team: other.team,
);
}
}
@@ -250,6 +250,38 @@ class Message {
deletedAt: deletedAt ?? this.deletedAt,
status: status ?? this.status,
);
/// Returns a new [Message] that is a combination of this message and the given
/// [other] message.
Message merge(Message other) {
if (other == null) return this;
return copyWith(
id: other.id,
text: other.text,
type: other.type,
attachments: other.attachments,
mentionedUsers: other.mentionedUsers,
reactionCounts: other.reactionCounts,
reactionScores: other.reactionScores,
latestReactions: other.latestReactions,
ownReactions: other.ownReactions,
parentId: other.parentId,
quotedMessage: other.quotedMessage,
quotedMessageId: other.quotedMessageId,
replyCount: other.replyCount,
threadParticipants: other.threadParticipants,
showInChannel: other.showInChannel,
command: other.command,
createdAt: other.createdAt,
silent: other.silent,
extraData: other.extraData,
user: other.user,
shadowed: other.shadowed,
updatedAt: other.updatedAt,
deletedAt: other.deletedAt,
status: other.status,
);
}
}
/// A translated message
+1 -1
View File
@@ -2,4 +2,4 @@ import 'package:stream_chat/src/client.dart';
/// Current package version
/// Used in [Client] to build the `x-stream-client` header
const PACKAGE_VERSION = '0.2.23+3';
const PACKAGE_VERSION = '0.2.24';
+2 -2
View File
@@ -1,12 +1,12 @@
name: stream_chat
homepage: https://getstream.io/
description: The official Dart client for Stream Chat, a service for building chat applications.
version: 0.2.23+3
version: 0.2.24
repository: https://github.com/GetStream/stream-chat-dart
issue_tracker: https://github.com/GetStream/stream-chat-dart/issues
environment:
sdk: ">=2.2.2 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
@@ -266,13 +266,17 @@ void main() {
when(mockDio.post<String>('/devices', data: {
'id': 'test-id',
'push_provider': 'test',
'push_provider': 'firebase',
})).thenAnswer((_) async => Response(data: '{}', statusCode: 200));
await client.addDevice('test-id', 'test');
await client.addDevice('test-id', PushProvider.firebase);
verify(mockDio.post<String>('/devices',
data: {'id': 'test-id', 'push_provider': 'test'})).called(1);
verify(
mockDio.post<String>(
'/devices',
data: {'id': 'test-id', 'push_provider': 'firebase'},
),
).called(1);
});
test('getDevices', () async {
@@ -27,7 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
ndkVersion '22.0.7026061'
ndkVersion '21.3.6528147'
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -5,27 +5,27 @@
<testcase classname="fastlane.lanes" name="0: Verifying fastlane version" time="0.000341">
<testcase classname="fastlane.lanes" name="0: Verifying fastlane version" time="0.000357">
</testcase>
<testcase classname="fastlane.lanes" name="1: default_platform" time="0.000173">
<testcase classname="fastlane.lanes" name="1: default_platform" time="0.000265">
</testcase>
<testcase classname="fastlane.lanes" name="2: is_ci" time="0.000178">
<testcase classname="fastlane.lanes" name="2: is_ci" time="0.000184">
</testcase>
<testcase classname="fastlane.lanes" name="3: is_ci" time="0.0002">
<testcase classname="fastlane.lanes" name="3: is_ci" time="0.000154">
</testcase>
<testcase classname="fastlane.lanes" name="4: match" time="41.729101">
<testcase classname="fastlane.lanes" name="4: match" time="49.725249">
</testcase>
@@ -222,8 +222,23 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
),
Spacer(),
RaisedButton(
color: Theme.of(context).brightness == Brightness.light
? StreamChatTheme.of(context).colorTheme.accentBlue
: Colors.white,
elevation: 0,
child: Text('Login', style: TextStyle(fontSize: 16)),
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;
@@ -41,11 +41,12 @@ class MyApp extends StatelessWidget {
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: StreamChatTheme.of(context).colorTheme.black,
messageBackgroundColor: colorTheme.black,
messageText: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white,
color: colorTheme.white,
),
avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(8),
@@ -80,6 +80,17 @@ class ChannelListPage extends StatelessWidget {
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,
),
@@ -97,7 +97,7 @@ class ChannelPage extends StatelessWidget {
List<Message> messages,
) {
final message = details.message;
final color = details.isMyMessage ? Colors.blueGrey : Colors.blue;
final color = details.isMyMessage ? Colors.red : Colors.blue;
if (message.isSystem) {
return SizedBox();
}
@@ -110,7 +110,10 @@ class ChannelPage extends StatelessWidget {
color: color,
width: 2,
),
padding: const EdgeInsets.all(2),
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 4,
),
attachmentBorderSide: BorderSide(
color: color,
width: 2,
@@ -56,7 +56,7 @@ void initNotifications(Client client) {
if (connector.token.value != null) {
client.addDevice(
connector.token.value,
Platform.isAndroid ? 'firebase' : 'apn',
Platform.isAndroid ? PushProvider.firebase : PushProvider.apn,
);
}
});
@@ -1,6 +1,7 @@
name: example
description: A new Flutter project.
version: 1.1.2+2
publish_to: 'none'
version: 1.1.3+3
environment:
sdk: ">=2.2.2 <3.0.0"
@@ -529,123 +529,97 @@ class _ChannelListViewState extends State<ChannelListView>
};
}
final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke;
return StreamChannel(
key: ValueKey<String>('CHANNEL-${channel.id}'),
channel: channel,
child: Builder(
builder: (context) {
Widget child;
if (widget.channelPreviewBuilder != null) {
child = Stack(
children: [
widget.channelPreviewBuilder(
context,
channel,
),
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
onTap(channel, widget.channelWidget);
},
onLongPress: widget.onChannelLongPress != null
? () {
widget.onChannelLongPress(channel);
}
: null,
return Slidable(
controller: _slideController,
enabled: widget.swipeToAction,
actionPane: SlidableBehindActionPane(),
actionExtentRatio: 0.12,
closeOnScroll: true,
secondaryActions: <Widget>[
IconSlideAction(
color: backgroundColor,
icon: Icons.more_horiz,
onTap: () {
showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
),
),
],
);
} else {
final backgroundColor =
StreamChatTheme.of(context).colorTheme.whiteSmoke;
child = Slidable(
controller: _slideController,
enabled: widget.swipeToAction,
actionPane: SlidableBehindActionPane(),
actionExtentRatio: 0.12,
closeOnScroll: true,
secondaryActions: <Widget>[
context: context,
builder: (context) {
return StreamChannel(
child: ChannelBottomSheet(
onViewInfoTap: () {
widget.onViewInfoTap(channel);
},
),
channel: channel,
);
},
);
},
),
if ([
'admin',
'owner',
].contains(channel.state.members
.firstWhere((m) => m.userId == channel.client.state.user.id,
orElse: () => null)
?.role))
IconSlideAction(
color: backgroundColor,
icon: Icons.more_horiz,
onTap: () {
showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
context: context,
builder: (context) {
return StreamChannel(
child: ChannelBottomSheet(
onViewInfoTap: () {
widget.onViewInfoTap(channel);
},
),
channel: channel,
);
},
);
},
),
if ([
'admin',
'owner',
].contains(channel.state.members
.firstWhere(
(m) => m.userId == channel.client.state.user.id,
orElse: () => null)
?.role))
IconSlideAction(
color: backgroundColor,
iconWidget: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
onTap: () 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,
),
);
if (res == true) {
await channel.delete();
}
},
iconWidget: StreamSvgIcon.delete(
color: StreamChatTheme.of(context).colorTheme.accentRed,
),
],
child: Container(
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
child: ChannelPreview(
onLongPress: widget.onChannelLongPress,
channel: channel,
onImageTap: widget.onImageTap != null
? () {
widget.onImageTap(channel);
}
: null,
onTap: (channel) {
onTap(channel, widget.channelWidget);
onTap: () 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,
),
);
if (res == true) {
await channel.delete();
}
},
),
),
);
}
return child;
],
child: Container(
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
child: widget.channelPreviewBuilder != null
? widget.channelPreviewBuilder(
context,
channel,
)
: ChannelPreview(
onLongPress: widget.onChannelLongPress,
channel: channel,
onImageTap: widget.onImageTap != null
? () {
widget.onImageTap(channel);
}
: null,
onTap: (channel) {
onTap(channel, widget.channelWidget);
},
),
),
);
},
),
);
@@ -41,7 +41,7 @@ class _FileAttachmentState extends State<FileAttachment> {
@override
void initState() {
super.initState();
if (MediaUtils.getMimeType(widget.attachment.title).type == 'video') {
if (MediaUtils.getMimeType(widget.attachment.title)?.type == 'video') {
if (widget.attachmentType == FileAttachmentType.online) {
_controller = VideoPlayerController.network(
widget.attachment.assetUrl,
@@ -62,7 +62,13 @@ class _FileAttachmentState extends State<FileAttachment> {
child: Container(
width: widget.size?.width ?? 100,
height: 56.0,
color: StreamChatTheme.of(context).colorTheme.white,
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -122,7 +128,7 @@ class _FileAttachmentState extends State<FileAttachment> {
}
Widget _getFileTypeImage() {
if ((MediaUtils.getMimeType(widget.attachment.title).type == 'image')) {
if ((MediaUtils.getMimeType(widget.attachment.title)?.type == 'image')) {
switch (widget.attachmentType) {
case FileAttachmentType.local:
return Image.memory(
@@ -159,7 +165,7 @@ class _FileAttachmentState extends State<FileAttachment> {
}
}
if ((MediaUtils.getMimeType(widget.attachment.title).type == 'video')) {
if ((MediaUtils.getMimeType(widget.attachment.title)?.type == 'video')) {
switch (widget.attachmentType) {
case FileAttachmentType.local:
return FutureBuilder<File>(
@@ -68,27 +68,7 @@ class GiphyAttachment extends StatelessWidget {
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () async {
var res = await Navigator.push(context,
MaterialPageRoute(builder: (_) {
final channel = StreamChannel.of(context).channel;
return StreamChannel(
channel: channel,
child: FullScreenMedia(
mediaAttachments: [
attachment,
],
userName: message.user.name,
sentAt: message.createdAt,
message: message,
onShowMessage: onShowMessage,
),
);
}));
if (res != null) {
onReturnAction(res);
}
_onImageTap(context);
},
child: ClipRRect(
borderRadius: BorderRadius.only(
@@ -324,6 +304,31 @@ class GiphyAttachment extends StatelessWidget {
);
}
void _onImageTap(BuildContext context) async {
var res = await Navigator.push(context, MaterialPageRoute(
builder: (_) {
final channel = StreamChannel.of(context).channel;
return StreamChannel(
channel: channel,
child: FullScreenMedia(
mediaAttachments: [
attachment,
],
userName: message.user.name,
sentAt: message.createdAt,
message: message,
onShowMessage: onShowMessage,
),
);
},
));
if (res != null) {
onReturnAction(res);
}
}
Widget _buildSentAttachment(context) {
return Container(
child: GestureDetector(
@@ -159,8 +159,8 @@ class MessageWidget extends StatefulWidget {
this.showReactionPickerIndicator = false,
this.showUserAvatar = DisplayWidget.show,
this.showSendingIndicator = true,
this.showThreadReplyIndicator = true,
this.showInChannelIndicator = true,
this.showThreadReplyIndicator = false,
this.showInChannelIndicator = false,
this.onReplyTap,
this.onThreadTap,
this.showUsername = true,
@@ -371,15 +371,24 @@ class _MessageWidgetState extends State<MessageWidget> {
alignment: Alignment.center,
transform: Matrix4.rotationY(
widget.reverse ? pi : 0),
child: DeletedMessage(
reverse: widget.reverse,
borderRadiusGeometry: widget
.borderRadiusGeometry,
borderSide:
widget.borderSide,
shape: widget.shape,
messageTheme:
widget.messageTheme,
child: Container(
margin: EdgeInsets.symmetric(
horizontal:
widget.showUserAvatar ==
DisplayWidget
.gone
? 0
: 4.0),
child: DeletedMessage(
reverse: widget.reverse,
borderRadiusGeometry: widget
.borderRadiusGeometry,
borderSide:
widget.borderSide,
shape: widget.shape,
messageTheme:
widget.messageTheme,
),
),
)
: Card(
@@ -67,7 +67,7 @@ class ReactionBubble extends StatelessWidget {
children: [
if (constraints.maxWidth < double.infinity)
...reactions
.take((constraints.maxWidth) ~/ 22)
.take((constraints.maxWidth) ~/ 24)
.map((reaction) {
return _buildReaction(
reactionIcons,
@@ -45,9 +45,6 @@ class StreamChatThemeData {
/// The text themes used in the widgets
final TextTheme textTheme;
/// The button themes used in the widgets
final ButtonThemeData buttonTheme;
/// The text themes used in the widgets
final ColorTheme colorTheme;
@@ -78,7 +75,6 @@ class StreamChatThemeData {
/// Create a theme from scratch
const StreamChatThemeData({
this.textTheme,
this.buttonTheme,
this.colorTheme,
this.channelPreviewTheme,
this.channelTheme,
@@ -93,24 +89,20 @@ class StreamChatThemeData {
/// Create a theme from a Material [Theme]
factory StreamChatThemeData.fromTheme(ThemeData theme) {
final defaultTheme = getDefaultTheme(theme);
final customizedTheme = StreamChatThemeData(
primaryIconTheme: theme.primaryIconTheme,
ownMessageTheme: MessageTheme(
replies: TextStyle(color: theme.accentColor),
messageLinks: TextStyle(color: theme.accentColor),
final customizedTheme = StreamChatThemeData.fromColorAndTextTheme(
defaultTheme.colorTheme.copyWith(
accentBlue: theme.accentColor,
),
otherMessageTheme: MessageTheme(
replies: TextStyle(color: theme.accentColor),
messageLinks: TextStyle(color: theme.accentColor),
),
);
defaultTheme.textTheme,
).copyWith(
// primaryIconTheme: theme.primaryIconTheme,
);
return defaultTheme.merge(customizedTheme) ?? customizedTheme;
}
/// Creates a copy of [StreamChatThemeData] with specified attributes overridden.
StreamChatThemeData copyWith({
TextTheme textTheme,
ButtonThemeData buttonTheme,
ColorTheme colorTheme,
ChannelPreviewTheme channelPreviewTheme,
ChannelTheme channelTheme,
@@ -123,7 +115,6 @@ class StreamChatThemeData {
}) =>
StreamChatThemeData(
textTheme: textTheme ?? this.textTheme,
buttonTheme: buttonTheme ?? this.buttonTheme,
colorTheme: colorTheme ?? this.colorTheme,
primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage,
@@ -139,7 +130,6 @@ class StreamChatThemeData {
if (other == null) return this;
return copyWith(
textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme,
buttonTheme: other.buttonTheme,
colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme,
primaryIconTheme: other.primaryIconTheme,
defaultChannelImage: other.defaultChannelImage,
@@ -157,26 +147,14 @@ class StreamChatThemeData {
);
}
/// Get the default Stream Chat theme
static StreamChatThemeData getDefaultTheme(ThemeData theme) {
final accentColor = Color(0xff006cff);
final isDark = theme.brightness == Brightness.dark;
final textTheme = isDark ? TextTheme.dark() : TextTheme.light();
final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light();
static StreamChatThemeData fromColorAndTextTheme(
ColorTheme colorTheme,
TextTheme textTheme,
) {
final accentColor = colorTheme.accentBlue;
return StreamChatThemeData(
textTheme: textTheme,
colorTheme: colorTheme,
buttonTheme: ButtonThemeData(
height: 48.0,
buttonColor: isDark ? Color(0xffffffff) : Color(0xff006aff),
textTheme: ButtonTextTheme.accent,
colorScheme: theme.colorScheme.copyWith(
secondary: isDark ? Color(0xff005eff) : Color(0xffffffff),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
),
primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)),
defaultChannelImage: (context, channel) => SizedBox(),
defaultUserImage: (context, user) => Center(
@@ -204,7 +182,7 @@ class StreamChatThemeData {
),
indicatorIconSize: 16.0),
channelTheme: ChannelTheme(
messageInputButtonIconTheme: theme.iconTheme.copyWith(
messageInputButtonIconTheme: IconThemeData(
color: accentColor,
),
channelHeaderTheme: ChannelHeaderTheme(
@@ -291,6 +269,17 @@ class StreamChatThemeData {
],
);
}
/// Get the default Stream Chat theme
static StreamChatThemeData getDefaultTheme(ThemeData theme) {
final isDark = theme.brightness == Brightness.dark;
final textTheme = isDark ? TextTheme.dark() : TextTheme.light();
final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light();
return fromColorAndTextTheme(
colorTheme,
textTheme,
);
}
}
enum TextThemeType {
+2 -2
View File
@@ -6,7 +6,7 @@ repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment:
sdk: ">=2.6.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
@@ -29,7 +29,7 @@ dependencies:
image_picker: ^0.6.7+17
flutter_keyboard_visibility: ^4.0.2
mime: ^0.9.7
stream_chat: ^0.2.23+3
stream_chat: ^0.2.24
video_compress: ^2.1.1
visibility_detector: ^0.1.5
http_parser: ^3.1.4
@@ -2,5 +2,4 @@ package example.example;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}
public class MainActivity extends FlutterActivity {}
@@ -154,12 +154,12 @@ class _UserListViewCoreState extends State<UserListViewCore>
temp = users..sort((curr, next) => curr.name.compareTo(next.name));
}
final groupedUsers = <String, List<User>>{};
for (var e in temp) {
for (final e in temp) {
final alphabet = e.name[0]?.toUpperCase();
groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e];
}
final items = <ListItem>[];
for (var key in groupedUsers.keys) {
for (final key in groupedUsers.keys) {
items.add(ListHeaderItem(key));
items.addAll(groupedUsers[key].map((e) => ListUserItem(e)));
}
@@ -1,5 +1 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
void main() {}