remove dart client example

This commit is contained in:
Neevash Ramdial
2021-01-15 12:19:41 -04:00
parent 4589dd8645
commit f4f6285cdb
73 changed files with 0 additions and 2739 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" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

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" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"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.5.2
url_launcher: ^5.4.2
video_player: ^1.0.1
chewie: 0.12.1+1
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"
}
]
}