Merge pull request #215 from GetStream/feature/lint-cleanup

Lint and pub clean up
This commit is contained in:
Salvatore Giordano
2021-01-18 09:41:24 +01:00
committed by GitHub
94 changed files with 58 additions and 2874 deletions
-1
View File
@@ -1 +0,0 @@
# Stream Chat Example App
@@ -1,7 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
@@ -1,67 +0,0 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.stream_chat"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.stream_chat">
<!-- 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>
@@ -1,31 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.stream_chat">
<!-- 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. -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name="io.flutter.app.FlutterApplication"
android:label="stream_chat"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
@@ -1,12 +0,0 @@
package com.example.stream_chat
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);
}
}
@@ -1,12 +0,0 @@
<?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.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.stream_chat">
<!-- 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>
@@ -1,31 +0,0 @@
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
}
@@ -1,4 +0,0 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
@@ -1,6 +0,0 @@
#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
@@ -1,15 +0,0 @@
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
}
@@ -1,32 +0,0 @@
*.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
@@ -1,26 +0,0 @@
<?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>
@@ -1,2 +0,0 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
@@ -1,2 +0,0 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -1,91 +0,0 @@
<?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>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
@@ -1,13 +0,0 @@
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)
}
}
@@ -1,122 +0,0 @@
{
"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.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

@@ -1,23 +0,0 @@
{
"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.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

@@ -1,5 +0,0 @@
# 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.
@@ -1,37 +0,0 @@
<?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>
@@ -1,26 +0,0 @@
<?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>
@@ -1,50 +0,0 @@
<?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>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</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>stream_chat</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>
@@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"
@@ -1,83 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:timeago/timeago.dart' as timeago;
import './channel_name_text.dart';
import 'channel_image.dart';
import 'stream_channel.dart';
class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
final bool showBackButton;
final VoidCallback onBackPressed;
ChannelHeader({
Key key,
this.showBackButton = true,
this.onBackPressed,
}) : preferredSize = Size.fromHeight(kToolbarHeight),
super(key: key);
@override
Widget build(BuildContext context) {
final streamChat = StreamChannel.of(context);
return AppBar(
leading: showBackButton ? _buildBackButton(context) : Container(),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: ChannelImage(channel: streamChat.channel),
),
],
centerTitle: true,
title: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ChannelNameText(
channel: streamChat.channel,
),
_buildLastActive(context, streamChat.channel),
],
),
);
}
StatelessWidget _buildLastActive(BuildContext context, Channel channel) {
return (channel.lastMessageAt != null)
? Text(
'Active ${timeago.format(channel.lastMessageAt)}',
style: Theme.of(context).textTheme.caption,
)
: Container();
}
Padding _buildBackButton(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(14.0),
child: RawMaterialButton(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
elevation: 0,
highlightElevation: 0,
focusElevation: 0,
disabledElevation: 0,
hoverElevation: 0,
onPressed: () {
if (onBackPressed != null) {
onBackPressed();
} else {
Navigator.of(context).pop();
}
},
fillColor: Colors.black.withOpacity(.1),
padding: EdgeInsets.all(4),
child: Icon(
Icons.arrow_back_ios,
size: 15,
color: Colors.black,
),
),
);
}
@override
final Size preferredSize;
}
@@ -1,25 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class ChannelImage extends StatelessWidget {
const ChannelImage({
Key key,
@required this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: 20,
backgroundImage: channel.extraData.containsKey('image')
? CachedNetworkImageProvider(channel.extraData['image'] as String)
: null,
child: channel.extraData.containsKey('image')
? null
: Text(channel.config.name[0]),
);
}
}
@@ -1,138 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_chat/stream_chat.dart';
import './connection_indicator.dart';
import './message_page.dart';
import './stream_channel.dart';
import 'channel_list_view.dart';
import 'channel_page_app_bar.dart';
import 'stream_chat.dart';
class ChannelListPage extends StatefulWidget {
ChannelListPage({
this.filter,
this.options,
this.sort,
this.pagination,
});
final Map<String, dynamic> filter;
final Map<String, dynamic> options;
final List<SortOption> sort;
final PaginationParams pagination;
@override
ChannelListPageState createState() => ChannelListPageState();
}
class ChannelListPageState extends State<ChannelListPage> {
String _selectedChannelId;
bool showSplit;
IndicatorController _indicatorController = IndicatorController();
@override
Widget build(BuildContext context) {
showSplit = MediaQuery.of(context).size.width > 1000;
return Flex(
direction: Axis.horizontal,
children: <Widget>[
Flexible(
flex: 1,
child: Scaffold(
bottomNavigationBar: ConnectionIndicator(
indicatorController: _indicatorController,
),
appBar: ChannelPageAppBar(),
body: ChannelListView(
channelWidget: MessagePage(),
options: widget.options,
filter: widget.filter,
pagination: widget.pagination,
sort: widget.sort,
onChannelTap: showSplit
? (channelClient, _) {
_navigateToChannel(context, channelClient);
}
: null,
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.white,
child: Icon(
Icons.send,
),
),
),
),
showSplit ? _buildMessageView(context) : Container(),
],
);
}
Flexible _buildMessageView(BuildContext context) {
return Flexible(
flex: 2,
child: _selectedChannelId == null
? Scaffold(
body: Center(
child: Text(
'Pick a channel to show the messages 💬',
style: Theme.of(context).textTheme.headline,
),
),
)
: StreamChannel(
channelClient: StreamChat.of(context)
.client
.state
.channels
.firstWhere((c) => c.id == _selectedChannelId),
child: MessagePage(),
),
);
}
void _navigateToChannel(
BuildContext context,
Channel channel,
) {
setState(() {
_selectedChannelId = channel.id;
});
}
@override
void initState() {
super.initState();
final streamChat = StreamChat.of(context);
streamChat.client.wsConnectionStatus.addListener(() {
if (streamChat.client.wsConnectionStatus.value ==
ConnectionStatus.disconnected) {
_indicatorController.showIndicator(
duration: Duration(minutes: 1),
color: Colors.red,
text: 'Disconnected',
);
} else if (streamChat.client.wsConnectionStatus.value ==
ConnectionStatus.connecting) {
_indicatorController.showIndicator(
duration: Duration(minutes: 1),
color: Colors.yellow,
text: 'Reconnecting',
);
} else if (streamChat.client.wsConnectionStatus.value ==
ConnectionStatus.connected) {
_indicatorController.showIndicator(
duration: Duration(seconds: 5),
color: Colors.green,
text: 'Connected',
);
streamChat.clearChannels();
streamChat.queryChannels();
}
});
}
}
@@ -1,208 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'channel_preview.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
typedef ChannelTapCallback = void Function(Channel, Widget);
class ChannelListView extends StatefulWidget {
ChannelListView({
Key key,
this.filter,
this.options,
this.sort,
this.pagination,
this.onChannelTap,
this.channelWidget,
this.channelPreview,
}) : assert(channelWidget != null || onChannelTap != null),
super(key: key);
final Map<String, dynamic> filter;
final Map<String, dynamic> options;
final List<SortOption> sort;
final PaginationParams pagination;
final ScrollController _scrollController = ScrollController();
final ChannelTapCallback onChannelTap;
final Widget channelWidget;
final Widget channelPreview;
@override
_ChannelListViewState createState() => _ChannelListViewState();
}
class _ChannelListViewState extends State<ChannelListView> {
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
return RefreshIndicator(
onRefresh: () async {
streamChat.clearChannels();
return streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: StreamBuilder<List<Channel>>(
stream: streamChat.client.state.channelsStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
print((snapshot.error as Error).stackTrace);
return Center(
child: Text(snapshot.error.toString()),
);
}
final channelsStates = snapshot.data;
return ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: widget._scrollController,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
return _itemBuilder(context, i, channelsStates);
},
childCount: (channelsStates.length * 2) + 1,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = channelsStates
.indexWhere((cs) => 'CHANNEL-${cs.id}' == valueKey.value);
return index != -1 ? (index * 2) : null;
},
),
);
}),
);
}
Widget _itemBuilder(context, int i, List<Channel> channelsStates) {
if (i % 2 != 0) {
return _separatorBuilder(context, i);
}
i = i ~/ 2;
final streamChat = StreamChat.of(context);
if (i < channelsStates.length) {
final channelState = channelsStates[i];
final channelClient = streamChat.client.state.channels
.firstWhere((c) => c.cid == channelState.cid);
ChannelTapCallback onTap;
if (widget.onChannelTap != null) {
onTap = widget.onChannelTap;
} else {
onTap = (client, _) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: widget.channelWidget,
channelClient: client,
);
},
),
);
};
}
Widget child;
if (widget.channelPreview != null) {
child = Stack(
children: [
widget.channelPreview,
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
onTap(channelClient, widget.channelWidget);
},
),
),
),
],
);
} else {
child = ChannelPreview(
onTap: (channelClient) {
onTap(channelClient, widget.channelWidget);
},
);
}
return StreamChannel(
key: ValueKey<String>('CHANNEL-${channelClient?.id}'),
child: child,
channelClient: channelClient,
);
} else {
return _buildQueryProgressIndicator(context, streamChat);
}
}
Widget _buildQueryProgressIndicator(context, StreamChat streamChat) {
return StreamBuilder<bool>(
stream: streamChat.queryChannelsLoading,
initialData: false,
builder: (context, snapshot) {
return Container(
height: 100,
padding: EdgeInsets.all(32),
child: Center(
child: snapshot.data ? CircularProgressIndicator() : Container(),
),
);
});
}
Widget _separatorBuilder(context, i) {
return Container(
height: 1,
color: Colors.black.withOpacity(0.1),
margin: EdgeInsets.symmetric(horizontal: 16),
);
}
void _listenChannelPagination(StreamChat streamChat) {
if (widget._scrollController.position.maxScrollExtent ==
widget._scrollController.position.pixels) {
streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith(
offset: streamChat.channels.length,
),
options: widget.options,
);
}
}
@override
void initState() {
super.initState();
final streamChat = StreamChat.of(context);
streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
widget._scrollController.addListener(() {
_listenChannelPagination(streamChat);
});
}
}
@@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class ChannelNameText extends StatelessWidget {
const ChannelNameText({
Key key,
this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return Text(
channel.extraData['name'] as String ?? channel.config.name,
style: Theme.of(context).textTheme.body2,
);
}
}
@@ -1,41 +0,0 @@
import 'package:flutter/material.dart';
class ChannelPageAppBar extends StatelessWidget implements PreferredSizeWidget {
ChannelPageAppBar({
Key key,
}) : preferredSize = Size.fromHeight(kToolbarHeight),
super(key: key);
@override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
elevation: 4,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 30),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(5),
borderRadius: BorderRadius.circular(32.0),
border: Border.all(color: Colors.black.withOpacity(.2))),
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: TextField(
style: Theme.of(context).textTheme.body1,
autofocus: false,
decoration: InputDecoration(
hintText: 'Search',
prefixText: ' ',
border: InputBorder.none,
),
),
),
),
),
),
);
}
@override
final Size preferredSize;
}
@@ -1,139 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat/stream_chat.dart';
import 'channel_image.dart';
import 'channel_name_text.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
class ChannelPreview extends StatelessWidget {
final void Function(Channel) onTap;
const ChannelPreview({
Key key,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context);
return _buildChannelPreview(
context,
streamChannel,
);
}
StreamChannel _buildChannelPreview(
BuildContext context,
StreamChannelState streamChannel,
) {
final channelClient = StreamChat.of(context)
.client
.state
.channels
.firstWhere((c) => c.cid == streamChannel.channel.cid);
return StreamChannel(
channelClient: channelClient,
child: ListTile(
onTap: () {
onTap(channelClient);
},
leading: ChannelImage(
channel: streamChannel.channel,
),
title: ChannelNameText(
channel: streamChannel.channel,
),
subtitle: _buildSubtitle(
streamChannel,
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
_buildDate(context, streamChannel.channel.lastMessageAt),
],
),
),
);
}
Text _buildDate(BuildContext context, DateTime lastMessageAt) {
String stringDate;
final now = DateTime.now();
if (now.year != lastMessageAt.year ||
now.month != lastMessageAt.month ||
now.day != lastMessageAt.day) {
stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy');
} else {
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
}
return Text(
stringDate,
style: Theme.of(context).textTheme.caption,
);
}
Widget _buildSubtitle(
StreamChannelState streamChannel,
) {
return StreamBuilder<List<User>>(
stream: streamChannel.channelClient.state.typingEventsStream,
initialData: [],
builder: (context, snapshot) {
final typings = snapshot.data;
final opacity =
streamChannel.channelClient.state.unreadCount > .0 ? 1.0 : 0.5;
return typings.isNotEmpty
? _buildTypings(typings, context, opacity)
: _buildLastMessage(context, streamChannel, opacity);
});
}
Widget _buildLastMessage(
BuildContext context, StreamChannelState streamChannel, double opacity) {
final lastMessage = streamChannel.channel.state.messages.isNotEmpty
? streamChannel.channel.state.messages.last
: null;
if (lastMessage == null) {
return SizedBox.fromSize(
size: Size.zero,
);
}
final prefix = lastMessage.attachments
.map((e) {
if (e.type == 'image') {
return '📷';
} else if (e.type == 'video') {
return '🎬';
}
return null;
})
.where((e) => e != null)
.join(' ');
return Text(
'$prefix ${lastMessage.text ?? ''}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black.withOpacity(opacity),
),
);
}
Text _buildTypings(List<User> typings, BuildContext context, double opacity) {
return Text(
'${typings.map((u) => u.extraData.containsKey('name') ? u.extraData['name'] : u.id).join(',')} ${typings.length == 1 ? 'is' : 'are'} typing...',
maxLines: 1,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black.withOpacity(opacity),
),
);
}
}
@@ -1,100 +0,0 @@
import 'package:flutter/material.dart';
class ConnectionIndicator extends StatefulWidget {
final IndicatorController indicatorController;
const ConnectionIndicator({
Key key,
this.indicatorController,
}) : super(key: key);
@override
_ConnectionIndicatorState createState() => _ConnectionIndicatorState();
}
class _ConnectionIndicatorState extends State<ConnectionIndicator> {
double _height = 0;
String _text;
Color _color;
VoidCallback _listener;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
height: _height,
child: Container(
width: double.infinity,
child: Material(
color: _color ?? Theme.of(context).snackBarTheme.backgroundColor,
child: Center(
child: Text(_text ?? ''),
),
),
),
);
}
@override
void initState() {
super.initState();
_listener = () {
final values = widget.indicatorController.indicatorValues.value;
if (mounted) {
setState(() {
_height = 30;
_text = values.text;
_color = values.color;
});
}
if (values.duration != null) {
Future.delayed(values.duration, () {
if (mounted) {
setState(() {
_height = 0;
_text = '';
});
}
});
}
};
widget.indicatorController.indicatorValues.addListener(_listener);
}
@override
void dispose() {
widget.indicatorController.indicatorValues.removeListener(_listener);
super.dispose();
}
}
class IndicatorValues {
final String text;
final Color color;
final Duration duration;
IndicatorValues({
this.duration,
this.text,
this.color,
});
}
class IndicatorController {
ValueNotifier<IndicatorValues> indicatorValues = ValueNotifier(null);
void showIndicator({
Duration duration,
Color color,
String text,
}) {
indicatorValues.value = IndicatorValues(
text: text,
color: color,
duration: duration,
);
}
}
@@ -1,94 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'channel_list_page.dart';
import 'stream_chat.dart';
void main() async {
final client = Client(
"qk4nn7rpcn75",
logLevel: Level.INFO,
);
await client.setUser(
User(id: "wild-breeze-7"),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoid2lsZC1icmVlemUtNyJ9.VM2EX1EXOfgqa-bTH_3JzeY0T99ngWzWahSauP3dBMo',
);
runApp(StreamChat(
child: MyApp(),
client: client,
));
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Stream Chat Example',
home: ChatLoader(),
theme: ThemeData(
scaffoldBackgroundColor: Color(0xfff1f1f3),
primaryColor: Color(0xfff1f1f3),
accentColor: Color(0xff006bff),
iconTheme: IconThemeData(
color: Color(0xff006bff),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
foregroundColor: Color(0xff006bff),
),
backgroundColor: Color(0xfff1f1f3),
canvasColor: Color(0xfff1f1f3),
),
);
}
@override
void dispose() {
StreamChat.of(context).dispose();
super.dispose();
}
}
class ChatLoader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
return StreamBuilder<User>(
stream: streamChat.userStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text('${snapshot.error}'),
),
);
} else if (!snapshot.hasData) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ChannelListPage(
filter: {
'members': {
'\$in': [StreamChat.of(context).user.id],
}
},
sort: [SortOption("last_message_at")],
pagination: PaginationParams(
limit: 20,
),
);
}
},
);
}
}
@@ -1,137 +0,0 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'stream_channel.dart';
class MessageInput extends StatefulWidget {
MessageInput({
Key key,
this.onMessageSent,
this.parent,
}) : super(key: key);
final void Function(Message) onMessageSent;
final Message parent;
@override
_MessageInputState createState() => _MessageInputState();
}
class _MessageInputState extends State<MessageInput> {
final _textController = TextEditingController();
bool _messageIsPresent = false;
bool _typingStarted = false;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
gradient: _typingStarted
? LinearGradient(colors: [Color(0xFF00AEFF), Color(0xFF0076FF)])
: null,
),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
borderRadius: BorderRadius.circular(10.0),
),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(5),
borderRadius: BorderRadius.circular(10.0),
border: Border.all(color: Colors.black.withOpacity(.2)),
),
child: Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
child: TextField(
minLines: null,
maxLines: null,
onSubmitted: (_) {
_sendMessage(context);
},
controller: _textController,
onChanged: (s) {
StreamChannel.of(context).channelClient.keyStroke();
setState(() {
_messageIsPresent = s.trim().isNotEmpty;
});
},
onTap: () {
setState(() {
_typingStarted = true;
});
},
style: Theme.of(context).textTheme.body1,
autofocus: false,
decoration: InputDecoration(
hintText: 'Write a message',
prefixText: ' ',
border: InputBorder.none,
),
),
),
AnimatedCrossFade(
crossFadeState: _messageIsPresent
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: _buildSendButton(context),
secondChild: Container(),
duration: Duration(milliseconds: 300),
alignment: Alignment.center,
),
],
),
),
),
),
);
}
IconButton _buildSendButton(BuildContext context) {
return IconButton(
onPressed: () {
_sendMessage(context);
},
icon: Icon(
Icons.send,
),
);
}
void _sendMessage(BuildContext context) {
final text = _textController.text.trim();
if (text.isEmpty) {
return;
}
_textController.clear();
setState(() {
_messageIsPresent = false;
_typingStarted = false;
});
FocusScope.of(context).unfocus();
StreamChannel.of(context)
.channelClient
.sendMessage(
Message(
parentId: widget.parent?.id,
text: text,
),
)
.then((_) {
if (widget.onMessageSent != null) {
widget.onMessageSent(Message(text: text));
}
});
}
}
@@ -1,280 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_widgets/flutter_widgets.dart';
import 'package:stream_chat/stream_chat.dart';
import 'message_widget.dart';
import 'stream_channel.dart';
typedef MessageBuilder = Widget Function(BuildContext, Message, int index);
typedef ParentMessageBuilder = Widget Function(BuildContext, Message);
typedef OnThreadSelectCallback = void Function(Message parent);
class MessageListView extends StatefulWidget {
MessageListView({
Key key,
MessageBuilder messageBuilder,
this.parentMessageBuilder,
this.parentMessage,
this.onThreadSelect,
}) : _messageBuilder = messageBuilder,
super(key: key);
final MessageBuilder _messageBuilder;
final ParentMessageBuilder parentMessageBuilder;
final OnThreadSelectCallback onThreadSelect;
final Message parentMessage;
@override
_MessageListViewState createState() => _MessageListViewState();
}
class _MessageListViewState extends State<MessageListView> {
static const _newMessageLoadingOffset = 100;
final ScrollController _scrollController = ScrollController();
bool _isBottom = true;
bool _topWasVisible = false;
List<Message> _messages = [];
List<Message> _newMessageList = [];
@override
Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context);
/// TODO: find a better solution when (https://github.com/flutter/flutter/issues/21023) is fixed
return NotificationListener<ScrollNotification>(
onNotification: (_) {
if (_scrollController.offset < 150 && _newMessageList.isNotEmpty) {
setState(() {
_messages.insertAll(0, _newMessageList);
_newMessageList.clear();
});
}
return true;
},
child: ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
reverse: true,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
if (i == _messages.length + 1) {
if (widget.parentMessage != null) {
if (widget.parentMessageBuilder != null) {
return widget.parentMessageBuilder(
context, widget.parentMessage);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessageWidget(
key: ValueKey<String>(
'PARENT-MESSAGE-${widget.parentMessage.id}'),
previousMessage: null,
message: widget.parentMessage.copyWith(replyCount: 0),
nextMessage: null,
onThreadSelect: widget.onThreadSelect,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Container(
padding: const EdgeInsets.all(8),
child: Text(
'Start of a new thread',
textAlign: TextAlign.center,
),
color: Theme.of(context).primaryColorLight,
),
),
],
);
}
} else {
return SizedBox();
}
}
if (i == _messages.length) {
return _buildLoadingIndicator(streamChannel);
}
final message = _messages[i];
if (widget._messageBuilder != null) {
return widget._messageBuilder(context, message, i);
}
final previousMessage =
i < _messages.length - 1 ? _messages[i + 1] : null;
final nextMessage = i > 0 ? _messages[i - 1] : null;
if (i == 0) {
return _buildBottomMessage(
streamChannel,
previousMessage,
message,
context,
);
}
if (i == _messages.length - 1) {
return _buildTopMessage(
message,
nextMessage,
streamChannel,
context,
);
}
return MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'),
previousMessage: previousMessage,
message: message,
nextMessage: nextMessage,
onThreadSelect: widget.onThreadSelect,
);
},
childCount: _messages.length + 2,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = _messages
.indexWhere((m) => 'MESSAGE-${m.id}' == valueKey.value);
return index != -1 ? index : null;
},
),
),
);
}
Container _buildLoadingIndicator(StreamChannelState streamChannel) {
return Container(
height: 50,
child: StreamBuilder<bool>(
stream: streamChannel.queryMessage,
initialData: false,
builder: (context, snapshot) {
if (snapshot.hasError) {
print((snapshot.error as Error).stackTrace.toString());
return Center(
child: Text(snapshot.error.toString()),
);
}
if (!snapshot.data) {
return Container();
}
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}),
);
}
Widget _buildTopMessage(
Message message,
Message nextMessage,
StreamChannelState streamChannelState,
BuildContext context,
) {
return VisibilityDetector(
key: ValueKey<String>('TOP-MESSAGE'),
child: MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'),
previousMessage: null,
message: message,
nextMessage: nextMessage,
onThreadSelect: widget.onThreadSelect,
),
onVisibilityChanged: (visibility) {
final topIsVisible = visibility.visibleBounds != Rect.zero;
if (topIsVisible && !_topWasVisible) {
streamChannelState.queryMessages();
}
_topWasVisible = topIsVisible;
},
);
}
Widget _buildBottomMessage(
StreamChannelState channelBloc,
Message previousMessage,
Message message,
BuildContext context,
) {
return VisibilityDetector(
key: ValueKey<String>('BOTTOM-MESSAGE'),
onVisibilityChanged: (visibility) {
_isBottom = visibility.visibleBounds != Rect.zero;
if (_isBottom) {
if (channelBloc.channelClient.state.unreadCount > 0) {
channelBloc.channelClient.markRead();
}
}
},
child: MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'),
previousMessage: previousMessage,
message: message,
nextMessage: null,
onThreadSelect: widget.onThreadSelect,
),
);
}
StreamSubscription _streamListener;
@override
void initState() {
super.initState();
final streamChannel = StreamChannel.of(context);
if (streamChannel.channelClient.state.unreadCount > 0) {
streamChannel.channelClient.markRead();
}
Stream<List<Message>> stream;
if (widget.parentMessage == null) {
stream = streamChannel.channelStateStream.map((c) => c.messages);
} else {
streamChannel.getReplies(widget.parentMessage.id);
stream = streamChannel.channelClient.state.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage.id))
.map((threads) => threads[widget.parentMessage.id]);
}
_streamListener = stream.listen((newMessages) {
newMessages = newMessages.reversed.toList();
if (_messages.isEmpty || newMessages.first.id != _messages.first.id) {
if (!_scrollController.hasClients ||
_scrollController.offset < _newMessageLoadingOffset) {
setState(() {
_messages = newMessages;
});
} else if (newMessages.first.user.id ==
streamChannel.channelClient.client.state.user.id) {
_scrollController.jumpTo(0);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_messages = newMessages;
});
});
} else {
_newMessageList = newMessages;
}
} else {
setState(() {
_messages = newMessages;
});
}
});
}
@override
void dispose() {
_streamListener.cancel();
super.dispose();
}
}
@@ -1,110 +0,0 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import './channel_header.dart';
import 'connection_indicator.dart';
import 'message_input.dart';
import 'message_list_view.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
class MessagePage extends StatefulWidget {
final PreferredSizeWidget _channelHeader;
const MessagePage({
Key key,
PreferredSizeWidget channelHeader,
this.parentMessage,
}) : _channelHeader = channelHeader,
super(key: key);
final Message parentMessage;
@override
_MessagePageState createState() => _MessagePageState();
}
class _MessagePageState extends State<MessagePage> {
IndicatorController _indicatorController = IndicatorController();
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: widget._channelHeader ?? ChannelHeader(),
body: Column(
children: <Widget>[
ConnectionIndicator(
indicatorController: _indicatorController,
),
Expanded(
child: MessageListView(
parentMessage: widget.parentMessage,
onThreadSelect: (message) {
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (_, __, ___) => StreamChannel(
channelClient: StreamChannel.of(context).channelClient,
child: MessagePage(
parentMessage: message,
channelHeader: widget._channelHeader,
),
),
transitionsBuilder: (
_,
animation,
secondaryAnimation,
child,
) =>
SharedAxisTransition(
child: child,
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
),
),
);
},
),
),
MessageInput(),
],
),
);
}
@override
void initState() {
super.initState();
final streamChat = StreamChat.of(context);
streamChat.client.wsConnectionStatus.addListener(() {
if (streamChat.client.wsConnectionStatus.value ==
ConnectionStatus.disconnected) {
_indicatorController.showIndicator(
duration: Duration(minutes: 1),
color: Colors.red,
text: 'Disconnected',
);
} else if (streamChat.client.wsConnectionStatus.value ==
ConnectionStatus.connecting) {
_indicatorController.showIndicator(
duration: Duration(minutes: 1),
color: Colors.yellow,
text: 'Reconnecting',
);
} else if (streamChat.client.wsConnectionStatus.value ==
ConnectionStatus.connected) {
_indicatorController.showIndicator(
duration: Duration(seconds: 5),
color: Colors.green,
text: 'Connected',
);
final streamChat = StreamChannel.of(context);
streamChat.queryMessages();
}
});
}
}
@@ -1,353 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:date_format/date_format.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:video_player/video_player.dart';
import 'message_list_view.dart';
import 'stream_chat.dart';
import 'user_avatar.dart';
class MessageWidget extends StatefulWidget {
const MessageWidget({
Key key,
@required this.previousMessage,
@required this.message,
@required this.nextMessage,
this.onThreadSelect,
}) : super(key: key);
final Message previousMessage;
final Message message;
final Message nextMessage;
final OnThreadSelectCallback onThreadSelect;
@override
_MessageWidgetState createState() => _MessageWidgetState();
}
class _MessageWidgetState extends State<MessageWidget>
with AutomaticKeepAliveClientMixin {
final Map<String, ChangeNotifier> _videoControllers = {};
final Map<String, ChangeNotifier> _chuwieControllers = {};
@override
Widget build(BuildContext context) {
super.build(context);
final streamChat = StreamChat.of(context);
final currentUserId = streamChat.user.id;
final messageUserId = widget.message.user.id;
final previousUserId = widget.previousMessage?.user?.id;
final nextUserId = widget.nextMessage?.user?.id;
final isMyMessage = messageUserId == currentUserId;
final isLastUser = previousUserId == messageUserId;
final isNextUser = nextUserId == messageUserId;
final alignment =
isMyMessage ? Alignment.centerRight : Alignment.centerLeft;
var row = <Widget>[
Column(
crossAxisAlignment:
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
_buildBubble(context, isMyMessage, isLastUser),
widget.message.replyCount > 0
? GestureDetector(
onTap: () {
if (widget.onThreadSelect != null) {
widget.onThreadSelect(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
children: <Widget>[
Text(
'Replies: ${widget.message.replyCount}',
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.blue),
),
Icon(
Icons.subdirectory_arrow_left,
color: Colors.black12,
),
],
),
),
)
: Container(),
isNextUser ? Container() : _buildTimestamp(isMyMessage, alignment),
],
),
isNextUser
? Container(
width: 40,
)
: Padding(
padding: EdgeInsets.only(
left: isMyMessage ? 8.0 : 0,
right: isMyMessage ? 0 : 8.0,
),
child: UserAvatar(user: widget.message.user),
),
];
if (!isMyMessage) {
row = row.reversed.toList();
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
margin: EdgeInsets.only(
top: isLastUser ? 5 : 24,
bottom: widget.nextMessage == null ? 30 : 0,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment:
isMyMessage ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: row,
),
);
}
Widget _buildBubble(
BuildContext context,
bool isMyMessage,
bool isLastUser,
) {
var nOfAttachmentWidgets = 0;
final column = Column(
crossAxisAlignment:
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: widget.message.attachments.map((attachment) {
nOfAttachmentWidgets++;
Widget attachmentWidget;
if (attachment.type == 'video') {
attachmentWidget = _buildVideo(attachment, isMyMessage, isLastUser);
} else if (attachment.type == 'image' || attachment.type == 'giphy') {
attachmentWidget = _buildImage(isMyMessage, isLastUser, attachment);
}
if (attachmentWidget != null) {
final boxDecoration = _buildBoxDecoration(isMyMessage, isLastUser)
.copyWith(color: Color(0xffebebeb));
return ClipRRect(
borderRadius: boxDecoration.borderRadius,
child: Container(
decoration: boxDecoration,
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
attachmentWidget,
attachment.title != null
? Container(
constraints:
BoxConstraints.loose(Size.fromHeight(70)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
attachment.title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.blue),
),
Text(
Uri.parse(attachment.thumbUrl)
.authority
.split('.')
.reversed
.take(2)
.toList()
.reversed
.join('.'),
overflow: TextOverflow.ellipsis,
style:
Theme.of(context).textTheme.caption,
),
],
),
),
color: Color(0xffebebeb),
)
: Container(),
],
),
attachment.type == 'image' && attachment.titleLink != null
? Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _launchURL(attachment.titleLink),
),
),
)
: SizedBox.fromSize(
size: Size.zero,
),
],
),
margin: EdgeInsets.only(
top: nOfAttachmentWidgets > 1 ? 5 : 0,
),
),
);
}
nOfAttachmentWidgets--;
return Container();
}).toList(),
);
if (widget.message.text.trim().isNotEmpty) {
column.children.add(Container(
margin: EdgeInsets.only(
top: nOfAttachmentWidgets > 0 ? 5 : 0,
),
decoration: _buildBoxDecoration(
isMyMessage, isLastUser || nOfAttachmentWidgets > 0),
padding: EdgeInsets.all(10),
constraints: BoxConstraints.loose(Size.fromWidth(300)),
child: MarkdownBody(
data: '${widget.message.text}',
onTapLink: (link) {
_launchURL(link);
},
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)),
),
));
}
return column;
}
Widget _buildImage(
bool isMyMessage,
bool isLastUser,
Attachment attachment,
) {
return CachedNetworkImage(
imageUrl: attachment.thumbUrl ?? attachment.imageUrl,
fit: BoxFit.cover,
);
}
Widget _buildVideo(
Attachment attachment,
bool isMyMessage,
bool isLastUser,
) {
VideoPlayerController videoController;
if (_videoControllers.containsKey(attachment.assetUrl)) {
videoController = _videoControllers[attachment.assetUrl];
} else {
videoController = VideoPlayerController.network(attachment.assetUrl);
_videoControllers[attachment.assetUrl] = videoController;
}
ChewieController chewieController;
if (_chuwieControllers.containsKey(attachment.assetUrl)) {
chewieController = _chuwieControllers[attachment.assetUrl];
} else {
chewieController = ChewieController(
videoPlayerController: videoController,
autoInitialize: true,
errorBuilder: (_, e) {
return Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
attachment.thumbUrl,
),
),
),
),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _launchURL(attachment.titleLink),
),
),
],
);
});
_chuwieControllers[attachment.assetUrl] = chewieController;
}
return Chewie(
key: ValueKey<String>(
'ATTACHMENT-${attachment.title}-${widget.message.id}'),
controller: chewieController,
);
}
Future<void> _launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Cannot launch the url'),
),
);
}
}
@override
void dispose() {
_videoControllers.values.forEach((element) {
element.dispose();
});
super.dispose();
}
Widget _buildTimestamp(bool isMyMessage, Alignment alignment) {
return Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Text(
formatDate(widget.message.createdAt.toLocal(), [HH, ':', nn]),
),
);
}
BoxDecoration _buildBoxDecoration(bool isMyMessage, bool isLastUser) {
return BoxDecoration(
border: isMyMessage ? null : Border.all(color: Colors.black.withAlpha(8)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular((isMyMessage || !isLastUser) ? 16 : 2),
bottomLeft: Radius.circular(isMyMessage ? 16 : 2),
topRight: Radius.circular((isMyMessage && isLastUser) ? 2 : 16),
bottomRight: Radius.circular(isMyMessage ? 2 : 16),
),
color: isMyMessage ? Color(0xffebebeb) : Colors.white,
);
}
@override
bool get wantKeepAlive {
return widget.message.attachments.isNotEmpty;
}
}
@@ -1,128 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart';
class StreamChannel extends StatefulWidget {
StreamChannel({
Key key,
@required this.child,
@required this.channelClient,
}) : super(
key: key,
);
final Widget child;
final Channel channelClient;
static StreamChannelState of(BuildContext context) {
StreamChannelState streamChannelState;
streamChannelState = context.findAncestorStateOfType<StreamChannelState>();
if (streamChannelState == null) {
throw Exception(
'You must have a StreamChannel widget at the top of your widget tree');
}
return streamChannelState;
}
@override
StreamChannelState createState() => StreamChannelState();
}
class StreamChannelState extends State<StreamChannel> {
StreamChannelState();
Channel get channelClient => widget.channelClient;
Channel get channel => widget.channelClient;
Stream<ChannelState> get channelStateStream =>
widget.channelClient.state.channelStateStream;
final BehaviorSubject<bool> _queryMessageController = BehaviorSubject();
Stream<bool> get queryMessage => _queryMessageController.stream;
void queryMessages() {
_queryMessageController.add(true);
String firstId;
if (channel.state.messages.isNotEmpty) {
firstId = channel.state.messages.first.id;
}
widget.channelClient
.query(
messagesPagination: PaginationParams(
lessThan: firstId,
limit: 100,
),
)
.then((res) {
_queryMessageController.add(false);
}).catchError((e, stack) {
_queryMessageController.addError(e, stack);
});
}
Future<void> getReplies(String parentId) async {
_queryMessageController.add(true);
String firstId;
if (widget.channelClient.state.threads.containsKey(parentId)) {
firstId = widget.channelClient.state.threads[parentId].first.id;
}
return widget.channelClient
.getReplies(
parentId,
PaginationParams(
lessThan: firstId,
limit: 100,
),
)
.then((res) {
_queryMessageController.add(false);
}).catchError((e, stack) {
_queryMessageController.addError(e, stack);
});
}
@override
void dispose() {
_queryMessageController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.channelClient == null) {
return Center(
child: CircularProgressIndicator(),
);
}
return FutureBuilder<bool>(
future: widget.channelClient.initialized,
initialData: widget.channelClient.state != null,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (snapshot.hasError) {
return Center(
child: Text(snapshot.error),
);
} else {
return widget.child;
}
},
);
}
}
@@ -1,95 +0,0 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart';
class StreamChat extends InheritedWidget {
final Client client;
final List<StreamSubscription> _subscriptions = [];
StreamChat({
Key key,
@required this.client,
@required Widget child,
}) : super(
key: key,
child: child,
) {
_subscriptions.add(client.on(EventType.messageNew).listen((Event e) {
final index = channels.indexWhere((c) => c.cid == e.cid);
if (index > 0) {
final channel = channels.removeAt(index);
channels.insert(0, channel);
}
}));
}
User get user => client.state.user;
Stream<User> get userStream => client.state.userStream;
final List<Channel> channels = [];
final BehaviorSubject<bool> _queryChannelsLoadingController =
BehaviorSubject.seeded(false);
Stream<bool> get queryChannelsLoading =>
_queryChannelsLoadingController.stream;
Future<void> queryChannels({
Map<String, dynamic> filter,
List<SortOption> sortOptions,
PaginationParams paginationParams,
Map<String, dynamic> options,
}) async {
if (_queryChannelsLoadingController.value) {
return;
}
_queryChannelsLoadingController.sink.add(true);
try {
client.queryChannels(
filter: filter,
sort: sortOptions,
options: options,
paginationParams: paginationParams,
);
} finally {
_queryChannelsLoadingController.sink.add(false);
}
}
void clearChannels() {
channels.clear();
}
void dispose() {
client.dispose();
_subscriptions.forEach((s) => s.cancel());
_queryChannelsLoadingController.close();
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
static StreamChat of(BuildContext context, [bool listen = false]) {
StreamChat streamChat;
if (listen) {
streamChat = context.dependOnInheritedWidgetOfExactType<StreamChat>();
} else {
streamChat = context.findAncestorWidgetOfExactType<StreamChat>();
}
if (streamChat == null) {
throw Exception(
'You must have a StreamChat widget at the top of your widget tree');
}
return streamChat;
}
}
@@ -1,29 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class UserAvatar extends StatelessWidget {
const UserAvatar({
Key key,
@required this.user,
this.radius = 16,
}) : super(key: key);
final User user;
final double radius;
@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: this.radius,
backgroundImage: user.extraData.containsKey('image')
? CachedNetworkImageProvider(user.extraData['image'] as String)
: null,
child: user.extraData.containsKey('image')
? null
: Text(user?.extraData?.containsKey('name') ?? false
? user.extraData['name'][0]
: ''),
);
}
}
-33
View File
@@ -1,33 +0,0 @@
name: stream_chat_example
description: example Flutter project using Stream Chat service
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_widgets: ^0.1.11
timeago: ^2.0.26
jiffy: ^3.0.1
date_format: ^1.0.8
cached_network_image: ^2.0.0
flutter_markdown: ^0.3.4
url_launcher: ^5.4.2
video_player: ^0.10.8+1
chewie: ^0.9.10
animations: ^1.0.0+5
stream_chat:
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: any
build_web_compilers: any
flutter:
uses-material-design: true
@@ -1,29 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="stream_chat">
<link rel="apple-touch-icon" href="/icons/Icon-192.png">
<title>stream_chat</title>
<link rel="manifest" href="/manifest.json">
</head>
<body>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
@@ -1,23 +0,0 @@
{
"name": "stream_chat",
"short_name": "stream_chat",
"start_url": ".",
"display": "minimal-ui",
"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"
}
]
}
@@ -14,7 +14,11 @@ import 'web_socket_channel_stub.dart'
if (dart.library.html) 'web_socket_channel_html.dart'
if (dart.library.io) 'web_socket_channel_io.dart';
/// Typedef which exposes an [Event] as the only parameter.
typedef EventHandler = void Function(Event);
/// Typedef used for connecting to a websocket. Method returns a [WebSocketChannel]
/// and accepts a connection [url] and an optional [Iterable] of `protocols`.
typedef ConnectWebSocket = WebSocketChannel Function(String url,
{Iterable<String> protocols});
@@ -236,7 +240,9 @@ class WebSocket {
try {
await connect();
} catch (e) {}
} catch (e) {
logger.log(Level.SEVERE, e.toString());
}
await Future.delayed(
Duration(seconds: min(_retryAttempt * 5, 25)),
() {
+11 -3
View File
@@ -13,6 +13,7 @@ import 'package:stream_chat/src/models/channel_model.dart';
import 'package:stream_chat/src/models/own_user.dart';
import 'package:stream_chat/version.dart';
import 'package:uuid/uuid.dart';
import 'package:pedantic/pedantic.dart' show unawaited;
import 'api/channel.dart';
import 'api/connection_status.dart';
@@ -25,8 +26,15 @@ import 'models/event.dart';
import 'models/message.dart';
import 'models/user.dart';
/// Handler function used for logging records. Function requires a single [LogRecord]
/// as the only parameter.
typedef LogHandlerFunction = void Function(LogRecord record);
/// Used for decoding [Map] data to a generic type `T`.
typedef DecoderFunction<T> = T Function(Map<String, dynamic>);
/// A function which can be used to request a Stream Chat API token from your
/// own backend server. Function requires a single [userId].
typedef TokenProvider = Future<String> Function(String userId);
/// The key used to save the userId to sharedPreferences
@@ -462,7 +470,7 @@ class Client {
if (value == ConnectionStatus.connected &&
state.channels?.isNotEmpty == true) {
queryChannels(filter: {
unawaited(queryChannels(filter: {
'cid': {
'\$in': state.channels.keys.toList(),
},
@@ -474,7 +482,7 @@ class Client {
online: true,
));
},
);
));
} else {
_synced = false;
}
@@ -712,7 +720,7 @@ class Client {
}
}
_parseError(DioError error) {
dynamic _parseError(DioError error) {
if (error.type == DioErrorType.RESPONSE) {
final apiError =
ApiError(error.response?.data, error.response?.statusCode);
@@ -1,3 +1,4 @@
//ignore_for_file: public_member_api_docs
import 'dart:io';
import 'dart:isolate';
import 'package:flutter/material.dart';
@@ -9,7 +10,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:stream_chat/src/db/offline_storage.dart';
class SharedDB {
static constructDatabase(dbName) async {
static Future<VmDatabase> constructDatabase(dbName) async {
final dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, dbName);
final file = File(path);
@@ -1,3 +1,5 @@
//ignore_for_file: public_member_api_docs
//ignore_for_file: always_declare_return_types
class SharedDB {
static constructDatabase(dbName) async {
print('Unsupported Platform');
@@ -1,3 +1,5 @@
//ignore_for_file: public_member_api_docs
//ignore_for_file: always_declare_return_types
import 'package:moor/moor_web.dart';
import 'package:stream_chat/src/db/offline_storage.dart';
+1 -1
View File
@@ -25,6 +25,7 @@ dependencies:
rxdart: ^0.24.1
collection: ^1.14.12
sqlite3_flutter_libs: ^0.3.0
pedantic: ^1.9.2
dev_dependencies:
build_runner: ^1.10.0
@@ -32,5 +33,4 @@ dev_dependencies:
moor_generator: ^3.1.0
flutter_test:
sdk: flutter
pedantic: ^1.8.0+1
mockito: ^4.1.1
@@ -1,4 +1,3 @@
import 'package:emojis/emojis.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
@@ -907,8 +907,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
);
}
void _showRemoveUserModal(User user) {}
Widget _buildConnectedTitleState(User user) {
var alternativeWidget;
@@ -115,7 +115,7 @@ class _HomePageState extends State<HomePage> {
return <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Stack(
overflow: Overflow.visible,
clipBehavior: Clip.none,
children: [
StreamSvgIcon.message(
color: _isSelected(0)
@@ -133,7 +133,7 @@ class _HomePageState extends State<HomePage> {
),
BottomNavigationBarItem(
icon: Stack(
overflow: Overflow.visible,
clipBehavior: Clip.none,
children: [
StreamSvgIcon.mentions(
color: _isSelected(1)
@@ -79,6 +79,9 @@ class AppRoutes {
builder: (_) {
return GroupInfoScreen();
});
// Default case, should not reach here.
default:
return null;
}
}
}
@@ -8,8 +8,7 @@ environment:
dependencies:
flutter:
sdk: flutter
stream_chat_flutter:
path: ../
stream_chat_flutter: 0.2.13+1
flutter_apns: ^1.4.1
flutter_local_notifications: ^2.0.2
flutter_svg: ^0.19.1
@@ -160,8 +160,6 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
),
child: ListView.builder(
itemBuilder: (context, position) {
var channel = StreamChannel.of(context).channel;
return Padding(
padding: const EdgeInsets.all(1.0),
child: Padding(
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -1,7 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
@@ -9,8 +7,6 @@ import 'package:dio/dio.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -58,15 +54,16 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
}
class _ImageFooterState extends State<ImageFooter> {
//ignore:unused_field
bool _userSearchMode = false;
TextEditingController _searchController;
final TextEditingController _messageController = TextEditingController();
final FocusNode _messageFocusNode = FocusNode();
//ignore:unused_field
String _channelNameQuery;
final List<Channel> _selectedChannels = [];
bool _loading = false;
Timer _debounce;
@@ -311,95 +308,6 @@ class _ImageFooterState extends State<ImageFooter> {
);
}
Widget _buildShareTextInputSection(modalSetState) {
return Align(
alignment: Alignment.bottomCenter,
child: BottomAppBar(
child: Container(
height: 40.0,
margin: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: _loading
? Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
)
: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
focusNode: _messageFocusNode,
onChanged: (val) {
modalSetState(() {});
},
onTap: () {
modalSetState(() {});
setState(() {});
},
decoration: InputDecoration(
prefixText: ' ',
hintText: 'Add a comment',
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.0),
borderSide: BorderSide(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.16),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.0),
borderSide: BorderSide(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.16),
)),
contentPadding: const EdgeInsets.all(0),
),
),
),
SizedBox(width: 8),
IconTheme(
data: StreamChatTheme.of(context)
.channelTheme
.messageInputButtonIconTheme,
child: IconButton(
onPressed: () async {
modalSetState(() => _loading = true);
await sendMessage();
modalSetState(() => _loading = false);
},
splashRadius: 24,
visualDensity: VisualDensity.compact,
constraints: BoxConstraints.tightFor(
height: 24,
width: 24,
),
padding: EdgeInsets.zero,
icon: Transform.rotate(
angle: -pi / 2,
child: StreamSvgIcon.Icon_send_message(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
),
),
),
],
),
),
),
);
}
/// Sends the current message
Future sendMessage() async {
var text = _messageController.text.trim();
@@ -420,25 +328,6 @@ class _ImageFooterState extends State<ImageFooter> {
_selectedChannels.clear();
Navigator.pop(context);
}
Future<void> _saveImage(String url) async {
var response = await Dio()
.get(url, options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 60,
name: "${DateTime.now().millisecondsSinceEpoch}");
return result;
}
Future<void> _saveVideo(String url) async {
var appDocDir = await getTemporaryDirectory();
var savePath =
appDocDir.path + "/${DateTime.now().millisecondsSinceEpoch}.mp4";
await Dio().download(url, savePath);
final result = await ImageGallerySaver.saveFile(savePath);
print(result);
}
}
/// Used for clipping textfield prefix icon
@@ -798,6 +798,8 @@ class MessageInputState extends State<MessageInput> {
? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2)
: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5);
break;
default:
return Colors.black;
}
}
@@ -1014,6 +1016,8 @@ class MessageInputState extends State<MessageInput> {
);
});
break;
default:
return SizedBox();
}
}
@@ -1038,6 +1042,7 @@ class MessageInputState extends State<MessageInput> {
final mediaInfo = await CompressVideoService.compressVideo(file.path);
if (mediaInfo.filesize / (1024 * 1024) > _kMaxAttachmentSize) {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
@@ -1058,6 +1063,7 @@ class MessageInputState extends State<MessageInput> {
path: mediaInfo.path,
);
} else {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
@@ -1110,6 +1116,7 @@ class MessageInputState extends State<MessageInput> {
});
print(e);
print(s);
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Error adding the attachment: $e'),
@@ -2013,6 +2020,7 @@ class MessageInputState extends State<MessageInput> {
attachment.file = file;
});
} else {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
@@ -220,7 +220,7 @@ class MessageReactionsModal extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(
overflow: Overflow.visible,
clipBehavior: Clip.none,
children: [
UserAvatar(
onTap: onUserAvatarTap,
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
class OptionListTile extends StatelessWidget {
final String title;
@@ -491,7 +491,11 @@ class ColorTheme {
stops: [0, 1],
),
this.borderTop = const Effect(
sigmaX: 0, sigmaY: -1, color: Color(0xff000000), blur: 0.0),
sigmaX: 0,
sigmaY: -1,
color: Color(0xff000000),
blur: 0.0,
alpha: 0.08),
this.borderBottom = const Effect(
sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0.0, alpha: 0.08),
this.shadowIconButton = const Effect(
@@ -524,6 +524,7 @@ abstract class ListItem {
final user = (this as ListUserItem).user;
return 'USER-${user.id}';
}
return null;
}
Widget when({
@@ -536,6 +537,7 @@ abstract class ListItem {
if (this is ListUserItem) {
return userItem((this as ListUserItem).user);
}
return SizedBox();
}
}
+4 -2
View File
@@ -9,6 +9,7 @@ Future<void> launchURL(BuildContext context, String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Cannot launch the url'),
@@ -34,6 +35,7 @@ Future<bool> showConfirmationDialog(
topRight: Radius.circular(16.0),
)),
builder: (context) {
final effect = StreamChatTheme.of(context).colorTheme.borderTop;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -51,8 +53,8 @@ Future<bool> showConfirmationDialog(
),
SizedBox(height: 36.0),
Container(
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
height: 1.0,
color: effect.color.withOpacity(effect.alpha ?? 1),
height: 1,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
+5 -1
View File
@@ -44,6 +44,9 @@ dependencies:
transparent_image: ^1.0.0
ezanimation: ^0.4.1
synchronized: ^2.2.0+2
characters: ^1.0.0
dio: ^3.0.10
path_provider: ^1.6.27
flutter:
assets:
@@ -53,7 +56,8 @@ flutter:
- animations/
dev_dependencies:
pedantic: ^1.9.2
flutter_test:
sdk: flutter
mockito: ^4.1.3
pedantic: ^1.9.2