├── android ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ └── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── gen │ │ │ └── com │ │ │ │ └── yourcompany │ │ │ │ └── talkcasually │ │ │ │ ├── R.java │ │ │ │ ├── Manifest.java │ │ │ │ └── BuildConfig.java │ │ │ ├── java │ │ │ └── com │ │ │ │ └── yourcompany │ │ │ │ └── talkcasually │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ └── google-services.json ├── .gitignore ├── settings.gradle ├── build.gradle ├── gradlew.bat └── gradlew ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── LaunchImage-1@2x.png │ │ │ ├── LaunchImage-568h@2x.png │ │ │ ├── LaunchImage-800-667h@2x.png │ │ │ ├── LaunchImage-800-Portrait-736h@3x.png │ │ │ └── Contents.json │ ├── AppDelegate.h │ ├── main.m │ ├── AppDelegate.m │ ├── GoogleService-Info.plist │ ├── Info.plist │ └── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── project.pbxproj ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── .gitignore ├── Podfile └── Podfile.lock ├── lib ├── .DS_Store ├── main.dart ├── prompt_wait.dart ├── app_settings.dart ├── image_zoomable.dart ├── modify_password.dart ├── group_chat_list_body.dart ├── group_chat_list.dart ├── sign_in.dart ├── add_session.dart ├── personal_data.dart ├── sign_up.dart └── chat_screen.dart ├── images ├── .DS_Store └── talk_casually.png ├── .gitignore ├── talk_casually.iml ├── android.iml ├── LICENSE ├── talk_casually_android.iml ├── pubspec.yaml └── README.md /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/lib/.DS_Store -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/images/.DS_Store -------------------------------------------------------------------------------- /images/talk_casually.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/images/talk_casually.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea 4 | .packages 5 | .pub/ 6 | build/ 7 | ios/.generated/ 8 | packages 9 | pubspec.lock 10 | .flutter-plugins 11 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | GeneratedPluginRegistrant.java 10 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-1@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-568h@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-800-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-800-667h@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-800-Portrait-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hekaiyou/talk_casually/HEAD/ios/Runner/Assets.xcassets/LaunchImage.launchimage/LaunchImage-800-Portrait-736h@3x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/gen/com/yourcompany/talkcasually/R.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.yourcompany.talkcasually; 4 | 5 | /* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */ 6 | public final class R { 7 | } -------------------------------------------------------------------------------- /android/app/src/main/gen/com/yourcompany/talkcasually/Manifest.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.yourcompany.talkcasually; 4 | 5 | /* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */ 6 | public final class Manifest { 7 | } -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/main/gen/com/yourcompany/talkcasually/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.yourcompany.talkcasually; 4 | 5 | /* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */ 6 | public final class BuildConfig { 7 | public final static boolean DEBUG = Boolean.parseBoolean(null); 8 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/yourcompany/talkcasually/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yourcompany.talkcasually; 2 | 3 | import android.os.Bundle; 4 | 5 | import io.flutter.app.FlutterActivity; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | GeneratedPluginRegistrant.registerWith(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 7 | [GeneratedPluginRegistrant registerWithRegistry:self]; 8 | // Override point for customization after application launch. 9 | // 启动图片延时: 1秒 10 | [NSThread sleepForTimeInterval:1]; 11 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withInputStream { stream -> plugins.load(stream) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /talk_casually.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | *.pbxuser 16 | *.mode1v3 17 | *.mode2v3 18 | *.perspectivev3 19 | 20 | !default.pbxuser 21 | !default.mode1v3 22 | !default.mode2v3 23 | !default.perspectivev3 24 | 25 | xcuserdata 26 | 27 | *.moved-aside 28 | 29 | *.pyc 30 | *sync/ 31 | Icon? 32 | .tags* 33 | 34 | /Flutter/app.flx 35 | /Flutter/app.zip 36 | /Flutter/App.framework 37 | /Flutter/Flutter.framework 38 | /Flutter/Generated.xcconfig 39 | /ServiceDefinitions.json 40 | 41 | Pods/ 42 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | maven { 5 | url "https://maven.google.com" 6 | } 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:2.3.3' 11 | classpath 'com.google.gms:google-services:3.0.0' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | maven { 19 | url "https://maven.google.com" 20 | } 21 | } 22 | } 23 | 24 | rootProject.buildDir = '../build' 25 | subprojects { 26 | project.buildDir = "${rootProject.buildDir}/${project.name}" 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | MinimumOSVersion 28 | 8.0 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ChinaFlutterPioneerTeam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | if ENV['FLUTTER_FRAMEWORK_DIR'] == nil 5 | abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') 6 | end 7 | 8 | target 'Runner' do 9 | # Pods for Runner 10 | 11 | # Flutter Pods 12 | pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] 13 | 14 | if File.exists? '../.flutter-plugins' 15 | flutter_root = File.expand_path('..') 16 | File.foreach('../.flutter-plugins') { |line| 17 | plugin = line.split(pattern='=') 18 | if plugin.length == 2 19 | name = plugin[0].strip() 20 | path = plugin[1].strip() 21 | resolved_path = File.expand_path("#{path}/ios", flutter_root) 22 | pod name, :path => resolved_path 23 | else 24 | puts "Invalid plugin specification: #{line}" 25 | end 26 | } 27 | end 28 | end 29 | 30 | post_install do |installer| 31 | installer.pods_project.targets.each do |target| 32 | target.build_configurations.each do |config| 33 | config.build_settings['ENABLE_BITCODE'] = 'NO' 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "736h", 7 | "filename" : "LaunchImage-800-Portrait-736h@3x.png", 8 | "minimum-system-version" : "8.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "extent" : "full-screen", 14 | "idiom" : "iphone", 15 | "subtype" : "667h", 16 | "filename" : "LaunchImage-800-667h@2x.png", 17 | "minimum-system-version" : "8.0", 18 | "orientation" : "portrait", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "orientation" : "portrait", 23 | "idiom" : "iphone", 24 | "filename" : "LaunchImage-1@2x.png", 25 | "extent" : "full-screen", 26 | "minimum-system-version" : "7.0", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "extent" : "full-screen", 31 | "idiom" : "iphone", 32 | "subtype" : "retina4", 33 | "filename" : "LaunchImage-568h@2x.png", 34 | "minimum-system-version" : "7.0", 35 | "orientation" : "portrait", 36 | "scale" : "2x" 37 | } 38 | ], 39 | "info" : { 40 | "version" : 1, 41 | "author" : "xcode" 42 | } 43 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:path_provider/path_provider.dart'; 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | import 'sign_in.dart'; 6 | import 'group_chat_list.dart'; 7 | 8 | void main() { 9 | _getLandingFile().then((onValue) { 10 | runApp(new TalkcasuallyApp(onValue.existsSync())); 11 | }); 12 | } 13 | 14 | Future _getLandingFile() async { 15 | String dir = (await getApplicationDocumentsDirectory()).path; 16 | return new File('$dir/LandingInformation'); 17 | } 18 | 19 | class TalkcasuallyApp extends StatelessWidget { 20 | TalkcasuallyApp(this.landing); 21 | 22 | final bool landing; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return new MaterialApp( 27 | theme: new ThemeData( 28 | primarySwatch: Colors.blue, 29 | primaryColor: Colors.grey[50], 30 | scaffoldBackgroundColor: Colors.grey[50], 31 | dialogBackgroundColor: Colors.grey[50], 32 | primaryColorBrightness: Brightness.light, 33 | buttonColor: Colors.blue, 34 | iconTheme: new IconThemeData( 35 | color: Colors.grey[700], 36 | ), 37 | hintColor: Colors.grey[400], 38 | ), 39 | title: '纸聊', 40 | home: landing ? new GroupChatList() : new SignIn()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /talk_casually_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AD_UNIT_ID_FOR_BANNER_TEST 6 | ca-app-pub-3940256099942544/2934735716 7 | AD_UNIT_ID_FOR_INTERSTITIAL_TEST 8 | ca-app-pub-3940256099942544/4411468910 9 | CLIENT_ID 10 | 895837916274-doqpl137tq67qafqqqks4ramv5mfdcc0.apps.googleusercontent.com 11 | REVERSED_CLIENT_ID 12 | com.googleusercontent.apps.895837916274-doqpl137tq67qafqqqks4ramv5mfdcc0 13 | API_KEY 14 | AIzaSyAMVms-0lN0cqT4NHQrQ6Dh8uQQ98ywmAU 15 | GCM_SENDER_ID 16 | 895837916274 17 | PLIST_VERSION 18 | 1 19 | BUNDLE_ID 20 | com.yourcompany.talkCasually 21 | PROJECT_ID 22 | talk-casually-app 23 | STORAGE_BUCKET 24 | talk-casually-app.appspot.com 25 | IS_ADS_ENABLED 26 | 27 | IS_ANALYTICS_ENABLED 28 | 29 | IS_APPINVITE_ENABLED 30 | 31 | IS_GCM_ENABLED 32 | 33 | IS_SIGNIN_ENABLED 34 | 35 | GOOGLE_APP_ID 36 | 1:895837916274:ios:b1477f4343bef738 37 | DATABASE_URL 38 | https://talk-casually-app.firebaseio.com 39 | 40 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | 纸聊 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | takephoto 27 | NSPhotoLibraryUsageDescription 28 | choosephoto 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | arm64 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withInputStream { stream -> 5 | localProperties.load(stream) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 25 19 | buildToolsVersion '25.0.3' 20 | 21 | lintOptions { 22 | disable 'InvalidPackage' 23 | } 24 | 25 | defaultConfig { 26 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 27 | applicationId "com.yourcompany.talkcasually" 28 | minSdkVersion 16 29 | targetSdkVersion 25 30 | versionCode 1 31 | versionName "1.0" 32 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 33 | } 34 | 35 | buildTypes { 36 | release { 37 | // TODO: Add your own signing config for the release build. 38 | // Signing with the debug keys for now, so `flutter run --release` works. 39 | signingConfig signingConfigs.debug 40 | } 41 | } 42 | } 43 | 44 | flutter { 45 | source '../..' 46 | } 47 | 48 | dependencies { 49 | androidTestCompile 'com.android.support:support-annotations:25.4.0' 50 | androidTestCompile 'com.android.support.test:runner:0.5' 51 | androidTestCompile 'com.android.support.test:rules:0.5' 52 | } 53 | 54 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lib/prompt_wait.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | void showMessage(BuildContext context, String text) { 5 | showDialog( 6 | context: context, 7 | child: new AlertDialog( 8 | title: new Text("提示"), 9 | content: new Text(text), 10 | actions: [ 11 | new FlatButton( 12 | onPressed: () { 13 | Navigator.pop(context); 14 | }, 15 | child: const Text('确定')) 16 | ])); 17 | } 18 | 19 | class ShowAwait extends StatefulWidget { 20 | ShowAwait(this.requestCallback); 21 | final Future requestCallback; 22 | 23 | @override 24 | _ShowAwaitState createState() => new _ShowAwaitState(); 25 | } 26 | 27 | class _ShowAwaitState extends State { 28 | @override 29 | initState() { 30 | super.initState(); 31 | new Timer(new Duration(seconds: 1), () { 32 | widget.requestCallback.then((int onValue) { 33 | Navigator.of(context).pop(onValue); 34 | }); 35 | }); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return new Center( 41 | child: new CircularProgressIndicator(), 42 | ); 43 | } 44 | } 45 | 46 | String ReadableTime(String timestamp) { 47 | List timeList = timestamp.split(" "); 48 | List times = timeList[1].split(":"); 49 | String time; 50 | if (new DateTime.now().toString().split(" ")[0] == timeList[0]) { 51 | if (int.parse(times[0]) < 6) { 52 | time = "凌晨${times[0]}:${times[1]}"; 53 | } else if (int.parse(times[0]) < 12) { 54 | time = "上午${times[0]}:${times[1]}"; 55 | } else if (int.parse(times[0]) == 12) { 56 | time = "中午${times[0]}:${times[1]}"; 57 | } else { 58 | time = 59 | "下午${(int.parse(times[0])- 12).toString().padLeft(2,'0')}:${times[1]}"; 60 | } 61 | } else { 62 | time = timeList[0]; 63 | } 64 | return time; 65 | } 66 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "895837916274", 4 | "firebase_url": "https://talk-casually-app.firebaseio.com", 5 | "project_id": "talk-casually-app", 6 | "storage_bucket": "talk-casually-app.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:895837916274:android:95e004e54d285639", 12 | "android_client_info": { 13 | "package_name": "com.yourcompany.talkcasually" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "895837916274-2d6bv061q5o0k03akm644diol4le8gvl.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.yourcompany.talkcasually", 22 | "certificate_hash": "a9afde4c09ed333d9989afcb11fb3f4f8273b788" 23 | } 24 | }, 25 | { 26 | "client_id": "895837916274-9d9aikvreqjvnvtmscchn42o5i5mip9r.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyD7pKCMqKxPdxIdRNb2E8KlwapxVQTNciM" 33 | } 34 | ], 35 | "services": { 36 | "analytics_service": { 37 | "status": 1 38 | }, 39 | "appinvite_service": { 40 | "status": 2, 41 | "other_platform_oauth_client": [ 42 | { 43 | "client_id": "895837916274-9d9aikvreqjvnvtmscchn42o5i5mip9r.apps.googleusercontent.com", 44 | "client_type": 3 45 | }, 46 | { 47 | "client_id": "895837916274-uvh1db1jps3k91nugiollh1pl14b6q03.apps.googleusercontent.com", 48 | "client_type": 2, 49 | "ios_info": { 50 | "bundle_id": "com.yourcompany.talk-casually" 51 | } 52 | } 53 | ] 54 | }, 55 | "ads_service": { 56 | "status": 2 57 | } 58 | } 59 | } 60 | ], 61 | "configuration_version": "1" 62 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: talk_casually 2 | description: A new Flutter project. 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | image_picker: ^0.1.1 8 | firebase_database: ^0.0.14 9 | firebase_storage: ^0.0.6 10 | path_provider: ^0.2.1+1 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | 17 | # For information on the generic Dart part of this file, see the 18 | # following page: https://www.dartlang.org/tools/pub/pubspec 19 | 20 | # The following section is specific to Flutter. 21 | flutter: 22 | 23 | # The following line ensures that the Material Icons font is 24 | # included with your application, so that you can use the icons in 25 | # the Icons class. 26 | uses-material-design: true 27 | 28 | # To add assets to your application, add an assets section, like this: 29 | assets: 30 | - images/talk_casually.png 31 | 32 | # An image asset can refer to one or more resolution-specific "variants", see 33 | # https://flutter.io/assets-and-images/. 34 | 35 | # To add assets from package dependencies, first ensure the asset 36 | # is in the lib/ directory of the dependency. Then, 37 | # refer to the asset with a path prefixed with 38 | # `packages/PACKAGE_NAME/`. The `lib/` is implied, do not 39 | # include `lib/` in the asset path. 40 | # 41 | # Here is an example: 42 | # 43 | # assets: 44 | # - packages/PACKAGE_NAME/path/to/asset 45 | 46 | # To add custom fonts to your application, add a fonts section here, 47 | # in this "flutter" section. Each entry in this list should have a 48 | # "family" key with the font family name, and a "fonts" key with a 49 | # list giving the asset and other descriptors for the font. For 50 | # example: 51 | # fonts: 52 | # - family: Schyler 53 | # fonts: 54 | # - asset: fonts/Schyler-Regular.ttf 55 | # - asset: fonts/Schyler-Italic.ttf 56 | # style: italic 57 | # - family: Trajan Pro 58 | # fonts: 59 | # - asset: fonts/TrajanPro.ttf 60 | # - asset: fonts/TrajanPro_Bold.ttf 61 | # weight: 700 62 | -------------------------------------------------------------------------------- /lib/app_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:path_provider/path_provider.dart'; 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | import 'sign_in.dart'; 6 | import 'modify_password.dart'; 7 | 8 | class AppSettings extends StatefulWidget { 9 | AppSettings(this.myPhone); 10 | final String myPhone; 11 | 12 | @override 13 | State createState() => new _AppSettingsState(); 14 | } 15 | 16 | class _AppSettingsState extends State { 17 | Future _logOut() async { 18 | String dir = (await getApplicationDocumentsDirectory()).path; 19 | await new File('$dir/LandingInformation').delete(); 20 | Navigator.of(context).push(new MaterialPageRoute( 21 | builder: (BuildContext context) { 22 | return new SignIn(); 23 | }, 24 | )); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return new Scaffold( 30 | appBar: new AppBar( 31 | title: new Text("设置"), 32 | centerTitle: true, 33 | elevation: 0.0, 34 | ), 35 | body: new Column( 36 | children: [ 37 | new GestureDetector( 38 | onTap: () { 39 | Navigator.of(context).push(new MaterialPageRoute( 40 | builder: (BuildContext context) { 41 | return new ModifyPassword(widget.myPhone); 42 | }, 43 | )); 44 | }, 45 | child: new Container( 46 | height: 40.0, 47 | decoration: new BoxDecoration(), 48 | alignment: FractionalOffset.centerLeft, 49 | width: MediaQuery.of(context).size.width * 0.9, 50 | child: new Text("修改密码", textScaleFactor: 1.1), 51 | ), 52 | ), 53 | new Divider(height: 0.0), 54 | new GestureDetector( 55 | onTap: () { 56 | _logOut(); 57 | }, 58 | child: new Container( 59 | height: 40.0, 60 | margin: const EdgeInsets.symmetric(vertical: 10.0), 61 | decoration: new BoxDecoration(), 62 | alignment: FractionalOffset.centerLeft, 63 | width: MediaQuery.of(context).size.width * 0.9, 64 | child: new Text("退出登录", textScaleFactor: 1.1), 65 | ), 66 | ), 67 | new Divider(height: 0.0), 68 | ], 69 | )); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 纸聊 2 | 3 | 这个应用程序使用Google的Flutter移动框架开发,是一个实时聊天应用程序,为了能专注于APP设计,应用程序的服务端使用Googler的Firebase平台。程序程序的名称为纸聊,意为像传递小纸条一样的简约聊天,登录后所有好友都在,消息无缝同步,还能发送图片。支持Android、iOS等多个平台,多端消息漫游同步。*(由于使用了谷歌的后端服务,该应用程序必须在科学上网时才能正常使用。)* 4 | 5 | ## 应用截图 6 | 7 | ![截图一](http://img.blog.csdn.net/20170917173738967?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGVrYWl5b3U=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 8 | 9 | ![截图二](http://img.blog.csdn.net/20170917174439503?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGVrYWl5b3U=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 10 | 11 | ![截图三](http://img.blog.csdn.net/20170917174831030?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGVrYWl5b3U=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 12 | 13 | ![截图四](http://img.blog.csdn.net/20170917180203315?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGVrYWl5b3U=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 14 | 15 | ## 开发过程 16 | 17 | - [《Flutter实战一Flutter聊天应用(一)》](http://blog.csdn.net/hekaiyou/article/details/72870759 "教程一") 18 | - [《Flutter实战一Flutter聊天应用(二)》](http://blog.csdn.net/hekaiyou/article/details/72884897 "教程二") 19 | - [《Flutter实战一Flutter聊天应用(三)》](http://blog.csdn.net/hekaiyou/article/details/72897702 "教程三") 20 | - [《Flutter实战一Flutter聊天应用(四)》](http://blog.csdn.net/hekaiyou/article/details/72902691 "教程四") 21 | - [《Flutter实战一Flutter聊天应用(五)》](http://blog.csdn.net/hekaiyou/article/details/72921061 "教程五") 22 | - [《Flutter实战一Flutter聊天应用(六)》](http://blog.csdn.net/hekaiyou/article/details/72972599 "教程六") 23 | - [《Flutter实战一Flutter聊天应用(七)》](http://blog.csdn.net/hekaiyou/article/details/73130991 "教程七") 24 | - [《Flutter实战一Flutter聊天应用(八)》](http://blog.csdn.net/hekaiyou/article/details/73196458 "教程八") 25 | - [《Flutter实战一Flutter聊天应用(九)》](http://blog.csdn.net/hekaiyou/article/details/73239486 "教程九") 26 | - [《Flutter实战一Flutter聊天应用(十)》](http://blog.csdn.net/hekaiyou/article/details/73501793 "教程十") 27 | - [《Flutter实战一Flutter聊天应用(十一)》](http://blog.csdn.net/hekaiyou/article/details/73550576 "教程十一") 28 | - [《Flutter实战一Flutter聊天应用(十二)》](http://blog.csdn.net/hekaiyou/article/details/73819668 "教程十二") 29 | - [《Flutter实战一Flutter聊天应用(十三)》](http://blog.csdn.net/hekaiyou/article/details/73862821 "教程十三") 30 | - [《Flutter实战一Flutter聊天应用(十四)》](http://blog.csdn.net/hekaiyou/article/details/75808392 "教程十四") 31 | - [《Flutter实战一Flutter聊天应用(十五)》](http://blog.csdn.net/hekaiyou/article/details/76267418 "教程十五") 32 | - [《Flutter实战一Flutter聊天应用(十六)》](http://blog.csdn.net/hekaiyou/article/details/76578336 "教程十六") 33 | - [《Flutter实战一Flutter聊天应用(十七)》](http://blog.csdn.net/hekaiyou/article/details/76922454 "教程十七") 34 | - [《Flutter实战一Flutter聊天应用(十八)》](http://blog.csdn.net/hekaiyou/article/details/77164514 "教程十八") 35 | - [《Flutter实战一Flutter聊天应用(十九)》](http://blog.csdn.net/hekaiyou/article/details/77509066 "教程十九") 36 | - [《Flutter实战一Flutter聊天应用(二十)》](http://blog.csdn.net/hekaiyou/article/details/77641545 "教程二十") 37 | - [《Flutter实战一Flutter聊天应用(二十一)》](http://blog.csdn.net/hekaiyou/article/details/78006726 "教程二十一") 38 | 39 | ## 下载应用 40 | 41 | [百度下载链接](http://pan.baidu.com/s/1bzg3xW "百度下载链接") 42 | 43 | [![Google Play下载链接](https://static-goldenfrog.netdna-ssl.com/images/google_play_badge.png "Google Play图标")](https://play.google.com/store/apps/details?id=com.yourcompany.talkcasually) 44 | 45 | 由于iOS开发者账号太贵了,所以没有在iOS平台上架。 46 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Firebase/Core (4.1.0): 3 | - FirebaseAnalytics (= 4.0.3) 4 | - FirebaseCore (= 4.0.5) 5 | - Firebase/Database (4.1.0): 6 | - Firebase/Core 7 | - FirebaseDatabase (= 4.0.1) 8 | - Firebase/Storage (4.1.0): 9 | - Firebase/Core 10 | - FirebaseStorage (= 2.0.1) 11 | - firebase_database (0.0.1): 12 | - Firebase/Database 13 | - Flutter 14 | - firebase_storage (0.0.1): 15 | - Firebase/Storage 16 | - Flutter 17 | - FirebaseAnalytics (4.0.3): 18 | - FirebaseCore (~> 4.0) 19 | - FirebaseInstanceID (~> 2.0) 20 | - GoogleToolboxForMac/NSData+zlib (~> 2.1) 21 | - nanopb (~> 0.3) 22 | - FirebaseCore (4.0.5): 23 | - GoogleToolboxForMac/NSData+zlib (~> 2.1) 24 | - nanopb (~> 0.3) 25 | - FirebaseDatabase (4.0.1): 26 | - FirebaseAnalytics (~> 4.0) 27 | - FirebaseCore (~> 4.0) 28 | - leveldb-library (~> 1.18) 29 | - FirebaseInstanceID (2.0.1): 30 | - FirebaseCore (~> 4.0) 31 | - FirebaseStorage (2.0.1): 32 | - FirebaseAnalytics (~> 4.0) 33 | - FirebaseCore (~> 4.0) 34 | - GTMSessionFetcher/Core (~> 1.1) 35 | - Flutter (1.0.0) 36 | - GoogleToolboxForMac/Defines (2.1.1) 37 | - GoogleToolboxForMac/NSData+zlib (2.1.1): 38 | - GoogleToolboxForMac/Defines (= 2.1.1) 39 | - GTMSessionFetcher/Core (1.1.11) 40 | - image_picker (0.0.1): 41 | - Flutter 42 | - leveldb-library (1.18.3) 43 | - nanopb (0.3.8): 44 | - nanopb/decode (= 0.3.8) 45 | - nanopb/encode (= 0.3.8) 46 | - nanopb/decode (0.3.8) 47 | - nanopb/encode (0.3.8) 48 | - path_provider (0.0.1): 49 | - Flutter 50 | 51 | DEPENDENCIES: 52 | - firebase_database (from `/Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/firebase_database-0.0.14/ios`) 53 | - firebase_storage (from `/Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/firebase_storage-0.0.7/ios`) 54 | - Flutter (from `/Users/hekaiyou/flutter/bin/cache/artifacts/engine/ios`) 55 | - image_picker (from `/Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/image_picker-0.1.3/ios`) 56 | - path_provider (from `/Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/path_provider-0.2.1+1/ios`) 57 | 58 | EXTERNAL SOURCES: 59 | firebase_database: 60 | :path: /Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/firebase_database-0.0.14/ios 61 | firebase_storage: 62 | :path: /Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/firebase_storage-0.0.7/ios 63 | Flutter: 64 | :path: /Users/hekaiyou/flutter/bin/cache/artifacts/engine/ios 65 | image_picker: 66 | :path: /Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/image_picker-0.1.3/ios 67 | path_provider: 68 | :path: /Users/hekaiyou/.pub-cache/hosted/pub.dartlang.org/path_provider-0.2.1+1/ios 69 | 70 | SPEC CHECKSUMS: 71 | Firebase: ebebf41db7f10e0c7668b6eaaa857fbe599aa478 72 | firebase_database: 8d50f5c188b769afc186b0a700b2c6cdb8208df9 73 | firebase_storage: eb8091489a1cfb657eeefc933bb1ccfab23195a3 74 | FirebaseAnalytics: 76f754d37ca5b04f36856729b6af3ca0152d1069 75 | FirebaseCore: 7d876ea97a830cbe62ba7fbbe7670c833a324ba0 76 | FirebaseDatabase: 94c38c783d23dc6679441050772d42e801d06e9e 77 | FirebaseInstanceID: 0500e3cb54a1a4e01a8cffcc09323b8bb8fc7e1e 78 | FirebaseStorage: 661fc1f8d4131891d256b62e82a45ace8b3f0c3b 79 | Flutter: d674e78c937094a75ac71dd77e921e840bea3dbf 80 | GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0 81 | GTMSessionFetcher: 5ad62e8200fa00ed011fe5e08d27fef72c5b1429 82 | image_picker: a211f28b95a560433c00f5cd3773f4710a20404d 83 | leveldb-library: 10fb39c39e243db4af1828441162405bbcec1404 84 | nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 85 | path_provider: f96fff6166a8867510d2c25fdcc346327cc4b259 86 | 87 | PODFILE CHECKSUM: 351e02e34b831289961ec3558a535cbd2c4965d2 88 | 89 | COCOAPODS: 1.3.1 90 | -------------------------------------------------------------------------------- /lib/image_zoomable.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'dart:ui' as ui; 4 | 5 | class ImageZoomable extends StatefulWidget { 6 | ImageZoomable(this.image, {Key key, this.scale = 2.0, this.onTap}) : super(key: key); 7 | 8 | final ImageProvider image; 9 | final double scale; 10 | final GestureTapCallback onTap; 11 | 12 | @override 13 | _ImageZoomableState createState() => new _ImageZoomableState(scale); 14 | } 15 | 16 | class _ImageZoomableState extends State { 17 | _ImageZoomableState(this._scale); 18 | 19 | final double _scale; 20 | ImageStream _imageStream; 21 | ui.Image _image; 22 | 23 | Offset _startingFocalPoint; 24 | Offset _previousOffset; 25 | Offset _offset = Offset.zero; 26 | 27 | double _previousZoom; 28 | double _zoom = 1.0; 29 | 30 | void _handleScaleStart(ScaleStartDetails details) { 31 | if (_image == null) { 32 | return; 33 | } 34 | _startingFocalPoint = details.focalPoint / _scale; 35 | _previousOffset = _offset; 36 | _previousZoom = _zoom; 37 | } 38 | 39 | void _handleScaleUpdate(Size size, ScaleUpdateDetails details) { 40 | if (_image == null) { 41 | return; 42 | } 43 | double newZoom = _previousZoom * details.scale; 44 | bool tooZoomedIn = _image.width * _scale / newZoom <= size.width || 45 | _image.height * _scale / newZoom <= size.height || newZoom <= 0.8; 46 | if (tooZoomedIn) { 47 | return; 48 | } 49 | 50 | setState(() { 51 | _zoom = newZoom; 52 | final Offset normalizedOffset = (_startingFocalPoint - _previousOffset) / _previousZoom; 53 | _offset = details.focalPoint / _scale - normalizedOffset * _zoom; 54 | }); 55 | } 56 | 57 | @override 58 | void didChangeDependencies() { 59 | _resolveImage(); 60 | super.didChangeDependencies(); 61 | } 62 | 63 | @override 64 | void reassemble() { 65 | _resolveImage(); 66 | super.reassemble(); 67 | } 68 | 69 | void _resolveImage() { 70 | _imageStream = widget.image.resolve(createLocalImageConfiguration(context)); 71 | _imageStream.addListener(_handleImageLoaded); 72 | } 73 | 74 | void _handleImageLoaded(ImageInfo info, bool synchronousCall) { 75 | setState(() { 76 | _image = info.image; 77 | }); 78 | } 79 | 80 | @override 81 | void dispose() { 82 | _imageStream.removeListener(_handleImageLoaded); 83 | super.dispose(); 84 | } 85 | 86 | Widget _drawImage() { 87 | if (_image == null) { 88 | return null; 89 | } 90 | 91 | return new Transform( 92 | transform: new Matrix4.diagonal3Values(_scale, _scale, _scale), 93 | child: new CustomPaint( 94 | painter: new _ImageZoomablePainter( 95 | image: _image, 96 | offset: _offset, 97 | zoom: _zoom / _scale, 98 | ) 99 | ) 100 | ); 101 | } 102 | 103 | @override 104 | Widget build(BuildContext context) { 105 | return new GestureDetector( 106 | child: _drawImage(), 107 | onTap: widget.onTap, 108 | onScaleStart: _handleScaleStart, 109 | onScaleUpdate: (d) => _handleScaleUpdate(context.size, d), 110 | ); 111 | } 112 | } 113 | 114 | class _ImageZoomablePainter extends CustomPainter { 115 | const _ImageZoomablePainter({this.image, this.offset, this.zoom}); 116 | 117 | final ui.Image image; 118 | final Offset offset; 119 | final double zoom; 120 | 121 | @override 122 | void paint(Canvas canvas, Size size) { 123 | paintImage(canvas: canvas, rect: offset & (size * zoom), image: image); 124 | } 125 | 126 | @override 127 | bool shouldRepaint(_ImageZoomablePainter old) { 128 | return old.image != image || old.offset != offset || old.zoom != zoom; 129 | } 130 | } -------------------------------------------------------------------------------- /lib/modify_password.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_database/firebase_database.dart'; 3 | import 'prompt_wait.dart'; 4 | import 'dart:async'; 5 | 6 | class ModifyPassword extends StatefulWidget { 7 | ModifyPassword(this.myPhone); 8 | final String myPhone; 9 | 10 | @override 11 | State createState() => new _ModifyPasswordState(); 12 | } 13 | 14 | class _ModifyPasswordState extends State { 15 | static final GlobalKey _scaffoldKey = 16 | new GlobalKey(); 17 | final TextEditingController _passwordController = new TextEditingController(); 18 | final TextEditingController _confirmController = new TextEditingController(); 19 | bool _correctPassword = true; 20 | bool _correctConfirm = true; 21 | final reference = FirebaseDatabase.instance.reference().child('users'); 22 | 23 | void _handleSubmitted() { 24 | FocusScope.of(context).requestFocus(new FocusNode()); 25 | _checkInput(); 26 | if (_confirmController.text == '' || _passwordController.text == '') { 27 | showMessage(context, "修改密码所需信息不完整!"); 28 | return; 29 | } else if (!_correctConfirm || !_correctPassword) { 30 | showMessage(context, "修改密码输入格式不正确!"); 31 | return; 32 | } 33 | showDialog( 34 | context: context, 35 | barrierDismissible: false, 36 | child: new ShowAwait(_saveModify())).then((int onValue) { 37 | if (onValue == 1) { 38 | _scaffoldKey.currentState.showSnackBar(new SnackBar( 39 | content: new Text("密码修改成功!"), 40 | )); 41 | } else { 42 | print("暂时没有处理!"); 43 | } 44 | }); 45 | } 46 | 47 | Future _saveModify() async { 48 | await reference 49 | .child('${widget.myPhone}/password') 50 | .set(_passwordController.text.trim()); 51 | return 1; 52 | } 53 | 54 | void _checkInput() { 55 | if (_passwordController.text.isNotEmpty && 56 | _passwordController.text.trim().length < 6) { 57 | _correctPassword = false; 58 | } else { 59 | _correctPassword = true; 60 | } 61 | if (_confirmController.text.isNotEmpty && 62 | _confirmController.text.trim() != _passwordController.text.trim()) { 63 | _correctConfirm = false; 64 | } else { 65 | _correctConfirm = true; 66 | } 67 | setState(() {}); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | return new Scaffold( 73 | key: _scaffoldKey, 74 | appBar: new AppBar( 75 | title: new Text("修改密码"), 76 | centerTitle: true, 77 | elevation: 0.0, 78 | actions: [ 79 | new IconButton( 80 | icon: new Icon(Icons.save), 81 | onPressed: () { 82 | _handleSubmitted(); 83 | }) 84 | ], 85 | ), 86 | body: new Stack(children: [ 87 | new GestureDetector( 88 | onTap: () { 89 | FocusScope.of(context).requestFocus(new FocusNode()); 90 | _checkInput(); 91 | }, 92 | child: new Container( 93 | decoration: new BoxDecoration(), 94 | )), 95 | new Row( 96 | mainAxisAlignment: MainAxisAlignment.center, 97 | children: [ 98 | new Column( 99 | children: [ 100 | new Container( 101 | margin: const EdgeInsets.only(top: 10.0), 102 | width: MediaQuery.of(context).size.width * 0.7, 103 | child: new TextField( 104 | controller: _passwordController, 105 | keyboardType: TextInputType.number, 106 | decoration: new InputDecoration( 107 | hintText: "新密码", 108 | errorText: _correctPassword ? null : '密码的长度应该大于6位', 109 | ), 110 | onSubmitted: (value) { 111 | _checkInput(); 112 | }, 113 | ), 114 | ), 115 | new Container( 116 | width: MediaQuery.of(context).size.width * 0.7, 117 | child: new TextField( 118 | controller: _confirmController, 119 | keyboardType: TextInputType.number, 120 | decoration: new InputDecoration( 121 | hintText: "确认密码", 122 | errorText: _correctConfirm ? null : '确认密码与新密码不一致', 123 | ), 124 | onSubmitted: (value) { 125 | _checkInput(); 126 | }, 127 | ), 128 | ) 129 | ], 130 | ), 131 | ], 132 | ), 133 | ])); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/group_chat_list_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_database/firebase_database.dart'; 3 | import 'package:firebase_database/ui/firebase_animated_list.dart'; 4 | import 'chat_screen.dart'; 5 | import 'prompt_wait.dart'; 6 | 7 | class GroupChatListBodyItem extends StatelessWidget { 8 | GroupChatListBodyItem({ 9 | this.name, 10 | this.lastMessage, 11 | this.timestamp, 12 | this.messages, 13 | this.myName, 14 | this.myPhone, 15 | this.shePhone, 16 | this.shePortrait, 17 | this.myPortrait, 18 | }); 19 | final String name; 20 | final String lastMessage; 21 | final String timestamp; 22 | final String messages; 23 | final String myName; 24 | final String myPhone; 25 | final String shePhone; 26 | final String shePortrait; 27 | final String myPortrait; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return new GestureDetector( 32 | onTap: () { 33 | Navigator.of(context).push(new MaterialPageRoute( 34 | builder: (BuildContext context) { 35 | return new ChatScreen( 36 | messages: messages, 37 | myName: myName, 38 | sheName: name, 39 | myPhone: myPhone, 40 | shePhone: shePhone, 41 | shePortrait: shePortrait, 42 | myPortrait: myPortrait, 43 | ); 44 | }, 45 | )); 46 | }, 47 | child: new Container( 48 | decoration: new BoxDecoration(), 49 | padding: new EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), 50 | child: new Row( 51 | children: [ 52 | new CircleAvatar( 53 | backgroundImage: new NetworkImage(shePortrait)), 54 | new Flexible( 55 | child: new Column( 56 | crossAxisAlignment: CrossAxisAlignment.start, 57 | children: [ 58 | new Row( 59 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 60 | children: [ 61 | new Text(" " + name, textScaleFactor: 1.2), 62 | new Text(ReadableTime(timestamp), 63 | textAlign: TextAlign.right, 64 | style: new TextStyle( 65 | color: Theme.of(context).hintColor)), 66 | ]), 67 | new Container( 68 | padding: new EdgeInsets.only(top: 2.0), 69 | child: new Text(" " + lastMessage, 70 | overflow: TextOverflow.ellipsis, 71 | style: new TextStyle( 72 | color: Theme.of(context).hintColor))), 73 | ], 74 | )) 75 | ], 76 | ))); 77 | } 78 | } 79 | 80 | class GroupChatListBody extends StatefulWidget { 81 | GroupChatListBody({ 82 | this.phone, 83 | this.myName, 84 | this.portrait, 85 | Key key, 86 | }) 87 | : super(key: key); 88 | final String phone; 89 | final String myName; 90 | final String portrait; 91 | @override 92 | _GroupChatListBodyState createState() => new _GroupChatListBodyState(phone); 93 | } 94 | 95 | class _GroupChatListBodyState extends State { 96 | _GroupChatListBodyState(this._phone); 97 | final String _phone; 98 | 99 | DatabaseReference _chatsReference; 100 | 101 | @override 102 | void initState() { 103 | super.initState(); 104 | _chatsReference = 105 | FirebaseDatabase.instance.reference().child('chats/$_phone'); 106 | FirebaseDatabase.instance.setPersistenceEnabled(true); 107 | _chatsReference.keepSynced(true); 108 | } 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | return new FirebaseAnimatedList( 113 | query: _chatsReference, 114 | sort: (DataSnapshot a, DataSnapshot b) => 115 | b.value["timestamp"].compareTo(a.value["timestamp"]), 116 | defaultChild: new CircularProgressIndicator(), 117 | itemBuilder: (BuildContext context, DataSnapshot snapshot, 118 | Animation animation) { 119 | return new SizeTransition( 120 | sizeFactor: animation, 121 | child: snapshot.value["activate"] == "false" 122 | ? null 123 | : new GroupChatListBodyItem( 124 | name: snapshot.value["name"], 125 | lastMessage: snapshot.value["lastMessage"], 126 | timestamp: snapshot.value["timestamp"], 127 | messages: snapshot.value["messages"], 128 | myName: widget.myName, 129 | myPhone: _phone, 130 | shePhone: snapshot.value["phone"], 131 | shePortrait: snapshot.value["portrait"], 132 | myPortrait: widget.portrait, 133 | ), 134 | ); 135 | }, 136 | ); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /lib/group_chat_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:path_provider/path_provider.dart'; 3 | import 'dart:async'; 4 | import 'dart:convert'; 5 | import 'dart:io'; 6 | import 'group_chat_list_body.dart'; 7 | import 'add_session.dart'; 8 | import 'chat_screen.dart'; 9 | import 'personal_data.dart'; 10 | import 'app_settings.dart'; 11 | 12 | class GroupChatList extends StatefulWidget { 13 | @override 14 | State createState() => new _GroupChatListState(); 15 | } 16 | 17 | class _GroupChatListState extends State { 18 | String name = "null"; 19 | String phone = "null"; 20 | String email = "null"; 21 | String portrait = "null"; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _readLoginData().then((Map onValue) { 27 | setState(() { 28 | name = onValue["name"]; 29 | phone = onValue["phone"]; 30 | email = onValue["email"]; 31 | portrait = onValue["portrait"]; 32 | }); 33 | }); 34 | } 35 | 36 | Future _readLoginData() async { 37 | String dir = (await getApplicationDocumentsDirectory()).path; 38 | File file = new File('$dir/LandingInformation'); 39 | String data = await file.readAsString(); 40 | Map json = new JsonDecoder().convert(data); 41 | return json; 42 | } 43 | 44 | Widget _drawerOption(Icon icon, String name) { 45 | return new Container( 46 | padding: const EdgeInsets.only(top: 19.0), 47 | child: new Row( 48 | children: [ 49 | new Container( 50 | padding: const EdgeInsets.only(right: 28.0), child: icon), 51 | new Text(name, textScaleFactor: 1.1) 52 | ], 53 | ), 54 | ); 55 | } 56 | 57 | void _openModify() { 58 | Navigator.of(context).push(new MaterialPageRoute( 59 | builder: (BuildContext context) { 60 | return new PersonalData( 61 | name: name, 62 | email: email, 63 | portrait: portrait, 64 | phone: phone, 65 | ); 66 | }, 67 | )).then((onValue) { 68 | _readLoginData().then((Map onValue) { 69 | setState(() { 70 | name = onValue["name"]; 71 | phone = onValue["phone"]; 72 | email = onValue["email"]; 73 | portrait = onValue["portrait"]; 74 | }); 75 | }); 76 | }); 77 | } 78 | 79 | void _floatingButtonCallback() { 80 | showDialog>( 81 | context: context, 82 | barrierDismissible: false, 83 | child: new AddSession(phone, name, portrait)) 84 | .then((List onValue) { 85 | if (onValue != null) { 86 | Navigator.of(context).push(new MaterialPageRoute( 87 | builder: (BuildContext context) { 88 | return new ChatScreen( 89 | messages: onValue[2], 90 | myName: name, 91 | sheName: onValue[0], 92 | myPhone: phone, 93 | shePhone: onValue[1], 94 | myPortrait: portrait, 95 | shePortrait: onValue[3], 96 | ); 97 | }, 98 | )); 99 | } 100 | }); 101 | } 102 | 103 | Widget build(BuildContext context) { 104 | Drawer drawer = new Drawer( 105 | child: new ListView( 106 | children: [ 107 | new DrawerHeader( 108 | child: new Column( 109 | children: [ 110 | new GestureDetector( 111 | onTap: _openModify, 112 | child: new Row( 113 | children: [ 114 | new Container( 115 | padding: const EdgeInsets.only(right: 12.0), 116 | child: new CircleAvatar( 117 | backgroundImage: new NetworkImage(portrait), 118 | radius: 22.0, 119 | ), 120 | ), 121 | new Column( 122 | crossAxisAlignment: CrossAxisAlignment.start, 123 | children: [ 124 | new Text( 125 | name, 126 | textScaleFactor: 1.4, 127 | ), 128 | new Text( 129 | phone, 130 | textScaleFactor: 1.1, 131 | ) 132 | ], 133 | ) 134 | ], 135 | ), 136 | ), 137 | new GestureDetector( 138 | onTap: _openModify, 139 | child: new Container( 140 | decoration: new BoxDecoration(), 141 | child: _drawerOption(new Icon(Icons.account_circle), "个人资料"), 142 | )), 143 | new GestureDetector( 144 | onTap: () { 145 | Navigator.of(context).push(new MaterialPageRoute( 146 | builder: (BuildContext context) { 147 | return new AppSettings(phone); 148 | }, 149 | )); 150 | }, 151 | child: new Container( 152 | decoration: new BoxDecoration(), 153 | child: _drawerOption(new Icon(Icons.settings), "设置"), 154 | )), 155 | ], 156 | )) 157 | ], 158 | )); 159 | 160 | return new Scaffold( 161 | appBar: new AppBar( 162 | title: new Text("纸聊"), 163 | centerTitle: true, 164 | elevation: 0.0, 165 | ), 166 | drawer: drawer, 167 | body: new Center( 168 | child: phone == "null" 169 | ? null 170 | : new GroupChatListBody( 171 | phone: phone, myName: name, portrait: portrait), 172 | ), 173 | floatingActionButton: new FloatingActionButton( 174 | backgroundColor: Theme.of(context).buttonColor, 175 | elevation: 0.0, 176 | onPressed: _floatingButtonCallback, 177 | child: new Icon(Icons.person_add))); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /lib/sign_in.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_database/firebase_database.dart'; 3 | import 'package:path_provider/path_provider.dart'; 4 | import 'dart:async'; 5 | import 'dart:io'; 6 | import 'sign_up.dart'; 7 | import 'prompt_wait.dart'; 8 | import 'group_chat_list.dart'; 9 | 10 | class SignIn extends StatefulWidget { 11 | @override 12 | State createState() => new _SignInState(); 13 | } 14 | 15 | class _SignInState extends State { 16 | static final GlobalKey _scaffoldKey = 17 | new GlobalKey(); 18 | final TextEditingController _phoneController = new TextEditingController(); 19 | final TextEditingController _passwordController = new TextEditingController(); 20 | final reference = FirebaseDatabase.instance.reference().child('users'); 21 | bool _correctPhone = true; 22 | bool _correctPassword = true; 23 | 24 | void _handleSubmitted() { 25 | FocusScope.of(context).requestFocus(new FocusNode()); 26 | _checkInput(); 27 | if (_phoneController.text == '' || _passwordController.text == '') { 28 | showMessage(context, "登录信息填写不完整!"); 29 | return; 30 | } else if (!_correctPhone || !_correctPassword) { 31 | showMessage(context, "登录信息的格式不正确!"); 32 | return; 33 | } 34 | showDialog( 35 | context: context, 36 | barrierDismissible: false, 37 | child: new ShowAwait( 38 | _userLogIn(_passwordController.text, _phoneController.text))) 39 | .then((int onValue) { 40 | if (onValue == 0) { 41 | showMessage(context, "这个手机号码没有被注册!"); 42 | } else if (onValue == 1) { 43 | showMessage(context, "手机号码或登陆密码不正确!"); 44 | } else if (onValue == 2) { 45 | Navigator 46 | .of(context) 47 | .push(new MaterialPageRoute(builder: (BuildContext context) { 48 | return new GroupChatList(); 49 | })); 50 | } 51 | }); 52 | } 53 | 54 | Future _userLogIn(String phone, String password) async { 55 | return await reference 56 | .child(_phoneController.text) 57 | .once() 58 | .then((DataSnapshot onValue) { 59 | if (onValue.value != null) { 60 | if (onValue.value["password"] == _passwordController.text) { 61 | _saveLogin( 62 | onValue.value["phone"], 63 | onValue.value["name"], 64 | onValue.value["email"], 65 | onValue.value["portrait"]); 66 | return 2; 67 | } else { 68 | return 1; 69 | } 70 | } else { 71 | return 0; 72 | } 73 | }); 74 | } 75 | 76 | Future _saveLogin(String phone, String name, 77 | String email, String portrait) async { 78 | String dir = (await getApplicationDocumentsDirectory()).path; 79 | await new File('$dir/LandingInformation').writeAsString( 80 | '{"phone":"$phone","name":"$name","email":"$email","portrait":"$portrait"}'); 81 | } 82 | 83 | void _openSignUp() { 84 | setState(() { 85 | Navigator.of(context).push(new MaterialPageRoute>( 86 | builder: (BuildContext context) { 87 | return new SignUp(); 88 | }, 89 | )).then((onValue) { 90 | if (onValue != null) { 91 | _phoneController.text = onValue[0]; 92 | _passwordController.text = onValue[1]; 93 | FocusScope.of(context).requestFocus(new FocusNode()); 94 | _scaffoldKey.currentState.showSnackBar(new SnackBar( 95 | content: new Text("注册成功!"), 96 | )); 97 | } 98 | }); 99 | }); 100 | } 101 | 102 | void _checkInput() { 103 | if (_phoneController.text.isNotEmpty && 104 | (_phoneController.text.trim().length < 7 || 105 | _phoneController.text.trim().length > 12)) { 106 | _correctPhone = false; 107 | } else { 108 | _correctPhone = true; 109 | } 110 | if (_passwordController.text.isNotEmpty && 111 | _passwordController.text.trim().length < 6) { 112 | _correctPassword = false; 113 | } else { 114 | _correctPassword = true; 115 | } 116 | setState(() {}); 117 | } 118 | 119 | @override 120 | Widget build(BuildContext context) { 121 | return new Scaffold( 122 | key: _scaffoldKey, 123 | body: new Stack(children: [ 124 | new GestureDetector( 125 | onTap: () { 126 | FocusScope.of(context).requestFocus(new FocusNode()); 127 | _checkInput(); 128 | }, 129 | child: new Container( 130 | decoration: new BoxDecoration(), 131 | )), 132 | new Column( 133 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 134 | crossAxisAlignment: CrossAxisAlignment.start, 135 | children: [ 136 | new Center( 137 | child: new Image.asset( 138 | 'images/talk_casually.png', 139 | width: MediaQuery.of(context).size.width * 0.4, 140 | )), 141 | new Container( 142 | width: MediaQuery.of(context).size.width * 0.96, 143 | child: new Column( 144 | mainAxisAlignment: MainAxisAlignment.center, 145 | children: [ 146 | new TextField( 147 | controller: _phoneController, 148 | keyboardType: TextInputType.phone, 149 | decoration: new InputDecoration( 150 | hintText: '手机号码', 151 | errorText: _correctPhone ? null : '号码的长度应该在7到12位之间', 152 | icon: new Icon( 153 | Icons.phone, 154 | color: Theme.of(context).iconTheme.color, 155 | ), 156 | ), 157 | onSubmitted: (value) { 158 | _checkInput(); 159 | }, 160 | ), 161 | new TextField( 162 | controller: _passwordController, 163 | obscureText: true, 164 | keyboardType: TextInputType.number, 165 | decoration: new InputDecoration( 166 | hintText: '登陆密码', 167 | errorText: _correctPassword ? null : '密码的长度应该大于6位', 168 | icon: new Icon( 169 | Icons.lock_outline, 170 | color: Theme.of(context).iconTheme.color, 171 | ), 172 | ), 173 | onSubmitted: (value) { 174 | _checkInput(); 175 | }, 176 | ), 177 | ])), 178 | new FlatButton( 179 | child: new Container( 180 | decoration: new BoxDecoration( 181 | color: Theme.of(context).buttonColor, 182 | ), 183 | child: new Center( 184 | child: new Text( 185 | "登 录", 186 | textScaleFactor: 1.1, 187 | style: new TextStyle(color: Theme.of(context).primaryColor), 188 | )), 189 | ), 190 | onPressed: () { 191 | _handleSubmitted(); 192 | }, 193 | ), 194 | new Center( 195 | child: new FlatButton( 196 | child: new Text("没有帐户? 注册"), 197 | onPressed: _openSignUp, 198 | )) 199 | ], 200 | ) 201 | ])); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /lib/add_session.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_database/firebase_database.dart'; 3 | import 'dart:async'; 4 | import 'prompt_wait.dart'; 5 | import 'dart:math'; 6 | 7 | class AddSession extends StatefulWidget { 8 | AddSession(this.myPhone, this.myName, this.myPortrait); 9 | final String myPhone; 10 | final String myName; 11 | final String myPortrait; 12 | 13 | @override 14 | State createState() => new _AddSessionState(myPhone, myPortrait); 15 | } 16 | 17 | class _AddSessionState extends State { 18 | _AddSessionState(this._myPhone, this._myPortrait); 19 | final String _myPhone; 20 | final String _myPortrait; 21 | 22 | final TextEditingController _phoneController = new TextEditingController(); 23 | final usersReference = FirebaseDatabase.instance.reference().child('users'); 24 | final chatsReference = FirebaseDatabase.instance.reference().child('chats'); 25 | String _searchUsername = ""; 26 | String _searchPhone = ""; 27 | String _searchMessages = ""; 28 | String _searchPortrait = ""; 29 | String _errorPrompt = ""; 30 | bool _nullText = true; 31 | 32 | void _handleAppend() { 33 | showDialog( 34 | context: context, 35 | barrierDismissible: false, 36 | child: new ShowAwait(_addSession())).then((int onValue) { 37 | if (onValue == 1 || onValue == 2) { 38 | Navigator.of(context).pop(null); 39 | } else if (onValue == 0) { 40 | Navigator.of(context).pop( 41 | [_searchUsername, _searchPhone, _searchMessages, _searchPortrait]); 42 | } 43 | }); 44 | } 45 | 46 | void _handleFind() { 47 | FocusScope.of(context).requestFocus(new FocusNode()); 48 | if (_phoneController.text.isEmpty) { 49 | setState(() { 50 | _errorPrompt = "手机号码不能为空!"; 51 | _searchUsername = ""; 52 | }); 53 | return; 54 | } else if (_phoneController.text.trim() == widget.myPhone) { 55 | setState(() { 56 | _errorPrompt = "这是你的手机号码哦!"; 57 | _searchUsername = ""; 58 | }); 59 | return; 60 | } else if (_phoneController.text.trim().length < 7 || 61 | _phoneController.text.trim().length > 12) { 62 | setState(() { 63 | _errorPrompt = "手机号码的格式不正确!"; 64 | _searchUsername = ""; 65 | }); 66 | return; 67 | } 68 | showDialog( 69 | context: context, 70 | barrierDismissible: false, 71 | child: new ShowAwait(_findUser(_phoneController.text))) 72 | .then((int onValue) { 73 | if (onValue == 0) { 74 | setState(() { 75 | _errorPrompt = "该用户不存在!"; 76 | _searchUsername = ""; 77 | }); 78 | } else if (onValue == 1) { 79 | setState(() { 80 | _errorPrompt = ""; 81 | }); 82 | } 83 | }); 84 | } 85 | 86 | Future _findUser(String phone) async { 87 | return await usersReference 88 | .child(phone) 89 | .once() 90 | .then((DataSnapshot onValue) { 91 | if (onValue.value != null) { 92 | _searchUsername = onValue.value["name"]; 93 | _searchPhone = onValue.value["phone"]; 94 | _searchPortrait = onValue.value["portrait"]; 95 | return 1; 96 | } else { 97 | return 0; 98 | } 99 | }); 100 | } 101 | 102 | Future _addSession() async { 103 | String time = new DateTime.now().toString(); 104 | int random = new Random().nextInt(100000); 105 | String message = time.split(' ')[0].replaceAll('-', '') + random.toString(); 106 | return await chatsReference 107 | .child('$_myPhone/$_searchPhone') 108 | .once() 109 | .then((DataSnapshot onValue) { 110 | if (onValue.value == null) { 111 | _writeNewSession(time, message); 112 | return 1; 113 | } else { 114 | if (onValue.value["activate"] == "true") { 115 | _searchMessages = onValue.value["messages"]; 116 | return 0; 117 | } else { 118 | _writeNewSession(time, message); 119 | return 2; 120 | } 121 | } 122 | }); 123 | } 124 | 125 | void _writeNewSession(String time, String message) { 126 | chatsReference.child('$_myPhone/$_searchPhone').set({ 127 | "name": _searchUsername, 128 | "phone": _searchPhone, 129 | "messages": "$_myPhone$_searchPhone$message", 130 | "lastMessage": "一起来聊天吧!", 131 | "timestamp": time, 132 | "activate": "true", 133 | "portrait": _searchPortrait 134 | }); 135 | chatsReference.child('$_searchPhone/$_myPhone').set({ 136 | "name": widget.myName, 137 | "phone": _myPhone, 138 | "messages": "$_myPhone$_searchPhone$message", 139 | "lastMessage": "一起来聊天吧!", 140 | "timestamp": time, 141 | "activate": "true", 142 | "portrait": _myPortrait 143 | }); 144 | } 145 | 146 | @override 147 | Widget build(BuildContext context) { 148 | return new SimpleDialog( 149 | title: new Text("添加会话"), 150 | contentPadding: const EdgeInsets.symmetric(horizontal: 23.0), 151 | children: [ 152 | new Container( 153 | child: new Row( 154 | crossAxisAlignment: CrossAxisAlignment.center, 155 | children: [ 156 | new Flexible( 157 | child: new TextField( 158 | controller: _phoneController, 159 | keyboardType: TextInputType.phone, 160 | decoration: 161 | new InputDecoration.collapsed(hintText: '点击此处输入手机号码'), 162 | onChanged: (text) { 163 | if (text == "") { 164 | _nullText = true; 165 | } else { 166 | _nullText = false; 167 | } 168 | setState(() {}); 169 | }, 170 | )), 171 | _nullText 172 | ? new Text("") 173 | : new IconButton( 174 | icon: new Icon(Icons.clear), 175 | onPressed: () { 176 | _phoneController.clear(); 177 | _nullText = true; 178 | _searchUsername = ""; 179 | _errorPrompt = ""; 180 | setState(() {}); 181 | }, 182 | ), 183 | ], 184 | ), 185 | height: 40.0, 186 | ), 187 | new Container( 188 | child: _searchUsername == "" 189 | ? _errorPrompt == "" 190 | ? new Text("") 191 | : new Container( 192 | margin: const EdgeInsets.only(top: 10.0), 193 | child: new Text( 194 | _errorPrompt, 195 | style: new TextStyle(color: Colors.red), 196 | )) 197 | : new Row( 198 | children: [ 199 | new CircleAvatar( 200 | backgroundImage: new NetworkImage(_searchPortrait)), 201 | new Flexible( 202 | child: new Column( 203 | crossAxisAlignment: CrossAxisAlignment.start, 204 | children: [ 205 | new Text( 206 | " " + _searchUsername, 207 | textScaleFactor: 1.2, 208 | overflow: TextOverflow.ellipsis, 209 | ), 210 | new Text(" " + _searchPhone) 211 | ], 212 | )) 213 | ], 214 | ), 215 | height: 40.0, 216 | ), 217 | new Container( 218 | margin: const EdgeInsets.only(top: 19.0, bottom: 23.0), 219 | child: new Row( 220 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 221 | children: [ 222 | new RaisedButton( 223 | elevation: 0.0, 224 | onPressed: () { 225 | Navigator.of(context).pop(); 226 | }, 227 | colorBrightness: Brightness.dark, 228 | color: Theme.of(context).hintColor, 229 | child: new Text('取消'), 230 | ), 231 | new RaisedButton( 232 | elevation: 0.0, 233 | onPressed: 234 | _searchUsername == "" ? _handleFind : _handleAppend, 235 | colorBrightness: Brightness.dark, 236 | child: 237 | _searchUsername == "" ? new Text('查找') : new Text('添加'), 238 | ), 239 | ], 240 | )) 241 | ]); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /lib/personal_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:image_picker/image_picker.dart'; 3 | import 'package:firebase_storage/firebase_storage.dart'; 4 | import 'package:firebase_database/firebase_database.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | import 'dart:io'; 7 | import 'dart:math'; 8 | import 'dart:async'; 9 | import 'prompt_wait.dart'; 10 | import 'image_zoomable.dart'; 11 | 12 | class PersonalData extends StatefulWidget { 13 | PersonalData({this.name, this.email, this.portrait, this.phone}); 14 | final String name; 15 | final String email; 16 | final String portrait; 17 | final String phone; 18 | 19 | @override 20 | State createState() => new _PersonalDataState(name, email, portrait); 21 | } 22 | 23 | class _PersonalDataState extends State { 24 | _PersonalDataState(this._name, this._email, this._portrait); 25 | String _name; 26 | String _email; 27 | String _portrait; 28 | String _newPortrait; 29 | 30 | final usersReference = FirebaseDatabase.instance.reference().child('users'); 31 | final TextEditingController _usernameController = new TextEditingController(); 32 | final TextEditingController _emailController = new TextEditingController(); 33 | bool _editableState = false; 34 | bool _correctUsername = true; 35 | bool _correctEmail = true; 36 | 37 | void _handleSubmitted() { 38 | FocusScope.of(context).requestFocus(new FocusNode()); 39 | _checkInput(); 40 | if (_usernameController.text == '' || 41 | !_correctUsername || 42 | _emailController.text == '' || 43 | !_correctEmail) { 44 | showMessage(context, "资料信息填写不完整!"); 45 | return; 46 | } 47 | _editableState = false; 48 | showDialog( 49 | context: context, 50 | barrierDismissible: false, 51 | child: new ShowAwait(_saveModify())).then((int onValue) { 52 | if (onValue == 1) { 53 | _editableState = false; 54 | setState(() {}); 55 | } else { 56 | print("暂时没有处理!"); 57 | } 58 | }); 59 | setState(() {}); 60 | } 61 | 62 | Future _saveModify() async { 63 | if (_usernameController.text.trim() != _name) { 64 | _name = _usernameController.text.trim(); 65 | await usersReference 66 | .child('${widget.phone}/name') 67 | .set(_usernameController.text.trim()); 68 | } 69 | if (_emailController.text.trim() != _email) { 70 | _email = _emailController.text.trim(); 71 | await usersReference 72 | .child('${widget.phone}/email') 73 | .set(_emailController.text.trim()); 74 | } 75 | if (_newPortrait != _portrait) { 76 | _portrait = _newPortrait; 77 | await usersReference.child('${widget.phone}/portrait').set(_newPortrait); 78 | } 79 | _saveLogin(widget.phone, _name, _email, _newPortrait); 80 | return 1; 81 | } 82 | 83 | Future _saveLogin( 84 | String phone, String name, String email, String portrait) async { 85 | String dir = (await getApplicationDocumentsDirectory()).path; 86 | await new File('$dir/LandingInformation').writeAsString( 87 | '{"phone":"$phone","name":"$name","email":"$email","portrait":"$portrait"}'); 88 | } 89 | 90 | void _checkInput() { 91 | if (_usernameController.text.isNotEmpty && 92 | _usernameController.text.trim().length < 2) { 93 | _correctUsername = false; 94 | } else { 95 | _correctUsername = true; 96 | } 97 | if (_emailController.text.isNotEmpty && 98 | _emailController.text.trim().length < 3) { 99 | _correctEmail = false; 100 | } else { 101 | _correctEmail = true; 102 | } 103 | setState(() {}); 104 | } 105 | 106 | @override 107 | Widget build(BuildContext context) { 108 | return new Scaffold( 109 | appBar: new AppBar( 110 | title: new Text("个人资料"), 111 | centerTitle: true, 112 | elevation: 0.0, 113 | leading: _editableState 114 | ? new IconButton( 115 | icon: new Icon(Icons.undo), 116 | onPressed: () { 117 | _editableState = false; 118 | setState(() {}); 119 | }) 120 | : null, 121 | actions: [ 122 | new IconButton( 123 | icon: new Icon(_editableState ? Icons.save : Icons.create), 124 | onPressed: () { 125 | if (!_editableState) { 126 | _editableState = true; 127 | _usernameController.text = _name; 128 | _emailController.text = _email; 129 | _newPortrait = _portrait; 130 | _correctUsername = true; 131 | _correctEmail = true; 132 | setState(() {}); 133 | } else { 134 | _handleSubmitted(); 135 | } 136 | }) 137 | ], 138 | ), 139 | body: new Stack(children: [ 140 | new GestureDetector( 141 | onTap: () { 142 | FocusScope.of(context).requestFocus(new FocusNode()); 143 | _checkInput(); 144 | }, 145 | child: new Container( 146 | decoration: new BoxDecoration(), 147 | )), 148 | new Row( 149 | mainAxisAlignment: MainAxisAlignment.center, 150 | children: [ 151 | new Column( 152 | children: [ 153 | new Container( 154 | margin: const EdgeInsets.only(top: 30.0), 155 | child: _editableState 156 | ? new Stack(children: [ 157 | new Opacity( 158 | opacity: 0.4, 159 | child: new CircleAvatar( 160 | backgroundImage: new NetworkImage(_newPortrait), 161 | radius: 50.0, 162 | )), 163 | new GestureDetector( 164 | onTap: () async { 165 | File imageFile = await ImagePicker.pickImage(); 166 | int random = new Random().nextInt(100000); 167 | StorageReference ref = FirebaseStorage.instance 168 | .ref() 169 | .child( 170 | "custom-portraits/portrait_$random.jpg"); 171 | StorageUploadTask uploadTask = ref.put(imageFile); 172 | Uri downloadUrl = 173 | (await uploadTask.future).downloadUrl; 174 | setState(() { 175 | _newPortrait = downloadUrl.toString(); 176 | }); 177 | }, 178 | child: new Container( 179 | padding: const EdgeInsets.only( 180 | top: 30.0, left: 30.0), 181 | child: new Icon( 182 | Icons.add_a_photo, 183 | size: 40.0, 184 | )), 185 | ) 186 | ]) 187 | : new GestureDetector( 188 | onTap: () { 189 | Navigator.of(context).push( 190 | new MaterialPageRoute( 191 | builder: (BuildContext context) { 192 | return new ImageZoomable( 193 | new NetworkImage(_portrait), 194 | onTap: () { 195 | Navigator.of(context).pop(); 196 | }, 197 | ); 198 | })); 199 | }, 200 | child: new CircleAvatar( 201 | backgroundImage: new NetworkImage(_portrait), 202 | radius: 50.0, 203 | )), 204 | ), 205 | _editableState 206 | ? new Container( 207 | margin: const EdgeInsets.only(top: 10.0), 208 | width: MediaQuery.of(context).size.width * 0.7, 209 | child: new TextField( 210 | controller: _usernameController, 211 | decoration: new InputDecoration( 212 | hintText: "用户名称", 213 | errorText: _correctUsername ? null : '名称的长度应该大于2位', 214 | ), 215 | onSubmitted: (value) { 216 | _checkInput(); 217 | }, 218 | ), 219 | ) 220 | : new Container( 221 | padding: const EdgeInsets.only(top: 20.0, bottom: 10.0), 222 | child: new Text(_name, textScaleFactor: 1.4), 223 | ), 224 | _editableState 225 | ? new Container( 226 | width: MediaQuery.of(context).size.width * 0.7, 227 | child: new TextField( 228 | controller: _emailController, 229 | decoration: new InputDecoration( 230 | hintText: "电子邮箱", 231 | errorText: _correctEmail ? null : '邮箱格式不正确', 232 | ), 233 | onSubmitted: (value) { 234 | _checkInput(); 235 | }, 236 | ), 237 | ) 238 | : new Text(_email, textScaleFactor: 1.1), 239 | ], 240 | ), 241 | ], 242 | ) 243 | ]), 244 | ); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /lib/sign_up.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_database/firebase_database.dart'; 3 | import 'dart:async'; 4 | import 'prompt_wait.dart'; 5 | import 'dart:math'; 6 | 7 | class SignUp extends StatefulWidget { 8 | @override 9 | State createState() => new SignUpState(); 10 | } 11 | 12 | class SignUpState extends State { 13 | final TextEditingController _usernameController = new TextEditingController(); 14 | final TextEditingController _passwordController = new TextEditingController(); 15 | final TextEditingController _emailController = new TextEditingController(); 16 | final TextEditingController _phoneController = new TextEditingController(); 17 | final reference = FirebaseDatabase.instance.reference().child('users'); 18 | bool _correctPhone = true; 19 | bool _correctUsername = true; 20 | bool _correctPassword = true; 21 | 22 | List defaultAvatarList = [ 23 | "portrait-1.png?alt=media&token=ba15c433-0fdd-46fc-a912-05e5a2bbfa47", 24 | "portrait-2.png?alt=media&token=3e561a09-e19b-47f9-95dc-a89787c3f3a7", 25 | "portrait-3.png?alt=media&token=e2728607-ed37-415c-baa6-1bf10ba64d0d", 26 | "portrait-4.png?alt=media&token=d9b130a2-1b33-4728-b356-e7d819550552", 27 | "portrait-5.png?alt=media&token=5ee20a07-6c95-4d1d-b8b4-5dca5642029f", 28 | "portrait-6.png?alt=media&token=5139443d-48a9-46f8-9356-bb4a02a5f3cf", 29 | "portrait-7.png?alt=media&token=908993dc-b591-46af-a680-c01ed0499332", 30 | "portrait-8.png?alt=media&token=89bd3d6d-8a7f-4249-b0da-a94f49012de7", 31 | "portrait-9.png?alt=media&token=40660b58-f6ca-4eda-a97a-fd6c749cf55c", 32 | "portrait-10.png?alt=media&token=13c34b46-834a-485c-afea-4cfe49753021", 33 | "portrait-11.png?alt=media&token=09906f02-ea1b-4437-960f-1a39d695e5a1", 34 | "portrait-12.png?alt=media&token=c42a5146-28ff-4097-9b36-e93937edc536", 35 | "portrait-13.png?alt=media&token=46b3aa5b-ffab-4568-af52-3baea450d99c", 36 | "portrait-14.png?alt=media&token=68b635fb-693d-4c96-a595-f66c70cf2be0", 37 | "portrait-15.png?alt=media&token=9fe05ee8-946d-4201-9b96-f06cf50b038f", 38 | "portrait-16.png?alt=media&token=8bb49cf9-01ec-436d-8d8c-af43e8bcaab3", 39 | "portrait-17.png?alt=media&token=f049e2ea-03e4-4357-8597-3adc2fc4189f", 40 | "portrait-18.png?alt=media&token=67cea20b-2bb0-42e2-923c-dc9eaf4fa125", 41 | ]; 42 | 43 | void _handleSubmitted() { 44 | FocusScope.of(context).requestFocus(new FocusNode()); 45 | _checkInput(); 46 | if (_usernameController.text == '' || _passwordController.text == '') { 47 | showMessage(context, "注册信息填写不完整!"); 48 | return; 49 | } else if (!_correctUsername || !_correctPassword || !_correctPhone) { 50 | showMessage(context, "注册信息的格式不正确!"); 51 | return; 52 | } 53 | showDialog( 54 | context: context, 55 | barrierDismissible: false, 56 | child: new ShowAwait(_userLogUp( 57 | _usernameController.text, _passwordController.text, 58 | email: _emailController.text, 59 | phone: _phoneController.text))).then((int onValue) { 60 | if (onValue == 0) { 61 | showMessage(context, "这个号码已经被注册!"); 62 | } else if (onValue == 1) { 63 | Navigator 64 | .of(context) 65 | .pop([_phoneController.text, _passwordController.text]); 66 | } 67 | }); 68 | } 69 | 70 | Future _userLogUp(String username, String password, 71 | {String email, String phone}) async { 72 | return await reference 73 | .child(_phoneController.text) 74 | .once() 75 | .then((DataSnapshot onValue) { 76 | if (onValue.value == null) { 77 | int random = new Random().nextInt(17); 78 | reference.child(phone).set({ 79 | 'name': username, 80 | 'password': password, 81 | 'email': email, 82 | 'phone': phone, 83 | 'portrait': 84 | "https://firebasestorage.googleapis.com/v0/b/talk-casually-app.appspot.com/o/portraits%2F" + 85 | defaultAvatarList[random], 86 | }); 87 | return 1; 88 | } else { 89 | return 0; 90 | } 91 | }); 92 | } 93 | 94 | void _checkInput() { 95 | if (_phoneController.text.isNotEmpty && 96 | (_phoneController.text.trim().length < 7 || 97 | _phoneController.text.trim().length > 12)) { 98 | _correctPhone = false; 99 | } else { 100 | _correctPhone = true; 101 | } 102 | if (_usernameController.text.isNotEmpty && 103 | _usernameController.text.trim().length < 2) { 104 | _correctUsername = false; 105 | } else { 106 | _correctUsername = true; 107 | } 108 | if (_passwordController.text.isNotEmpty && 109 | _passwordController.text.trim().length < 6) { 110 | _correctPassword = false; 111 | } else { 112 | _correctPassword = true; 113 | } 114 | setState(() {}); 115 | } 116 | 117 | @override 118 | Widget build(BuildContext context) { 119 | return new Scaffold( 120 | body: new Stack(children: [ 121 | new GestureDetector( 122 | onTap: () { 123 | FocusScope.of(context).requestFocus(new FocusNode()); 124 | _checkInput(); 125 | }, 126 | child: new Container( 127 | decoration: new BoxDecoration(), 128 | )), 129 | new Column( 130 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 131 | crossAxisAlignment: CrossAxisAlignment.start, 132 | children: [ 133 | new BackButton(), 134 | new Text( 135 | " 注册账户", 136 | textScaleFactor: 2.0, 137 | ), 138 | new Container( 139 | width: MediaQuery.of(context).size.width * 0.96, 140 | child: new Column( 141 | mainAxisAlignment: MainAxisAlignment.center, 142 | children: [ 143 | new TextField( 144 | controller: _phoneController, 145 | keyboardType: TextInputType.phone, 146 | decoration: new InputDecoration( 147 | helperText: '账号的唯一标识', 148 | hintText: '手机号码', 149 | errorText: _correctPhone ? null : '号码的长度应该在7到12位之间', 150 | icon: new Icon( 151 | Icons.phone, 152 | color: Theme.of(context).iconTheme.color, 153 | ), 154 | ), 155 | onSubmitted: (value) { 156 | _checkInput(); 157 | }, 158 | ), 159 | new TextField( 160 | controller: _usernameController, 161 | decoration: new InputDecoration( 162 | helperText: "应该怎么称呼你", 163 | hintText: '用户名称', 164 | errorText: _correctUsername ? null : '名称的长度应该大于2位', 165 | icon: new Icon( 166 | Icons.account_circle, 167 | color: Theme.of(context).iconTheme.color, 168 | ), 169 | ), 170 | onSubmitted: (value) { 171 | _checkInput(); 172 | }, 173 | ), 174 | new TextField( 175 | controller: _passwordController, 176 | obscureText: true, 177 | keyboardType: TextInputType.number, 178 | decoration: new InputDecoration( 179 | helperText: '账户的登陆密码', 180 | hintText: '登陆密码', 181 | errorText: _correctPassword ? null : '密码的长度应该大于6位', 182 | icon: new Icon( 183 | Icons.lock_outline, 184 | color: Theme.of(context).iconTheme.color, 185 | ), 186 | ), 187 | onSubmitted: (value) { 188 | _checkInput(); 189 | }, 190 | ), 191 | new TextField( 192 | controller: _emailController, 193 | keyboardType: TextInputType.emailAddress, 194 | decoration: new InputDecoration( 195 | helperText: '方便我们联系你(选填)', 196 | hintText: '电子邮箱', 197 | icon: new Icon( 198 | Icons.email, 199 | color: Theme.of(context).iconTheme.color, 200 | ), 201 | ), 202 | onSubmitted: (value) { 203 | _checkInput(); 204 | }, 205 | ), 206 | ])), 207 | new FlatButton( 208 | child: new Container( 209 | width: MediaQuery.of(context).size.width, 210 | decoration: new BoxDecoration( 211 | color: Theme.of(context).accentColor, 212 | ), 213 | child: new Center( 214 | child: new Text( 215 | "提 交", 216 | textScaleFactor: 1.1, 217 | style: new TextStyle(color: Theme.of(context).primaryColor), 218 | )), 219 | ), 220 | onPressed: () { 221 | _handleSubmitted(); 222 | }, 223 | ), 224 | new Center( 225 | child: new FlatButton( 226 | child: new Text("已经有账户了? 登录"), 227 | onPressed: () { 228 | Navigator.of(context).pop(); 229 | }, 230 | )) 231 | ]) 232 | ])); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /lib/chat_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:firebase_database/firebase_database.dart'; 4 | import 'package:firebase_database/ui/firebase_animated_list.dart'; 5 | import 'package:firebase_storage/firebase_storage.dart'; 6 | import 'package:image_picker/image_picker.dart'; 7 | import 'dart:async'; 8 | import 'dart:io'; 9 | import 'dart:math'; 10 | import 'image_zoomable.dart'; 11 | 12 | class ChatScreen extends StatefulWidget { 13 | ChatScreen({ 14 | this.messages, 15 | this.myName, 16 | this.sheName, 17 | this.myPhone, 18 | this.shePhone, 19 | this.shePortrait, 20 | this.myPortrait, 21 | }); 22 | final String messages; 23 | final String myName; 24 | final String sheName; 25 | final String myPhone; 26 | final String shePhone; 27 | final String shePortrait; 28 | final String myPortrait; 29 | 30 | @override 31 | State createState() => new ChatScreenState(messages); 32 | } 33 | 34 | class ChatScreenState extends State { 35 | ChatScreenState(this._messages); 36 | final String _messages; 37 | 38 | static final GlobalKey _scaffoldKey = 39 | new GlobalKey(); 40 | final TextEditingController _textController = new TextEditingController(); 41 | final chatsReference = FirebaseDatabase.instance.reference().child('chats'); 42 | final messagesReference = 43 | FirebaseDatabase.instance.reference().child('messages'); 44 | bool _isComposing = false; 45 | 46 | Future _handleSubmitted(String text) async { 47 | chatsReference 48 | .child('${widget.myPhone}/${widget.shePhone}/activate') 49 | .onValue 50 | .listen((Event event) { 51 | if (event.snapshot.value == "false") { 52 | _scaffoldKey.currentState.showSnackBar(new SnackBar( 53 | content: new Text("会话已经被删除了哦!"), 54 | )); 55 | } else { 56 | if (text.trim() == "") return; 57 | _textController.clear(); 58 | _isComposing = false; 59 | _sendMessage(text: text); 60 | } 61 | }); 62 | } 63 | 64 | void _sendMessage({String text, String imageUrl}) { 65 | String time = new DateTime.now().toString(); 66 | messagesReference.child(_messages).push().set({ 67 | 'text': text, 68 | 'imageUrl': imageUrl, 69 | 'senderName': widget.myName, 70 | 'timestamp': time 71 | }); 72 | chatsReference 73 | .child('${widget.shePhone}/${widget.myPhone}/lastMessage') 74 | .set(text); 75 | chatsReference 76 | .child('${widget.shePhone}/${widget.myPhone}/timestamp') 77 | .set(time); 78 | chatsReference 79 | .child('${widget.myPhone}/${widget.shePhone}/lastMessage') 80 | .set(text); 81 | chatsReference 82 | .child('${widget.myPhone}/${widget.shePhone}/timestamp') 83 | .set(time); 84 | } 85 | 86 | Widget _buildTextComposer() { 87 | return new IconTheme( 88 | data: new IconThemeData(color: Theme.of(context).accentColor), 89 | child: new Container( 90 | margin: const EdgeInsets.symmetric(horizontal: 8.0), 91 | child: new Row(children: [ 92 | new Container( 93 | margin: new EdgeInsets.symmetric(horizontal: 4.0), 94 | child: new IconButton( 95 | icon: new Icon(Icons.photo_camera), 96 | onPressed: () async { 97 | File imageFile = await ImagePicker.pickImage(); 98 | int random = new Random().nextInt(100000); 99 | _scaffoldKey.currentState.showSnackBar(new SnackBar( 100 | content: new Text("上传原图中〜请稍候!"), 101 | )); 102 | StorageReference ref = FirebaseStorage.instance 103 | .ref() 104 | .child("sessions/$_messages/image_$random.jpg"); 105 | StorageUploadTask uploadTask = ref.put(imageFile); 106 | Uri downloadUrl = (await uploadTask.future).downloadUrl; 107 | _sendMessage( 108 | text: "[图片]", imageUrl: downloadUrl.toString()); 109 | }), 110 | ), 111 | new Flexible( 112 | child: new TextField( 113 | controller: _textController, 114 | onChanged: (String text) { 115 | setState(() { 116 | _isComposing = text.length > 0; 117 | }); 118 | }, 119 | onSubmitted: _handleSubmitted, 120 | decoration: new InputDecoration.collapsed(hintText: '发送消息'), 121 | )), 122 | new Container( 123 | margin: new EdgeInsets.symmetric(horizontal: 4.0), 124 | child: new IconButton( 125 | icon: new Icon(Icons.send), 126 | onPressed: _isComposing 127 | ? () => _handleSubmitted(_textController.text) 128 | : null), 129 | ) 130 | ]))); 131 | } 132 | 133 | @override 134 | Widget build(BuildContext context) { 135 | return new Scaffold( 136 | key: _scaffoldKey, 137 | appBar: new AppBar( 138 | title: new Text(widget.sheName), 139 | centerTitle: true, 140 | elevation: 1.0, 141 | actions: [ 142 | new PopupMenuButton( 143 | onSelected: (String value) { 144 | if (value == "delete") { 145 | chatsReference 146 | .child('${widget.shePhone}/${widget.myPhone}/activate') 147 | .set("false"); 148 | chatsReference 149 | .child('${widget.myPhone}/${widget.shePhone}/activate') 150 | .set("false"); 151 | _scaffoldKey.currentState.showSnackBar(new SnackBar( 152 | content: new Text("删除成功!"), 153 | )); 154 | } 155 | }, 156 | itemBuilder: (BuildContext context) => >[ 157 | new PopupMenuItem( 158 | value: "delete", child: new Text('删除会话')), 159 | ]) 160 | ]), 161 | body: new Stack(children: [ 162 | new GestureDetector( 163 | onTap: () { 164 | FocusScope.of(context).requestFocus(new FocusNode()); 165 | }, 166 | child: new Container( 167 | decoration: new BoxDecoration(), 168 | )), 169 | new Column( 170 | children: [ 171 | new Flexible( 172 | child: new FirebaseAnimatedList( 173 | query: messagesReference.child(_messages), 174 | sort: (a, b) => b.key.compareTo(a.key), 175 | padding: new EdgeInsets.all(8.0), 176 | reverse: true, 177 | itemBuilder: (_, DataSnapshot snapshot, 178 | Animation animation) { 179 | return new ChatMessage( 180 | snapshot: snapshot, 181 | animation: animation, 182 | myName: widget.myName, 183 | shePortrait: widget.shePortrait, 184 | myPortrait: widget.myPortrait, 185 | ); 186 | })), 187 | new Divider(height: 1.0), 188 | new Container( 189 | decoration: new BoxDecoration( 190 | color: Theme.of(context).cardColor, 191 | ), 192 | child: _buildTextComposer(), 193 | ) 194 | ], 195 | ) 196 | ]), 197 | ); 198 | } 199 | } 200 | 201 | class ChatMessage extends StatelessWidget { 202 | ChatMessage( 203 | {this.snapshot, 204 | this.animation, 205 | this.myName, 206 | this.shePortrait, 207 | this.myPortrait}); 208 | final DataSnapshot snapshot; 209 | final Animation animation; 210 | final String myName; 211 | final String shePortrait; 212 | final String myPortrait; 213 | 214 | @override 215 | Widget build(BuildContext context) { 216 | Widget _sheSessionStyle() { 217 | return new Row( 218 | crossAxisAlignment: CrossAxisAlignment.start, 219 | children: [ 220 | new Container( 221 | margin: const EdgeInsets.only(right: 16.0), 222 | child: new CircleAvatar( 223 | backgroundImage: new NetworkImage(shePortrait)), 224 | ), 225 | new Flexible( 226 | child: new Column( 227 | crossAxisAlignment: CrossAxisAlignment.start, 228 | children: [ 229 | new Text(snapshot.value['senderName'], 230 | style: Theme.of(context).textTheme.subhead), 231 | new Container( 232 | margin: const EdgeInsets.only(top: 5.0), 233 | child: snapshot.value['imageUrl'] != null 234 | ? new GestureDetector( 235 | onTap: () { 236 | Navigator.of(context).push( 237 | new MaterialPageRoute( 238 | builder: (BuildContext context) { 239 | return new ImageZoomable( 240 | new NetworkImage(snapshot.value['imageUrl']), 241 | onTap: () { 242 | Navigator.of(context).pop(); 243 | }, 244 | ); 245 | })); 246 | }, 247 | child: new Image.network( 248 | snapshot.value['imageUrl'], 249 | width: 150.0, 250 | ), 251 | ) 252 | : new Text(snapshot.value['text']), 253 | ) 254 | ])), 255 | ]); 256 | } 257 | 258 | Widget _mySessionStyle() { 259 | return new Row( 260 | crossAxisAlignment: CrossAxisAlignment.start, 261 | mainAxisAlignment: MainAxisAlignment.end, 262 | children: [ 263 | new Flexible( 264 | child: new Column( 265 | crossAxisAlignment: CrossAxisAlignment.end, 266 | children: [ 267 | new Text(snapshot.value['senderName'], 268 | style: Theme.of(context).textTheme.subhead), 269 | new Container( 270 | margin: const EdgeInsets.only(top: 5.0), 271 | child: snapshot.value['imageUrl'] != null 272 | ? new GestureDetector( 273 | onTap: () { 274 | Navigator.of(context).push( 275 | new MaterialPageRoute( 276 | builder: (BuildContext context) { 277 | return new ImageZoomable( 278 | new NetworkImage(snapshot.value['imageUrl']), 279 | onTap: () { 280 | Navigator.of(context).pop(); 281 | }, 282 | ); 283 | })); 284 | }, 285 | child: new Image.network( 286 | snapshot.value['imageUrl'], 287 | width: 150.0, 288 | ), 289 | ) 290 | : new Text(snapshot.value['text']), 291 | ) 292 | ])), 293 | new Container( 294 | margin: const EdgeInsets.only(left: 16.0), 295 | child: new CircleAvatar( 296 | backgroundImage: new NetworkImage(myPortrait)), 297 | ), 298 | ]); 299 | } 300 | 301 | return new SizeTransition( 302 | sizeFactor: 303 | new CurvedAnimation(parent: animation, curve: Curves.easeOut), 304 | axisAlignment: 0.0, 305 | child: new Container( 306 | margin: const EdgeInsets.symmetric(vertical: 10.0), 307 | child: myName == snapshot.value['senderName'] 308 | ? _mySessionStyle() 309 | : _sheSessionStyle(), 310 | )); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 2BCFC1D85B907C52542855DB /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E830CC999583567B7703E22 /* libPods-Runner.a */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 3C3648A51F4890F700457795 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3C3648A41F4890F700457795 /* GoogleService-Info.plist */; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; }; 19 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 20 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 21 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 22 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 23 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = ""; 31 | dstSubfolderSpec = 10; 32 | files = ( 33 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 34 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 35 | ); 36 | name = "Embed Frameworks"; 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXCopyFilesBuildPhase section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 0E830CC999583567B7703E22 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 46 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 47 | 3C3648A41F4890F700457795 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 48 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 49 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 50 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = ""; }; 54 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 55 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 57 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 58 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 59 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 60 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 69 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 70 | 2BCFC1D85B907C52542855DB /* libPods-Runner.a in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 9740EEB11CF90186004384FC /* Flutter */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 9740EEB71CF902C7004384FC /* app.flx */, 81 | 3B80C3931E831B6300D905FE /* App.framework */, 82 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 83 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 84 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 85 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 86 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 87 | ); 88 | name = Flutter; 89 | sourceTree = ""; 90 | }; 91 | 97C146E51CF9000F007C117D = { 92 | isa = PBXGroup; 93 | children = ( 94 | 9740EEB11CF90186004384FC /* Flutter */, 95 | 97C146F01CF9000F007C117D /* Runner */, 96 | 97C146EF1CF9000F007C117D /* Products */, 97 | E0009E0B8D195E6466DB74F7 /* Pods */, 98 | F65153AEC1DE2718BDE24E04 /* Frameworks */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | 97C146EF1CF9000F007C117D /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 97C146EE1CF9000F007C117D /* Runner.app */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | 97C146F01CF9000F007C117D /* Runner */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 114 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 115 | 3C3648A41F4890F700457795 /* GoogleService-Info.plist */, 116 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 117 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 118 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 119 | 97C147021CF9000F007C117D /* Info.plist */, 120 | 97C146F11CF9000F007C117D /* Supporting Files */, 121 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 122 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 123 | ); 124 | path = Runner; 125 | sourceTree = ""; 126 | }; 127 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 97C146F21CF9000F007C117D /* main.m */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | E0009E0B8D195E6466DB74F7 /* Pods */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | ); 139 | name = Pods; 140 | sourceTree = ""; 141 | }; 142 | F65153AEC1DE2718BDE24E04 /* Frameworks */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 0E830CC999583567B7703E22 /* libPods-Runner.a */, 146 | ); 147 | name = Frameworks; 148 | sourceTree = ""; 149 | }; 150 | /* End PBXGroup section */ 151 | 152 | /* Begin PBXNativeTarget section */ 153 | 97C146ED1CF9000F007C117D /* Runner */ = { 154 | isa = PBXNativeTarget; 155 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 156 | buildPhases = ( 157 | 4185047736D206029BC3A10E /* [CP] Check Pods Manifest.lock */, 158 | 9740EEB61CF901F6004384FC /* Run Script */, 159 | 97C146EA1CF9000F007C117D /* Sources */, 160 | 97C146EB1CF9000F007C117D /* Frameworks */, 161 | 97C146EC1CF9000F007C117D /* Resources */, 162 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 163 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 164 | FFD94C01EF7BE5782CEC57B8 /* [CP] Embed Pods Frameworks */, 165 | A268D1D91557D2CEE0F92B73 /* [CP] Copy Pods Resources */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | ); 171 | name = Runner; 172 | productName = Runner; 173 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 174 | productType = "com.apple.product-type.application"; 175 | }; 176 | /* End PBXNativeTarget section */ 177 | 178 | /* Begin PBXProject section */ 179 | 97C146E61CF9000F007C117D /* Project object */ = { 180 | isa = PBXProject; 181 | attributes = { 182 | LastUpgradeCheck = 0830; 183 | ORGANIZATIONNAME = "The Chromium Authors"; 184 | TargetAttributes = { 185 | 97C146ED1CF9000F007C117D = { 186 | CreatedOnToolsVersion = 7.3.1; 187 | DevelopmentTeam = 9BQNL96S8U; 188 | ProvisioningStyle = Automatic; 189 | }; 190 | }; 191 | }; 192 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 193 | compatibilityVersion = "Xcode 3.2"; 194 | developmentRegion = English; 195 | hasScannedForEncodings = 0; 196 | knownRegions = ( 197 | en, 198 | Base, 199 | ); 200 | mainGroup = 97C146E51CF9000F007C117D; 201 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 202 | projectDirPath = ""; 203 | projectRoot = ""; 204 | targets = ( 205 | 97C146ED1CF9000F007C117D /* Runner */, 206 | ); 207 | }; 208 | /* End PBXProject section */ 209 | 210 | /* Begin PBXResourcesBuildPhase section */ 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 9740EEBB1CF902C7004384FC /* app.flx in Resources */, 216 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 217 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 218 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 219 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 220 | 3C3648A51F4890F700457795 /* GoogleService-Info.plist in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXResourcesBuildPhase section */ 225 | 226 | /* Begin PBXShellScriptBuildPhase section */ 227 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 228 | isa = PBXShellScriptBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | inputPaths = ( 233 | ); 234 | name = "Thin Binary"; 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 240 | }; 241 | 4185047736D206029BC3A10E /* [CP] Check Pods Manifest.lock */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | ); 246 | inputPaths = ( 247 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 248 | "${PODS_ROOT}/Manifest.lock", 249 | ); 250 | name = "[CP] Check Pods Manifest.lock"; 251 | outputPaths = ( 252 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | shellPath = /bin/sh; 256 | 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"; 257 | showEnvVarsInLog = 0; 258 | }; 259 | 9740EEB61CF901F6004384FC /* Run Script */ = { 260 | isa = PBXShellScriptBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | inputPaths = ( 265 | ); 266 | name = "Run Script"; 267 | outputPaths = ( 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | shellPath = /bin/sh; 271 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 272 | }; 273 | A268D1D91557D2CEE0F92B73 /* [CP] Copy Pods Resources */ = { 274 | isa = PBXShellScriptBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | ); 278 | inputPaths = ( 279 | ); 280 | name = "[CP] Copy Pods Resources"; 281 | outputPaths = ( 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | shellPath = /bin/sh; 285 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; 286 | showEnvVarsInLog = 0; 287 | }; 288 | FFD94C01EF7BE5782CEC57B8 /* [CP] Embed Pods Frameworks */ = { 289 | isa = PBXShellScriptBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | ); 293 | inputPaths = ( 294 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 295 | "${PODS_ROOT}/../../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework", 296 | ); 297 | name = "[CP] Embed Pods Frameworks"; 298 | outputPaths = ( 299 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | shellPath = /bin/sh; 303 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 304 | showEnvVarsInLog = 0; 305 | }; 306 | /* End PBXShellScriptBuildPhase section */ 307 | 308 | /* Begin PBXSourcesBuildPhase section */ 309 | 97C146EA1CF9000F007C117D /* Sources */ = { 310 | isa = PBXSourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 314 | 97C146F31CF9000F007C117D /* main.m in Sources */, 315 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXSourcesBuildPhase section */ 320 | 321 | /* Begin PBXVariantGroup section */ 322 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 97C146FB1CF9000F007C117D /* Base */, 326 | ); 327 | name = Main.storyboard; 328 | sourceTree = ""; 329 | }; 330 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 331 | isa = PBXVariantGroup; 332 | children = ( 333 | 97C147001CF9000F007C117D /* Base */, 334 | ); 335 | name = LaunchScreen.storyboard; 336 | sourceTree = ""; 337 | }; 338 | /* End PBXVariantGroup section */ 339 | 340 | /* Begin XCBuildConfiguration section */ 341 | 97C147031CF9000F007C117D /* Debug */ = { 342 | isa = XCBuildConfiguration; 343 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_ANALYZER_NONNULL = YES; 347 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 348 | CLANG_CXX_LIBRARY = "libc++"; 349 | CLANG_ENABLE_MODULES = YES; 350 | CLANG_ENABLE_OBJC_ARC = YES; 351 | CLANG_WARN_BOOL_CONVERSION = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 354 | CLANG_WARN_EMPTY_BODY = YES; 355 | CLANG_WARN_ENUM_CONVERSION = YES; 356 | CLANG_WARN_INFINITE_RECURSION = YES; 357 | CLANG_WARN_INT_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 360 | CLANG_WARN_UNREACHABLE_CODE = YES; 361 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 363 | COPY_PHASE_STRIP = NO; 364 | DEBUG_INFORMATION_FORMAT = dwarf; 365 | ENABLE_STRICT_OBJC_MSGSEND = YES; 366 | ENABLE_TESTABILITY = YES; 367 | GCC_C_LANGUAGE_STANDARD = gnu99; 368 | GCC_DYNAMIC_NO_PIC = NO; 369 | GCC_NO_COMMON_BLOCKS = YES; 370 | GCC_OPTIMIZATION_LEVEL = 0; 371 | GCC_PREPROCESSOR_DEFINITIONS = ( 372 | "DEBUG=1", 373 | "$(inherited)", 374 | ); 375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 377 | GCC_WARN_UNDECLARED_SELECTOR = YES; 378 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 379 | GCC_WARN_UNUSED_FUNCTION = YES; 380 | GCC_WARN_UNUSED_VARIABLE = YES; 381 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 382 | MTL_ENABLE_DEBUG_INFO = YES; 383 | ONLY_ACTIVE_ARCH = YES; 384 | SDKROOT = iphoneos; 385 | TARGETED_DEVICE_FAMILY = "1,2"; 386 | }; 387 | name = Debug; 388 | }; 389 | 97C147041CF9000F007C117D /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 392 | buildSettings = { 393 | ALWAYS_SEARCH_USER_PATHS = NO; 394 | CLANG_ANALYZER_NONNULL = YES; 395 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 396 | CLANG_CXX_LIBRARY = "libc++"; 397 | CLANG_ENABLE_MODULES = YES; 398 | CLANG_ENABLE_OBJC_ARC = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_CONSTANT_CONVERSION = YES; 401 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 402 | CLANG_WARN_EMPTY_BODY = YES; 403 | CLANG_WARN_ENUM_CONVERSION = YES; 404 | CLANG_WARN_INFINITE_RECURSION = YES; 405 | CLANG_WARN_INT_CONVERSION = YES; 406 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 407 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 411 | COPY_PHASE_STRIP = NO; 412 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 413 | ENABLE_NS_ASSERTIONS = NO; 414 | ENABLE_STRICT_OBJC_MSGSEND = YES; 415 | GCC_C_LANGUAGE_STANDARD = gnu99; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 418 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 419 | GCC_WARN_UNDECLARED_SELECTOR = YES; 420 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 421 | GCC_WARN_UNUSED_FUNCTION = YES; 422 | GCC_WARN_UNUSED_VARIABLE = YES; 423 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 424 | MTL_ENABLE_DEBUG_INFO = NO; 425 | SDKROOT = iphoneos; 426 | TARGETED_DEVICE_FAMILY = "1,2"; 427 | VALIDATE_PRODUCT = YES; 428 | }; 429 | name = Release; 430 | }; 431 | 97C147061CF9000F007C117D /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 434 | buildSettings = { 435 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 436 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 437 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 438 | DEVELOPMENT_TEAM = 9BQNL96S8U; 439 | ENABLE_BITCODE = NO; 440 | FRAMEWORK_SEARCH_PATHS = ( 441 | "$(inherited)", 442 | "$(PROJECT_DIR)/Flutter", 443 | ); 444 | INFOPLIST_FILE = Runner/Info.plist; 445 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 446 | LIBRARY_SEARCH_PATHS = ( 447 | "$(inherited)", 448 | "$(PROJECT_DIR)/Flutter", 449 | ); 450 | PRODUCT_BUNDLE_IDENTIFIER = com.company.talkCasually; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | PROVISIONING_PROFILE_SPECIFIER = ""; 453 | TARGETED_DEVICE_FAMILY = "1,2"; 454 | }; 455 | name = Debug; 456 | }; 457 | 97C147071CF9000F007C117D /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 460 | buildSettings = { 461 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 462 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 463 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 464 | DEVELOPMENT_TEAM = 9BQNL96S8U; 465 | ENABLE_BITCODE = NO; 466 | FRAMEWORK_SEARCH_PATHS = ( 467 | "$(inherited)", 468 | "$(PROJECT_DIR)/Flutter", 469 | ); 470 | INFOPLIST_FILE = Runner/Info.plist; 471 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 472 | LIBRARY_SEARCH_PATHS = ( 473 | "$(inherited)", 474 | "$(PROJECT_DIR)/Flutter", 475 | ); 476 | PRODUCT_BUNDLE_IDENTIFIER = com.company.talkCasually; 477 | PRODUCT_NAME = "$(TARGET_NAME)"; 478 | PROVISIONING_PROFILE_SPECIFIER = ""; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | }; 481 | name = Release; 482 | }; 483 | /* End XCBuildConfiguration section */ 484 | 485 | /* Begin XCConfigurationList section */ 486 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | 97C147031CF9000F007C117D /* Debug */, 490 | 97C147041CF9000F007C117D /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 97C147061CF9000F007C117D /* Debug */, 499 | 97C147071CF9000F007C117D /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | /* End XCConfigurationList section */ 505 | }; 506 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 507 | } 508 | --------------------------------------------------------------------------------