Merge pull request #25 from GetStream/develop

Develop
This commit is contained in:
Salvatore Giordano
2021-03-16 17:21:58 +01:00
committed by GitHub
295 changed files with 4144 additions and 536 deletions
+30 -4
View File
@@ -2,12 +2,12 @@ name: build
on:
push:
branches:
- main
branches:
- main
workflow_dispatch:
defaults:
run:
working-directory: stream_chat_v1
working-directory: packages/stream_chat_v1
jobs:
cleanup-runs:
@@ -16,6 +16,32 @@ jobs:
- uses: rokroskar/workflow-run-cleanup-action@master
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
build_and_deploy_ios:
runs-on: [macos-latest]
steps:
- uses: actions/checkout@v2
- name: Install RubyGems
run: |
cd ios
bundle install
- uses: subosito/flutter-action@v1.4.0
with:
channel: 'stable'
- name: Flutter setup
run: |
flutter pub get
- name: Build and release
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
run: |
flutter build ios --release --no-codesign
cd ios
bundle exec fastlane deploy_to_testflight
build_and_deploy_android:
runs-on: ubuntu-latest
steps:
@@ -35,4 +61,4 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: android-stream-chat-v1
path: stream_chat_v1/build/app/outputs/apk/release/app-release.apk
path: packages/stream_chat_v1/build/app/outputs/apk/release/app-release.apk
+3 -3
View File
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
defaults:
run:
working-directory: stream_chat_v1
working-directory: packages/stream_chat_v1
jobs:
cleanup-runs:
@@ -63,9 +63,9 @@ jobs:
token: ${{secrets.FIREBASE_TOKEN}}
groups: stream-testers
debug: true
file: stream_chat_v1/build/app/outputs/apk/release/app-release.apk
file: packages/stream_chat_v1/build/app/outputs/apk/release/app-release.apk
- name: upload apk
uses: actions/upload-artifact@v2
with:
name: android-stream-chat-v1
path: stream_chat_v1/build/app/outputs/apk/release/app-release.apk
path: packages/stream_chat_v1/build/app/outputs/apk/release/app-release.apk
+6 -3
View File
@@ -24,13 +24,16 @@ With Stream's chat components, developers quickly add chat to their app for a va
## Repo Overview 😎
This repo contains projects and samples developed by the team and Stream community. Projects are broke up into directories containing the source code for each project.
This repo contains projects and samples developed by the team and Stream community. Projects are broke up into directories under the `packages` folder.
Each project contains a README with build and execution instructions.
## **Projects 🚀**
- [Stream Chat v1](https://github.com/GetStream/flutter-samples/tree/main/stream_chat_v1): a sample app implemented using Stream Chat and Flutter. It is a fully fledged messaging app built using a combination of our pre-made widgets and custom Flutter widgets.
- [Stream Chat v1](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1): a sample app implemented using Stream Chat and Flutter. It is a fully fledged messaging app built using a combination of our pre-made widgets and custom Flutter widgets.
- [iMessage clone](https://github.com/GetStream/flutter-samples/tree/main/imessage): an iMessage clone implemented using Flutter and the `stream_chat_flutter_core` package.
- [Stream Chatty](https://github.com/GetStream/flutter-samples/tree/main/packages/chatty) Stream Chatty is a sample chat app made in Flutter using Stream Chat, Firebase, and flutter_bloc. It has full light and dark mode support, real-time chat, and full authentication using Firebase auth.
- [iMessage clone](https://github.com/GetStream/flutter-samples/tree/main/packages/imessage): an iMessage clone implemented using Flutter and the `stream_chat_flutter_core` package.
## Requirements 🛠
+43
View File
@@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
ios/Runner/GoogleService-Info.plist
android/app/google-services.json
+10
View File
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
channel: stable
project_type: app
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2021, Diego Velásquez López
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+41
View File
@@ -0,0 +1,41 @@
# Stream Chatty
Stream Chatty is a sample chat app made in Flutter using [Stream Chat](https://getstream.io/chat/sdk/flutter/), [Firebase](https://firebase.google.com/), and [flutter_bloc](https://bloclibrary.dev/#/). It has full light and dark mode support, real-time chat, and full authentication using Firebase auth.
Stream Chatty was created by [Diego Velasquez](http://www.twitter.com/diegoveloper) as part of a Youtube series. A step by step guide to building Stream Chatty from scratch can be found here:
- Part 1 - https://www.youtube.com/watch?v=H7FjCWHmP9Y => Project Structure, StreamChat, flutter_bloc
- Part 2 - https://www.youtube.com/watch?v=xGXvgrA_vNY => Clean Architecture, dependency injection, auth, storage, chat
- Part 3 - https://www.youtube.com/watch?v=EVvGdFc4SvQ => UI/UX, customizing StreamChat and demo
> Activate English subtitles
# Image
![design](art/fluchat.png?raw=true "Stream Chatty")
# Design
- https://dribbble.com/shots/15263978-Fluchat
- https://projects.invisionapp.com/prototype/Chat-App-cklk8tiia002tqz01u0q1u3cl/play/9ef3ca44
# Diego's Networks
- www.twitter.com/diegoveloper
- www.youtube.com/diegoveloper
## Features
- Login using Firebase Auth and Google Sign In
- Upload pictures using Firebase Storage
- Realtime chat using StreamChat package
- Dark mode/Light mode
- State Management using flutter_bloc and cubits
## Installation
- Clone project
- Configure Firebase (google-services.json and Google-Service.info.plist files)
- Configure Google Sign In
- Configure your Stream project
+69
View File
@@ -0,0 +1,69 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
checkReleaseBuilds false
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.diegoveloper.fluchat"
minSdkVersion 26
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
minifyEnabled false
useProguard false
shrinkResources false
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
apply plugin: 'com.google.gms.google-services'
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.diegoveloper.fluchat">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,48 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.diegoveloper.fluchat">
<!-- 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="Chatty"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
@@ -0,0 +1,6 @@
package com.diegoveloper.fluchat
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 721 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.diegoveloper.fluchat">
<!-- 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>
+32
View File
@@ -0,0 +1,32 @@
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
</dict>
</plist>
@@ -0,0 +1,609 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
F5CFF8E625F08BBE00C304A9 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F5CFF8E525F08BBE00C304A9 /* GoogleService-Info.plist */; };
FB7765FF5CC347F9C712FA62 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40B0FCA72C62C135629B3D83 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
40B0FCA72C62C135629B3D83 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7430D861790AE1F50B4E922F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7D7209A1F031C39E3DC3421F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A8573CD2B6898A29F5C8B294 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
F5CFF8E525F08BBE00C304A9 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FB7765FF5CC347F9C712FA62 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
DF144A162B77171986C53884 /* Pods */,
E16EDD54509B5CAB32FBDFC1 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
F5CFF8E525F08BBE00C304A9 /* GoogleService-Info.plist */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
DF144A162B77171986C53884 /* Pods */ = {
isa = PBXGroup;
children = (
7D7209A1F031C39E3DC3421F /* Pods-Runner.debug.xcconfig */,
7430D861790AE1F50B4E922F /* Pods-Runner.release.xcconfig */,
A8573CD2B6898A29F5C8B294 /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
E16EDD54509B5CAB32FBDFC1 /* Frameworks */ = {
isa = PBXGroup;
children = (
40B0FCA72C62C135629B3D83 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
794C749773D7932A43B8BD34 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C3991DF06AD2C2FF0E1CACF7 /* [CP] Embed Pods Frameworks */,
50CF9F5875290FEF1EABAC13 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
F5CFF8E625F08BBE00C304A9 /* GoogleService-Info.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
50CF9F5875290FEF1EABAC13 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
794C749773D7932A43B8BD34 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C3991DF06AD2C2FF0E1CACF7 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = YPGH95V8AL;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.teamgo.fluchat;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = YPGH95V8AL;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.teamgo.fluchat;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = YPGH95V8AL;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.teamgo.fluchat;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Chatty</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.880353337418-6ogtdb83ub9sjfovcv2k98dte87fmpnv</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Used to demonstrate image picker plugin</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used to capture audio for image picker plugin</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Used to demonstrate image picker plugin</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,7 @@
import 'package:stream_chatter/domain/models/auth_user.dart';
abstract class AuthRepository {
Future<AuthUser> getAuthUser();
Future<AuthUser> signIn();
Future<void> logout();
}
@@ -0,0 +1,5 @@
import 'dart:io';
abstract class ImagePickerRepository {
Future<File> pickImage();
}
@@ -0,0 +1,21 @@
import 'package:stream_chatter/data/auth_repository.dart';
import 'package:stream_chatter/domain/models/auth_user.dart';
class AuthLocalImpl extends AuthRepository {
@override
Future<AuthUser> getAuthUser() async {
await Future.delayed(const Duration(seconds: 2));
return AuthUser('diego');
}
@override
Future<AuthUser> signIn() async {
await Future.delayed(const Duration(seconds: 2));
return AuthUser('diego');
}
@override
Future<void> logout() async {
return;
}
}
@@ -0,0 +1,12 @@
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:stream_chatter/data/image_picker_repository.dart';
class ImagePickerImpl extends ImagePickerRepository {
@override
Future<File> pickImage() async {
final picker = ImagePicker();
final pickedFile = await picker.getImage(source: ImageSource.gallery, maxWidth: 400);
return File(pickedFile.path);
}
}
@@ -0,0 +1,15 @@
import 'package:stream_chatter/data/persistent_storage_repository.dart';
class PersistentStorageLocalImpl extends PersistentStorageRepository {
@override
Future<bool> isDarkMode() async {
await Future.delayed(const Duration(milliseconds: 50));
return false;
}
@override
Future<void> updateDarkMode(bool isDarkMode) async {
await Future.delayed(const Duration(milliseconds: 50));
return;
}
}
@@ -0,0 +1,86 @@
import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/domain/models/chat_user.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class StreamApiLocalImpl extends StreamApiRepository {
StreamApiLocalImpl(this._client);
final StreamChatClient _client;
@override
Future<ChatUser> connectUser(ChatUser user, String token) async {
Map<String, dynamic> extraData = {};
if (user.image != null) {
extraData['image'] = user.image;
}
if (user.name != null) {
extraData['name'] = user.name;
}
await _client.disconnect();
await _client.connectUser(
User(id: user.id, extraData: extraData),
token,
);
return user;
}
@override
Future<List<ChatUser>> getChatUsers() async {
final result = await _client.queryUsers();
final chatUsers = result.users
.where((element) => element.id != _client.state.user.id)
.map(
(e) => ChatUser(
id: e.id,
name: e.name,
image: e.extraData['image'],
),
)
.toList();
return chatUsers;
}
@override
Future<String> getToken(String userId) async {
return _client.devToken(userId);
}
@override
Future<Channel> createGroupChat(String channelId, String name, List<String> members, {String image}) async {
final channel = _client.channel('messaging', id: channelId, extraData: {
'name': name,
'image': image,
'members': [_client.state.user.id, ...members],
});
await channel.watch();
return channel;
}
@override
Future<Channel> createSimpleChat(String friendId) async {
final channel =
_client.channel('messaging', id: '${_client.state.user.id.hashCode}${friendId.hashCode}', extraData: {
'members': [
friendId,
_client.state.user.id,
],
});
await channel.watch();
return channel;
}
@override
Future<void> logout() {
return _client.disconnect();
}
@override
Future<bool> connectIfExist(String userId) async {
final token = await getToken(userId);
await _client.connectUser(
User(id: userId),
token,
);
return _client.state.user.name != null && _client.state.user.name != userId;
}
}
@@ -0,0 +1,10 @@
import 'dart:io';
import 'package:stream_chatter/data/upload_storage_repository.dart';
class UploadStorageLocalImpl extends UploadStorageRepository {
@override
Future<String> uploadPhoto(File file, String path) async {
return 'https://lh3.googleusercontent.com/a-/AOh14GjhqGZ-V7tNXS1pOIp9vbBij4OS9JbzxXgxgy1t=s600-k-no-rp-mo';
}
}
@@ -0,0 +1,4 @@
abstract class PersistentStorageRepository {
Future<bool> isDarkMode();
Future<void> updateDarkMode(bool isDarkMode);
}
@@ -0,0 +1,41 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:stream_chatter/data/auth_repository.dart';
import 'package:stream_chatter/domain/models/auth_user.dart';
import 'package:google_sign_in/google_sign_in.dart';
class AuthImpl extends AuthRepository {
FirebaseAuth _auth = FirebaseAuth.instance;
@override
Future<AuthUser> getAuthUser() async {
final user = _auth.currentUser;
if (user != null) {
return AuthUser(user.uid);
}
return null;
}
@override
Future<AuthUser> signIn() async {
try {
UserCredential userCredential;
final GoogleSignInAccount googleUser = await GoogleSignIn().signIn();
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final GoogleAuthCredential googleAuthCredential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
userCredential = await _auth.signInWithCredential(googleAuthCredential);
final user = userCredential.user;
return AuthUser(user.uid);
} catch (e) {
print(e);
throw Exception('login error');
}
}
@override
Future<void> logout() async {
return _auth.signOut();
}
}
@@ -0,0 +1,18 @@
import 'package:stream_chatter/data/persistent_storage_repository.dart';
import 'package:shared_preferences/shared_preferences.dart';
const _isDarkMode = 'isDarkMode';
class PersistentStorageImpl extends PersistentStorageRepository {
@override
Future<bool> isDarkMode() async {
final preference = await SharedPreferences.getInstance();
return preference.getBool(_isDarkMode) ?? false;
}
@override
Future<void> updateDarkMode(bool isDarkMode) async {
final preference = await SharedPreferences.getInstance();
return await preference.setBool(_isDarkMode, isDarkMode);
}
}
@@ -0,0 +1,102 @@
import 'dart:convert';
import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/domain/models/chat_user.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:http/http.dart' as http;
class StreamApiImpl extends StreamApiRepository {
StreamApiImpl(this._client);
final StreamChatClient _client;
@override
Future<ChatUser> connectUser(ChatUser user, String token) async {
Map<String, dynamic> extraData = {};
if (user.image != null) {
extraData['image'] = user.image;
}
if (user.name != null) {
extraData['name'] = user.name;
}
await _client.disconnect();
await _client.connectUser(
User(id: user.id, extraData: extraData),
token,
);
return user;
}
@override
Future<List<ChatUser>> getChatUsers() async {
final result = await _client.queryUsers();
final chatUsers = result.users
.where((element) => element.id != _client.state.user.id)
.map(
(e) => ChatUser(
id: e.id,
name: e.name,
image: e.extraData['image'],
),
)
.toList();
return chatUsers;
}
@override
Future<String> getToken(String userId) async {
//TODO: use your own implementation in Production
final response = await http.post(
'your_backend_url',
body: jsonEncode(<String, String>{'id': userId}),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
final token = jsonDecode(response.body)['token'];
//In Development mode you can just use :
// _client.devToken(userId);
return token;
}
@override
Future<Channel> createGroupChat(String id, String name, List<String> members, {String image}) async {
final channel = _client.channel('messaging', id: id, extraData: {
'name': name,
'image': image,
'members': [_client.state.user.id, ...members],
});
await channel.watch();
return channel;
}
@override
Future<Channel> createSimpleChat(String friendId) async {
final channel =
_client.channel('messaging', id: '${_client.state.user.id.hashCode}${friendId.hashCode}', extraData: {
'members': [
friendId,
_client.state.user.id,
],
});
await channel.watch();
return channel;
}
@override
Future<void> logout() async {
return _client.disconnect();
}
@override
Future<bool> connectIfExist(String userId) async {
final token = await getToken(userId);
await _client.connectUser(
User(id: userId),
token,
);
return _client.state.user.name != null && _client.state.user.name != userId;
}
}
@@ -0,0 +1,13 @@
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;
import 'package:stream_chatter/data/upload_storage_repository.dart';
class UploadStorageImpl extends UploadStorageRepository {
@override
Future<String> uploadPhoto(File file, String path) async {
final ref = firebase_storage.FirebaseStorage.instance.ref(path);
final uploadTask = ref.putFile(file);
await uploadTask;
return await ref.getDownloadURL();
}
}
@@ -0,0 +1,12 @@
import 'package:stream_chatter/domain/models/chat_user.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
abstract class StreamApiRepository {
Future<List<ChatUser>> getChatUsers();
Future<String> getToken(String userId);
Future<bool> connectIfExist(String userId);
Future<ChatUser> connectUser(ChatUser user, String token);
Future<Channel> createGroupChat(String channelId, String name, List<String> members, {String image});
Future<Channel> createSimpleChat(String friendId);
Future<void> logout();
}
@@ -0,0 +1,5 @@
import 'dart:io';
abstract class UploadStorageRepository {
Future<String> uploadPhoto(File file, String path);
}
+52
View File
@@ -0,0 +1,52 @@
import 'package:stream_chatter/data/auth_repository.dart';
import 'package:stream_chatter/data/image_picker_repository.dart';
import 'package:stream_chatter/data/local/image_picker_impl.dart';
import 'package:stream_chatter/data/persistent_storage_repository.dart';
import 'package:stream_chatter/data/prod/auth_impl.dart';
import 'package:stream_chatter/data/prod/persistent_storage_impl.dart';
import 'package:stream_chatter/data/prod/stream_api_impl.dart';
import 'package:stream_chatter/data/prod/upload_storage_impl.dart';
import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/data/upload_storage_repository.dart';
import 'package:stream_chatter/domain/usecases/create_group_usecase.dart';
import 'package:stream_chatter/domain/usecases/login_usecase.dart';
import 'package:stream_chatter/domain/usecases/logout_usecase.dart';
import 'package:stream_chatter/domain/usecases/profile_sign_in_usecase.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
List<RepositoryProvider> buildRepositories(StreamChatClient client) {
//TODO: Here you can use your local implementations of your repositories
return [
RepositoryProvider<StreamApiRepository>(create: (_) => StreamApiImpl(client)),
RepositoryProvider<PersistentStorageRepository>(create: (_) => PersistentStorageImpl()),
RepositoryProvider<AuthRepository>(create: (_) => AuthImpl()),
RepositoryProvider<UploadStorageRepository>(create: (_) => UploadStorageImpl()),
RepositoryProvider<ImagePickerRepository>(create: (_) => ImagePickerImpl()),
RepositoryProvider<ProfileSignInUseCase>(
create: (context) => ProfileSignInUseCase(
context.read(),
context.read(),
context.read(),
),
),
RepositoryProvider<CreateGroupUseCase>(
create: (context) => CreateGroupUseCase(
context.read(),
context.read(),
),
),
RepositoryProvider<LogoutUseCase>(
create: (context) => LogoutUseCase(
context.read(),
context.read(),
),
),
RepositoryProvider<LoginUseCase>(
create: (context) => LoginUseCase(
context.read(),
context.read(),
),
),
];
}
@@ -0,0 +1,10 @@
enum AuthErrorCode {
not_auth,
not_chat_user,
}
class AuthException implements Exception {
AuthException(this.error);
final AuthErrorCode error;
}
@@ -0,0 +1,4 @@
class AuthUser {
AuthUser(this.id);
final String id;
}
@@ -0,0 +1,6 @@
class ChatUser {
const ChatUser({this.name, this.image, this.id});
final String name;
final String image;
final String id;
}
@@ -0,0 +1,38 @@
import 'dart:io';
import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/data/upload_storage_repository.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:uuid/uuid.dart';
class CreateGroupInput {
CreateGroupInput({this.imageFile, this.name, this.members});
final File imageFile;
final String name;
final List<String> members;
}
class CreateGroupUseCase {
CreateGroupUseCase(
this._streamApiRepository,
this._uploadStorageRepository,
);
final UploadStorageRepository _uploadStorageRepository;
final StreamApiRepository _streamApiRepository;
Future<Channel> createGroup(CreateGroupInput input) async {
final channelId = Uuid().v4();
String image;
if (input.imageFile != null) {
image = await _uploadStorageRepository.uploadPhoto(input.imageFile, 'channels/$channelId');
}
final channel = await _streamApiRepository.createGroupChat(
channelId,
input.name,
input.members,
image: image,
);
return channel;
}
}
@@ -0,0 +1,30 @@
import 'package:stream_chatter/data/auth_repository.dart';
import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/domain/exceptions/auth_exception.dart';
import 'package:stream_chatter/domain/models/auth_user.dart';
class LoginUseCase {
LoginUseCase(this.authRepository, this.streamApiRepository);
final AuthRepository authRepository;
final StreamApiRepository streamApiRepository;
Future<bool> validateLogin() async {
print('validateLogin');
final user = await authRepository.getAuthUser();
print('user: ${user?.id}');
if (user != null) {
final result = await streamApiRepository.connectIfExist(user.id);
if (result) {
return true;
} else {
throw AuthException(AuthErrorCode.not_chat_user);
}
}
throw AuthException(AuthErrorCode.not_auth);
}
Future<AuthUser> signIn() async {
return await authRepository.signIn();
}
}
@@ -0,0 +1,13 @@
import 'package:stream_chatter/data/auth_repository.dart';
import 'package:stream_chatter/data/stream_api_repository.dart';
class LogoutUseCase {
LogoutUseCase(this.streamApiRepository, this.authRepository);
final StreamApiRepository streamApiRepository;
final AuthRepository authRepository;
Future<void> logout() async {
await streamApiRepository.logout();
await authRepository.logout();
}
}
@@ -0,0 +1,34 @@
import 'dart:io';
import 'package:stream_chatter/data/auth_repository.dart';
import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/data/upload_storage_repository.dart';
import 'package:stream_chatter/domain/models/chat_user.dart';
class ProfileInput {
ProfileInput({this.imageFile, this.name});
final File imageFile;
final String name;
}
class ProfileSignInUseCase {
ProfileSignInUseCase(
this._authRepository,
this._streamApiRepository,
this._uploadStorageRepository,
);
final AuthRepository _authRepository;
final UploadStorageRepository _uploadStorageRepository;
final StreamApiRepository _streamApiRepository;
Future<void> verify(ProfileInput input) async {
final auth = await _authRepository.getAuthUser();
final token = await _streamApiRepository.getToken(auth.id);
String image;
if (input.imageFile != null) {
image = await _uploadStorageRepository.uploadPhoto(input.imageFile, 'users/${auth.id}');
}
await _streamApiRepository.connectUser(ChatUser(name: input.name, id: auth.id, image: image), token);
}
}
+50
View File
@@ -0,0 +1,50 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:stream_chatter/dependencies.dart';
import 'package:stream_chatter/ui/app_theme_cubit.dart';
import 'package:stream_chatter/ui/splash/splash_view.dart';
import 'package:stream_chatter/ui/themes.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final _streamChatClient = StreamChatClient('c2rynysx9x6b');
@override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
return MultiRepositoryProvider(
providers: buildRepositories(_streamChatClient),
child: BlocProvider(
create: (context) => AppThemeCubit(context.read())..init(),
child: BlocBuilder<AppThemeCubit, bool>(builder: (context, snapshot) {
return MaterialApp(
title: 'Stream Chatty',
home: SplashView(),
theme: snapshot ? Themes.themeDark : Themes.themeLight,
builder: (context, child) {
return StreamChat(
child: child,
client: _streamChatClient,
streamChatThemeData: StreamChatThemeData.fromTheme(Theme.of(context)).copyWith(
ownMessageTheme: MessageTheme(
messageBackgroundColor: Theme.of(context).accentColor,
messageText: TextStyle(color: Colors.white),
),
),
);
},
);
}),
),
);
}
}
+22
View File
@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
Future pushToPage(BuildContext context, Widget widget) async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => widget,
),
);
}
Future pushAndReplaceToPage(BuildContext context, Widget widget) async {
await Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => widget,
),
);
}
Future popAllAndPush(BuildContext context, Widget widget) async {
await Navigator.pushAndRemoveUntil(
context, MaterialPageRoute(builder: (BuildContext context) => widget), ModalRoute.withName('/'));
}
@@ -0,0 +1,21 @@
import 'package:stream_chatter/data/persistent_storage_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AppThemeCubit extends Cubit<bool> {
AppThemeCubit(this._persistentStorageRepository) : super(false);
final PersistentStorageRepository _persistentStorageRepository;
bool _isDark = false;
bool get isDark => _isDark;
Future<void> init() async {
_isDark = await _persistentStorageRepository.isDarkMode();
emit(_isDark);
}
Future<void> updateTheme(bool isDarkMode) async {
_isDark = isDarkMode;
await _persistentStorageRepository.updateDarkMode(isDarkMode);
emit(_isDark);
}
}
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
class AvatarImageView extends StatelessWidget {
const AvatarImageView({Key key, this.onTap, this.child}) : super(key: key);
final Widget child;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Stack(
clipBehavior: Clip.none,
children: [
ClipOval(
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[100],
),
height: 180,
width: 180,
child: child,
),
),
Positioned(
bottom: -15,
right: 0,
child: GestureDetector(
onTap: onTap,
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 30,
child: Icon(
Icons.camera_alt_outlined,
color: Colors.black,
),
),
),
),
],
),
);
}
}
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
class InitialBackgroundView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: 20,
right: -75,
child: Image.asset(
'assets/icon-top-right.png',
height: 150,
),
),
Positioned(
bottom: -50,
right: -50,
child: Image.asset(
'assets/icon-bottom-right.png',
height: 200,
),
),
],
);
}
}
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
class LoadingView extends StatelessWidget {
final bool isLoading;
final Widget child;
const LoadingView({
Key key,
@required this.child,
this.isLoading = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
child,
if (isLoading)
Container(
color: Colors.black26,
child: Center(
child: CircularProgressIndicator(),
),
),
],
),
);
}
}
@@ -0,0 +1,326 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: widget.channelWidget,
channel: client,
);
},
),
);
*/
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png)
///
/// It shows the current [Channel] preview.
///
/// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates.
///
/// Usually you don't use this widget as it's the default channel preview used by [ChannelListView].
///
/// The widget renders the ui based on the first ancestor of type [StreamChatTheme].
/// Modify it to change the widget appearance.
class MyChannelPreview extends StatelessWidget {
/// Function called when tapping this widget
final void Function(Channel) onTap;
/// Function called when long pressing this widget
final void Function(Channel) onLongPress;
/// Channel displayed
final Channel channel;
/// The function called when the image is tapped
final VoidCallback onImageTap;
final String heroTag;
MyChannelPreview({
@required this.channel,
Key key,
this.onTap,
this.onLongPress,
this.onImageTap,
this.heroTag,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
stream: channel.isMutedStream,
initialData: channel.isMuted,
builder: (context, snapshot) {
return Opacity(
opacity: snapshot.data ? 0.5 : 1,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 8,
),
onTap: () {
if (onTap != null) {
onTap(channel);
}
},
onLongPress: () {
if (onLongPress != null) {
onLongPress(channel);
}
},
leading: Material(
child: Hero(
tag: heroTag,
child: StreamChannel(
channel: channel,
child: ChannelImage(
onTap: onImageTap,
),
),
),
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: ChannelName(
textStyle: StreamChatTheme.of(context).channelPreviewTheme.title,
),
),
StreamBuilder<List<Member>>(
stream: channel.state.membersStream,
initialData: channel.state.members,
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.data.isEmpty ||
!snapshot.data.any((Member e) => e.user.id == channel.client.state.user.id)) {
return SizedBox();
}
return ChannelUnreadIndicator(
channel: channel,
);
}),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(child: _buildSubtitle(context)),
Builder(
builder: (context) {
final lastMessage = channel.state.messages.lastWhere(
(m) => !m.isDeleted && m.shadowed != true,
orElse: () => null,
);
if (lastMessage?.user?.id == StreamChat.of(context).user.id) {
return Padding(
padding: const EdgeInsets.only(right: 4.0),
child: SendingIndicator(
message: lastMessage,
size: StreamChatTheme.of(context).channelPreviewTheme.indicatorIconSize,
isMessageRead: channel.state.read
?.where((element) => element.user.id != channel.client.state.user.id)
?.where((element) => element.lastRead.isAfter(lastMessage.createdAt))
?.isNotEmpty ==
true,
),
);
}
return SizedBox();
},
),
_buildDate(context),
],
),
),
);
});
}
Widget _buildDate(BuildContext context) {
return StreamBuilder<DateTime>(
stream: channel.lastMessageAtStream,
initialData: channel.lastMessageAt,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SizedBox();
}
final lastMessageAt = snapshot.data.toLocal();
String stringDate;
final now = DateTime.now();
var startOfDay = DateTime(now.year, now.month, now.day);
if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.millisecondsSinceEpoch) {
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
} else if (lastMessageAt.millisecondsSinceEpoch >=
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
stringDate = 'Yesterday';
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
} else {
stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy');
}
return Text(
stringDate,
style: StreamChatTheme.of(context).channelPreviewTheme.lastMessageAt,
);
},
);
}
Widget _buildSubtitle(BuildContext context) {
if (channel.isMuted) {
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
StreamSvgIcon.mute(
size: 16,
),
Text(
' Channel is muted',
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
),
),
],
);
}
return TypingIndicator(
channel: channel,
alternativeWidget: _buildLastMessage(context),
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
),
);
}
Widget _buildLastMessage(BuildContext context) {
return StreamBuilder<List<Message>>(
stream: channel.state.messagesStream,
initialData: channel.state.messages,
builder: (context, snapshot) {
final lastMessage = snapshot.data?.lastWhere((m) => m.shadowed != true && !m.isDeleted, orElse: () => null);
if (lastMessage == null) {
return SizedBox();
}
var text = lastMessage.text;
if (lastMessage.attachments != null) {
final parts = <String>[
...lastMessage.attachments.map((e) {
if (e.type == 'image') {
return '📷';
} else if (e.type == 'video') {
return '🎬';
} else if (e.type == 'giphy') {
return '[GIF]';
}
return e == lastMessage.attachments.last ? (e.title ?? 'File') : '${e.title ?? 'File'} , ';
}).where((e) => e != null),
lastMessage.text ?? '',
];
text = parts.join(' ');
}
return Text.rich(
_getDisplayText(
text,
lastMessage.mentionedUsers,
lastMessage.attachments,
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal),
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal,
fontWeight: FontWeight.bold),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
},
);
}
TextSpan _getDisplayText(String text, List<User> mentions, List<Attachment> attachments, TextStyle normalTextStyle,
TextStyle mentionsTextStyle) {
var textList = text.split(' ');
var resList = <TextSpan>[];
for (var e in textList) {
if (mentions != null && mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) {
resList.add(TextSpan(
text: '$e ',
style: mentionsTextStyle,
));
} else if (attachments != null &&
attachments.isNotEmpty &&
attachments.where((e) => e.title != null).any((element) => element.title == e)) {
resList.add(TextSpan(
text: '$e ',
style: normalTextStyle.copyWith(fontStyle: FontStyle.italic),
));
} else {
resList.add(TextSpan(
text: e == textList.last ? '$e' : '$e ',
style: normalTextStyle,
));
}
}
return TextSpan(children: resList);
}
}
class ChannelUnreadIndicator extends StatelessWidget {
const ChannelUnreadIndicator({
Key key,
@required this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: channel.state.unreadCountStream,
initialData: channel.state.unreadCount,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == 0) {
return SizedBox();
}
return Material(
borderRadius: BorderRadius.circular(8),
color: StreamChatTheme.of(context).channelPreviewTheme.unreadCounterColor,
child: Padding(
padding: const EdgeInsets.only(
left: 5.0,
right: 5.0,
top: 2,
bottom: 1,
),
child: Center(
child: Text(
'${snapshot.data > 99 ? '99+' : snapshot.data}',
style: TextStyle(
fontSize: 11,
color: Colors.white,
),
),
),
),
);
},
);
}
}
@@ -0,0 +1,160 @@
import 'package:stream_chatter/ui/common/my_channel_preview.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ChatView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final textColor = Theme.of(context).appBarTheme.color;
return Scaffold(
backgroundColor: Theme.of(context).canvasColor,
appBar: AppBar(
title: Text(
'Chats',
style: TextStyle(
fontSize: 24,
color: textColor,
fontWeight: FontWeight.w800,
),
),
centerTitle: false,
elevation: 0,
backgroundColor: Theme.of(context).canvasColor,
),
body: ChannelsBloc(
child: ChannelListView(
filter: {
'members': {
'\$in': [StreamChat.of(context).user?.id],
}
},
sort: [SortOption('last_message_at')],
channelPreviewBuilder: (context, channel) {
return Container(
color: Theme.of(context).canvasColor,
child: MyChannelPreview(
channel: channel,
heroTag: channel.id,
onImageTap: () {
String name;
String image;
final currentUser = StreamChat.of(context).client.state.user;
if (channel.isGroup) {
name = channel.extraData['name'];
image = channel.extraData['image'];
} else {
final friend =
channel.state.members.where((element) => element.userId != currentUser.id).first.user;
name = friend.name;
image = friend.extraData['image'];
}
return Navigator.of(context).push(
PageRouteBuilder(
barrierColor: Colors.black45,
barrierDismissible: true,
opaque: false,
pageBuilder: (context, animation1, _) {
return FadeTransition(
opacity: animation1,
child: ChatDetailView(
channelId: channel.id,
image: image,
name: name,
),
);
}),
);
},
onTap: (channel) => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: ChannelPage(),
channel: channel,
);
},
),
)
},
),
);
},
channelWidget: ChannelPage(),
),
),
);
}
}
class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
body: Column(
children: [
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
);
}
}
class ChatDetailView extends StatelessWidget {
const ChatDetailView({
Key key,
this.image,
this.name,
this.channelId,
}) : super(key: key);
final String image;
final String name;
final String channelId;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: Navigator.of(context).pop,
child: Material(
color: Colors.transparent,
child: Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Hero(
tag: channelId,
child: ClipOval(
child: Image.network(
image,
height: 180,
width: 180,
fit: BoxFit.cover,
),
),
),
Text(
name,
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 22,
),
),
],
),
),
),
),
);
}
}
@@ -0,0 +1,38 @@
import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/domain/models/chat_user.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ChatUserState {
const ChatUserState(this.chatUser, {this.selected = false});
final ChatUser chatUser;
final bool selected;
}
class FriendsSelectionCubit extends Cubit<List<ChatUserState>> {
FriendsSelectionCubit(this._streamApiRepository) : super([]);
final StreamApiRepository _streamApiRepository;
List<ChatUserState> get selectedUsers => state.where((element) => element.selected).toList();
Future<void> init() async {
final chatUsers = (await _streamApiRepository.getChatUsers()).map((e) => ChatUserState(e)).toList();
emit(chatUsers);
}
void selectUser(ChatUserState chatUser) {
final index = state.indexWhere((element) => element.chatUser.id == chatUser.chatUser.id);
state[index] = ChatUserState(state[index].chatUser, selected: !chatUser.selected);
emit(List<ChatUserState>.from(state));
}
Future<Channel> createFriendChannel(ChatUserState chatUserState) async {
return await _streamApiRepository.createSimpleChat(chatUserState.chatUser.id);
}
}
class FriendsGroupCubit extends Cubit<bool> {
FriendsGroupCubit() : super(false);
void changeToGroup() => emit(!state);
}
@@ -0,0 +1,190 @@
import 'package:stream_chatter/navigator_utils.dart';
import 'package:stream_chatter/ui/home/chat/chat_view.dart';
import 'package:stream_chatter/ui/home/chat/selection/friends_selection_cubit.dart';
import 'package:stream_chatter/ui/home/chat/selection/group_selection_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class FriendsSelectionView extends StatelessWidget {
void _createFriendChannel(BuildContext context, ChatUserState chatUserState) async {
final channel = await context.read<FriendsSelectionCubit>().createFriendChannel(chatUserState);
pushAndReplaceToPage(
context,
Scaffold(
body: StreamChannel(
channel: channel,
child: ChannelPage(),
),
),
);
}
@override
Widget build(BuildContext context) {
final textColor = Theme.of(context).appBarTheme.color;
final accentColor = Theme.of(context).accentColor;
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => FriendsSelectionCubit(context.read())..init()),
BlocProvider(create: (_) => FriendsGroupCubit()),
],
child: BlocBuilder<FriendsGroupCubit, bool>(builder: (context, isGroup) {
return BlocBuilder<FriendsSelectionCubit, List<ChatUserState>>(builder: (context, snapshot) {
final selectedUsers = context.read<FriendsSelectionCubit>().selectedUsers;
return Scaffold(
floatingActionButton: isGroup && selectedUsers.isNotEmpty
? FloatingActionButton(
child: Icon(Icons.arrow_right_alt_rounded),
onPressed: () {
pushAndReplaceToPage(context, GroupSelectionView(selectedUsers));
})
: null,
backgroundColor: Theme.of(context).canvasColor,
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 20,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isGroup)
Row(
children: [
BackButton(
onPressed: () {
context.read<FriendsGroupCubit>().changeToGroup();
},
),
Text(
'New Group',
style: TextStyle(
fontSize: 24,
color: textColor,
fontWeight: FontWeight.w800,
),
),
],
)
else
Row(
children: [
BackButton(
onPressed: Navigator.of(context).pop,
),
Text(
'People',
style: TextStyle(
fontSize: 24,
color: textColor,
fontWeight: FontWeight.w800,
),
),
],
),
if (!isGroup)
ListTile(
onTap: context.read<FriendsGroupCubit>().changeToGroup,
leading: CircleAvatar(
backgroundColor: accentColor,
child: Icon(Icons.group_outlined),
),
title: Text('Create group', style: TextStyle(fontWeight: FontWeight.w700)),
subtitle: Text('Talk with 2 or more contacts'),
)
else if (isGroup && selectedUsers.isEmpty)
Padding(
padding: const EdgeInsets.only(top: 15.0, left: 20.0, bottom: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
backgroundColor: Colors.grey[200],
),
Text(
'Add a friend',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
],
),
)
else
SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: selectedUsers.length,
itemBuilder: (context, index) {
final chatUserState = selectedUsers[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 13.0),
child: Stack(
clipBehavior: Clip.none,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(chatUserState.chatUser.image),
),
Text(chatUserState.chatUser.name),
],
),
Positioned(
bottom: 40,
right: -4,
child: InkWell(
onTap: () => context.read<FriendsSelectionCubit>().selectUser(chatUserState),
child: CircleAvatar(
radius: 9,
backgroundColor: accentColor,
child: Icon(Icons.close_rounded, size: 12),
),
),
),
],
),
);
})),
Expanded(
child: ListView.builder(
itemCount: snapshot.length,
itemBuilder: (context, index) {
final chatUserState = snapshot[index];
return ListTile(
onTap: () {
_createFriendChannel(context, chatUserState);
},
leading: CircleAvatar(
backgroundImage: NetworkImage(chatUserState.chatUser.image),
),
title: Text(chatUserState.chatUser.name),
trailing: isGroup
? Checkbox(
value: chatUserState.selected,
onChanged: (val) {
print('select user for group');
context.read<FriendsSelectionCubit>().selectUser(chatUserState);
},
)
: null,
);
},
),
),
],
),
),
);
});
}),
);
}
}

Some files were not shown because too many files have changed in this diff Show More