├── .firebaserc ├── .gitignore ├── .metadata ├── README.md ├── SECURITY.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── project_message_demo │ │ │ │ └── Application.java │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── project_message_demo │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── 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 │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets ├── calling.mp3 └── message.mp3 ├── firebase.json ├── functions ├── .eslintrc.js ├── .eslintrc.json ├── .gitignore ├── package-lock.json ├── package.json ├── src │ └── index.ts └── tsconfig.json ├── images ├── avt.jpg └── background.jpg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── 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 │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GoogleService-Info.plist │ ├── Info.plist │ └── Runner-Bridging-Header.h └── build │ └── XCBuildData │ ├── 10c79b38152e34f2a9c51d06cab5c2c0-desc.xcbuild │ ├── 10c79b38152e34f2a9c51d06cab5c2c0-manifest.xcbuild │ ├── 4ad262fa702ad4022d24711c2ea76076-desc.xcbuild │ ├── 4ad262fa702ad4022d24711c2ea76076-manifest.xcbuild │ ├── 4e993dc3077a0c038363e034c90eb811-desc.xcbuild │ ├── 4e993dc3077a0c038363e034c90eb811-manifest.xcbuild │ ├── 6072702be6084f103dc13ed17afffb5f-desc.xcbuild │ ├── 6072702be6084f103dc13ed17afffb5f-manifest.xcbuild │ ├── 8be53b2722081bfcbc2104eb272b8dc8-desc.xcbuild │ ├── 8be53b2722081bfcbc2104eb272b8dc8-manifest.xcbuild │ ├── BuildDescriptionCacheIndex-2f874282badcdaa4b07a7bb620484549 │ ├── BuildDescriptionCacheIndex-67522bec3edf0f0dc585944b11ec7e1c │ ├── build.db │ ├── ced3ee9f042c373fc5cb613a54f24c90-desc.xcbuild │ └── ced3ee9f042c373fc5cb613a54f24c90-manifest.xcbuild ├── lib ├── main.dart └── src │ ├── animation │ └── fade_animation.dart │ ├── app.dart │ ├── model │ └── user.dart │ ├── page │ ├── auth │ │ ├── auth_page.dart │ │ ├── forgot_page.dart │ │ └── login_page.dart │ ├── call │ │ ├── call_page.dart │ │ └── receive_call_page.dart │ ├── home_page │ │ └── home_page.dart │ ├── notification_page │ │ └── notification_page.dart │ ├── receive_page │ │ ├── receive_page.dart │ │ └── room_page.dart │ ├── request_page.dart │ │ └── request_page.dart │ └── user │ │ └── profile_page.dart │ ├── service │ └── auth.dart │ └── widget │ ├── general │ ├── cached_image.dart │ ├── loading.dart │ └── photo_viewer.dart │ ├── notification_widget │ └── notification_item.dart │ ├── receive_widget │ ├── build_chat_line.dart │ ├── inbox_card.dart │ ├── inbox_list.dart │ └── input_bottom.dart │ └── search_widget │ └── user_card.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "project-message-65c25" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: d408d302e22179d598f467e11da5dd968dbdc9ec 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Smart Intercom for Apartment :office::office::office: 2 | 3 | #### You can video call, notification to members and check your history :watch::watch: 4 | 5 | #### How I can run it? :tada: 6 | 7 | - :rocket: Clone this repository 8 | - :rocket: Run 'flutter get packages' on terminal in project 9 | - :rocket: Edit your firebase config 10 | - :rocket: Run app & register user 11 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :white_check_mark: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :white_check_mark: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 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 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.lambiengcode.intercom" 42 | minSdkVersion 21 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | multiDexEnabled true 47 | } 48 | 49 | buildTypes { 50 | release { 51 | signingConfig signingConfigs.debug 52 | minifyEnabled false 53 | shrinkResources false 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | implementation platform('com.google.firebase:firebase-bom:25.12.0') 65 | implementation 'com.google.firebase:firebase-analytics' 66 | implementation 'com.google.firebase:firebase-messaging:20.1.6' 67 | } 68 | 69 | apply plugin: 'com.google.gms.google-services' 70 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "375608740631", 4 | "firebase_url": "https://video-call-intercom-default-rtdb.firebaseio.com", 5 | "project_id": "video-call-intercom", 6 | "storage_bucket": "video-call-intercom.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:375608740631:android:cd6ccab3cb202f04540552", 12 | "android_client_info": { 13 | "package_name": "com.lambiengcode.intercom" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "375608740631-bd415qqs63g5onsfa7qd35p62bvh28k1.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyC41j6MlW90MKjxZZoY_8isJQqPk9HAldU" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "375608740631-bd415qqs63g5onsfa7qd35p62bvh28k1.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | ], 39 | "configuration_version": "1" 40 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | // Add the following permission if your scenario involves reading the external storage: 17 | 18 | 19 | 20 | 21 | 25 | 32 | 36 | 40 | 45 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/project_message_demo/Application.java: -------------------------------------------------------------------------------- 1 | package com.lambiengcode.intercom; 2 | 3 | import io.flutter.app.FlutterApplication; 4 | import io.flutter.plugin.common.PluginRegistry; 5 | import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService; 8 | import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin; 9 | 10 | public class Application extends FlutterApplication implements PluginRegistrantCallback { 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | FlutterFirebaseMessagingService.setPluginRegistrant(this); 15 | } 16 | 17 | @Override 18 | public void registerWith(PluginRegistry registry) { 19 | FirebaseMessagingPlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")); 20 | } 21 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/project_message_demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.lambiengcode.intercom 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.4' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/calling.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/assets/calling.mp3 -------------------------------------------------------------------------------- /assets/message.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/assets/message.mp3 -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "predeploy": [ 4 | "npm --prefix \"$RESOURCE_DIR\" run lint", 5 | "npm --prefix \"$RESOURCE_DIR\" run build" 6 | ], 7 | "source": "functions" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /functions/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | "plugin:import/errors", 9 | "plugin:import/warnings", 10 | "plugin:import/typescript", 11 | ], 12 | parser: "@typescript-eslint/parser", 13 | parserOptions: { 14 | project: "tsconfig.json", 15 | sourceType: "module", 16 | }, 17 | plugins: [ 18 | "@typescript-eslint", 19 | "import", 20 | ], 21 | rules: { 22 | "@typescript-eslint/adjacent-overload-signatures": "error", 23 | "@typescript-eslint/no-empty-function": "error", 24 | "@typescript-eslint/no-empty-interface": "warn", 25 | "@typescript-eslint/no-floating-promises": "error", 26 | "@typescript-eslint/no-namespace": "error", 27 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 28 | "@typescript-eslint/prefer-for-of": "warn", 29 | "@typescript-eslint/triple-slash-reference": "error", 30 | "@typescript-eslint/unified-signatures": "warn", 31 | "comma-dangle": ["error", "always-multiline"], 32 | "constructor-super": "error", 33 | eqeqeq: ["warn", "always"], 34 | "import/no-deprecated": "warn", 35 | "import/no-extraneous-dependencies": "error", 36 | "import/no-unassigned-import": "warn", 37 | "no-cond-assign": "error", 38 | "no-duplicate-case": "error", 39 | "no-duplicate-imports": "error", 40 | "no-empty": [ 41 | "error", 42 | { 43 | allowEmptyCatch: true, 44 | }, 45 | ], 46 | "no-invalid-this": "error", 47 | "no-new-wrappers": "error", 48 | "no-param-reassign": "error", 49 | "no-redeclare": "error", 50 | "no-sequences": "error", 51 | "no-shadow": [ 52 | "error", 53 | { 54 | hoist: "all", 55 | }, 56 | ], 57 | "no-throw-literal": "error", 58 | "no-unsafe-finally": "error", 59 | "no-unused-labels": "error", 60 | "no-var": "warn", 61 | "no-void": "error", 62 | "prefer-const": "warn", 63 | }, 64 | settings: { 65 | jsdoc: { 66 | tagNamePreference: { 67 | returns: "return", 68 | }, 69 | }, 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /functions/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | // Required for certain syntax usages 4 | "ecmaVersion": 2017 5 | }, 6 | "plugins": [ 7 | "promise" 8 | ], 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | // Removed rule "disallow the use of console" from recommended eslint rules 12 | "no-console": "off", 13 | 14 | // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules 15 | "no-regex-spaces": "off", 16 | 17 | // Removed rule "disallow the use of debugger" from recommended eslint rules 18 | "no-debugger": "off", 19 | 20 | // Removed rule "disallow unused variables" from recommended eslint rules 21 | "no-unused-vars": "off", 22 | 23 | // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules 24 | "no-mixed-spaces-and-tabs": "off", 25 | 26 | // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules 27 | "no-undef": "off", 28 | 29 | // Warn against template literal placeholder syntax in regular strings 30 | "no-template-curly-in-string": 1, 31 | 32 | // Warn if return statements do not either always or never specify values 33 | "consistent-return": 1, 34 | 35 | // Warn if no return statements in callbacks of array methods 36 | "array-callback-return": 1, 37 | 38 | // Require the use of === and !== 39 | "eqeqeq": 2, 40 | 41 | // Disallow the use of alert, confirm, and prompt 42 | "no-alert": 2, 43 | 44 | // Disallow the use of arguments.caller or arguments.callee 45 | "no-caller": 2, 46 | 47 | // Disallow null comparisons without type-checking operators 48 | "no-eq-null": 2, 49 | 50 | // Disallow the use of eval() 51 | "no-eval": 2, 52 | 53 | // Warn against extending native types 54 | "no-extend-native": 1, 55 | 56 | // Warn against unnecessary calls to .bind() 57 | "no-extra-bind": 1, 58 | 59 | // Warn against unnecessary labels 60 | "no-extra-label": 1, 61 | 62 | // Disallow leading or trailing decimal points in numeric literals 63 | "no-floating-decimal": 2, 64 | 65 | // Warn against shorthand type conversions 66 | "no-implicit-coercion": 1, 67 | 68 | // Warn against function declarations and expressions inside loop statements 69 | "no-loop-func": 1, 70 | 71 | // Disallow new operators with the Function object 72 | "no-new-func": 2, 73 | 74 | // Warn against new operators with the String, Number, and Boolean objects 75 | "no-new-wrappers": 1, 76 | 77 | // Disallow throwing literals as exceptions 78 | "no-throw-literal": 2, 79 | 80 | // Require using Error objects as Promise rejection reasons 81 | "prefer-promise-reject-errors": 2, 82 | 83 | // Enforce “for” loop update clause moving the counter in the right direction 84 | "for-direction": 2, 85 | 86 | // Enforce return statements in getters 87 | "getter-return": 2, 88 | 89 | // Disallow await inside of loops 90 | "no-await-in-loop": 2, 91 | 92 | // Disallow comparing against -0 93 | "no-compare-neg-zero": 2, 94 | 95 | // Warn against catch clause parameters from shadowing variables in the outer scope 96 | "no-catch-shadow": 1, 97 | 98 | // Disallow identifiers from shadowing restricted names 99 | "no-shadow-restricted-names": 2, 100 | 101 | // Enforce return statements in callbacks of array methods 102 | "callback-return": 2, 103 | 104 | // Require error handling in callbacks 105 | "handle-callback-err": 2, 106 | 107 | // Warn against string concatenation with __dirname and __filename 108 | "no-path-concat": 1, 109 | 110 | // Prefer using arrow functions for callbacks 111 | "prefer-arrow-callback": 1, 112 | 113 | // Return inside each then() to create readable and reusable Promise chains. 114 | // Forces developers to return console logs and http calls in promises. 115 | "promise/always-return": 2, 116 | 117 | //Enforces the use of catch() on un-returned promises 118 | "promise/catch-or-return": 2, 119 | 120 | // Warn against nested then() or catch() statements 121 | "promise/no-nesting": 1 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled JavaScript files 2 | **/*.js 3 | **/*.js.map 4 | 5 | # Except the ESLint config file 6 | !.eslintrc.js 7 | 8 | # TypeScript v1 declaration files 9 | typings/ 10 | 11 | # Node.js dependency directory 12 | node_modules/ 13 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "scripts": { 4 | "lint": "eslint \"src/**/*\"", 5 | "build": "tsc", 6 | "serve": "npm run build && firebase emulators:start --only functions", 7 | "shell": "npm run build && firebase functions:shell", 8 | "start": "npm run shell", 9 | "deploy": "firebase deploy --only functions", 10 | "logs": "firebase functions:log" 11 | }, 12 | "engines": { 13 | "node": "12" 14 | }, 15 | "main": "lib/index.js", 16 | "dependencies": { 17 | "firebase-admin": "^9.2.0", 18 | "firebase-functions": "^3.11.0" 19 | }, 20 | "devDependencies": { 21 | "@typescript-eslint/eslint-plugin": "^3.9.1", 22 | "@typescript-eslint/parser": "^3.8.0", 23 | "eslint": "^7.6.0", 24 | "eslint-plugin-import": "^2.22.0", 25 | "typescript": "^3.8.0", 26 | "firebase-functions-test": "^0.2.0" 27 | }, 28 | "private": true 29 | } 30 | -------------------------------------------------------------------------------- /functions/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as functions from "firebase-functions"; 2 | import * as admin from "firebase-admin"; 3 | admin.initializeApp(); 4 | 5 | const db = admin.firestore(); 6 | const fcm = admin.messaging(); 7 | 8 | export const sendToDevice = functions.firestore 9 | .document("requests/{requestsID}") 10 | .onCreate(async (snapshot) => { 11 | const message = snapshot.data(); 12 | 13 | const querySnapshot = await db 14 | .collection("users") 15 | .where("id", "==", message.receiveID) 16 | .get(); 17 | 18 | const tokens = querySnapshot.docs.map((snap) => snap.data().token); 19 | 20 | const payload: admin.messaging.MessagingPayload = { 21 | notification: { 22 | title: "Admin", 23 | body: `Calling...`, 24 | icon: "your-icon-url", 25 | click_action: "FLUTTER_NOTIFICATION_CLICK", 26 | priority: "high", 27 | }, 28 | }; 29 | 30 | return fcm.sendToDevice(tokens, payload); 31 | }); 32 | 33 | export const sendToTopic = functions.firestore 34 | .document("notifications/{notificationsId}") 35 | .onCreate(async (snapshot) => { 36 | const notification = snapshot.data(); 37 | 38 | if (notification.all) { 39 | const querySnapshot = await db 40 | .collection("users") 41 | .where("key", "==", notification.key) 42 | .where("notifications", '==', true) 43 | .get(); 44 | 45 | const tokens = querySnapshot.docs.map((snap) => snap.data().token); 46 | 47 | const payload: admin.messaging.MessagingPayload = { 48 | notification: { 49 | title: `${notification.title}`, 50 | body: `${notification.body}`, 51 | image: `${notification.urlToImage}`, 52 | icon: "your-icon-url", 53 | click_action: "FLUTTER_NOTIFICATION_CLICK", // required only for onResume or onLaunch callbacks 54 | priority: "high", 55 | }, 56 | }; 57 | 58 | return fcm.sendToDevice(tokens, payload); 59 | } else { 60 | const querySnapshot = await db 61 | .collection("users") 62 | .where("key", "==", notification.key) 63 | .where("id", "in", notification.members) 64 | .where("notifications", '==', true) 65 | .get(); 66 | 67 | const tokens = querySnapshot.docs.map((snap) => snap.data().token); 68 | 69 | const payload: admin.messaging.MessagingPayload = { 70 | notification: { 71 | title: `${notification.title}`, 72 | body: `${notification.body}`, 73 | image: `${notification.urlToImage}`, 74 | icon: "your-icon-url", 75 | click_action: "FLUTTER_NOTIFICATION_CLICK", // required only for onResume or onLaunch callbacks 76 | priority: "high", 77 | }, 78 | }; 79 | 80 | return fcm.sendToDevice(tokens, payload); 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitReturns": true, 5 | "noUnusedLocals": true, 6 | "outDir": "lib", 7 | "sourceMap": true, 8 | "strict": true, 9 | "target": "es2017" 10 | }, 11 | "compileOnSave": true, 12 | "include": [ 13 | "src" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /images/avt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/images/avt.jpg -------------------------------------------------------------------------------- /images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/images/background.jpg -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | target.build_configurations.each do |config| 40 | config.build_settings['ENABLE_BITCODE'] = 'NO' 41 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0' 42 | end 43 | end 44 | end 45 | 46 | # add the Firebase pod for Google Analytics 47 | pod 'Firebase/Analytics' 48 | # add pods for any other desired Firebase products 49 | # https://firebase.google.com/docs/ios/setup#available-pods 50 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import Firebase 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | if #available(iOS 10.0, *) { 12 | UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate 13 | } 14 | FirebaseApp.configure() 15 | GeneratedPluginRegistrant.register(with: self) 16 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/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/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /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 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 1037020081116-873uu6crie5druud34u9ef7caje18t0r.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.1037020081116-873uu6crie5druud34u9ef7caje18t0r 9 | API_KEY 10 | AIzaSyClAZPb14oPd86pKnkRTFU9L6wdwXN03jQ 11 | GCM_SENDER_ID 12 | 1037020081116 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.example.projectMessageDemo 17 | PROJECT_ID 18 | project-message-65c25 19 | STORAGE_BUCKET 20 | project-message-65c25.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:1037020081116:ios:813d5d711c73a56293fd19 33 | DATABASE_URL 34 | https://project-message-65c25.firebaseio.com 35 | 36 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | project_message_demo 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | FirebaseAppDelegateProxyEnabled 24 | 25 | LSRequiresIPhoneOS 26 | 27 | NSCameraUsageDescription 28 | $(PRODUCT_NAME) Camera Usage! 29 | NSMicrophoneUsageDescription 30 | $(PRODUCT_NAME) Microphone Usage! 31 | NSPhotoLibraryUsageDescription 32 | photos description. 33 | UILaunchStoryboardName 34 | LaunchScreen 35 | UIMainStoryboardFile 36 | Main 37 | UIStatusBarHidden 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UISupportedInterfaceOrientations~ipad 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationPortraitUpsideDown 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/build/XCBuildData/10c79b38152e34f2a9c51d06cab5c2c0-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/10c79b38152e34f2a9c51d06cab5c2c0-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/4ad262fa702ad4022d24711c2ea76076-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/4ad262fa702ad4022d24711c2ea76076-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/4e993dc3077a0c038363e034c90eb811-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/4e993dc3077a0c038363e034c90eb811-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/6072702be6084f103dc13ed17afffb5f-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/6072702be6084f103dc13ed17afffb5f-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/8be53b2722081bfcbc2104eb272b8dc8-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/8be53b2722081bfcbc2104eb272b8dc8-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/BuildDescriptionCacheIndex-2f874282badcdaa4b07a7bb620484549: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/BuildDescriptionCacheIndex-2f874282badcdaa4b07a7bb620484549 -------------------------------------------------------------------------------- /ios/build/XCBuildData/BuildDescriptionCacheIndex-67522bec3edf0f0dc585944b11ec7e1c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/BuildDescriptionCacheIndex-67522bec3edf0f0dc585944b11ec7e1c -------------------------------------------------------------------------------- /ios/build/XCBuildData/build.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/build.db -------------------------------------------------------------------------------- /ios/build/XCBuildData/ced3ee9f042c373fc5cb613a54f24c90-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambiengcode/flutter-intercom-mobile/6f4d41e438700dae65973f52cfdbb254cfb3e48a/ios/build/XCBuildData/ced3ee9f042c373fc5cb613a54f24c90-desc.xcbuild -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:project_message_demo/src/app.dart'; 3 | import 'package:project_message_demo/src/model/user.dart'; 4 | import 'package:project_message_demo/src/service/auth.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | void main() { 8 | runApp(MyApp()); 9 | } 10 | 11 | class MyApp extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return StreamProvider.value( 15 | value: AuthService().user, 16 | child: MaterialApp( 17 | debugShowCheckedModeBanner: false, 18 | title: 'Intercom', 19 | theme: ThemeData( 20 | brightness: Brightness.light, 21 | primarySwatch: Colors.blue, 22 | ), 23 | home: App(), 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/animation/fade_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:simple_animations/simple_animations/controlled_animation.dart'; 3 | import 'package:simple_animations/simple_animations/multi_track_tween.dart'; 4 | 5 | class FadeAnimation extends StatelessWidget { 6 | final double delay; 7 | final Widget child; 8 | 9 | FadeAnimation(this.delay, this.child); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final tween = MultiTrackTween([ 14 | Track("opacity") 15 | .add(Duration(milliseconds: 500), Tween(begin: 0.0, end: 1.0)), 16 | Track("translateY").add( 17 | Duration(milliseconds: 500), Tween(begin: -30.0, end: 0.0), 18 | curve: Curves.easeOut) 19 | ]); 20 | 21 | return ControlledAnimation( 22 | delay: Duration(milliseconds: (500 * delay).round()), 23 | duration: tween.duration, 24 | tween: tween, 25 | child: child, 26 | builderWithChild: (context, child, animation) => Opacity( 27 | opacity: animation["opacity"], 28 | child: Transform.translate( 29 | offset: Offset(0, animation["translateY"]), child: child), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:project_message_demo/src/model/user.dart'; 6 | import 'package:project_message_demo/src/page/auth/auth_page.dart'; 7 | import 'package:project_message_demo/src/page/call/call_page.dart'; 8 | import 'package:project_message_demo/src/page/call/receive_call_page.dart'; 9 | import 'package:project_message_demo/src/page/home_page/home_page.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class App extends StatefulWidget { 13 | static bool dark; 14 | static String systemLocales = Platform.localeName.substring(0, 2); 15 | 16 | @override 17 | State createState() => _AppState(); 18 | } 19 | 20 | class _AppState extends State { 21 | @override 22 | void initState() { 23 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 24 | statusBarColor: Colors.transparent, 25 | )); 26 | super.initState(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | final user = Provider.of(context); 32 | final size = MediaQuery.of(context).size; 33 | 34 | return user == null 35 | ? AuthenticatePage() 36 | : StreamBuilder( 37 | stream: Firestore.instance 38 | .collection('requests') 39 | .where('receiveID', isEqualTo: user.uid) 40 | .where('completed', isEqualTo: false) 41 | .snapshots(), 42 | builder: 43 | (BuildContext context, AsyncSnapshot snapshot) { 44 | if (!snapshot.hasData) { 45 | return Container(); 46 | } 47 | 48 | return snapshot.data.documents.length != 0 49 | ? snapshot.data.documents[0]['request'] 50 | ? ReceiveCallPage( 51 | idSend: snapshot.data.documents[0]['idSend'], 52 | index: snapshot.data.documents[0].reference, 53 | ) 54 | : CallPage( 55 | idSend: snapshot.data.documents[0]['idSend'], 56 | index: snapshot.data.documents[0].reference, 57 | info: snapshot.data.documents[0], 58 | ) 59 | : HomePage( 60 | uid: user.uid, 61 | size: size, 62 | ); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/model/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | final String uid; 3 | 4 | User({this.uid}); 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/page/auth/auth_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:project_message_demo/src/page/auth/login_page.dart'; 4 | 5 | class AuthenticatePage extends StatefulWidget { 6 | @override 7 | State createState() => _AuthenticatePageState(); 8 | } 9 | 10 | class _AuthenticatePageState extends State { 11 | bool showSignIn = true; 12 | 13 | @override 14 | void initState() { 15 | SystemChrome.setPreferredOrientations([ 16 | DeviceOrientation.portraitDown, 17 | DeviceOrientation.portraitUp, 18 | ]); 19 | super.initState(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return LoginPage(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/page/auth/forgot_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:project_message_demo/src/animation/fade_animation.dart'; 3 | import 'package:project_message_demo/src/service/auth.dart'; 4 | import 'package:project_message_demo/src/widget/general/loading.dart'; 5 | 6 | class ForgotPage extends StatefulWidget { 7 | @override 8 | _ForgotPageState createState() => _ForgotPageState(); 9 | } 10 | 11 | class _ForgotPageState extends State { 12 | final _formKey = GlobalKey(); 13 | String email; 14 | bool loading = false; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final sizeHeight = MediaQuery.of(context).size.height; 19 | final sizeWidth = MediaQuery.of(context).size.width; 20 | final AuthService _auth = AuthService(); 21 | 22 | return loading 23 | ? Loading() 24 | : Scaffold( 25 | backgroundColor: Colors.white, 26 | body: Stack( 27 | children: [ 28 | Form( 29 | key: _formKey, 30 | child: SingleChildScrollView( 31 | child: Column( 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | children: [ 35 | SizedBox( 36 | height: sizeHeight / 3, 37 | ), 38 | Padding( 39 | padding: 40 | EdgeInsets.symmetric(horizontal: sizeHeight / 20), 41 | child: Column( 42 | crossAxisAlignment: CrossAxisAlignment.start, 43 | children: [ 44 | Row( 45 | mainAxisAlignment: 46 | MainAxisAlignment.spaceBetween, 47 | children: [ 48 | FadeAnimation( 49 | 1.5, 50 | Text( 51 | "Reset PW", 52 | style: TextStyle( 53 | color: Colors.white70, 54 | fontWeight: FontWeight.bold, 55 | fontSize: 30), 56 | )), 57 | FadeAnimation( 58 | 1.5, 59 | IconButton( 60 | icon: Icon( 61 | Icons.exit_to_app, 62 | color: Colors.white, 63 | size: sizeWidth / 12, 64 | ), 65 | onPressed: () { 66 | Navigator.of(context).pop(context); 67 | }, 68 | ), 69 | ), 70 | ], 71 | ), 72 | SizedBox( 73 | height: 20, 74 | ), 75 | FadeAnimation( 76 | 1.7, 77 | Container( 78 | decoration: BoxDecoration( 79 | borderRadius: BorderRadius.circular(10), 80 | color: Colors.white, 81 | boxShadow: [ 82 | BoxShadow( 83 | color: Color.fromRGBO( 84 | 196, 135, 198, .3), 85 | blurRadius: 20, 86 | offset: Offset(0, 10), 87 | ) 88 | ]), 89 | child: Column( 90 | children: [ 91 | Container( 92 | padding: EdgeInsets.all(10), 93 | child: TextFormField( 94 | validator: (val) => val.length == 0 95 | ? 'Enter email' 96 | : null, 97 | onChanged: (val) => 98 | email = val.trim(), 99 | decoration: InputDecoration( 100 | border: InputBorder.none, 101 | hintText: "Email", 102 | hintStyle: TextStyle( 103 | color: Colors.grey)), 104 | ), 105 | ), 106 | ], 107 | ), 108 | )), 109 | SizedBox( 110 | height: 20.0, 111 | ), 112 | FadeAnimation( 113 | 1.9, 114 | Row( 115 | mainAxisAlignment: MainAxisAlignment.center, 116 | children: [ 117 | GestureDetector( 118 | onTap: () async { 119 | if (_formKey.currentState 120 | .validate()) { 121 | setState(() { 122 | loading = true; 123 | }); 124 | dynamic result = await _auth 125 | .sendPasswordResetEmail(email); 126 | if (result == null) { 127 | setState(() { 128 | loading = false; 129 | }); 130 | } else { 131 | Navigator.of(context) 132 | .pop(context); 133 | } 134 | } 135 | }, 136 | child: Container( 137 | height: 50, 138 | width: 130.0, 139 | margin: EdgeInsets.symmetric( 140 | horizontal: 60), 141 | decoration: BoxDecoration( 142 | borderRadius: 143 | BorderRadius.circular(50), 144 | color: Colors.deepPurple[600], 145 | ), 146 | child: Center( 147 | child: Text( 148 | "Request", 149 | style: TextStyle( 150 | color: Colors.white), 151 | ), 152 | ), 153 | ), 154 | ) 155 | ], 156 | )), 157 | SizedBox( 158 | height: 20, 159 | ), 160 | ], 161 | ), 162 | ) 163 | ], 164 | ), 165 | ), 166 | ), 167 | ], 168 | ), 169 | ); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/src/page/auth/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:project_message_demo/src/animation/fade_animation.dart'; 3 | import 'package:project_message_demo/src/service/auth.dart'; 4 | import 'package:project_message_demo/src/widget/general/loading.dart'; 5 | 6 | class LoginPage extends StatefulWidget { 7 | @override 8 | _LoginPageState createState() => _LoginPageState(); 9 | } 10 | 11 | class _LoginPageState extends State { 12 | final AuthService _auth = AuthService(); 13 | 14 | final _formKey = GlobalKey(); 15 | 16 | FocusNode textFieldFocus = FocusNode(); 17 | String email = ''; 18 | String password = ''; 19 | 20 | bool hidePassword = true; 21 | bool loading = false; 22 | 23 | hideKeyboard() => textFieldFocus.unfocus(); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final sizeHeight = MediaQuery.of(context).size.height; 28 | final sizeWidth = MediaQuery.of(context).size.width; 29 | 30 | return loading 31 | ? Loading() 32 | : Scaffold( 33 | backgroundColor: Colors.white, 34 | body: Stack( 35 | children: [ 36 | Form( 37 | key: _formKey, 38 | child: SingleChildScrollView( 39 | child: Column( 40 | crossAxisAlignment: CrossAxisAlignment.end, 41 | mainAxisAlignment: MainAxisAlignment.center, 42 | children: [ 43 | SizedBox( 44 | height: sizeHeight * .18, 45 | ), 46 | Container( 47 | height: sizeHeight * .26, 48 | decoration: BoxDecoration( 49 | image: DecorationImage( 50 | image: AssetImage('images/background.jpg'), 51 | fit: BoxFit.fitHeight, 52 | ), 53 | ), 54 | ), 55 | SizedBox( 56 | height: sizeHeight * .1, 57 | ), 58 | Padding( 59 | padding: 60 | EdgeInsets.symmetric(horizontal: sizeWidth * 0.1), 61 | child: Column( 62 | crossAxisAlignment: CrossAxisAlignment.start, 63 | children: [ 64 | FadeAnimation( 65 | 1.7, 66 | Container( 67 | decoration: BoxDecoration( 68 | borderRadius: BorderRadius.circular(6.0), 69 | color: Colors.white, 70 | border: Border.all( 71 | color: Colors.grey.shade200, 72 | width: 1.2, 73 | )), 74 | child: Column( 75 | children: [ 76 | Container( 77 | padding: EdgeInsets.all(8), 78 | decoration: BoxDecoration( 79 | border: Border( 80 | bottom: BorderSide( 81 | color: Colors.grey[200]))), 82 | child: TextFormField( 83 | style: TextStyle( 84 | color: Colors.black87, 85 | fontSize: sizeWidth / 24, 86 | fontWeight: FontWeight.w400, 87 | ), 88 | validator: (val) => val.length == 0 89 | ? 'Enter your Email' 90 | : null, 91 | onChanged: (val) => 92 | email = val.trim(), 93 | decoration: InputDecoration( 94 | contentPadding: EdgeInsets.only( 95 | left: 12.0, 96 | ), 97 | border: InputBorder.none, 98 | hintText: "Email", 99 | hintStyle: TextStyle( 100 | color: Colors.grey, 101 | fontSize: sizeWidth / 24, 102 | fontWeight: FontWeight.w400, 103 | ), 104 | ), 105 | ), 106 | ), 107 | Container( 108 | padding: EdgeInsets.all(8), 109 | child: TextFormField( 110 | style: TextStyle( 111 | color: Colors.black87, 112 | fontSize: sizeWidth / 24, 113 | fontWeight: FontWeight.w400, 114 | ), 115 | focusNode: textFieldFocus, 116 | validator: (val) => val.length == 0 117 | ? 'Enter your password' 118 | : null, 119 | onChanged: (val) => 120 | password = val.trim(), 121 | obscureText: hidePassword, 122 | decoration: InputDecoration( 123 | contentPadding: EdgeInsets.only( 124 | left: 12.0, 125 | ), 126 | border: InputBorder.none, 127 | hintText: "Password", 128 | hintStyle: TextStyle( 129 | color: Colors.grey, 130 | fontSize: sizeWidth / 24, 131 | fontWeight: FontWeight.w400, 132 | ), 133 | ), 134 | ), 135 | ) 136 | ], 137 | ), 138 | ), 139 | ), 140 | SizedBox( 141 | height: 10, 142 | ), 143 | SizedBox( 144 | height: 32, 145 | ), 146 | FadeAnimation( 147 | 1.9, 148 | GestureDetector( 149 | onTap: () async { 150 | if (_formKey.currentState.validate()) { 151 | setState(() { 152 | loading = true; 153 | }); 154 | dynamic result = await _auth 155 | .signInWithEmailAndPassword( 156 | email, password); 157 | if (result == null) { 158 | setState(() { 159 | loading = false; 160 | }); 161 | } else {} 162 | } 163 | }, 164 | child: Container( 165 | height: sizeHeight * 0.065, 166 | decoration: BoxDecoration( 167 | borderRadius: BorderRadius.circular(6.0), 168 | color: Colors.blue, 169 | ), 170 | child: Center( 171 | child: Text( 172 | "Login", 173 | style: TextStyle( 174 | color: Colors.white.withOpacity(.88), 175 | fontSize: 14.0, 176 | fontWeight: FontWeight.w800, 177 | ), 178 | ), 179 | ), 180 | ), 181 | ), 182 | ), 183 | SizedBox( 184 | height: 10, 185 | ), 186 | SizedBox( 187 | height: 30, 188 | ), 189 | ], 190 | ), 191 | ) 192 | ], 193 | ), 194 | ), 195 | ), 196 | ], 197 | ), 198 | ); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /lib/src/page/call/call_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:math'; 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:firebase_database/firebase_database.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_webrtc/webrtc.dart'; 8 | import 'package:project_message_demo/src/widget/general/photo_viewer.dart'; 9 | import 'package:sdp_transform/sdp_transform.dart'; 10 | 11 | class CallPage extends StatefulWidget { 12 | final String idSend; 13 | final DocumentSnapshot info; 14 | final index; 15 | 16 | CallPage({this.idSend, this.index, this.info}); 17 | 18 | @override 19 | State createState() => _CallPageState(); 20 | } 21 | 22 | class _CallPageState extends State { 23 | Timer _timmerInstance; 24 | int _start = 0; 25 | String _timmer = ''; 26 | 27 | //VideoCallVariables 28 | RTCPeerConnection _peerConnection; 29 | MediaStream _localStream; 30 | RTCVideoRenderer _localRenderer = new RTCVideoRenderer(); 31 | RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer(); 32 | bool isFrontCamera = true; 33 | int id = 12022000; 34 | 35 | void startTimmer() { 36 | var oneSec = Duration(seconds: 1); 37 | _timmerInstance = Timer.periodic( 38 | oneSec, 39 | (Timer timer) => setState(() { 40 | if (_start < 0) { 41 | _timmerInstance.cancel(); 42 | } else { 43 | _start = _start + 1; 44 | _timmer = getTimerTime(_start); 45 | } 46 | })); 47 | } 48 | 49 | String getTimerTime(int start) { 50 | int minutes = (start ~/ 60); 51 | String sMinute = ''; 52 | if (minutes.toString().length == 1) { 53 | sMinute = '0' + minutes.toString(); 54 | } else 55 | sMinute = minutes.toString(); 56 | 57 | int seconds = (start % 60); 58 | String sSeconds = ''; 59 | if (seconds.toString().length == 1) { 60 | sSeconds = '0' + seconds.toString(); 61 | } else 62 | sSeconds = seconds.toString(); 63 | 64 | return sMinute + ':' + sSeconds; 65 | } 66 | 67 | Future _responce(responce) async { 68 | _peerConnection.close(); 69 | _localStream.dispose(); 70 | _localRenderer.dispose(); 71 | _timmerInstance.cancel(); 72 | Firestore.instance.runTransaction((Transaction transaction) async { 73 | DocumentSnapshot snapshot = await transaction.get(widget.index); 74 | await transaction.update(widget.index, { 75 | 'completed': true, 76 | 'responce': responce, 77 | 'responcedTime': DateTime.now(), 78 | }); 79 | }); 80 | } 81 | 82 | @override 83 | void initState() { 84 | super.initState(); 85 | 86 | initRenderers(); 87 | _createPeerConnection().then((pc) { 88 | _peerConnection = pc; 89 | _setRemoteDescription( 90 | jsonDecode(widget.info['sdp'])['sdp']['sdp'].toString()); 91 | }); 92 | startTimmer(); 93 | } 94 | 95 | @override 96 | void dispose() { 97 | _peerConnection.close(); 98 | _localStream.dispose(); 99 | _localRenderer.dispose(); 100 | _timmerInstance.cancel(); 101 | super.dispose(); 102 | } 103 | 104 | initRenderers() async { 105 | await _localRenderer.initialize(); 106 | await _remoteRenderer.initialize(); 107 | } 108 | 109 | void switchCamera() async { 110 | if (_localStream != null) { 111 | bool value = await _localStream.getVideoTracks()[0].switchCamera(); 112 | while (value == this.isFrontCamera) 113 | value = await _localStream.getVideoTracks()[0].switchCamera(); 114 | this.isFrontCamera = value; 115 | } 116 | } 117 | 118 | void _createAnswer() async { 119 | RTCSessionDescription description = await _peerConnection.createAnswer({ 120 | 'offerToReceiveVideo': 1, 121 | 'offerToReceiveAudio': 1, 122 | }); 123 | 124 | var session = parse(description.sdp); 125 | String sdp = write(session, null); 126 | await sendSdp(sdp); 127 | 128 | _peerConnection.setLocalDescription(description); 129 | 130 | _peerConnection.onIceCandidate = (event) => { 131 | sendCandidates(event.candidate.toString(), event.sdpMid.toString(), 132 | event.sdpMlineIndex), 133 | }; 134 | } 135 | 136 | void _setRemoteDescription(sdp) async { 137 | RTCSessionDescription description = new RTCSessionDescription(sdp, 'offer'); 138 | await _peerConnection.setRemoteDescription(description); 139 | _createAnswer(); 140 | } 141 | 142 | _createPeerConnection() async { 143 | Map configuration = { 144 | "iceServers": [ 145 | {"url": "stun:stun.l.google.com:19302"}, 146 | ] 147 | }; 148 | 149 | final Map offerSdpConstraints = { 150 | "mandatory": { 151 | "OfferToReceiveAudio": true, 152 | "OfferToReceiveVideo": true, 153 | }, 154 | "optional": [], 155 | }; 156 | 157 | _localStream = await _getUserMedia(); 158 | 159 | RTCPeerConnection pc = 160 | await createPeerConnection(configuration, offerSdpConstraints); 161 | // if (pc != null) print(pc); 162 | pc.addStream(_localStream); 163 | 164 | pc.onIceCandidate = (e) { 165 | if (e.candidate != null) { 166 | print(json.encode({ 167 | 'candidate': e.candidate.toString(), 168 | 'sdpMid': e.sdpMid.toString(), 169 | 'sdpMlineIndex': e.sdpMlineIndex, 170 | })); 171 | } 172 | }; 173 | 174 | pc.onIceConnectionState = (e) { 175 | print(e); 176 | }; 177 | 178 | pc.onAddStream = (stream) { 179 | print('addStream: ' + stream.id); 180 | _remoteRenderer.srcObject = stream; 181 | }; 182 | 183 | return pc; 184 | } 185 | 186 | String _chars = 187 | 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; 188 | Random _rnd = Random(); 189 | 190 | String getRandomString(int length) => String.fromCharCodes(Iterable.generate( 191 | length, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)))); 192 | 193 | Future sendCandidates( 194 | String candidate, 195 | String sdpMid, 196 | int sdpMLineIndex, 197 | ) async { 198 | DatabaseReference _candidateRef = FirebaseDatabase().reference(); 199 | String jsonString = 200 | '{"ice":{"candidate":${jsonEncode(candidate)},"sdpMid":$sdpMid,"sdpMlineIndex":$sdpMLineIndex}}'; 201 | _candidateRef.child(getRandomString(20)).set({ 202 | 'sender': id, 203 | 'message': jsonString, 204 | }); 205 | setState(() { 206 | id++; 207 | }); 208 | _candidateRef.remove(); 209 | } 210 | 211 | Future sendSdp( 212 | String sdp, 213 | ) async { 214 | DatabaseReference _sdpRef = FirebaseDatabase().reference(); 215 | String jsonString = '{"sdp":{"type":"answer","sdp":${jsonEncode(sdp)}}}'; 216 | _sdpRef.child(getRandomString(20)).set({ 217 | 'sender': id, 218 | 'message': jsonString, 219 | }); 220 | setState(() { 221 | id++; 222 | }); 223 | _sdpRef.remove(); 224 | } 225 | 226 | _getUserMedia() async { 227 | final Map mediaConstraints = { 228 | 'audio': true, 229 | 'video': { 230 | 'facingMode': 'user', 231 | }, 232 | }; 233 | 234 | MediaStream stream = await navigator.getUserMedia(mediaConstraints); 235 | 236 | // _localStream = stream; 237 | _localRenderer.srcObject = stream; 238 | _localRenderer.mirror = false; 239 | 240 | // _peerConnection.addStream(stream); 241 | 242 | return stream; 243 | } 244 | 245 | @override 246 | Widget build(BuildContext context) { 247 | Size size = MediaQuery.of(context).size; 248 | return Scaffold( 249 | body: Container( 250 | height: size.height, 251 | width: size.width, 252 | child: Column( 253 | crossAxisAlignment: CrossAxisAlignment.center, 254 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 255 | children: [ 256 | Stack( 257 | children: [ 258 | Container( 259 | width: size.width, 260 | height: size.height - size.width * .15, 261 | child: _remoteRenderer.textureId == null 262 | ? Container() 263 | : FittedBox( 264 | fit: BoxFit.cover, 265 | child: new Center( 266 | child: new SizedBox( 267 | width: size.height * 1.34, 268 | height: size.height, 269 | child: new Transform( 270 | transform: Matrix4.identity()..rotateY(0.0), 271 | alignment: FractionalOffset.center, 272 | child: new Texture( 273 | textureId: _remoteRenderer.textureId), 274 | ), 275 | ), 276 | ), 277 | ), 278 | ), 279 | Positioned( 280 | top: 40.0, 281 | left: 15.0, 282 | child: Column( 283 | children: [ 284 | Text( 285 | _timmer, 286 | style: TextStyle( 287 | color: _remoteRenderer.textureId == null 288 | ? Colors.white 289 | : Colors.black, 290 | fontSize: size.width / 26.5, 291 | ), 292 | ), 293 | SizedBox( 294 | height: 8.0, 295 | ), 296 | Container( 297 | height: size.width * .54, 298 | width: size.width * .32, 299 | decoration: BoxDecoration( 300 | borderRadius: BorderRadius.all(Radius.circular(6.0)), 301 | border: 302 | Border.all(color: Colors.blueAccent, width: 2.0), 303 | ), 304 | child: _localRenderer.textureId == null 305 | ? Container() 306 | : SizedBox( 307 | width: size.height, 308 | height: size.height, 309 | child: new Transform( 310 | transform: Matrix4.identity() 311 | ..rotateY( 312 | isFrontCamera ? -pi : 0.0, 313 | ), 314 | alignment: FractionalOffset.center, 315 | child: new Texture( 316 | textureId: _localRenderer.textureId), 317 | ), 318 | ), 319 | ), 320 | SizedBox( 321 | height: 12.0, 322 | ), 323 | GestureDetector( 324 | onTap: () { 325 | Navigator.of(context).push( 326 | MaterialPageRoute( 327 | builder: (context) => PhotoViewer( 328 | image: widget.info['urlToImage'], 329 | ), 330 | ), 331 | ); 332 | }, 333 | child: Container( 334 | height: size.width * .3, 335 | width: size.width * .3, 336 | decoration: BoxDecoration( 337 | borderRadius: 338 | BorderRadius.all(Radius.circular(6.0)), 339 | border: Border.all( 340 | color: Colors.blueAccent, width: 2.0), 341 | image: DecorationImage( 342 | image: widget.info['urlToImage'] == '' 343 | ? AssetImage('images/avt.jpg') 344 | : NetworkImage(widget.info['urlToImage']), 345 | fit: BoxFit.cover, 346 | ), 347 | ), 348 | ), 349 | ), 350 | SizedBox( 351 | height: 8.0, 352 | ), 353 | GestureDetector( 354 | onTap: () { 355 | switchCamera(); 356 | }, 357 | child: Container( 358 | height: size.width * .125, 359 | width: size.width * .125, 360 | decoration: BoxDecoration( 361 | borderRadius: 362 | BorderRadius.all(Radius.circular(4.0)), 363 | border: Border.all( 364 | color: Colors.blueAccent, width: 2.0), 365 | color: Colors.blueAccent, 366 | ), 367 | alignment: Alignment.center, 368 | child: Icon( 369 | Icons.switch_camera, 370 | color: Colors.white, 371 | size: size.width / 14.0, 372 | ), 373 | ), 374 | ), 375 | ], 376 | ), 377 | ), 378 | ], 379 | ), 380 | Row( 381 | children: [ 382 | Expanded( 383 | flex: 1, 384 | child: GestureDetector( 385 | onTap: () async { 386 | await _responce('Reject'); 387 | }, 388 | child: Container( 389 | height: size.width * .15, 390 | decoration: BoxDecoration( 391 | color: Colors.redAccent, 392 | ), 393 | child: Icon( 394 | Icons.close, 395 | color: Colors.white, 396 | size: size.width / 14.0, 397 | ), 398 | ), 399 | ), 400 | ), 401 | Expanded( 402 | flex: 1, 403 | child: GestureDetector( 404 | onTap: () async { 405 | await _responce('Accept'); 406 | }, 407 | child: Container( 408 | height: size.width * .15, 409 | decoration: BoxDecoration( 410 | color: Colors.green, 411 | ), 412 | child: Icon( 413 | Icons.check, 414 | color: Colors.white, 415 | size: size.width / 14.0, 416 | ), 417 | ), 418 | ), 419 | ), 420 | ], 421 | ), 422 | ], 423 | ), 424 | ), 425 | ); 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /lib/src/page/call/receive_call_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:assets_audio_player/assets_audio_player.dart'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class ReceiveCallPage extends StatefulWidget { 6 | final String idSend; 7 | final index; 8 | 9 | ReceiveCallPage({this.idSend, this.index}); 10 | 11 | @override 12 | State createState() => _ReceiveCallPageState(); 13 | } 14 | 15 | class _ReceiveCallPageState extends State { 16 | final assetsAudioPlayer = AssetsAudioPlayer(); 17 | Future _responce(bool accept) async { 18 | Firestore.instance.runTransaction((Transaction transaction) async { 19 | await transaction.update(widget.index, { 20 | 'request': false, 21 | 'completed': !accept, 22 | 'responce': accept == false ? 'Rejected Call' : '', 23 | 'responcedTime': DateTime.now(), 24 | }); 25 | }); 26 | assetsAudioPlayer.stop(); 27 | } 28 | 29 | playLocal() async { 30 | try { 31 | await assetsAudioPlayer.open( 32 | Audio('assets/calling.mp3'), 33 | loopMode: LoopMode.single, 34 | ); 35 | } catch (t) { 36 | //stream unreachable 37 | } 38 | } 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | playLocal(); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | super.dispose(); 49 | assetsAudioPlayer.stop(); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | Size size = MediaQuery.of(context).size; 55 | return Scaffold( 56 | body: Container( 57 | height: size.height, 58 | width: size.width, 59 | child: Column( 60 | crossAxisAlignment: CrossAxisAlignment.center, 61 | children: [ 62 | SizedBox( 63 | height: size.height * 0.15, 64 | ), 65 | StreamBuilder( 66 | stream: Firestore.instance 67 | .collection('users') 68 | .where('id', isEqualTo: widget.idSend) 69 | .snapshots(), 70 | builder: (BuildContext context, 71 | AsyncSnapshot snapshot) { 72 | if (!snapshot.hasData) { 73 | return Container(); 74 | } 75 | 76 | return Column( 77 | children: [ 78 | Text( 79 | 'Incoming Call', 80 | style: TextStyle( 81 | color: Colors.green.shade700, 82 | fontSize: size.width / 18.4, 83 | fontWeight: FontWeight.w400, 84 | ), 85 | ), 86 | SizedBox( 87 | height: size.height * .08, 88 | ), 89 | Container( 90 | height: size.height * .18, 91 | width: size.height * .18, 92 | decoration: BoxDecoration( 93 | border: Border.all( 94 | color: Colors.grey.shade400, 95 | width: .8, 96 | ), 97 | borderRadius: BorderRadius.all(Radius.circular(30.0)), 98 | image: DecorationImage( 99 | image: snapshot.data.documents[0]['urlToImage'] == '' 100 | ? AssetImage('images/avt.jpg') 101 | : NetworkImage( 102 | snapshot.data.documents[0]['urlToImage'], 103 | ), 104 | fit: BoxFit.cover, 105 | ), 106 | ), 107 | ), 108 | SizedBox( 109 | height: 24.0, 110 | ), 111 | Text( 112 | snapshot.data.documents[0]['username'], 113 | style: TextStyle( 114 | color: Colors.blueAccent, 115 | fontSize: size.width / 16.8, 116 | fontWeight: FontWeight.w600, 117 | ), 118 | ), 119 | SizedBox( 120 | height: size.height * .24, 121 | ), 122 | Row( 123 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 124 | children: [ 125 | GestureDetector( 126 | onTap: () async { 127 | await _responce(false); 128 | }, 129 | child: Container( 130 | height: size.width * .18, 131 | width: size.width * .18, 132 | decoration: BoxDecoration( 133 | shape: BoxShape.circle, 134 | color: Colors.redAccent, 135 | ), 136 | child: Icon( 137 | Icons.call_end, 138 | color: Colors.white, 139 | size: size.width / 18.0, 140 | ), 141 | ), 142 | ), 143 | GestureDetector( 144 | onTap: () async { 145 | await _responce(true); 146 | }, 147 | child: Container( 148 | height: size.width * .18, 149 | width: size.width * .18, 150 | decoration: BoxDecoration( 151 | shape: BoxShape.circle, 152 | color: Colors.green, 153 | ), 154 | child: Icon( 155 | Icons.call, 156 | color: Colors.white, 157 | size: size.width / 18.0, 158 | ), 159 | ), 160 | ), 161 | ], 162 | ), 163 | ], 164 | ); 165 | }, 166 | ), 167 | ], 168 | ), 169 | ), 170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/src/page/home_page/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:firebase_messaging/firebase_messaging.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:project_message_demo/src/page/notification_page/notification_page.dart'; 7 | import 'package:project_message_demo/src/page/receive_page/receive_page.dart'; 8 | 9 | class HomePage extends StatefulWidget { 10 | final String uid; 11 | final Size size; 12 | HomePage({ 13 | this.uid, 14 | this.size, 15 | }); 16 | @override 17 | State createState() => _HomePageState(); 18 | } 19 | 20 | class _HomePageState extends State with WidgetsBindingObserver { 21 | final Firestore _db = Firestore.instance; 22 | final FirebaseMessaging _fcm = FirebaseMessaging(); 23 | StreamSubscription iosSubscription; 24 | 25 | _saveDeviceToken() async { 26 | // Get the current user 27 | String uid = widget.uid; 28 | print(widget.uid); 29 | // FirebaseUser user = await _auth.currentUser(); 30 | 31 | // Get the token for this device 32 | String fcmToken = await _fcm.getToken(); 33 | 34 | // Save it to Firestore 35 | if (fcmToken != null) { 36 | var tokens = _db.collection('users').document(uid); 37 | 38 | await tokens.updateData({ 39 | 'token': fcmToken, 40 | }); 41 | } 42 | } 43 | 44 | @override 45 | void initState() { 46 | super.initState(); 47 | 48 | if (Platform.isIOS) { 49 | iosSubscription = _fcm.onIosSettingsRegistered.listen((data) { 50 | _saveDeviceToken(); 51 | }); 52 | 53 | _fcm.requestNotificationPermissions(IosNotificationSettings()); 54 | } else { 55 | _saveDeviceToken(); 56 | } 57 | 58 | _fcm.configure( 59 | onMessage: (Map message) async { 60 | message['notification']['title'] == 'Admin' 61 | ? print('lambiengcode') 62 | : showDialog( 63 | context: context, 64 | barrierDismissible: false, // user must tap button! 65 | builder: (BuildContext context) { 66 | return AlertDialog( 67 | title: Text( 68 | message['notification']['title'], 69 | style: TextStyle( 70 | color: Colors.black, 71 | fontSize: widget.size.width / 21.5, 72 | fontWeight: FontWeight.bold, 73 | ), 74 | ), 75 | content: SingleChildScrollView( 76 | child: ListBody( 77 | children: [ 78 | Text( 79 | message['notification']['body'], 80 | style: TextStyle( 81 | color: Colors.grey.shade800, 82 | fontSize: widget.size.width / 25.0, 83 | fontWeight: FontWeight.w400, 84 | ), 85 | ), 86 | ], 87 | ), 88 | ), 89 | actions: [ 90 | TextButton( 91 | child: Text('Close'), 92 | onPressed: () { 93 | Navigator.of(context).pop(); 94 | }, 95 | ), 96 | TextButton( 97 | child: Text('Notifications'), 98 | onPressed: () { 99 | Navigator.of(context).pop(); 100 | Navigator.of(context).push( 101 | MaterialPageRoute( 102 | builder: (context) => NotificationPage(), 103 | ), 104 | ); 105 | }, 106 | ), 107 | ], 108 | ); 109 | }, 110 | ); 111 | }, 112 | ); 113 | } 114 | 115 | @override 116 | Widget build(BuildContext context) { 117 | return Scaffold( 118 | body: ReceivePage(), 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/page/notification_page/notification_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_icons/flutter_icons.dart'; 4 | import 'package:project_message_demo/src/widget/notification_widget/notification_item.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import '../../model/user.dart'; 8 | 9 | class NotificationPage extends StatefulWidget { 10 | @override 11 | State createState() => _NotificationPageState(); 12 | } 13 | 14 | class _NotificationPageState extends State { 15 | Future _updateNotification(index) async { 16 | Firestore.instance.runTransaction((Transaction transaction) async { 17 | DocumentSnapshot snapshot = await transaction.get(index); 18 | bool notification = snapshot['notifications']; 19 | await transaction.update(index, { 20 | 'notifications': !notification, 21 | }); 22 | }); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final user = Provider.of(context); 28 | final sizeWidth = MediaQuery.of(context).size.width; 29 | return Scaffold( 30 | appBar: AppBar( 31 | brightness: Brightness.light, 32 | elevation: 1.5, 33 | backgroundColor: Colors.white, 34 | centerTitle: true, 35 | leading: IconButton( 36 | icon: Icon( 37 | Feather.arrow_left, 38 | color: Colors.grey.shade800, 39 | size: sizeWidth / 14.5, 40 | ), 41 | onPressed: () { 42 | Navigator.of(context).pop(context); 43 | }, 44 | ), 45 | title: Text( 46 | 'Notifications', 47 | style: TextStyle( 48 | fontSize: sizeWidth / 18.8, 49 | fontWeight: FontWeight.bold, 50 | color: Colors.grey.shade800, 51 | height: 2, 52 | ), 53 | ), 54 | actions: [ 55 | StreamBuilder( 56 | stream: Firestore.instance 57 | .collection('users') 58 | .where('id', isEqualTo: user.uid) 59 | .snapshots(), 60 | builder: (context, AsyncSnapshot snapshot) { 61 | if (!snapshot.hasData) { 62 | return IconButton( 63 | icon: Icon( 64 | Feather.bell, 65 | color: Colors.grey.shade800, 66 | size: sizeWidth / 16.5, 67 | ), 68 | onPressed: () {}, 69 | ); 70 | } 71 | 72 | return IconButton( 73 | icon: Icon( 74 | snapshot.data.documents[0]['notifications'] 75 | ? Feather.bell 76 | : Feather.bell_off, 77 | color: Colors.grey.shade800, 78 | size: sizeWidth / 16.5, 79 | ), 80 | onPressed: () async { 81 | await _updateNotification( 82 | snapshot.data.documents[0].reference); 83 | }, 84 | ); 85 | }, 86 | ), 87 | ], 88 | ), 89 | body: Container( 90 | child: Column( 91 | children: [ 92 | Expanded( 93 | child: StreamBuilder( 94 | stream: Firestore.instance 95 | .collection('users') 96 | .where('id', isEqualTo: user.uid) 97 | .snapshots(), 98 | builder: (context, AsyncSnapshot snapshot) { 99 | if (!snapshot.hasData) { 100 | return Container(); 101 | } 102 | 103 | return StreamBuilder( 104 | stream: Firestore.instance 105 | .collection('notifications') 106 | .where('key', 107 | isEqualTo: snapshot.data.documents[0]['key']) 108 | .orderBy('publishAt', descending: true) 109 | .snapshots(), 110 | builder: (context, AsyncSnapshot result) { 111 | if (!result.hasData) { 112 | return Container(); 113 | } 114 | 115 | List docs = result.data.documents; 116 | 117 | docs 118 | .where((doc) { 119 | if (doc['all'] == false) { 120 | int count = 0; 121 | List members = doc['members']; 122 | for (int j = 0; j < members.length; j++) { 123 | if (user.uid == members[j]) { 124 | count++; 125 | } 126 | } 127 | 128 | return count == 0 ? true : false; 129 | } 130 | return false; 131 | }) // filter keys 132 | .toList() // create a copy to avoid concurrent modifications 133 | .forEach(docs.remove); 134 | 135 | return ListView.builder( 136 | itemCount: docs.length, 137 | itemBuilder: (context, index) { 138 | return NotificationItem( 139 | title: docs[index]['title'], 140 | body: docs[index]['body'], 141 | urlToImage: docs[index]['urlToImage'], 142 | ); 143 | }, 144 | ); 145 | }, 146 | ); 147 | }, 148 | ), 149 | ), 150 | ], 151 | ), 152 | ), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/src/page/receive_page/receive_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_icons/flutter_icons.dart'; 4 | import 'package:project_message_demo/src/animation/fade_animation.dart'; 5 | import 'package:project_message_demo/src/model/user.dart'; 6 | import 'package:project_message_demo/src/page/notification_page/notification_page.dart'; 7 | import 'package:project_message_demo/src/page/user/profile_page.dart'; 8 | import 'package:project_message_demo/src/widget/receive_widget/inbox_list.dart'; 9 | import 'package:provider/provider.dart'; 10 | import 'package:intl/intl.dart'; 11 | 12 | class ReceivePage extends StatefulWidget { 13 | @override 14 | State createState() => _ReceivePageState(); 15 | } 16 | 17 | class _ReceivePageState extends State { 18 | DateTime _fromDate; 19 | DateTime _toDate; 20 | List _states = [ 21 | 'All', 22 | 'Accept', 23 | 'Reject', 24 | 'Missing', 25 | 'Rejected Call', 26 | ]; 27 | String _state; 28 | String _from; 29 | String _to; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | _toDate = DateTime.now(); 35 | _fromDate = _toDate.subtract(Duration(days: 14)); 36 | _from = DateFormat('dd/MM/yyyy').format(_fromDate); 37 | _to = DateFormat('dd/MM/yyyy').format(_toDate); 38 | _state = _states[0]; 39 | } 40 | 41 | Future _selectDateFrom(BuildContext context) async { 42 | final DateTime picked = await showDatePicker( 43 | context: context, 44 | initialDate: _fromDate, 45 | firstDate: DateTime(2020, 10), 46 | lastDate: DateTime.now()); 47 | if (picked != null && picked != _fromDate) 48 | setState(() { 49 | if (_toDate.compareTo(picked) != -1) { 50 | _fromDate = picked; 51 | _from = DateFormat('dd/MM/yyyy').format(_fromDate); 52 | } 53 | }); 54 | 55 | Navigator.of(context).pop(context); 56 | showFilterBottomSheet(); 57 | } 58 | 59 | Future _selectDateTo(BuildContext context) async { 60 | final DateTime picked = await showDatePicker( 61 | context: context, 62 | initialDate: _toDate, 63 | firstDate: DateTime(2020, 10), 64 | lastDate: DateTime.now().add(Duration(days: 1))); 65 | if (picked != null && picked != _toDate) 66 | setState(() { 67 | if (_fromDate.compareTo(picked) != 1) { 68 | _toDate = picked; 69 | _to = DateFormat('dd/MM/yyyy').format(_toDate); 70 | } 71 | }); 72 | Navigator.of(context).pop(context); 73 | showFilterBottomSheet(); 74 | } 75 | 76 | void showFilterBottomSheet() { 77 | showModalBottomSheet( 78 | isScrollControlled: true, 79 | shape: RoundedRectangleBorder( 80 | borderRadius: BorderRadius.all( 81 | Radius.circular(12.0), 82 | ), 83 | ), 84 | context: context, 85 | builder: (context) { 86 | return _filterBottomSheet(context); 87 | }, 88 | ); 89 | } 90 | 91 | @override 92 | Widget build(BuildContext context) { 93 | Size size = MediaQuery.of(context).size; 94 | final user = Provider.of(context); 95 | return Scaffold( 96 | appBar: AppBar( 97 | brightness: Brightness.light, 98 | backgroundColor: Colors.white, 99 | elevation: 2.0, 100 | centerTitle: false, 101 | title: Row( 102 | mainAxisAlignment: MainAxisAlignment.start, 103 | crossAxisAlignment: CrossAxisAlignment.center, 104 | children: [ 105 | StreamBuilder( 106 | stream: Firestore.instance 107 | .collection('users') 108 | .where('id', isEqualTo: user.uid) 109 | .snapshots(), 110 | builder: (BuildContext context, 111 | AsyncSnapshot snapshot) { 112 | if (!snapshot.hasData) { 113 | return Container(); 114 | } 115 | 116 | String urlToImage = snapshot.data.documents[0]['urlToImage']; 117 | 118 | return Row( 119 | children: [ 120 | CircleAvatar( 121 | backgroundImage: urlToImage == '' 122 | ? AssetImage('images/avt.jpg') 123 | : NetworkImage(urlToImage), 124 | radius: 16.96), 125 | SizedBox( 126 | width: 6.8, 127 | ), 128 | Text( 129 | "History", 130 | style: TextStyle( 131 | fontSize: size.width / 18.25, 132 | fontWeight: FontWeight.bold, 133 | color: Colors.black, 134 | ), 135 | ), 136 | ], 137 | ); 138 | }, 139 | ), 140 | ], 141 | ), 142 | actions: [ 143 | IconButton( 144 | icon: Icon( 145 | Feather.sliders, 146 | size: size.width / 16.5, 147 | color: Colors.grey.shade800, 148 | ), 149 | onPressed: () { 150 | showFilterBottomSheet(); 151 | }, 152 | ), 153 | IconButton( 154 | icon: Icon( 155 | Feather.bell, 156 | size: size.width / 16.5, 157 | color: Colors.grey.shade800, 158 | ), 159 | onPressed: () { 160 | Navigator.of(context).push( 161 | MaterialPageRoute( 162 | builder: (context) => NotificationPage(), 163 | ), 164 | ); 165 | }, 166 | ), 167 | IconButton( 168 | icon: Icon( 169 | Feather.settings, 170 | size: size.width / 16.5, 171 | color: Colors.grey.shade800, 172 | ), 173 | onPressed: () { 174 | Navigator.of(context).push( 175 | MaterialPageRoute(builder: (context) => EditProfilePage())); 176 | }, 177 | ), 178 | SizedBox( 179 | width: 4.0, 180 | ), 181 | ], 182 | ), 183 | body: Container( 184 | height: size.height, 185 | width: size.width, 186 | child: Column( 187 | children: [ 188 | SizedBox( 189 | height: 12.0, 190 | ), 191 | Expanded( 192 | child: StreamBuilder( 193 | stream: _state == 'All' 194 | ? Firestore.instance 195 | .collection('requests') 196 | .where('receiveID', isEqualTo: user.uid) 197 | //.where('sortVal', isGreaterThanOrEqualTo: _fromDate.millisecondsSinceEpoch) 198 | .snapshots() 199 | : Firestore.instance 200 | .collection('requests') 201 | .where('receiveID', isEqualTo: user.uid) 202 | .where('responce', isEqualTo: _state) 203 | .snapshots(), 204 | builder: (BuildContext context, 205 | AsyncSnapshot snapshot) { 206 | if (!snapshot.hasData) { 207 | return Container(); 208 | } 209 | 210 | List docs = snapshot.data.documents; 211 | 212 | //filter 213 | docs 214 | .where((doc) { 215 | Timestamp responsedTime = doc['responcedTime']; 216 | DateTime response = responsedTime.toDate(); 217 | return response.compareTo(_toDate) == 1 || 218 | response.compareTo(_fromDate) == -1; 219 | }) // filter keys 220 | .toList() // create a copy to avoid concurrent modifications 221 | .forEach(docs.remove); 222 | //sort 223 | for (int i = 0; i < docs.length - 1; i++) { 224 | for (int j = 0; j < docs.length - 1 - i; j++) { 225 | Timestamp t1 = docs[j]['responcedTime']; 226 | Timestamp t2 = docs[j + 1]['responcedTime']; 227 | if (t1.compareTo(t2) == -1) { 228 | DocumentSnapshot temp = docs[j]; 229 | docs[j] = docs[j + 1]; 230 | docs[j + 1] = temp; 231 | } 232 | } 233 | } 234 | 235 | return FadeAnimation( 236 | .25, 237 | InboxList( 238 | documents: docs, 239 | ), 240 | ); 241 | }, 242 | ), 243 | ) 244 | ], 245 | ), 246 | ), 247 | ); 248 | } 249 | 250 | Widget _filterBottomSheet(context) { 251 | Size size = MediaQuery.of(context).size; 252 | return Container( 253 | height: size.height * .3, 254 | child: Column( 255 | children: [ 256 | SizedBox( 257 | height: 28.0, 258 | ), 259 | Row( 260 | children: [ 261 | SizedBox( 262 | width: 20.0, 263 | ), 264 | Expanded( 265 | flex: 1, 266 | child: Text( 267 | 'From', 268 | style: TextStyle( 269 | color: Colors.grey.shade800, 270 | fontSize: size.width / 23.5, 271 | fontWeight: FontWeight.w600, 272 | ), 273 | ), 274 | ), 275 | Expanded( 276 | flex: 5, 277 | child: GestureDetector( 278 | onTap: () async { 279 | _selectDateFrom(context); 280 | }, 281 | child: Container( 282 | padding: EdgeInsets.only( 283 | left: 18.0, 284 | right: 12.0, 285 | top: 12.0, 286 | bottom: 12.0, 287 | ), 288 | margin: EdgeInsets.symmetric( 289 | horizontal: 12.0, 290 | vertical: 8.0, 291 | ), 292 | decoration: BoxDecoration( 293 | borderRadius: BorderRadius.all(Radius.circular(6.0)), 294 | color: Colors.grey.shade50, 295 | boxShadow: [ 296 | BoxShadow( 297 | color: Color(0xFFABBAD5), 298 | spreadRadius: .8, 299 | blurRadius: 2.0, 300 | offset: Offset(0, 2.0), // changes position of shadow 301 | ), 302 | ], 303 | ), 304 | child: Row( 305 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 306 | children: [ 307 | Text( 308 | _from, 309 | style: TextStyle( 310 | color: Colors.grey.shade800, 311 | fontSize: size.width / 28.0, 312 | fontWeight: FontWeight.w400, 313 | ), 314 | ), 315 | Icon( 316 | Feather.calendar, 317 | size: size.width / 20, 318 | color: Colors.grey.shade700, 319 | ), 320 | ], 321 | ), 322 | ), 323 | ), 324 | ), 325 | ], 326 | ), 327 | Row( 328 | children: [ 329 | SizedBox( 330 | width: 20.0, 331 | ), 332 | Expanded( 333 | flex: 1, 334 | child: Text( 335 | 'To', 336 | style: TextStyle( 337 | color: Colors.grey.shade800, 338 | fontSize: size.width / 23.5, 339 | fontWeight: FontWeight.w600, 340 | ), 341 | ), 342 | ), 343 | Expanded( 344 | flex: 5, 345 | child: GestureDetector( 346 | onTap: () async { 347 | await _selectDateTo(context); 348 | }, 349 | child: Container( 350 | padding: EdgeInsets.only( 351 | left: 18.0, 352 | right: 12.0, 353 | top: 12.0, 354 | bottom: 12.0, 355 | ), 356 | margin: EdgeInsets.symmetric( 357 | horizontal: 12.0, 358 | vertical: 8.0, 359 | ), 360 | decoration: BoxDecoration( 361 | borderRadius: BorderRadius.all(Radius.circular(6.0)), 362 | color: Colors.grey.shade50, 363 | boxShadow: [ 364 | BoxShadow( 365 | color: Color(0xFFABBAD5), 366 | spreadRadius: .8, 367 | blurRadius: 2.0, 368 | offset: Offset(0, 2.0), // changes position of shadow 369 | ), 370 | ], 371 | ), 372 | child: Row( 373 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 374 | children: [ 375 | Text( 376 | _to, 377 | style: TextStyle( 378 | color: Colors.grey.shade800, 379 | fontSize: size.width / 28.0, 380 | fontWeight: FontWeight.w400, 381 | ), 382 | ), 383 | Icon( 384 | Feather.calendar, 385 | size: size.width / 20, 386 | color: Colors.grey.shade700, 387 | ), 388 | ], 389 | ), 390 | ), 391 | ), 392 | ), 393 | ], 394 | ), 395 | Row( 396 | children: [ 397 | SizedBox( 398 | width: 20.0, 399 | ), 400 | Expanded( 401 | flex: 1, 402 | child: Text( 403 | 'State', 404 | style: TextStyle( 405 | color: Colors.grey.shade800, 406 | fontSize: size.width / 22.5, 407 | fontWeight: FontWeight.w600, 408 | ), 409 | ), 410 | ), 411 | Expanded( 412 | flex: 5, 413 | child: Container( 414 | padding: EdgeInsets.only(left: 18.0, right: 12.0), 415 | margin: EdgeInsets.symmetric( 416 | horizontal: 12.0, 417 | vertical: 8.0, 418 | ), 419 | decoration: BoxDecoration( 420 | borderRadius: BorderRadius.all(Radius.circular(6.0)), 421 | color: Colors.grey.shade50, 422 | boxShadow: [ 423 | BoxShadow( 424 | color: Color(0xFFABBAD5), 425 | spreadRadius: .8, 426 | blurRadius: 2.0, 427 | offset: Offset(0, 2.0), // changes position of shadow 428 | ), 429 | ], 430 | ), 431 | child: DropdownButtonHideUnderline( 432 | child: DropdownButtonFormField( 433 | icon: Icon( 434 | Feather.hash, 435 | size: size.width / 20, 436 | color: Colors.grey.shade700, 437 | ), 438 | iconEnabledColor: Colors.grey.shade800, 439 | decoration: InputDecoration( 440 | border: InputBorder.none, 441 | ), 442 | value: _state, 443 | items: _states.map((state) { 444 | return DropdownMenuItem( 445 | value: state, 446 | child: Text( 447 | state, 448 | style: TextStyle( 449 | fontSize: size.width / 24, 450 | color: Colors.grey.shade800, 451 | ), 452 | )); 453 | }).toList(), 454 | onChanged: (val) { 455 | setState(() { 456 | _state = val; 457 | }); 458 | }, 459 | ), 460 | ), 461 | ), 462 | ), 463 | ], 464 | ), 465 | ], 466 | ), 467 | ); 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /lib/src/page/receive_page/room_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/rendering.dart'; 5 | import 'package:flutter_icons/flutter_icons.dart'; 6 | 7 | class ChatRoomPage extends StatefulWidget { 8 | final String idRequest; 9 | final String idSend; 10 | final String idReceive; 11 | final String responce; 12 | final Timestamp publishAt; 13 | final Timestamp responcedTime; 14 | final String urlToImage; 15 | 16 | ChatRoomPage({ 17 | this.idRequest, 18 | this.idSend, 19 | this.idReceive, 20 | this.publishAt, 21 | this.responcedTime, 22 | this.responce, 23 | this.urlToImage, 24 | }); 25 | 26 | @override 27 | State createState() => _ChatRoomPageState(); 28 | } 29 | 30 | class _ChatRoomPageState extends State { 31 | DateTime responcedTime; 32 | DateTime publishAt; 33 | String _publish; 34 | String _responced; 35 | Color _color; 36 | 37 | String checkDate(int input) { 38 | if (input < 10) { 39 | return '0$input'; 40 | } else { 41 | return '$input'; 42 | } 43 | } 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | responcedTime = widget.responcedTime.toDate(); 49 | publishAt = widget.publishAt.toDate(); 50 | _color = widget.responce == 'Accept' 51 | ? Colors.green 52 | : widget.responce == 'Reject' 53 | ? Colors.blueAccent 54 | : widget.responce == 'Missing' 55 | ? Colors.redAccent 56 | : Colors.amber.shade700; 57 | 58 | _publish = 59 | '${checkDate(publishAt.hour)}:${checkDate(publishAt.minute)}:${checkDate(publishAt.second)} at ' + 60 | '${checkDate(publishAt.day)}/${checkDate(publishAt.month)}/${checkDate(publishAt.year)}'; 61 | 62 | _responced = 63 | '${checkDate(responcedTime.hour)}:${checkDate(responcedTime.minute)}:${checkDate(responcedTime.second)} at ' + 64 | '${checkDate(responcedTime.day)}/${checkDate(responcedTime.month)}/${checkDate(responcedTime.year)}'; 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | Size size = MediaQuery.of(context).size; 70 | 71 | return Container( 72 | height: size.height * .77, 73 | child: Column( 74 | crossAxisAlignment: CrossAxisAlignment.start, 75 | children: [ 76 | Container( 77 | height: 60.0, 78 | width: size.width, 79 | child: Row( 80 | crossAxisAlignment: CrossAxisAlignment.center, 81 | children: [ 82 | SizedBox( 83 | width: 8.0, 84 | ), 85 | IconButton( 86 | icon: Icon( 87 | Feather.arrow_left, 88 | color: Colors.grey.shade700, 89 | size: size.width / 16.0, 90 | ), 91 | onPressed: () { 92 | Navigator.of(context).pop(context); 93 | }, 94 | ), 95 | SizedBox( 96 | width: 24.0, 97 | ), 98 | Padding( 99 | padding: EdgeInsets.only(top: 2.0), 100 | child: Text( 101 | 'State :', 102 | style: TextStyle( 103 | fontSize: size.width / 20.2, 104 | color: Colors.grey.shade800, 105 | fontWeight: FontWeight.bold, 106 | ), 107 | ), 108 | ), 109 | SizedBox( 110 | width: 20.0, 111 | ), 112 | Icon( 113 | widget.responce == 'Accept' 114 | ? Icons.check 115 | : widget.responce == 'Reject' 116 | ? Icons.close 117 | : widget.responce == 'Missing' 118 | ? Icons.call_missed 119 | : Icons.call_end, 120 | color: _color, 121 | size: size.width / 16.0, 122 | ), 123 | ], 124 | ), 125 | ), 126 | Divider( 127 | height: .8, 128 | thickness: .8, 129 | color: Colors.grey.shade200, 130 | ), 131 | Container( 132 | height: size.height * .35, 133 | width: size.width, 134 | decoration: BoxDecoration( 135 | image: DecorationImage( 136 | image: widget.urlToImage == '' 137 | ? AssetImage( 138 | 'images/avt.jpg', 139 | ) 140 | : NetworkImage( 141 | widget.urlToImage, 142 | ), 143 | fit: BoxFit.cover, 144 | ), 145 | ), 146 | ), 147 | SizedBox( 148 | height: 20.0, 149 | ), 150 | Padding( 151 | padding: EdgeInsets.symmetric( 152 | horizontal: 20.0, 153 | ), 154 | child: Column( 155 | crossAxisAlignment: CrossAxisAlignment.start, 156 | children: [ 157 | RichText( 158 | overflow: TextOverflow.visible, 159 | text: TextSpan(children: [ 160 | TextSpan( 161 | text: 'Responce\t\t:\t\t', 162 | style: TextStyle( 163 | fontSize: size.width / 24.0, 164 | color: Colors.grey.shade800, 165 | fontWeight: FontWeight.bold, 166 | ), 167 | ), 168 | TextSpan( 169 | text: widget.responce, 170 | style: TextStyle( 171 | fontSize: size.width / 24.0, 172 | color: _color, 173 | fontWeight: FontWeight.w600, 174 | ), 175 | ), 176 | ]), 177 | ), 178 | SizedBox( 179 | height: 12.0, 180 | ), 181 | RichText( 182 | overflow: TextOverflow.visible, 183 | text: TextSpan(children: [ 184 | TextSpan( 185 | text: 'Responses Time\t:\t', 186 | style: TextStyle( 187 | fontSize: size.width / 24.0, 188 | color: Colors.grey.shade800, 189 | fontWeight: FontWeight.bold, 190 | ), 191 | ), 192 | TextSpan( 193 | text: _responced, 194 | style: TextStyle( 195 | fontSize: size.width / 24.0, 196 | color: Colors.grey.shade800, 197 | fontWeight: FontWeight.w600, 198 | ), 199 | ), 200 | ]), 201 | ), 202 | SizedBox( 203 | height: 12.0, 204 | ), 205 | RichText( 206 | overflow: TextOverflow.visible, 207 | text: TextSpan(children: [ 208 | TextSpan( 209 | text: 'PublishAt\t:\t', 210 | style: TextStyle( 211 | fontSize: size.width / 24.0, 212 | color: Colors.grey.shade800, 213 | fontWeight: FontWeight.bold, 214 | ), 215 | ), 216 | TextSpan( 217 | text: _publish, 218 | style: TextStyle( 219 | fontSize: size.width / 24.0, 220 | color: Colors.grey.shade700, 221 | fontWeight: FontWeight.w600, 222 | ), 223 | ), 224 | ]), 225 | ), 226 | ], 227 | ), 228 | ), 229 | ], 230 | ), 231 | ); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /lib/src/page/request_page.dart/request_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_icons/flutter_icons.dart'; 4 | import 'package:project_message_demo/src/model/user.dart'; 5 | import 'package:project_message_demo/src/widget/search_widget/user_card.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class RequestPage extends StatefulWidget { 9 | @override 10 | State createState() => _RequestPageState(); 11 | } 12 | 13 | class _RequestPageState extends State { 14 | String searchKey = ''; 15 | @override 16 | Widget build(BuildContext context) { 17 | Size size = MediaQuery.of(context).size; 18 | final user = Provider.of(context); 19 | return Scaffold( 20 | appBar: AppBar( 21 | backgroundColor: Colors.white, 22 | centerTitle: false, 23 | elevation: 2.0, 24 | title: Row( 25 | children: [ 26 | StreamBuilder( 27 | stream: Firestore.instance 28 | .collection('users') 29 | .where('id', isEqualTo: user.uid) 30 | .snapshots(), 31 | builder: (BuildContext context, 32 | AsyncSnapshot snapshot) { 33 | if (!snapshot.hasData) { 34 | return Container(); 35 | } 36 | 37 | String urlToImage = snapshot.data.documents[0]['urlToImage']; 38 | 39 | return CircleAvatar( 40 | backgroundColor: Colors.blueAccent, 41 | radius: 16.98, 42 | child: CircleAvatar( 43 | backgroundImage: urlToImage == '' 44 | ? AssetImage('images/avt.jpg') 45 | : NetworkImage(urlToImage), 46 | radius: 16.88, 47 | ), 48 | ); 49 | }, 50 | ), 51 | Expanded( 52 | child: TextFormField( 53 | autofocus: true, 54 | style: TextStyle( 55 | color: Colors.black87, 56 | fontSize: size.width / 22.8, 57 | fontWeight: FontWeight.w400, 58 | ), 59 | onChanged: (val) { 60 | setState(() { 61 | searchKey = val.trim(); 62 | }); 63 | }, 64 | decoration: InputDecoration( 65 | contentPadding: EdgeInsets.only(left: 16.0), 66 | hintText: 'Search', 67 | hintStyle: TextStyle( 68 | color: Colors.grey.shade600, 69 | fontSize: size.width / 22.5, 70 | fontWeight: FontWeight.w400, 71 | ), 72 | filled: true, 73 | fillColor: Colors.transparent, 74 | border: OutlineInputBorder( 75 | borderSide: BorderSide.none, 76 | ), 77 | ), 78 | ), 79 | ), 80 | ], 81 | ), 82 | actions: [ 83 | IconButton( 84 | icon: Icon( 85 | Feather.sliders, 86 | size: size.width / 16.0, 87 | color: Colors.grey.shade800, 88 | ), 89 | onPressed: () {}, 90 | ), 91 | ], 92 | ), 93 | body: searchKey.length == 0 94 | ? Container() 95 | : StreamBuilder( 96 | stream: Firestore.instance 97 | .collection('users') 98 | .orderBy('username', descending: false) 99 | .limit(200) 100 | .snapshots(), 101 | builder: (BuildContext context, 102 | AsyncSnapshot snapshot) { 103 | if (!snapshot.hasData) { 104 | return Container( 105 | child: Center( 106 | child: CircularProgressIndicator(), 107 | ), 108 | ); 109 | } 110 | 111 | List docs = snapshot.data.documents; 112 | 113 | for (int i = 0; i < docs.length; i++) { 114 | if (docs[i]['phone'] 115 | .toString() 116 | .toLowerCase() 117 | .startsWith(searchKey.toLowerCase())) { 118 | continue; 119 | } else { 120 | docs.removeAt(i); 121 | } 122 | } 123 | 124 | for (int i = 0; i < docs.length; i++) { 125 | if (user.uid == docs[i]['id']) { 126 | docs.removeAt(i); 127 | } 128 | } 129 | 130 | return docs.length == 0 131 | ? Container( 132 | height: 0.0, 133 | ) 134 | : ListView.builder( 135 | padding: EdgeInsets.only(top: 12.5), 136 | itemCount: docs.length, 137 | itemBuilder: (context, index) { 138 | return UserCard( 139 | user: docs[index], 140 | ); 141 | }, 142 | ); 143 | }, 144 | ), 145 | ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/src/service/auth.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:project_message_demo/src/model/user.dart'; 4 | 5 | class AuthService { 6 | final FirebaseAuth _auth = FirebaseAuth.instance; 7 | //final GoogleSignIn _googleSignIn = GoogleSignIn(scopes: ['email']); 8 | 9 | //create user obj based on FirebaseUser 10 | User _userFromFirebaseUser(FirebaseUser user) { 11 | return user != null ? User(uid: user.uid) : null; 12 | } 13 | 14 | //auth change user stream 15 | Stream get user { 16 | return _auth.onAuthStateChanged.map(_userFromFirebaseUser); 17 | } 18 | 19 | //sign in anon 20 | //cannot use in purchase 21 | Future signInAnon() async { 22 | try { 23 | AuthResult result = await _auth.signInAnonymously(); 24 | FirebaseUser user = result.user; 25 | 26 | //create info client 27 | await _createDataUser(user.uid, user.uid, '', '', ''); 28 | 29 | return _userFromFirebaseUser(user); 30 | } catch (e) { 31 | print(e.toString()); 32 | return null; 33 | } 34 | } 35 | 36 | //sign in email and password 37 | Future signInWithEmailAndPassword(String email, String password) async { 38 | try { 39 | AuthResult result = await _auth.signInWithEmailAndPassword( 40 | email: email, password: password); 41 | FirebaseUser user = result.user; 42 | 43 | return _userFromFirebaseUser(user); 44 | } catch (e) { 45 | print(e.toString()); 46 | return null; 47 | } 48 | } 49 | 50 | //register with email & password 51 | Future registerWithEmailAndPassword(String email, String password, 52 | String phone, String dept, String company) async { 53 | try { 54 | AuthResult result = await _auth.createUserWithEmailAndPassword( 55 | email: email, password: password); 56 | FirebaseUser user = result.user; 57 | 58 | //create info client 59 | await _createDataUser(email, user.uid, phone, dept, company); 60 | 61 | return _userFromFirebaseUser(user); 62 | } catch (e) { 63 | print(e.toString()); 64 | } 65 | } 66 | 67 | // Future signInWithGoogle() async { 68 | // try{ 69 | // GoogleSignInAccount googleUser = await _googleSignIn.signIn(); 70 | // GoogleSignInAuthentication googleAuth = await googleUser.authentication; 71 | // 72 | // AuthCredential credential = GoogleAuthProvider.getCredential( 73 | // idToken: googleAuth.idToken, 74 | // accessToken: googleAuth.accessToken, 75 | // ); 76 | // 77 | // AuthResult result = await _auth.signInWithCredential(credential); 78 | // FirebaseUser user = result.user; 79 | // 80 | // _updateDataUserGoogle(user.email, 'Your phone', user.uid, user.photoUrl, user.displayName); 81 | // 82 | // return _userFromFirebaseUser(user); 83 | // }catch(e){ 84 | // print(e.toString()); 85 | // return null; 86 | // } 87 | // } 88 | 89 | Future sendPasswordResetEmail(String email) async { 90 | return _auth.sendPasswordResetEmail(email: email); 91 | } 92 | 93 | //sign out 94 | Future signOut() async { 95 | try { 96 | return await _auth.signOut(); 97 | } catch (e) { 98 | print(e.toString()); 99 | return null; 100 | } 101 | } 102 | 103 | Future _createDataUser(email, uid, phone, dept, company) async { 104 | Firestore.instance.collection('users').document(uid).setData({ 105 | 'email': email, 106 | 'id': uid, 107 | 'username': email.toString().substring(0, email.toString().length - 10), 108 | 'publishAt': DateTime.now(), 109 | 'phone': phone, 110 | 'urlToImage': '', 111 | 'dept': dept, 112 | 'key': company, 113 | }); 114 | } 115 | 116 | void _createListUserGoogle(email, uid) { 117 | Firestore.instance.collection('usersGoogle').document(uid).setData({ 118 | 'email': email, 119 | }); 120 | } 121 | 122 | void _updateDataUserGoogle(email, phone, uid, url, displayName) { 123 | DocumentReference documentReference = 124 | Firestore.instance.collection('usersGoogle').document(uid); 125 | 126 | Firestore.instance.runTransaction((Transaction transaction) async { 127 | DocumentSnapshot snapshot = await transaction.get(documentReference); 128 | if (snapshot.exists) { 129 | print("USING NOW"); 130 | } else { 131 | //create nor data 132 | _createDataUser(email, uid, phone, '', ''); 133 | } 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/src/widget/general/cached_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CachedImage extends StatelessWidget { 5 | final String url; 6 | 7 | CachedImage({ 8 | @required this.url, 9 | }); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return SizedBox( 14 | child: ClipRRect( 15 | borderRadius: BorderRadius.circular(6), 16 | child: CachedNetworkImage( 17 | imageUrl: url, 18 | placeholder: (context, url) => 19 | Center(child: CircularProgressIndicator()), 20 | errorWidget: (context, url, error) => Center( 21 | child: Icon( 22 | Icons.error, 23 | color: Colors.white.withOpacity(.88), 24 | ), 25 | ), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/widget/general/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Loading extends StatefulWidget { 4 | @override 5 | State createState() => _LoadingState(); 6 | } 7 | 8 | class _LoadingState extends State { 9 | @override 10 | void initState() { 11 | super.initState(); 12 | } 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | body: Container( 18 | child: Center( 19 | child: CircularProgressIndicator(), 20 | ), 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/widget/general/photo_viewer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:photo_view/photo_view.dart'; 3 | 4 | class PhotoViewer extends StatefulWidget { 5 | final String image; 6 | 7 | PhotoViewer({this.image}); 8 | 9 | @override 10 | State createState() => _PhotoViewerState(); 11 | } 12 | 13 | class _PhotoViewerState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | child: PhotoView( 18 | minScale: PhotoViewComputedScale.contained, 19 | maxScale: PhotoViewComputedScale.covered * 1.8, 20 | initialScale: PhotoViewComputedScale.contained, 21 | basePosition: Alignment.center, 22 | imageProvider: NetworkImage(widget.image), 23 | )); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/widget/notification_widget/notification_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../general/photo_viewer.dart'; 4 | 5 | class NotificationItem extends StatefulWidget { 6 | final String title; 7 | final String body; 8 | final String urlToImage; 9 | NotificationItem({ 10 | this.title, 11 | this.body, 12 | this.urlToImage, 13 | }); 14 | @override 15 | State createState() => _NotificationItemState(); 16 | } 17 | 18 | class _NotificationItemState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | final size = MediaQuery.of(context).size; 22 | return Container( 23 | margin: EdgeInsets.only( 24 | top: 10.0, 25 | ), 26 | padding: EdgeInsets.only( 27 | left: 20.0, 28 | right: 8.0, 29 | bottom: 10.0, 30 | ), 31 | decoration: BoxDecoration( 32 | border: Border( 33 | bottom: BorderSide( 34 | color: Colors.grey.shade300, 35 | width: .4, 36 | ))), 37 | child: Row( 38 | children: [ 39 | Expanded( 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.start, 42 | children: [ 43 | Text( 44 | widget.title, 45 | style: TextStyle( 46 | color: Colors.blueAccent, 47 | fontSize: size.width / 20.5, 48 | fontWeight: FontWeight.bold, 49 | ), 50 | ), 51 | SizedBox( 52 | height: 4.0, 53 | ), 54 | Text( 55 | widget.body, 56 | style: TextStyle( 57 | color: Colors.grey.shade800, 58 | fontSize: size.width / 25.0, 59 | fontWeight: FontWeight.w400, 60 | ), 61 | ), 62 | ], 63 | ), 64 | ), 65 | SizedBox( 66 | width: 20.0, 67 | ), 68 | widget.urlToImage == '' 69 | ? Container() 70 | : GestureDetector( 71 | onTap: () { 72 | Navigator.of(context).push( 73 | MaterialPageRoute( 74 | builder: (context) => PhotoViewer( 75 | image: widget.urlToImage, 76 | ), 77 | ), 78 | ); 79 | }, 80 | child: Container( 81 | height: 55.0, 82 | width: 55.0, 83 | decoration: BoxDecoration( 84 | shape: BoxShape.circle, 85 | border: Border.all( 86 | color: Colors.blueGrey.shade100, 87 | width: 1.8, 88 | ), 89 | image: DecorationImage( 90 | image: NetworkImage(widget.urlToImage), 91 | fit: BoxFit.cover, 92 | ), 93 | ), 94 | ), 95 | ), 96 | ], 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/src/widget/receive_widget/build_chat_line.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:project_message_demo/src/widget/general/cached_image.dart'; 4 | import 'package:project_message_demo/src/widget/general/photo_viewer.dart'; 5 | 6 | class BuildChatLine extends StatefulWidget { 7 | final String message; 8 | final String type; 9 | final String name; 10 | final String idUser; 11 | final int hour; 12 | final int min; 13 | final int color; 14 | final bool isMe; 15 | final bool seen; 16 | final bool isLast; 17 | final Timestamp publishAt; 18 | final index; 19 | 20 | BuildChatLine( 21 | {this.message, 22 | this.type, 23 | this.name, 24 | this.hour, 25 | this.min, 26 | this.publishAt, 27 | this.isMe, 28 | this.seen, 29 | this.isLast, 30 | this.index, 31 | this.idUser, 32 | this.color}); 33 | 34 | @override 35 | State createState() => _BuildChatLineState(); 36 | } 37 | 38 | class _BuildChatLineState extends State { 39 | String time = ''; 40 | bool showTime = false; 41 | int secondLeft = 0; 42 | 43 | Future _updateSeen() async { 44 | Firestore.instance.runTransaction((Transaction transaction) async { 45 | DocumentSnapshot snapshot = await transaction.get(widget.index); 46 | await transaction.update(widget.index, { 47 | 'seen': true, 48 | }); 49 | }); 50 | } 51 | 52 | @override 53 | void initState() { 54 | super.initState(); 55 | if (widget.seen == false && widget.isMe == false) { 56 | _updateSeen(); 57 | } 58 | var dateTime = DateTime.now(); 59 | DateTime datePublish = widget.publishAt.toDate(); 60 | secondLeft = dateTime.difference(datePublish).inSeconds; 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | final sizeWidth = MediaQuery.of(context).size.width; 66 | 67 | void setTime() { 68 | if (widget.hour < 10 && widget.min < 10) { 69 | setState(() { 70 | time = '0${widget.hour}:0${widget.min}'; 71 | }); 72 | } else if (widget.hour < 10) { 73 | setState(() { 74 | time = '0${widget.hour}:${widget.min}'; 75 | }); 76 | } else if (widget.min < 10) { 77 | setState(() { 78 | time = '${widget.hour}:0${widget.min}'; 79 | }); 80 | } else { 81 | setState(() { 82 | time = '${widget.hour}:${widget.min}'; 83 | }); 84 | } 85 | } 86 | 87 | Future _deleteMessage() async { 88 | Firestore.instance.runTransaction((Transaction transaction) async { 89 | DocumentSnapshot snapshot = await transaction.get(widget.index); 90 | await transaction.update(widget.index, { 91 | 'message': 'This message has been deleted.', 92 | 'type': 'deleted', 93 | }); 94 | }); 95 | } 96 | 97 | return Row( 98 | mainAxisAlignment: 99 | widget.isMe ? MainAxisAlignment.end : MainAxisAlignment.start, 100 | children: [ 101 | GestureDetector( 102 | onTap: () { 103 | if (widget.type == 'image') { 104 | Navigator.of(context).push(MaterialPageRoute( 105 | builder: (context) => PhotoViewer( 106 | image: widget.message, 107 | ))); 108 | } else { 109 | setState(() { 110 | showTime = !showTime; 111 | }); 112 | setTime(); 113 | } 114 | }, 115 | child: Column( 116 | crossAxisAlignment: 117 | widget.isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, 118 | children: [ 119 | Row( 120 | crossAxisAlignment: CrossAxisAlignment.end, 121 | mainAxisAlignment: MainAxisAlignment.end, 122 | children: [ 123 | senderLayout(), 124 | widget.isLast && widget.isMe 125 | ? widget.seen 126 | ? StreamBuilder( 127 | stream: Firestore.instance 128 | .collection('users') 129 | .where('id', isEqualTo: widget.idUser) 130 | .snapshots(), 131 | builder: (BuildContext context, 132 | AsyncSnapshot snapshot) { 133 | if (!snapshot.hasData) { 134 | return Container( 135 | height: 12.0, 136 | width: 12.0, 137 | decoration: BoxDecoration( 138 | shape: BoxShape.circle, 139 | color: Color(widget.color)), 140 | child: Icon( 141 | Icons.check, 142 | color: Colors.grey.shade400, 143 | size: 6.0, 144 | ), 145 | alignment: Alignment.center, 146 | ); 147 | } 148 | 149 | String image = 150 | snapshot.data.documents[0]['urlToImage']; 151 | 152 | return Container( 153 | height: 12.0, 154 | width: 12.0, 155 | decoration: BoxDecoration( 156 | shape: BoxShape.circle, 157 | image: DecorationImage( 158 | image: image == '' 159 | ? AssetImage('images/avt.jpg') 160 | : NetworkImage(image), 161 | fit: BoxFit.cover)), 162 | ); 163 | }, 164 | ) 165 | : Container( 166 | height: 12.0, 167 | width: 12.0, 168 | decoration: BoxDecoration( 169 | shape: BoxShape.circle, 170 | color: Color(widget.color), 171 | ), 172 | child: Icon( 173 | Icons.check, 174 | color: Colors.white, 175 | size: 6.0, 176 | ), 177 | alignment: Alignment.center, 178 | ) 179 | : Container( 180 | height: 0.0, 181 | ), 182 | ], 183 | ), 184 | showTime 185 | ? Padding( 186 | padding: EdgeInsets.only( 187 | left: widget.isMe ? 0 : 8, 188 | right: widget.isMe ? 14 : 0, 189 | bottom: 4.0), 190 | child: Text( 191 | time, 192 | style: TextStyle(fontSize: sizeWidth / 38), 193 | ), 194 | ) 195 | : Container( 196 | height: 0.0, 197 | ), 198 | ], 199 | ), 200 | ), 201 | ], 202 | ); 203 | } 204 | 205 | Widget senderLayout() { 206 | return Container( 207 | margin: EdgeInsets.only( 208 | top: 6.0, right: widget.isMe && widget.isLast == false ? 10 : 0), 209 | constraints: BoxConstraints( 210 | maxWidth: MediaQuery.of(context).size.width * 0.65, 211 | maxHeight: MediaQuery.of(context).size.height * 0.4), 212 | decoration: BoxDecoration( 213 | color: widget.type == 'image' 214 | ? Colors.grey.shade200 215 | : widget.isMe 216 | ? Colors.grey.shade200 217 | : Colors.grey.shade300, 218 | borderRadius: widget.type == 'image' 219 | ? BorderRadius.all(Radius.circular(8.0)) 220 | : BorderRadius.all(Radius.circular(30.0)), 221 | ), 222 | child: Padding( 223 | padding: widget.type == 'image' 224 | ? EdgeInsets.all(1.5) 225 | : EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), 226 | child: getMessage(), 227 | ), 228 | ); 229 | } 230 | 231 | getMessage() { 232 | return widget.type != 'image' 233 | ? Text( 234 | widget.type == 'text' 235 | ? widget.message 236 | : widget.isMe 237 | ? '${widget.message.replaceAll('username', 'You')}' 238 | : '${widget.message.replaceAll('username', widget.name)}', 239 | style: TextStyle( 240 | color: widget.isMe ? Colors.grey.shade800 : Colors.black, 241 | fontSize: MediaQuery.of(context).size.width / 24, 242 | fontWeight: FontWeight.w400), 243 | ) 244 | : widget.message != null 245 | ? CachedImage(url: widget.message) 246 | : Text("Error!"); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /lib/src/widget/receive_widget/inbox_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_icons/flutter_icons.dart'; 5 | import 'package:project_message_demo/src/page/receive_page/room_page.dart'; 6 | 7 | class InboxCard extends StatelessWidget { 8 | final String responce; 9 | final Timestamp publishAt; 10 | final String uid; 11 | final String roomID; 12 | final bool isMe; 13 | final bool seen; 14 | final bool request; 15 | final bool completed; 16 | final index; 17 | final DocumentSnapshot doc; 18 | 19 | InboxCard({ 20 | this.uid, 21 | this.responce, 22 | this.publishAt, 23 | this.roomID, 24 | this.isMe, 25 | this.seen, 26 | this.request, 27 | this.index, 28 | this.completed, 29 | this.doc, 30 | }); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | int hour = publishAt.toDate().hour; 35 | int min = publishAt.toDate().minute; 36 | String time = ''; 37 | String lastMes; 38 | 39 | lastMes = responce.replaceAll('''\n''', ' '); 40 | lastMes = lastMes.length > 23 ? lastMes.substring(0, 20) + '...' : lastMes; 41 | 42 | Color parseColor(String color) { 43 | String hex = color.replaceAll("#", ""); 44 | if (hex.isEmpty) hex = "ffffff"; 45 | if (hex.length == 3) { 46 | hex = 47 | '${hex.substring(0, 1)}${hex.substring(0, 1)}${hex.substring(1, 2)}${hex.substring(1, 2)}${hex.substring(2, 3)}${hex.substring(2, 3)}'; 48 | } 49 | Color col = Color(int.parse(hex, radix: 16)).withOpacity(1.0); 50 | return col; 51 | } 52 | 53 | if (hour < 10 && min < 10) { 54 | time = '0$hour:0$min'; 55 | } else if (hour < 10) { 56 | time = '0$hour:$min'; 57 | } else if (min < 10) { 58 | time = '$hour:0$min'; 59 | } else { 60 | time = '$hour:$min'; 61 | } 62 | 63 | void showHistoryBottomSheet() { 64 | showModalBottomSheet( 65 | isScrollControlled: true, 66 | context: context, 67 | builder: (context) { 68 | return ChatRoomPage( 69 | urlToImage: doc['urlToImage'], 70 | idRequest: doc['id'], 71 | idSend: doc['idSend'], 72 | idReceive: doc['idReceive'], 73 | publishAt: doc['publishAt'], 74 | responcedTime: doc['responcedTime'], 75 | responce: doc['responce'], 76 | ); 77 | }); 78 | } 79 | 80 | final double sizeWidth = MediaQuery.of(context).size.width; 81 | 82 | return StreamBuilder( 83 | stream: Firestore.instance 84 | .collection('users') 85 | .where('id', isEqualTo: uid) 86 | .snapshots(), 87 | builder: (BuildContext context, AsyncSnapshot snapshot) { 88 | if (!snapshot.hasData) { 89 | return Container( 90 | padding: EdgeInsets.fromLTRB(14, 0, 14, 8), 91 | decoration: BoxDecoration( 92 | color: seen ? Colors.transparent : Colors.white.withOpacity(.08), 93 | border: Border( 94 | bottom: BorderSide( 95 | color: Colors.white.withOpacity(.3), width: 0.04)), 96 | ), 97 | child: Row( 98 | crossAxisAlignment: CrossAxisAlignment.center, 99 | children: [ 100 | Container( 101 | height: sizeWidth / 6.4, 102 | width: sizeWidth / 6.4, 103 | decoration: BoxDecoration( 104 | border: Border.all(color: Colors.white10, width: 0.4), 105 | shape: BoxShape.circle, 106 | image: DecorationImage( 107 | image: AssetImage('images/avt.jpg'), 108 | fit: BoxFit.cover, 109 | ), 110 | ), 111 | ), 112 | SizedBox( 113 | width: 10.0, 114 | ), 115 | Container( 116 | width: sizeWidth * 0.70, 117 | child: Column( 118 | crossAxisAlignment: CrossAxisAlignment.start, 119 | children: [ 120 | Text( 121 | 'Username', 122 | style: TextStyle( 123 | fontSize: sizeWidth / 21.5, 124 | fontWeight: FontWeight.bold), 125 | ), 126 | SizedBox( 127 | height: 6.0, 128 | ), 129 | Row( 130 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 131 | children: [ 132 | Expanded( 133 | child: Text( 134 | isMe ? 'You: message' : 'Stranger: message', 135 | style: TextStyle( 136 | fontSize: sizeWidth / 28, 137 | ), 138 | ), 139 | ), 140 | SizedBox( 141 | width: 12.0, 142 | ), 143 | Text( 144 | time, 145 | style: TextStyle( 146 | fontSize: sizeWidth / 28, 147 | ), 148 | ), 149 | ], 150 | ), 151 | ], 152 | ), 153 | ), 154 | ], 155 | ), 156 | ); 157 | } 158 | 159 | String username = snapshot.data.documents[0]['username']; 160 | String urlToImage = snapshot.data.documents[0]['urlToImage']; 161 | 162 | return GestureDetector( 163 | onTap: () { 164 | showHistoryBottomSheet(); 165 | }, 166 | child: Container( 167 | padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.5), 168 | decoration: BoxDecoration( 169 | color: Colors.transparent, 170 | ), 171 | child: Row( 172 | crossAxisAlignment: CrossAxisAlignment.center, 173 | children: [ 174 | urlToImage == '' 175 | ? Container( 176 | padding: EdgeInsets.symmetric(horizontal: 4.0), 177 | height: sizeWidth / 6.5, 178 | width: sizeWidth / 6.5, 179 | decoration: BoxDecoration( 180 | shape: BoxShape.circle, 181 | image: DecorationImage( 182 | image: AssetImage('images/avt.jpg'), 183 | fit: BoxFit.cover), 184 | ), 185 | alignment: Alignment.bottomRight, 186 | ) 187 | : CachedNetworkImage( 188 | imageBuilder: (BuildContext context, index) { 189 | return Container( 190 | padding: EdgeInsets.symmetric(horizontal: 8.0), 191 | height: sizeWidth / 6.5, 192 | width: sizeWidth / 6.5, 193 | decoration: BoxDecoration( 194 | shape: BoxShape.circle, 195 | image: DecorationImage( 196 | image: NetworkImage(urlToImage), 197 | fit: BoxFit.cover), 198 | ), 199 | alignment: Alignment.bottomRight, 200 | ); 201 | }, 202 | imageUrl: urlToImage, 203 | placeholder: (context, url) => 204 | Center(child: CircularProgressIndicator()), 205 | errorWidget: (context, url, error) => Center( 206 | child: Icon( 207 | Icons.error, 208 | color: Colors.white.withOpacity(.88), 209 | ), 210 | ), 211 | ), 212 | SizedBox( 213 | width: 12.0, 214 | ), 215 | Container( 216 | width: sizeWidth * 0.70, 217 | child: Column( 218 | crossAxisAlignment: CrossAxisAlignment.start, 219 | children: [ 220 | Row( 221 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 222 | crossAxisAlignment: CrossAxisAlignment.center, 223 | children: [ 224 | Text( 225 | username, 226 | style: TextStyle( 227 | fontSize: sizeWidth / 20.5, 228 | fontWeight: FontWeight.bold, 229 | color: request 230 | ? Colors.redAccent 231 | : Colors.blueAccent, 232 | ), 233 | ), 234 | Text( 235 | time, 236 | style: TextStyle( 237 | color: Colors.grey.shade800, 238 | fontSize: sizeWidth / 28.0, 239 | fontWeight: isMe == false 240 | ? seen 241 | ? FontWeight.w400 242 | : FontWeight.bold 243 | : FontWeight.w400, 244 | ), 245 | ), 246 | ], 247 | ), 248 | SizedBox( 249 | height: 2.0, 250 | ), 251 | Row( 252 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 253 | crossAxisAlignment: CrossAxisAlignment.center, 254 | children: [ 255 | Expanded( 256 | child: RichText( 257 | softWrap: true, 258 | overflow: TextOverflow.visible, 259 | text: TextSpan( 260 | children: [ 261 | TextSpan( 262 | text: isMe ? 'You:\b' : '$username:\b', 263 | style: TextStyle( 264 | fontSize: sizeWidth / 26.0, 265 | fontWeight: FontWeight.w600, 266 | color: Colors.grey.shade800, 267 | ), 268 | ), 269 | TextSpan( 270 | text: lastMes, 271 | style: TextStyle( 272 | fontSize: sizeWidth / 26.0, 273 | fontWeight: isMe == false 274 | ? seen 275 | ? FontWeight.w400 276 | : FontWeight.bold 277 | : FontWeight.w400, 278 | color: Colors.grey.shade600, 279 | ), 280 | ), 281 | ], 282 | ), 283 | ), 284 | ), 285 | Icon( 286 | Feather.check, 287 | color: seen 288 | ? parseColor('#00CC00') 289 | : Colors.grey.shade600, 290 | size: sizeWidth / 18.5, 291 | ) 292 | ], 293 | ), 294 | ], 295 | ), 296 | ), 297 | ], 298 | ), 299 | ), 300 | ); 301 | }, 302 | ); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /lib/src/widget/receive_widget/inbox_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:project_message_demo/src/model/user.dart'; 4 | import 'package:project_message_demo/src/widget/receive_widget/inbox_card.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | class InboxList extends StatefulWidget { 8 | final List documents; 9 | 10 | InboxList({this.documents}); 11 | 12 | @override 13 | State createState() => _InboxListState(); 14 | } 15 | 16 | class _InboxListState extends State { 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | final user = Provider.of(context); 25 | 26 | return ListView.builder( 27 | padding: const EdgeInsets.only(top: 0.0), 28 | itemCount: widget.documents.length, 29 | itemBuilder: (context, index) { 30 | String room = widget.documents[index]['id']; 31 | String responce = widget.documents[index]['responce']; 32 | 33 | return InboxCard( 34 | responce: responce.length == 0 ? '[\"Image\"]' : responce, 35 | publishAt: widget.documents[index]['responcedTime'], 36 | roomID: room, 37 | uid: widget.documents[index]['idSend'], 38 | isMe: responce.length == 0 ? false : true, 39 | seen: true, 40 | request: widget.documents[index]['idSend'] == user.uid ? true : false, 41 | index: widget.documents[index].reference, 42 | completed: widget.documents[index]['completed'], 43 | doc: widget.documents[index], 44 | ); 45 | }, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/widget/receive_widget/input_bottom.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:firebase_storage/firebase_storage.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:image_picker/image_picker.dart'; 7 | import 'package:project_message_demo/src/model/user.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class InputBottom extends StatefulWidget { 11 | final bool request; 12 | final String idReceive; 13 | final String idRoom; 14 | final index; 15 | final bool available; 16 | 17 | InputBottom({ 18 | this.request, 19 | this.idReceive, 20 | this.idRoom, 21 | this.index, 22 | this.available, 23 | }); 24 | 25 | @override 26 | State createState() => _InputBottomState(); 27 | } 28 | 29 | class _InputBottomState extends State { 30 | bool available = false; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | available = widget.available; 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return available == false 41 | ? _unAvailable(context) 42 | : widget.request 43 | ? _requestType(context) 44 | : _receiveType(context); 45 | } 46 | 47 | Future _sendMessage( 48 | idSend, 49 | idReceive, 50 | id, 51 | message, 52 | type, 53 | ) async { 54 | Firestore.instance.runTransaction((Transaction transaction) async { 55 | CollectionReference reference = Firestore.instance.collection("inboxs"); 56 | await reference.add({ 57 | 'idSend': idSend, 58 | 'receiveID': idReceive, 59 | 'id': id, 60 | 'publishAt': DateTime.now(), 61 | 'message': message, 62 | 'seen': false, 63 | 'type': type, 64 | 'hour': DateTime.now().hour, 65 | 'min': DateTime.now().minute, 66 | }); 67 | }); 68 | 69 | setState(() { 70 | available = false; 71 | }); 72 | } 73 | 74 | Future _updateRoom() async { 75 | Firestore.instance.runTransaction((Transaction transaction) async { 76 | DocumentSnapshot snapshot = await transaction.get(widget.index); 77 | await transaction.update(widget.index, { 78 | 'completed': true, 79 | }); 80 | }); 81 | } 82 | 83 | //receive 84 | Future uploadImage(file) async { 85 | String fileName = DateTime.now().microsecondsSinceEpoch.toString(); 86 | StorageReference firebaseStorageRef = 87 | FirebaseStorage.instance.ref().child('Images').child(fileName); 88 | StorageUploadTask uploadTask = firebaseStorageRef.putFile(file); 89 | StorageTaskSnapshot taskSnapshot = await uploadTask.onComplete; 90 | var downUrl = await taskSnapshot.ref.getDownloadURL(); 91 | String url = downUrl.toString(); 92 | return url; 93 | } 94 | 95 | Future _pickImage(ImageSource source, User user) async { 96 | File selected = await ImagePicker.pickImage(source: source); 97 | if (selected != null) { 98 | String message = await uploadImage(selected); 99 | await _sendMessage( 100 | user.uid, widget.idReceive, widget.idRoom, message, 'image'); 101 | } 102 | } 103 | 104 | Widget _requestType(context) { 105 | Size size = MediaQuery.of(context).size; 106 | final user = Provider.of(context); 107 | return Container( 108 | child: Row( 109 | children: [ 110 | Expanded( 111 | flex: 1, 112 | child: GestureDetector( 113 | onTap: () async { 114 | await _sendMessage( 115 | user.uid, widget.idReceive, widget.idRoom, 'False', 'text'); 116 | }, 117 | child: Container( 118 | decoration: BoxDecoration( 119 | color: Colors.redAccent, 120 | ), 121 | alignment: Alignment.center, 122 | padding: EdgeInsets.symmetric(vertical: 18.0), 123 | child: Text( 124 | 'False', 125 | style: TextStyle( 126 | color: Colors.white, 127 | fontWeight: FontWeight.w600, 128 | fontSize: size.width / 24.0, 129 | ), 130 | ), 131 | ), 132 | ), 133 | ), 134 | Expanded( 135 | flex: 1, 136 | child: GestureDetector( 137 | onTap: () async { 138 | await _sendMessage( 139 | user.uid, 140 | widget.idReceive, 141 | widget.idRoom, 142 | 'True', 143 | 'text', 144 | ); 145 | await _updateRoom(); 146 | }, 147 | child: Container( 148 | decoration: BoxDecoration(color: Colors.blueAccent), 149 | alignment: Alignment.center, 150 | padding: EdgeInsets.symmetric(vertical: 18.0), 151 | child: Text( 152 | 'True', 153 | style: TextStyle( 154 | color: Colors.white, 155 | fontWeight: FontWeight.w600, 156 | fontSize: size.width / 24.0, 157 | ), 158 | ), 159 | ), 160 | ), 161 | ), 162 | ], 163 | ), 164 | ); 165 | } 166 | 167 | Widget _receiveType(context) { 168 | Size size = MediaQuery.of(context).size; 169 | final user = Provider.of(context); 170 | return Container( 171 | child: Row( 172 | children: [ 173 | Expanded( 174 | flex: 1, 175 | child: GestureDetector( 176 | onTap: () async { 177 | try { 178 | _pickImage(ImageSource.gallery, user); 179 | } on Exception catch (_) { 180 | throw Exception('Error on server'); 181 | } 182 | }, 183 | child: Container( 184 | decoration: BoxDecoration( 185 | color: Colors.grey, 186 | ), 187 | alignment: Alignment.center, 188 | padding: EdgeInsets.symmetric(vertical: 14.0), 189 | child: Icon( 190 | Icons.image, 191 | size: size.width / 14.0, 192 | color: Colors.white, 193 | ), 194 | ), 195 | ), 196 | ), 197 | Expanded( 198 | flex: 1, 199 | child: GestureDetector( 200 | onTap: () async { 201 | try { 202 | _pickImage(ImageSource.camera, user); 203 | } on Exception catch (_) { 204 | throw Exception('Error on server'); 205 | } 206 | }, 207 | child: Container( 208 | decoration: BoxDecoration(color: Colors.blueAccent), 209 | alignment: Alignment.center, 210 | padding: EdgeInsets.symmetric(vertical: 14.0), 211 | child: Icon( 212 | Icons.camera_alt, 213 | size: size.width / 14.0, 214 | color: Colors.white, 215 | ), 216 | ), 217 | ), 218 | ), 219 | ], 220 | ), 221 | ); 222 | } 223 | 224 | Widget _unAvailable(context) { 225 | Size size = MediaQuery.of(context).size; 226 | return Container( 227 | decoration: BoxDecoration( 228 | color: Colors.black.withOpacity(.8), 229 | ), 230 | alignment: Alignment.center, 231 | padding: EdgeInsets.symmetric(vertical: 18.0), 232 | child: Text( 233 | 'Waiting Reply', 234 | style: TextStyle( 235 | color: Colors.white, 236 | fontWeight: FontWeight.w600, 237 | fontSize: size.width / 26.5, 238 | ), 239 | ), 240 | ); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /lib/src/widget/search_widget/user_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:project_message_demo/src/model/user.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | class UserCard extends StatefulWidget { 7 | final DocumentSnapshot user; 8 | 9 | UserCard({this.user}); 10 | 11 | @override 12 | State createState() => _UserCardState(); 13 | } 14 | 15 | class _UserCardState extends State { 16 | Future _request( 17 | idSend, 18 | idReceive, 19 | ) async { 20 | String id = idSend + DateTime.now().microsecondsSinceEpoch.toString(); 21 | Firestore.instance.runTransaction((Transaction transaction) async { 22 | CollectionReference reference = Firestore.instance.collection("requests"); 23 | 24 | await reference.add({ 25 | 'idSend': idSend, 26 | 'receiveID': idReceive, 27 | 'id': id, 28 | 'publishAt': DateTime.now(), 29 | 'completed': false, 30 | 'responce': '', 31 | 'responcedTime': DateTime.now(), 32 | 'request': true, 33 | 'urlToImage': '', 34 | }); 35 | }); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | final sizeWidth = MediaQuery.of(context).size.width; 41 | final user = Provider.of(context); 42 | 43 | return GestureDetector( 44 | onTap: () {}, 45 | child: Container( 46 | padding: EdgeInsets.symmetric(horizontal: 14.0, vertical: 8.0), 47 | child: Row( 48 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 49 | children: [ 50 | Row( 51 | mainAxisAlignment: MainAxisAlignment.start, 52 | crossAxisAlignment: CrossAxisAlignment.center, 53 | children: [ 54 | CircleAvatar( 55 | backgroundImage: widget.user['urlToImage'] == '' 56 | ? AssetImage('images/avt.jpg') 57 | : NetworkImage(widget.user['urlToImage']), 58 | radius: 26.0, 59 | ), 60 | SizedBox( 61 | width: 12.0, 62 | ), 63 | Column( 64 | crossAxisAlignment: CrossAxisAlignment.start, 65 | children: [ 66 | Text( 67 | widget.user['username'], 68 | style: TextStyle( 69 | color: Colors.black, 70 | fontSize: sizeWidth / 21.5, 71 | fontWeight: FontWeight.w600, 72 | ), 73 | ), 74 | SizedBox( 75 | height: 6.0, 76 | ), 77 | Text( 78 | widget.user['phone'], 79 | style: TextStyle( 80 | color: Colors.black, 81 | fontSize: sizeWidth / 25.0, 82 | fontWeight: FontWeight.w400, 83 | ), 84 | ), 85 | ], 86 | ), 87 | ], 88 | ), 89 | StreamBuilder( 90 | stream: Firestore.instance 91 | .collection('requests') 92 | .where('idSend', isEqualTo: user.uid) 93 | .where('receiveID', isEqualTo: widget.user['id']) 94 | .where('completed', isEqualTo: false) 95 | .snapshots(), 96 | builder: (BuildContext context, 97 | AsyncSnapshot snapshot) { 98 | if (!snapshot.hasData) { 99 | return Container(); 100 | } 101 | 102 | int length = snapshot.data.documents.length; 103 | 104 | return GestureDetector( 105 | onTap: () async { 106 | if (length == 0) { 107 | await _request(user.uid, widget.user['id']); 108 | } else if (snapshot.data.documents[0]['id'] == '') { 109 | await _request(user.uid, widget.user['id']); 110 | } else {} 111 | }, 112 | child: Container( 113 | padding: EdgeInsets.symmetric( 114 | horizontal: 16.0, 115 | vertical: 8.0, 116 | ), 117 | decoration: BoxDecoration( 118 | color: length == 0 119 | ? Colors.transparent 120 | : Colors.blueAccent, 121 | border: Border.all( 122 | color: length == 0 123 | ? Colors.grey.shade600 124 | : Colors.blueAccent, 125 | width: 1.0, 126 | ), 127 | borderRadius: BorderRadius.all(Radius.circular( 128 | 4.0, 129 | ))), 130 | child: Text( 131 | length == 0 ? 'Request' : 'Requested', 132 | style: TextStyle( 133 | color: 134 | length == 0 ? Colors.grey.shade800 : Colors.white, 135 | fontSize: sizeWidth / 28.8, 136 | ), 137 | ), 138 | alignment: Alignment.center, 139 | ), 140 | ); 141 | }, 142 | ), 143 | ], 144 | ), 145 | ), 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | assets_audio_player: 5 | dependency: "direct main" 6 | description: 7 | name: assets_audio_player 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13+1" 11 | assets_audio_player_web: 12 | dependency: transitive 13 | description: 14 | name: assets_audio_player_web 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.0.13+1" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.5.0-nullsafety.3" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.1.0-nullsafety.3" 32 | cached_network_image: 33 | dependency: "direct main" 34 | description: 35 | name: cached_network_image 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.3.3" 39 | characters: 40 | dependency: transitive 41 | description: 42 | name: characters 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0-nullsafety.5" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0-nullsafety.3" 53 | clock: 54 | dependency: transitive 55 | description: 56 | name: clock 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.1.0-nullsafety.3" 60 | cloud_firestore: 61 | dependency: "direct main" 62 | description: 63 | name: cloud_firestore 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.13.7" 67 | cloud_firestore_platform_interface: 68 | dependency: transitive 69 | description: 70 | name: cloud_firestore_platform_interface 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.1.2" 74 | cloud_firestore_web: 75 | dependency: transitive 76 | description: 77 | name: cloud_firestore_web 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "0.1.1+2" 81 | cloud_functions: 82 | dependency: "direct main" 83 | description: 84 | name: cloud_functions 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.5.0" 88 | cloud_functions_platform_interface: 89 | dependency: transitive 90 | description: 91 | name: cloud_functions_platform_interface 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.1.0" 95 | cloud_functions_web: 96 | dependency: transitive 97 | description: 98 | name: cloud_functions_web 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.1.0" 102 | collection: 103 | dependency: transitive 104 | description: 105 | name: collection 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "1.15.0-nullsafety.5" 109 | convert: 110 | dependency: transitive 111 | description: 112 | name: convert 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "2.1.1" 116 | crypto: 117 | dependency: transitive 118 | description: 119 | name: crypto 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "2.1.5" 123 | cupertino_icons: 124 | dependency: "direct main" 125 | description: 126 | name: cupertino_icons 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.0.1+1" 130 | fake_async: 131 | dependency: transitive 132 | description: 133 | name: fake_async 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "1.2.0-nullsafety.3" 137 | ffi: 138 | dependency: transitive 139 | description: 140 | name: ffi 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "0.1.3" 144 | file: 145 | dependency: transitive 146 | description: 147 | name: file 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "5.2.1" 151 | firebase: 152 | dependency: transitive 153 | description: 154 | name: firebase 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "7.3.2" 158 | firebase_auth: 159 | dependency: "direct main" 160 | description: 161 | name: firebase_auth 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "0.16.1" 165 | firebase_auth_platform_interface: 166 | dependency: transitive 167 | description: 168 | name: firebase_auth_platform_interface 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.1.8" 172 | firebase_auth_web: 173 | dependency: transitive 174 | description: 175 | name: firebase_auth_web 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "0.1.3+1" 179 | firebase_core: 180 | dependency: transitive 181 | description: 182 | name: firebase_core 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "0.4.5" 186 | firebase_core_platform_interface: 187 | dependency: transitive 188 | description: 189 | name: firebase_core_platform_interface 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "1.0.4" 193 | firebase_core_web: 194 | dependency: transitive 195 | description: 196 | name: firebase_core_web 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "0.1.1+2" 200 | firebase_database: 201 | dependency: "direct main" 202 | description: 203 | name: firebase_database 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "3.1.6" 207 | firebase_messaging: 208 | dependency: "direct main" 209 | description: 210 | name: firebase_messaging 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "6.0.16" 214 | firebase_storage: 215 | dependency: "direct main" 216 | description: 217 | name: firebase_storage 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "3.1.6" 221 | flutter: 222 | dependency: "direct main" 223 | description: flutter 224 | source: sdk 225 | version: "0.0.0" 226 | flutter_blurhash: 227 | dependency: transitive 228 | description: 229 | name: flutter_blurhash 230 | url: "https://pub.dartlang.org" 231 | source: hosted 232 | version: "0.5.0" 233 | flutter_cache_manager: 234 | dependency: transitive 235 | description: 236 | name: flutter_cache_manager 237 | url: "https://pub.dartlang.org" 238 | source: hosted 239 | version: "2.0.0" 240 | flutter_icons: 241 | dependency: "direct main" 242 | description: 243 | name: flutter_icons 244 | url: "https://pub.dartlang.org" 245 | source: hosted 246 | version: "1.1.0" 247 | flutter_plugin_android_lifecycle: 248 | dependency: transitive 249 | description: 250 | name: flutter_plugin_android_lifecycle 251 | url: "https://pub.dartlang.org" 252 | source: hosted 253 | version: "1.0.11" 254 | flutter_test: 255 | dependency: "direct dev" 256 | description: flutter 257 | source: sdk 258 | version: "0.0.0" 259 | flutter_web_plugins: 260 | dependency: transitive 261 | description: flutter 262 | source: sdk 263 | version: "0.0.0" 264 | flutter_webrtc: 265 | dependency: "direct main" 266 | description: 267 | name: flutter_webrtc 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "0.2.8" 271 | http: 272 | dependency: transitive 273 | description: 274 | name: http 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "0.12.2" 278 | http_parser: 279 | dependency: transitive 280 | description: 281 | name: http_parser 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "3.1.4" 285 | image_picker: 286 | dependency: "direct main" 287 | description: 288 | name: image_picker 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "0.6.7+14" 292 | image_picker_platform_interface: 293 | dependency: transitive 294 | description: 295 | name: image_picker_platform_interface 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "1.1.1" 299 | intl: 300 | dependency: "direct main" 301 | description: 302 | name: intl 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "0.16.1" 306 | js: 307 | dependency: transitive 308 | description: 309 | name: js 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "0.6.3-nullsafety.3" 313 | matcher: 314 | dependency: transitive 315 | description: 316 | name: matcher 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "0.12.10-nullsafety.3" 320 | meta: 321 | dependency: transitive 322 | description: 323 | name: meta 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "1.3.0-nullsafety.6" 327 | nested: 328 | dependency: transitive 329 | description: 330 | name: nested 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "0.0.4" 334 | octo_image: 335 | dependency: transitive 336 | description: 337 | name: octo_image 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "0.3.0" 341 | path: 342 | dependency: transitive 343 | description: 344 | name: path 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "1.8.0-nullsafety.3" 348 | path_provider: 349 | dependency: transitive 350 | description: 351 | name: path_provider 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "1.6.24" 355 | path_provider_linux: 356 | dependency: transitive 357 | description: 358 | name: path_provider_linux 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "0.0.1+2" 362 | path_provider_macos: 363 | dependency: transitive 364 | description: 365 | name: path_provider_macos 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "0.0.4+6" 369 | path_provider_platform_interface: 370 | dependency: transitive 371 | description: 372 | name: path_provider_platform_interface 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "1.0.4" 376 | path_provider_windows: 377 | dependency: transitive 378 | description: 379 | name: path_provider_windows 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "0.0.4+3" 383 | pedantic: 384 | dependency: transitive 385 | description: 386 | name: pedantic 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "1.9.2" 390 | photo_view: 391 | dependency: "direct main" 392 | description: 393 | name: photo_view 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "0.9.2" 397 | platform: 398 | dependency: transitive 399 | description: 400 | name: platform 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "2.2.1" 404 | plugin_platform_interface: 405 | dependency: transitive 406 | description: 407 | name: plugin_platform_interface 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "1.0.3" 411 | process: 412 | dependency: transitive 413 | description: 414 | name: process 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "3.0.13" 418 | provider: 419 | dependency: "direct main" 420 | description: 421 | name: provider 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "4.3.2+2" 425 | quiver: 426 | dependency: transitive 427 | description: 428 | name: quiver 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "2.1.5" 432 | rflutter_alert: 433 | dependency: "direct main" 434 | description: 435 | name: rflutter_alert 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "1.1.0" 439 | rxdart: 440 | dependency: transitive 441 | description: 442 | name: rxdart 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "0.24.1" 446 | sdp_transform: 447 | dependency: "direct main" 448 | description: 449 | name: sdp_transform 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "0.2.0" 453 | simple_animations: 454 | dependency: "direct main" 455 | description: 456 | name: simple_animations 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "1.3.12" 460 | sky_engine: 461 | dependency: transitive 462 | description: flutter 463 | source: sdk 464 | version: "0.0.99" 465 | source_span: 466 | dependency: transitive 467 | description: 468 | name: source_span 469 | url: "https://pub.dartlang.org" 470 | source: hosted 471 | version: "1.8.0-nullsafety.4" 472 | sqflite: 473 | dependency: transitive 474 | description: 475 | name: sqflite 476 | url: "https://pub.dartlang.org" 477 | source: hosted 478 | version: "1.3.2+1" 479 | sqflite_common: 480 | dependency: transitive 481 | description: 482 | name: sqflite_common 483 | url: "https://pub.dartlang.org" 484 | source: hosted 485 | version: "1.0.2+1" 486 | stack_trace: 487 | dependency: transitive 488 | description: 489 | name: stack_trace 490 | url: "https://pub.dartlang.org" 491 | source: hosted 492 | version: "1.10.0-nullsafety.6" 493 | stream_channel: 494 | dependency: transitive 495 | description: 496 | name: stream_channel 497 | url: "https://pub.dartlang.org" 498 | source: hosted 499 | version: "2.1.0-nullsafety.3" 500 | string_scanner: 501 | dependency: transitive 502 | description: 503 | name: string_scanner 504 | url: "https://pub.dartlang.org" 505 | source: hosted 506 | version: "1.1.0-nullsafety.3" 507 | synchronized: 508 | dependency: transitive 509 | description: 510 | name: synchronized 511 | url: "https://pub.dartlang.org" 512 | source: hosted 513 | version: "2.2.0+2" 514 | term_glyph: 515 | dependency: transitive 516 | description: 517 | name: term_glyph 518 | url: "https://pub.dartlang.org" 519 | source: hosted 520 | version: "1.2.0-nullsafety.3" 521 | test_api: 522 | dependency: transitive 523 | description: 524 | name: test_api 525 | url: "https://pub.dartlang.org" 526 | source: hosted 527 | version: "0.2.19-nullsafety.6" 528 | typed_data: 529 | dependency: transitive 530 | description: 531 | name: typed_data 532 | url: "https://pub.dartlang.org" 533 | source: hosted 534 | version: "1.3.0-nullsafety.5" 535 | uuid: 536 | dependency: transitive 537 | description: 538 | name: uuid 539 | url: "https://pub.dartlang.org" 540 | source: hosted 541 | version: "2.2.2" 542 | vector_math: 543 | dependency: transitive 544 | description: 545 | name: vector_math 546 | url: "https://pub.dartlang.org" 547 | source: hosted 548 | version: "2.1.0-nullsafety.5" 549 | win32: 550 | dependency: transitive 551 | description: 552 | name: win32 553 | url: "https://pub.dartlang.org" 554 | source: hosted 555 | version: "1.7.4" 556 | xdg_directories: 557 | dependency: transitive 558 | description: 559 | name: xdg_directories 560 | url: "https://pub.dartlang.org" 561 | source: hosted 562 | version: "0.1.2" 563 | sdks: 564 | dart: ">=2.12.0-0.0 <3.0.0" 565 | flutter: ">=1.22.2" 566 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: project_message_demo 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.0 31 | firebase_auth: 32 | cloud_firestore: ^0.13.7 33 | simple_animations: ^1.3.3 34 | image_picker: ^0.6.4 35 | firebase_storage: ^3.1.5 36 | provider: 37 | intl: ^0.16.1 38 | photo_view: ^0.9.2 39 | firebase_messaging: ^6.0.16 40 | cloud_functions: ^0.5.0 41 | flutter_icons: ^1.0.0+1 42 | cached_network_image: ^2.2.0+1 43 | assets_audio_player: ^2.0.9+2 44 | firebase_database: 45 | flutter_webrtc: ^0.2.8 46 | sdp_transform: ^0.2.0 47 | rflutter_alert: ^1.0.3 48 | 49 | dev_dependencies: 50 | flutter_test: 51 | sdk: flutter 52 | 53 | # For information on the generic Dart part of this file, see the 54 | # following page: https://dart.dev/tools/pub/pubspec 55 | 56 | # The following section is specific to Flutter. 57 | flutter: 58 | 59 | # The following line ensures that the Material Icons font is 60 | # included with your application, so that you can use the icons in 61 | # the material Icons class. 62 | uses-material-design: true 63 | 64 | # To add assets to your application, add an assets section, like this: 65 | assets: 66 | - images/ 67 | - assets/ 68 | 69 | # An image asset can refer to one or more resolution-specific "variants", see 70 | # https://flutter.dev/assets-and-images/#resolution-aware. 71 | 72 | # For details regarding adding assets from package dependencies, see 73 | # https://flutter.dev/assets-and-images/#from-packages 74 | 75 | # To add custom fonts to your application, add a fonts section here, 76 | # in this "flutter" section. Each entry in this list should have a 77 | # "family" key with the font family name, and a "fonts" key with a 78 | # list giving the asset and other descriptors for the font. For 79 | # example: 80 | # fonts: 81 | # - family: Schyler 82 | # fonts: 83 | # - asset: fonts/Schyler-Regular.ttf 84 | # - asset: fonts/Schyler-Italic.ttf 85 | # style: italic 86 | # - family: Trajan Pro 87 | # fonts: 88 | # - asset: fonts/TrajanPro.ttf 89 | # - asset: fonts/TrajanPro_Bold.ttf 90 | # weight: 700 91 | # 92 | # For details regarding fonts from package dependencies, 93 | # see https://flutter.dev/custom-fonts/#from-packages 94 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:project_message_demo/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------