Repository: realdiganta/hitup-messenger Branch: master Commit: 2011fe3378e6 Files: 108 Total size: 251.3 KB Directory structure: gitextract_qp4kc1fo/ ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android/ │ ├── .gitignore │ ├── app/ │ │ ├── build.gradle │ │ ├── google-services.json │ │ └── src/ │ │ ├── debug/ │ │ │ └── AndroidManifest.xml │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── digantakalita/ │ │ │ │ └── coocoo/ │ │ │ │ └── MainActivity.kt │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── values/ │ │ │ └── styles.xml │ │ └── profile/ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ └── settings.gradle ├── ios/ │ ├── .gitignore │ ├── Flutter/ │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.imageset/ │ │ │ ├── Contents.json │ │ │ └── README.md │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ ├── 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 ├── lib/ │ ├── blocs/ │ │ ├── AddFriends/ │ │ │ ├── add_friends_bloc.dart │ │ │ ├── add_friends_event.dart │ │ │ └── add_friends_state.dart │ │ ├── chats/ │ │ │ ├── chat_bloc.dart │ │ │ ├── chat_event.dart │ │ │ └── chat_state.dart │ │ ├── contacts/ │ │ │ ├── contacts_bloc.dart │ │ │ ├── contacts_event.dart │ │ │ └── contacts_state.dart │ │ ├── home/ │ │ │ ├── home_bloc.dart │ │ │ ├── home_event.dart │ │ │ └── home_state.dart │ │ └── timer/ │ │ ├── timer_bloc.dart │ │ ├── timer_event.dart │ │ └── timer_state.dart │ ├── config/ │ │ ├── Constants.dart │ │ └── Paths.dart │ ├── constants.dart │ ├── functions/ │ │ ├── AddFriendsFunction.dart │ │ ├── BaseFunctions.dart │ │ ├── ChatFunction.dart │ │ ├── MQTTFunction.dart │ │ └── UserDataFunction.dart │ ├── main.dart │ ├── managers/ │ │ ├── db_manager.dart │ │ └── mqtt_manager.dart │ ├── models/ │ │ ├── ChatMessage.dart │ │ ├── Conversation.dart │ │ ├── MyContact.dart │ │ ├── NonContact.dart │ │ └── User.dart │ ├── screens/ │ │ ├── ContactsHelpPage.dart │ │ ├── account_screen.dart │ │ ├── addFriends_screen.dart │ │ ├── chat_screen.dart │ │ ├── contacts_screen.dart │ │ ├── enter_name_screen.dart │ │ ├── friend_profile_screen.dart │ │ ├── help_screen.dart │ │ ├── home_screen.dart │ │ ├── login_screen.dart │ │ ├── otp_screen.dart │ │ ├── profile_screen.dart │ │ ├── settings_screen.dart │ │ └── update_profile.dart │ ├── splashscreen.dart │ ├── stateProviders/ │ │ ├── mqtt_state.dart │ │ ├── number_state.dart │ │ └── profilePicUrlState.dart │ ├── utils/ │ │ ├── Exceptions.dart │ │ └── SharedObjects.dart │ └── widgets/ │ ├── AddFriendCard.dart │ ├── ChatCard.dart │ ├── ChatItemWidget.dart │ ├── ContactCard.dart │ ├── ContactRowWidget.dart │ ├── DangerCard.dart │ ├── FriendRequestCard.dart │ ├── GradientSnackBar.dart │ ├── ImageFullScreenWidget.dart │ ├── ListTileProfile.dart │ ├── NameTextField.dart │ ├── NoRequestsCard.dart │ ├── NonContactCard.dart │ ├── SentRequestCard.dart │ └── SettingsTile.dart ├── pubspec.yaml └── test/ └── widget_test.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: bbfbf1770cca2da7c82e887e4e4af910034800b6 channel: stable project_type: app ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Diganta Kalita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Logo

HitUp Messenger

A Fully Functioning Chat Messenger (like Whatsapp) built using Flutter.



Screenshot Screenshot Screenshot ## About the project - Flutter for building the Android & IOS App. - Firestore database for storing user data. - Firebase Storage for storing images. - [MQTT](https://www.hivemq.com/mqtt-essentials/) as messaging protocol hosted in AWS EC2. - SQLite for storing contacts & chats in Local Database. - [OneSignal](https://onesignal.com/) for Push Notifications. ## Features ### Super Fast Messaging using MQTT Protocol (MQTT is used by Facebook Messenger) Architecture: > When you send a message from the app, the message first goes to the MQTT server. Then the MQTT server sends the message directly to the device of the client who is supposed to receive it. When received, the client stores the message in the local sqlite database first then shows it to the chat screen. (Firestore is in no way used to store or send text messages. Firestore is used only to store user & contacts data). To learn more about MQTT please [refer here](https://www.hivemq.com/mqtt-essentials/).
### Phone Number Authentication Sign-in Authentication is done using Firebase.
Screenshot Screenshot ### Sending Images Send & Receive Images (Snapchat style UI). The images are not stored in Gallery. Instead they are stored in the local sqlite database in bytes format.

Screenshot ### Super Fast Loading of Messages (Using SQLite as Local DB to store messages) Even when the client is offline, he/she can view the messages on opening the chat screen, because the messages are loaded directly from the local sqlite db. And that is the only place where the messages are stored. The MQTT server doesn't store old messages. The MQTT Server only stores the last message sent and then replaces it when a new message is sent. ### Sending texts to Phone Contacts On the Contacts Screen, you will get a list of all your phone contacts who are also using the messenger and can chat with them. (Just like sending messages to your contacts in Whatsapp)
Screenshot
### Adding contacts using username. Sending & Receiving Friend Requests You can also send friend request to someone using their username. On sending friend request, the other user will get a push notification & and a friend request. If he/she accepts the request then you will get an notification & will be able to chat with the user. (Same as the feature in Snapchat)

Screenshot ### Block Contact
Screenshot ### Realtime Push Notifications Using OneSignal. User will get realtime push notifications (even when the app is closed) when - He/She receives a new message - He/She receives a friend request - Someone accepts their friend request
Notification

### Change Profile Photo ### Image Compression Before Sending ### Emoji Support ### Neumorphic UI ### Invite Friends Feature
## Other Important Information ## Installation & Setup (Optional) 1. Firebase > To change the firestore database, just replace the google-services.json in android/app to your own google-services.json file from your firebase account. 2. MQTT Server > To transfer messages I am using an MQTT server which I have setup in a EC2 instance on AWS. For details on how to setup your own MQTT server please refer here : [Setup MQTT Server on AWS EC2](http://blog.yatis.io/install-secure-robust-mosquitto-mqtt-broker-aws-ubuntu/). Then change the serverAddress parameter in lib/functions/MQTTFunction.dart file, (connect() function) to the public address of your EC2 instance.
3. OneSignal > First create an account in [OneSignal](https://onesignal.com/). Then replace the app_id parameter in lib/functions/UserDataFunction.dart sendNotification() function with your own OneSignal app_id. ## Motivation & Contribution I want to build this messenger into the biggest open source messenger on the web with all functionalities from the top Messengers like Whatsapp, Telegram, Signal, Snapchat, etc. There a lot of features still to be added like end-to-end encryption, audio messages, audio/video calling, stories, etc. So, we are open to pull requests. Please contribute in any way you can, adding new features, finding or fixing bugs, adding to the documenation, better code commenting, etc. ## Contributing If you want to contribute to this project other than by coding, please contact me here digantakalita.ai@gmail.com ## License [MIT License](https://github.com/realdiganta/hitup-messenger/blob/master/LICENSE) ## Supporters [![Stargazers repo roster for @realdiganta/hitup-messenger](https://reporoster.com/stars/realdiganta/hitup-messenger)](https://github.com/realdiganta/hitup-messenger/stargazers) [![Forkers repo roster for @realdiganta/hitup-messenger](https://reporoster.com/forks/realdiganta/hitup-messenger)](https://github.com/realdiganta/hitup-messenger/network/members) ================================================ FILE: android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties ================================================ FILE: android/app/build.gradle ================================================ buildscript { repositories { // ... maven { url 'https://plugins.gradle.org/m2/' } // Gradle Plugin Portal } dependencies { // OneSignal-Gradle-Plugin classpath 'gradle.plugin.com.onesignal:onesignal-gradle-plugin:[0.12.6, 0.99.99]' } } apply plugin: 'com.onesignal.androidsdk.onesignal-gradle-plugin' def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply plugin: 'com.google.gms.google-services' def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } android { compileSdkVersion 28 sourceSets { main.java.srcDirs += 'src/main/kotlin' } lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.digantakalita.coocoo" minSdkVersion 21 targetSdkVersion 29 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. } } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.google.firebase:firebase-analytics:17.2.2' } ================================================ FILE: android/app/google-services.json ================================================ { "project_info": { "project_number": "754179280048", "firebase_url": "https://coocoo-private-fc1e0.firebaseio.com", "project_id": "coocoo-private-fc1e0", "storage_bucket": "coocoo-private-fc1e0.appspot.com" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:754179280048:android:05aeea871ac3c2980aff75", "android_client_info": { "package_name": "com.digantakalita.coocoo" } }, "oauth_client": [ { "client_id": "754179280048-it11k86jo0o6paa7j49h5822chkn3bfb.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCBIAEZd1QRl6aclVIvJ1Hwp53HRGz_cL0" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [ { "client_id": "754179280048-it11k86jo0o6paa7j49h5822chkn3bfb.apps.googleusercontent.com", "client_type": 3 } ] } } }, { "client_info": { "mobilesdk_app_id": "1:754179280048:android:9879c8ab91f94f440aff75", "android_client_info": { "package_name": "com.digantakalita.coocooprivate" } }, "oauth_client": [ { "client_id": "754179280048-7b457bisjiacvm9q5jtfc6v7qveldthc.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "com.digantakalita.coocooprivate", "certificate_hash": "95d0565459f5ab2a868748443dbeddb9f31ab585" } }, { "client_id": "754179280048-it11k86jo0o6paa7j49h5822chkn3bfb.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCBIAEZd1QRl6aclVIvJ1Hwp53HRGz_cL0" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [ { "client_id": "754179280048-it11k86jo0o6paa7j49h5822chkn3bfb.apps.googleusercontent.com", "client_type": 3 } ] } } } ], "configuration_version": "1" } ================================================ FILE: android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: android/app/src/main/kotlin/com/digantakalita/coocoo/MainActivity.kt ================================================ package com.digantakalita.coocoo import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ================================================ FILE: android/app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.3.50' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.3.3' } } allprojects { repositories { google() jcenter() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip ================================================ FILE: android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: android/settings.gradle ================================================ include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() assert localPropertiesFile.exists() localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" ================================================ FILE: ios/.gitignore ================================================ *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 8.0 ================================================ FILE: ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. ================================================ FILE: ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName coocoo CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1020; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.digantakalita.coocoo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.digantakalita.coocoo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.digantakalita.coocoo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: lib/blocs/AddFriends/add_friends_bloc.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'package:bloc/bloc.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/functions/AddFriendsFunction.dart'; import 'package:coocoo/functions/BaseFunctions.dart'; import 'package:coocoo/functions/ChatFunction.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:coocoo/widgets/GradientSnackBar.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; part 'add_friends_event.dart'; part 'add_friends_state.dart'; class AddFriendsBloc extends Bloc { AddFriendsBloc() : super(AddFriendsInitial()); Firestore _firestore = Firestore.instance; AddFriendsFunction addFriendsFunction = AddFriendsFunction(); ChatFunction chatFunction = ChatFunction(); UserDataFunction userDataFunction = UserDataFunction(); String myName = SharedObjects.prefs.getString(Constants.fullName); @override Stream mapEventToState( AddFriendsEvent event, ) async* { if (event is SearchHitUpIdEvent) { yield* mapSearchHitUpIdEventToState(event); } if (event is AddButtonClickEvent) { yield* mapAddButtonClickEventToState(event); } if (event is AcceptFriendRequestEvent) { yield* mapAcceptFriendRequestEventToState(event); } if (event is DeclineFriendRequestEvent) { yield* mapDeclineFriendRequestEventToState(event); } } Stream mapDeclineFriendRequestEventToState( DeclineFriendRequestEvent event) async* { yield DecliningFriendRequestState(); await addFriendsFunction .removeFromFriendRequestsCollection(event.phoneNumber); addFriendsFunction.removeFromSentRequestsCollection(event.phoneNumber); yield FriendRequestDeclinedState(); } Stream mapAcceptFriendRequestEventToState( AcceptFriendRequestEvent event) async* { yield AcceptingFriendRequestState(); String chatId = await addFriendsFunction.addToLocalDBAndSubscribe( event.context, event.phoneNumber); chatFunction.sendMessageToServer( event.context, 'Hi, I have accepted your friend request', chatId); await addFriendsFunction .removeFromFriendRequestsCollection(event.phoneNumber); yield FriendRequestAcceptedState(); userDataFunction.sendNotification(toUid:event.phoneNumber, title: "New Notification", content: "$myName has accepted your friend request"); GradientSnackBar.showMessage(event.context, "Friend Request Accepted", 1); addFriendsFunction.removeFromSentRequestsCollection(event.phoneNumber); } Stream mapAddButtonClickEventToState( AddButtonClickEvent event) async* { yield SendingFriendRequestState(); await addFriendsFunction.addToLocalDBAndSubscribe( event.context, event.friend.phoneNumber); await addFriendsFunction .addToSentRequestsCollection(event.friend.phoneNumber); yield FriendRequestSentState(event.friend); userDataFunction.sendNotification(toUid:event.friend.phoneNumber, title: "New Notification", content: "$myName has sent you a friend request"); addFriendsFunction.addToFriendRequestsCollection(event.friend.phoneNumber); } Stream mapSearchHitUpIdEventToState( SearchHitUpIdEvent event) async* { yield SearchingHitUpIdState(); final HitUpIdLocation hitUpIdLocation = await addFriendsFunction.checkHitUpId(event.hitUpId); // if hitUpId does not exists in local db, friend requests & sent requests if (hitUpIdLocation == HitUpIdLocation.Nowhere) { QuerySnapshot snapshot = await _firestore .collection(Paths.usersPath) .where("username", isEqualTo: event.hitUpId) .limit(1) .getDocuments(); if (snapshot.documents.length > 0) { DocumentSnapshot doc = snapshot.documents[0]; yield HitUpIdExistsState(MyContact.fromFireStore(doc)); } else { yield HitUpIdNotExistsState(event.hitUpId); } } else { yield HitUpIdAlreadyThere(event.hitUpId, hitUpIdLocation); } } } ================================================ FILE: lib/blocs/AddFriends/add_friends_event.dart ================================================ part of 'add_friends_bloc.dart'; abstract class AddFriendsEvent extends Equatable { const AddFriendsEvent(); } class SearchHitUpIdEvent extends AddFriendsEvent { final String hitUpId; SearchHitUpIdEvent(this.hitUpId); @override List get props => [hitUpId]; } class AddButtonClickEvent extends AddFriendsEvent { final MyContact friend; final BuildContext context; AddButtonClickEvent(this.context, this.friend); @override List get props => [context, friend]; } class AcceptFriendRequestEvent extends AddFriendsEvent { final String phoneNumber; final BuildContext context; AcceptFriendRequestEvent(this.phoneNumber, this.context); @override List get props => [phoneNumber, context]; } class DeclineFriendRequestEvent extends AddFriendsEvent{ final String phoneNumber; DeclineFriendRequestEvent(this.phoneNumber); @override List get props => [phoneNumber]; } ================================================ FILE: lib/blocs/AddFriends/add_friends_state.dart ================================================ part of 'add_friends_bloc.dart'; abstract class AddFriendsState extends Equatable { const AddFriendsState(); } class AddFriendsInitial extends AddFriendsState { @override List get props => []; } class SearchingHitUpIdState extends AddFriendsState { @override List get props => []; } class HitUpIdExistsState extends AddFriendsState { final MyContact friend; HitUpIdExistsState(this.friend); @override List get props => [friend]; } class HitUpIdNotExistsState extends AddFriendsState { final String hitupId; HitUpIdNotExistsState(this.hitupId); @override List get props => [hitupId]; } class HitUpIdAlreadyThere extends AddFriendsState { final String hitUpId; final HitUpIdLocation hitUpIdLocation; HitUpIdAlreadyThere(this.hitUpId, this.hitUpIdLocation); @override List get props => [hitUpId, hitUpIdLocation]; } class SendingFriendRequestState extends AddFriendsState { @override List get props => []; } class FriendRequestSentState extends AddFriendsState { final MyContact friend; FriendRequestSentState(this.friend); @override List get props => [friend]; } class AcceptingFriendRequestState extends AddFriendsState { @override List get props => []; } class FriendRequestAcceptedState extends AddFriendsState { @override List get props => []; } class DecliningFriendRequestState extends AddFriendsState{ @override List get props => []; } class FriendRequestDeclinedState extends AddFriendsState{ @override List get props => []; } ================================================ FILE: lib/blocs/chats/chat_bloc.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:coocoo/functions/ChatFunction.dart'; import 'package:coocoo/managers/db_manager.dart'; import 'package:coocoo/models/ChatMessage.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; part 'chat_event.dart'; part 'chat_state.dart'; class ChatBloc extends Bloc { ChatBloc() : super(ChatInitial()); ChatFunction chatFunction = ChatFunction(); @override Stream mapEventToState( ChatEvent event, ) async* { if (event is SendMessageEvent) { chatFunction.sendMessageToServer(event.context, event.msg, event.chatId); } if (event is SendImageEvent) { chatFunction.sendImageToServer( event.context, event.imageFile, event.chatId); } if (event is ReceivedMessageEvent) { List allMessages = await chatFunction.getAllMsgsFromMessagesTable(event.chatId); yield (ReceivedMessageState(allMessages)); } if (event is LoadInitialMessagesEvent) { List lastChatMsgs = await chatFunction.getAllMsgsFromMessagesTable(event.chatId); yield (InitialMessagesLoadedState(lastChatMsgs)); } if (event is BlockUserEvent) { chatFunction.blockUser(event.context, event.chatId); yield (BlockedUserState()); DBManager.db.isBlocked(event.chatId); } if (event is UnblockUserEvent) { chatFunction.unBlockUser(event.context, event.chatId); yield (UnblockedUserState()); } } } ================================================ FILE: lib/blocs/chats/chat_event.dart ================================================ part of 'chat_bloc.dart'; abstract class ChatEvent extends Equatable { const ChatEvent(); } class SendMessageEvent extends ChatEvent { final BuildContext context; final String msg; final String chatId; SendMessageEvent(this.context, this.msg, this.chatId); @override List get props => [msg, chatId]; } class SendImageEvent extends ChatEvent { final BuildContext context; final File imageFile; final String chatId; SendImageEvent(this.context, this.imageFile, this.chatId); @override List get props => [imageFile, chatId]; } class ReceivedMessageEvent extends ChatEvent { final String chatId; ReceivedMessageEvent(this.chatId); @override List get props => [chatId]; } class LoadInitialMessagesEvent extends ChatEvent { final String chatId; LoadInitialMessagesEvent(this.chatId); @override List get props => [chatId]; } class BlockUserEvent extends ChatEvent{ final BuildContext context; final String chatId; BlockUserEvent(this.context, this.chatId); @override List get props => [context, chatId]; } class UnblockUserEvent extends ChatEvent{ final BuildContext context; final String chatId; UnblockUserEvent(this.context, this.chatId); @override List get props => [context, chatId]; } ================================================ FILE: lib/blocs/chats/chat_state.dart ================================================ part of 'chat_bloc.dart'; abstract class ChatState extends Equatable { final List chatMessages; const ChatState(this.chatMessages); } class ChatInitial extends ChatState { ChatInitial() : super([]); @override List get props => []; } class ReceivedMessageState extends ChatState { final List chatMessages; ReceivedMessageState(this.chatMessages) : super(chatMessages); @override List get props => [chatMessages]; } class InitialMessagesLoadedState extends ChatState { final List chatMessages; InitialMessagesLoadedState(this.chatMessages) : super(chatMessages); @override List get props => [chatMessages]; } class BlockedUserState extends ChatState { BlockedUserState() : super([]); @override List get props => []; } class UnblockedUserState extends ChatState { UnblockedUserState() : super([]); @override List get props => []; } ================================================ FILE: lib/blocs/contacts/contacts_bloc.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:equatable/equatable.dart'; part 'contacts_event.dart'; part 'contacts_state.dart'; class ContactsBloc extends Bloc { ContactsBloc() : super(InitialContactsState()); UserDataFunction userDataFunction = UserDataFunction(); @override Stream mapEventToState( ContactsEvent event, ) async* { if (event is FetchContactsEvent) { try { yield FetchingContactsState(); List contacts = await userDataFunction.getContactsFromDB(); add(ReceivedContactsEvent(contacts)); } on Exception catch (e) { print(e); } } if (event is ReceivedContactsEvent) { yield FetchedContactsState(event.contacts); } } } ================================================ FILE: lib/blocs/contacts/contacts_event.dart ================================================ part of 'contacts_bloc.dart'; abstract class ContactsEvent extends Equatable { const ContactsEvent(); } class FetchContactsEvent extends ContactsEvent { @override String toString() => 'FetchContactsEvent'; @override List get props => []; } class ReceivedContactsEvent extends ContactsEvent { final List contacts; ReceivedContactsEvent(this.contacts); @override String toString() => "ReceivedContactsEvent"; @override List get props => [contacts]; } ================================================ FILE: lib/blocs/contacts/contacts_state.dart ================================================ part of 'contacts_bloc.dart'; abstract class ContactsState extends Equatable { const ContactsState(); } class InitialContactsState extends ContactsState { @override String toString() => "InitialContactsState"; @override List get props => []; } class FetchingContactsState extends ContactsState { @override String toString() => "FetchingContactsState"; @override List get props => []; } class FetchedContactsState extends ContactsState { final List contacts; FetchedContactsState(this.contacts); @override String toString() => "FetchedContactsState"; @override List get props => [contacts]; } class ErrorState extends ContactsState { // TODO: Implement errors @override List get props => []; } ================================================ FILE: lib/blocs/home/home_bloc.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:coocoo/functions/MQTTFunction.dart'; import 'package:coocoo/managers/db_manager.dart'; import 'package:coocoo/models/Conversation.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; part 'home_event.dart'; part 'home_state.dart'; class HomeBloc extends Bloc { HomeBloc() : super(HomeInitial()); MQTTFunction mqttFunction = MQTTFunction(); @override Stream mapEventToState( HomeEvent event, ) async* { if (event is ConnectToServerEvent) { try { await mqttFunction.connect(event.context); } catch (e) { print("Couln't Connect to Server. Most Probably Internet is Off"); } add(FetchHomeChatsEvent()); } if (event is DisconnectEvent) { mqttFunction.disconnect(); } if (event is FetchHomeChatsEvent) { yield* mapFetchHomeChatsEventToState(); } } Stream mapFetchHomeChatsEventToState() async* { List conversations = []; //TODO: Implement List> dbData = await DBManager.db.getAlConversationsFromChatTable(); dbData.forEach((dbMap) { conversations.add(Conversation.fromMap(dbMap)); }); conversations.sort((b, a) => a.time.compareTo(b.time)); yield (FetchedHomeChatsState(conversations)); } } ================================================ FILE: lib/blocs/home/home_event.dart ================================================ part of 'home_bloc.dart'; abstract class HomeEvent extends Equatable { const HomeEvent(); } class FetchHomeChatsEvent extends HomeEvent { @override List get props => []; } class ConnectToServerEvent extends HomeEvent { final BuildContext context; ConnectToServerEvent(this.context); @override List get props => [context]; } class DisconnectEvent extends HomeEvent { @override List get props => []; } ================================================ FILE: lib/blocs/home/home_state.dart ================================================ part of 'home_bloc.dart'; abstract class HomeState extends Equatable { final List conversations; const HomeState(this.conversations); } class HomeInitial extends HomeState { HomeInitial() : super([]); @override List get props => []; } class FetchedHomeChatsState extends HomeState { final List conversations; FetchedHomeChatsState(this.conversations) : super(conversations); @override List get props => [conversations]; } ================================================ FILE: lib/blocs/timer/timer_bloc.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:equatable/equatable.dart'; part 'timer_event.dart'; part 'timer_state.dart'; class TimerBloc extends Bloc { TimerBloc() : super(TimerStoppedState()); Stream timerStream; StreamSubscription timerSubscription; @override Stream mapEventToState( TimerEvent event, ) async* { if (event is StartTimerEvent) { timerStream = stopWatchStream(); timerSubscription?.cancel(); timerSubscription = timerStream.listen((int newTick) { add(TimerTickedEvent(newTick)); }); } if (event is TimerTickedEvent) { yield (TimerRunInProgressState(event.newTick)); } if (event is StopTimerEvent) { timerSubscription?.cancel(); timerStream = null; yield (TimerStoppedState()); } } Stream stopWatchStream() { StreamController streamController; Timer timer; Duration timerInterval = Duration(seconds: 1); int counter = Constants.resendOtpTime; void stopTimer() { if (timer != null) { timer.cancel(); timer = null; counter = Constants.resendOtpTime; streamController.close(); } } void tick(_) { counter--; streamController.add(counter); if (counter < 1) { add(StopTimerEvent()); } } void startTimer() { timer = Timer.periodic(timerInterval, tick); } streamController = StreamController( onListen: startTimer, onCancel: stopTimer, onResume: startTimer, onPause: stopTimer, ); return streamController.stream; } } ================================================ FILE: lib/blocs/timer/timer_event.dart ================================================ part of 'timer_bloc.dart'; abstract class TimerEvent extends Equatable { const TimerEvent(); } class StartTimerEvent extends TimerEvent { @override List get props => []; } class TimerTickedEvent extends TimerEvent { final int newTick; TimerTickedEvent(this.newTick); @override List get props => [newTick]; } class StopTimerEvent extends TimerEvent { @override List get props => []; } ================================================ FILE: lib/blocs/timer/timer_state.dart ================================================ part of 'timer_bloc.dart'; abstract class TimerState extends Equatable { const TimerState(); } class TimerInitial extends TimerState { @override List get props => []; } class TimerRunInProgressState extends TimerState { final int newTick; TimerRunInProgressState(this.newTick); @override List get props => [newTick]; } class TimerStoppedState extends TimerState { @override List get props => []; } ================================================ FILE: lib/config/Constants.dart ================================================ import 'package:flutter/material.dart'; class Constants { static final Color stuffColor = Colors.blueAccent[400]; static final Color textStuffColor = Colors.blueGrey[700]; static final int resendOtpTime = 35; static const firstRun = "firstRun"; static const sessionUid = "phoneNumber"; static const sessionUsername = 'sessionUsername'; static const fullName = 'fullName'; static const sessionName = 'sessionName'; static const sessionProfilePictureUrl = 'sessionProfilePictureUrl'; static const sessionCountryCode = 'sessionCountryCode'; static const configDarkMode = 'configDarkMode'; static String downloadsDirPath; static String cacheDirPath; static const profilePicChangeMsg = "profile_pic_change"; } ================================================ FILE: lib/config/Paths.dart ================================================ class Paths { /* Firebase paths */ static const String profilePicturePath = 'profile_pictures'; static const String imageAttachmentsPath = 'images'; static const String videoAttachmentsPath = 'videos'; static const String fileAttachmentsPath = 'files'; static const String usersPath = '/users'; static const String contactsPath = 'contacts'; static const String usernameUidMapPath = '/username_uid_map'; static const String chatsPath = '/chats'; static const String chat_messages = '/chat_messages'; static const String messagesPath = 'messages'; static const String friendRequestsPath = 'friend requests'; static const String sentRequestsPath = 'sent requests'; static const String MESSAGES_COLLECTION = "messages"; static const String USERS_COLLECTION = "users"; static const String CALL_COLLECTION = "call"; static const String TIMESTAMP_FIELD = "timestamp"; static const String EMAIL_FIELD = "email"; static const String MESSAGE_TYPE_IMAGE = "image"; } ================================================ FILE: lib/constants.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; const kMobileTextFieldDecoration = InputDecoration( hintText: 'Enter Mobile Number', contentPadding: EdgeInsets.symmetric(vertical: 1.5), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFF6A1B9A), width: 2.0, ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFF6A1B9A), width: 2.0, ), ), focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFF6A1B9A), width: 2.0, ), ), ); const kChatsGroupsTextStyle = TextStyle( fontSize: 17.0, fontWeight: FontWeight.w600, ); const kCircleNeumorphicStyle = NeumorphicStyle( shadowDarkColor: Colors.black, shadowLightColor: Colors.white, boxShape: NeumorphicBoxShape.circle(), depth: 7, intensity: 0.7, surfaceIntensity: 0.6, shape: NeumorphicShape.convex, ); const kChatCircleNeumorphicStyle = NeumorphicStyle( shadowDarkColor: Colors.black, shadowLightColor: Colors.white, boxShape: NeumorphicBoxShape.circle(), depth: 5, intensity: 0.65, surfaceIntensity: 0.6, ); const kRequestTitleStyle = TextStyle( fontWeight: FontWeight.w700, fontSize: 23.0, ); const double perfectWidth = 411.4; // 683.4; const kHitUpIdTextFieldDecoration = InputDecoration( contentPadding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 15.0), hintText: 'Search Username', fillColor: Colors.white, filled: true, border: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(40.0), ), ), focusedBorder: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(40.0), ), ), ); ================================================ FILE: lib/functions/AddFriendsFunction.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/functions/BaseFunctions.dart'; import 'package:coocoo/functions/ChatFunction.dart'; import 'package:coocoo/managers/db_manager.dart'; import 'package:coocoo/managers/mqtt_manager.dart'; import 'package:coocoo/stateProviders/mqtt_state.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; class AddFriendsFunction extends BaseAddFriendsFunction { Firestore _firestore = Firestore.instance; ChatFunction chatFunction = ChatFunction(); String uid = SharedObjects.prefs.getString(Constants.sessionUid); @override void dispose() {} @override Future addToFriendRequestsCollection(String phoneNumber) async { // add to friend requests collection of the other user CollectionReference usersRef = _firestore.collection(Paths.usersPath); // first get my data DocumentSnapshot snapshot = await usersRef.document(uid).get(); usersRef .document(phoneNumber) .collection(Paths.friendRequestsPath) .document(uid) .setData({ 'username': snapshot.data['username'], 'photoUrl': snapshot.data['photoUrl'], 'name': snapshot.data['name'], 'phoneNumber': snapshot.data['phoneNumber'], }); } @override Future addToLocalDBAndSubscribe( BuildContext context, String phoneNumber) async { DocumentSnapshot docSnapshot = await _firestore .collection(Paths.usersPath) .document(phoneNumber) .get(); String chatId = await chatFunction.createChatIdForContact(phoneNumber); // create a row for this user, i.e add the contact to my local db String photoUrl = docSnapshot.data["photoUrl"]; String username = docSnapshot.data["username"]; String name = docSnapshot.data["name"]; await DBManager.db.createRow(phoneNumber, chatId, name, username, photoUrl, 0); // 0 because here this is not a phone contact // Subscribe to the chatId MQTTManager manager = context.read().manager; print("SUBSCRIBING TO TOPIC : $chatId"); manager.subscribeTopic(chatId); return chatId; } @override Future addToSentRequestsCollection(String phoneNumber) async { // add to my sent requests collection CollectionReference usersRef = _firestore.collection(Paths.usersPath); // first get the data for the phoneNumber DocumentSnapshot snapshot = await usersRef.document(phoneNumber).get(); usersRef .document(uid) .collection(Paths.sentRequestsPath) .document(phoneNumber) .setData({ 'username': snapshot.data['username'], 'photoUrl': snapshot.data['photoUrl'], 'name': snapshot.data['name'], 'phoneNumber': snapshot.data['phoneNumber'], }); } @override Future checkHitUpId(String hitUpId) async { bool ans; // check if hitUpId exists in local db ans = await DBManager.db.checkIfUsernameExistsInDb(hitUpId); if (ans) { return HitUpIdLocation.InLocalDb; } else { // check if hitUpId exists in Firebase Friend Requests collection String uid = SharedObjects.prefs.getString(Constants.sessionUid); QuerySnapshot snapshot = await _firestore .collection(Paths.usersPath) .document(uid) .collection(Paths.friendRequestsPath) .where('username', isEqualTo: hitUpId) .limit(1) .getDocuments(); if (snapshot.documents.length > 0) { return HitUpIdLocation.InFriendRequests; } else { // check if hitUpId exists in Firebase Sent Requests Collection snapshot = await _firestore .collection(Paths.usersPath) .document(uid) .collection(Paths.sentRequestsPath) .where('username', isEqualTo: hitUpId) .limit(1) .getDocuments(); if (snapshot.documents.length > 0) { return HitUpIdLocation.InSentRequests; } else { return HitUpIdLocation.Nowhere; } } } } @override Future removeFromFriendRequestsCollection(String phoneNumber) async { // remove from my friend requests collection CollectionReference usersRef = _firestore.collection(Paths.usersPath); await usersRef .document(uid) .collection(Paths.friendRequestsPath) .document(phoneNumber) .delete(); } @override void removeFromSentRequestsCollection(String phoneNumber) { // remove from the other user's sent requests collection CollectionReference usersRef = _firestore.collection(Paths.usersPath); usersRef .document(phoneNumber) .collection(Paths.sentRequestsPath) .document(uid) .delete(); } } ================================================ FILE: lib/functions/BaseFunctions.dart ================================================ import 'dart:io'; import 'package:coocoo/models/ChatMessage.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/models/NonContact.dart'; import 'package:flutter/cupertino.dart'; import 'package:permission_handler/permission_handler.dart'; enum HitUpIdLocation { InLocalDb, InFriendRequests, InSentRequests, Nowhere, } abstract class BaseFunction { void dispose(); } abstract class BaseUserDataFunction extends BaseFunction { Future> getContactsFromDB(); Future loadPhoneContactsV2(BuildContext context); Future> loadNonContactsV2(); Future askContactPermissions(); void onShare(BuildContext context); void sendNotification({String toUid, String title, String content}); Future verifyPhoneNumber( BuildContext context, String phoneNum, Function verificationFailed); } abstract class BaseChatFunction extends BaseFunction { Future createChatIdForContact(String contactPhoneNumber); void sendMessageToServer(BuildContext context, String msg, String chatId); void sendImageToServer(BuildContext context, File imageFile, String chatId); void sendServiceMsgToServer(BuildContext context, String msg, String chatId); Future> getAllMsgsFromMessagesTable(String chatId); void blockUser(BuildContext context, String chatId); void unBlockUser(BuildContext context, String chatId); } abstract class BaseMQTTFunction extends BaseFunction { Future connect(BuildContext context); void disconnect(); } abstract class BaseUIFunction extends BaseFunction { double cleanValue(double screenWidth, double value); } abstract class BaseAddFriendsFunction extends BaseFunction { Future addToLocalDBAndSubscribe( BuildContext context, String phoneNumber); Future addToFriendRequestsCollection(String phoneNumber); Future addToSentRequestsCollection(String phoneNumber); Future checkHitUpId(String hitUpId); Future removeFromFriendRequestsCollection(String phoneNumber); void removeFromSentRequestsCollection(String phoneNumber); } ================================================ FILE: lib/functions/ChatFunction.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/functions/BaseFunctions.dart'; import 'package:coocoo/managers/db_manager.dart'; import 'package:coocoo/managers/mqtt_manager.dart'; import 'package:coocoo/models/ChatMessage.dart'; import 'package:coocoo/stateProviders/mqtt_state.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; class ChatFunction extends BaseChatFunction { Firestore _firestore = Firestore.instance; MQTTManager _manager; String myUid = SharedObjects.prefs.getString(Constants.sessionUid); @override Future createChatIdForContact(String contactPhoneNumber) async { String chatId; String uId = SharedObjects.prefs.getString(Constants.sessionUid); CollectionReference usersCollection = _firestore.collection(Paths.usersPath); DocumentReference userRef = usersCollection.document(uId); DocumentReference contactRef = usersCollection.document(contactPhoneNumber); DocumentSnapshot userSnapshot = await userRef.get(); // if chatId doesn't exists for that contact then create new ChatId and update it // for both the user and the contact // else use the chatId which already exists if (userSnapshot.data['chats'] == null || userSnapshot.data['chats'][contactPhoneNumber] == null) { chatId = createChatId(); await userRef.setData({ 'chats': {contactPhoneNumber: chatId} }, merge: true); await contactRef.setData({ 'chats': {uId: chatId} }, merge: true); } else { chatId = userSnapshot.data['chats'][contactPhoneNumber]; } return chatId; } String createChatId() { final Random _random = Random.secure(); final int idLength = 16; // length of the chatId var values = List.generate(idLength, (i) => _random.nextInt(256)); return base64Url.encode(values); } @override void dispose() {} @override void sendMessageToServer(BuildContext context, String msg, String chatId) { if (_manager == null) { _manager = context.read().manager; } print("calling send message to server"); Map msgMap = {"msg": "$msg", "type": "text", "uid": "$myUid"}; String msgToSend = json.encode(msgMap); _manager.publish(msgToSend, chatId); } @override Future> getAllMsgsFromMessagesTable(String chatId) async { return await DBManager.db.readAllMessagesfromMessagesTable(chatId); } // 2. compress file and get file. Future testCompressAndGetFile(File file) async { Directory tempDir = await getTemporaryDirectory(); String tempPath = tempDir.path; var result = await FlutterImageCompress.compressAndGetFile( file.absolute.path, tempPath + "/temp.jpg", minHeight: 400, minWidth: 400, quality: 80, ); return result; } @override void sendImageToServer( BuildContext context, File imageFile, String chatId) async { if (_manager == null) { _manager = context.read().manager; } // compress the image & make it smaller in size File newFile = await testCompressAndGetFile(imageFile); // change the image to bytes format String base64Image = base64Encode(newFile.readAsBytesSync()); String msgToSend = '{"msg" : "$base64Image", "type" : "image", "uid" : "$myUid"}'; while (!_manager.getConnectionStatus()) { print("NOT CONNECTED BEFORE PUBLISHING"); await Future.delayed(Duration(seconds: 1)); } print("CONNECTED BEFORE PUBLISHING"); _manager.publish(msgToSend, chatId); } @override void sendServiceMsgToServer(BuildContext context, String msg, String chatId) { if (_manager == null) { _manager = context.read().manager; } _manager.publish(msg, chatId); } @override void blockUser(BuildContext context, String chatId) { if (_manager == null) { _manager = context.read().manager; } DBManager.db.updateBlockStatus(0, chatId); _manager.unSubscribeTopic(chatId); } @override void unBlockUser(BuildContext context, String chatId) { if (_manager == null) { _manager = context.read().manager; } DBManager.db.updateBlockStatus(1, chatId); _manager.subscribeTopic(chatId); } } ================================================ FILE: lib/functions/MQTTFunction.dart ================================================ import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/functions/BaseFunctions.dart'; import 'package:coocoo/managers/mqtt_manager.dart'; import 'package:coocoo/stateProviders/mqtt_state.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:provider/provider.dart'; class MQTTFunction extends BaseMQTTFunction { MQTTManager _manager; String loggedInUser; @override Future connect(BuildContext context) async { loggedInUser = SharedObjects.prefs.getString(Constants.sessionUid); _manager = context.read().manager; final String password = "S8x${loggedInUser.substring(1, 6)}S,.@"; if (_manager == null) { _manager = MQTTManager( serverAddress: "13.127.199.45", clientName: loggedInUser, context: context, ); _manager.initializeMQTTClient(); // also set the mqtt_manager to the provider context.read().setNewManager(_manager); print(_manager); } print("Connecting to server"); await _manager.connect(loggedInUser, password); } @override void dispose() {} @override void disconnect() { if (_manager != null) { _manager.disconnect(); } } } ================================================ FILE: lib/functions/UserDataFunction.dart ================================================ import 'dart:convert'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:contacts_service/contacts_service.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/functions/BaseFunctions.dart'; import 'package:coocoo/functions/ChatFunction.dart'; import 'package:coocoo/managers/db_manager.dart'; import 'package:coocoo/managers/mqtt_manager.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/models/NonContact.dart'; import 'package:coocoo/stateProviders/mqtt_state.dart'; import 'package:coocoo/stateProviders/number_state.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:share/share.dart'; class UserDataFunction extends BaseUserDataFunction { Firestore _firestore = Firestore.instance; ChatFunction chatFunction = ChatFunction(); @override void dispose() {} @override Future> loadNonContactsV2() async { List _nonContacts = []; Iterable _contacts = await ContactsService.getContacts(withThumbnails: false); String countryCode = SharedObjects.prefs.getString(Constants.sessionCountryCode); await Future.forEach(_contacts, (_contact) async { String tempNum = cleanNumber(_contact, countryCode); if (tempNum != null) { bool contactExists = await DBManager.db.checkIfContactExistsInDb(tempNum); // if contact does not exist in local db then add it to the nonContacts list if (!contactExists) { print(tempNum); _nonContacts.add(NonContact(tempNum, name: _contact.displayName)); } } }); return _nonContacts; } @override Future loadPhoneContactsV2(BuildContext context) async { Iterable _contacts = await ContactsService.getContacts(withThumbnails: false); String countryCode = SharedObjects.prefs.getString(Constants.sessionCountryCode); try { await Future.forEach(_contacts, (_contact) async { String tempNum = cleanNumber(_contact, countryCode); String contactName = _contact.displayName; if (tempNum != null) { bool contactExists = await DBManager.db.checkIfContactExistsInDb(tempNum); if (!contactExists) { await addInstalledUserToDBANDSubscribe( context, tempNum, contactName); } else if (contactExists) { // however if contact exists in local db but does not exist in primary firestore // users database because he deleted his account then remove him from local db removeUninstalledUserFromDB(context, tempNum); } } }); } catch (e, s) { print(s); } } void removeUninstalledUserFromDB(BuildContext context, String tempNum) async { final contactRef = _firestore.collection(Paths.usersPath).document(tempNum); contactRef.get().then((docSnapshot) async { if (!docSnapshot.exists) { // delete the contact from local Database await DBManager.db.deleteContact(tempNum); } }); } Future addInstalledUserToDBANDSubscribe( BuildContext context, String tempNum, String contactName) async { final contactRef = _firestore.collection(Paths.usersPath).document(tempNum); contactRef.get().then((docSnapshot) async { // if the user has installed the app then add him as a contact to my db // else do nothing if (docSnapshot.exists) { print("USER $tempNum EXISTS IN DB"); String chatId = await chatFunction.createChatIdForContact(tempNum); // create a row for this user, i.e add the contact to my local db String photoUrl = docSnapshot.data["photoUrl"]; String username = docSnapshot.data["username"]; await DBManager.db.createRow( docSnapshot.documentID, chatId, contactName, username, photoUrl, 1); // true because here this is a phone contact // Subscribe to the chatId MQTTManager manager = context.read().manager; print("SUBSCRIBING TO TOPIC : $chatId"); manager.subscribeTopic(chatId); } }).catchError((e) { print("e"); }); } dynamic cleanNumber(Contact dirtyNumber, String countryCode) { // when we clean a number, we first remove all the white spaces and hyphens and then // if the number does not has a country code,i.e, if its length is less than 11 // then we add the user's country code. try { String num = dirtyNumber.phones.first.value; String num2 = num.replaceAll(RegExp(r"\D+"), ''); if (num2.length < 11) { return "$countryCode$num2"; } else { return num2; } } catch (e) { return null; } } @override Future askContactPermissions() async { PermissionStatus permissionStatus = await _getContactPermission(); return permissionStatus; } Future _getContactPermission() async { PermissionStatus permission = await Permission.contacts.status; if (permission != PermissionStatus.granted && permission != PermissionStatus.restricted) { Map permissionStatus = await [ Permission.contacts, ].request(); return permissionStatus[Permission.contacts] ?? PermissionStatus.undetermined; } else { return permission; } } @override Future> getContactsFromDB() async { List contacts = []; List> dbData = await DBManager.db.getAllContacts(); dbData.forEach((map) { contacts.add(MyContact.fromMap(map)); }); return contacts; } @override void onShare(BuildContext context) async { // A builder is used to retrieve the context immediately // surrounding the RaisedButton. // // The context's `findRenderObject` returns the first // RenderObject in its descendent tree when it's not // a RenderObjectWidget. The RaisedButton's RenderObject // has its position and size after it's built. final RenderBox box = context.findRenderObject(); await Share.share( "https://play.google.com/store/apps/details?id=com.digantakalita.coocoo", subject: "Lets have our private chats on" "this New Cool Messenger HitUp from now on. Its way safer than the others.", sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size); } @override void sendNotification({String toUid, String title, String content}) async { final String app_id = "98194ba4-9b9a-416b-ab0c-74b851af4f1a"; var body = jsonEncode({ "include_external_user_ids": [toUid], "app_id": app_id, "contents": {"en": content}, "headings": {"en": title}, "collapse_id": "123", "android_channel_id": "0940813c-e319-4ff3-8d52-a202bf767b3a" }); http.Response response = await http.post( 'https://onesignal.com/api/v1/notifications', body: body, headers: { "content-type": "application/json", "Authorization": "Basic ZGY5MWZjZjctYmQ0My00ZDhjLTliYmItNzE0ZjlmOWVkYjYz" }); if (response.statusCode == 200) { print("NOTIFICATION SENT SUCCESSFULLY"); } else { print('Notification Error | Error Code: ${response.statusCode}'); } } @override Future verifyPhoneNumber(BuildContext context, String phoneNum, Function verificationFailed) async { final FirebaseAuth _auth = FirebaseAuth.instance; await _auth.verifyPhoneNumber( phoneNumber: phoneNum, timeout: Duration(seconds: 30), verificationCompleted: null, verificationFailed: verificationFailed, codeSent: (String verificationId, [int forceResendingToken]) async { context.read().setOTP(verificationId); }, codeAutoRetrievalTimeout: null, ); } } ================================================ FILE: lib/main.dart ================================================ import 'package:coocoo/blocs/AddFriends/add_friends_bloc.dart'; import 'package:coocoo/blocs/chats/chat_bloc.dart'; import 'package:coocoo/blocs/contacts/contacts_bloc.dart'; import 'package:coocoo/blocs/timer/timer_bloc.dart'; import 'package:coocoo/screens/home_screen.dart'; import 'package:coocoo/splashscreen.dart'; import 'package:coocoo/stateProviders/number_state.dart'; import 'package:coocoo/stateProviders/profilePicUrlState.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'blocs/home/home_bloc.dart'; import 'stateProviders/mqtt_state.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); SharedObjects.prefs = await CachedSharedPreferences.getInstance(); runApp(MultiBlocProvider( providers: [ BlocProvider( create: (context) => ContactsBloc(), ), BlocProvider( create: (context) => ChatBloc(), ), BlocProvider( create: (context) => HomeBloc(), ), BlocProvider( create: (context) => AddFriendsBloc(), ), BlocProvider( create: (context) => TimerBloc(), ) ], child: MyApp(), )); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => MQTTState()), ChangeNotifierProvider( create: (context) => NumberState(), ), ChangeNotifierProvider( create: (context) => ProfilePicUrlState()), ], child: MaterialApp( debugShowCheckedModeBanner: false, title: 'HitUp Messenger', routes: { '/homeScreen': (context) => HomeScreen(), }, theme: ThemeData( primaryColor: Colors.white, primarySwatch: Colors.blueGrey, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: SplashScreen(), ), ); } } //Feature Ideas //TODO: Mood of a user //TODO: Online Offline...but only to the people whom I wanna show if I am online //TODO: whether the user is active or not, i.e if she is inside your chat window or is she chatting with someone else ================================================ FILE: lib/managers/db_manager.dart ================================================ import 'package:coocoo/models/ChatMessage.dart'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; class DBManager { DBManager._(); static final DBManager db = DBManager._(); Database _database; // final String contactsTable = "Contacts"; final String messagesTable = "Messages"; final String chatsTable = "Chats"; final String phoneNumberColumn = "phoneNumber"; final String chatIdColumn = "chatId"; final String nameColumn = 'name'; final String usernameColumn = 'username'; final String msgColumn = "msg"; final String timeColumn = "time"; final String photoUrlColumn = "photoUrl"; final String msgTypeColumn = 'msgType'; final String isContactColumn = 'isContact'; final String blockStatusColumn = 'blockStat'; // 0 => Blocked, 1 => Unblocked final String msgStatusColumn = "msgStatus"; // 0=> Received, 1=> Sent Future get database async { if (_database != null) { return _database; } _database = await initDB(); return _database; } initDB() async { String dbPath = await getDatabasesPath(); return await openDatabase( join(dbPath, "chat_database.db"), version: 1, // onCreate is called only if there was no prior database in the specified path onCreate: (Database db, int version) async { // we need 3 tables // 1. for storing users data // await db.execute( // "CREATE TABLE $contactsTable ($phoneNumberColumn TEXT PRIMARY KEY, $chatIdColumn TEXT, " // "$nameColumn TEXT, $usernameColumn TEXT," // "$photoUrlColumn TEXT, $isContactColumn INTEGER, $blockStatusColumn INTEGER)"); // 2. for storing the messages for the chat screen await db.execute("CREATE TABLE $messagesTable (" "$chatIdColumn TEXT, $msgColumn TEXT, $msgTypeColumn TEXT, " "$timeColumn INTEGER, $msgStatusColumn INTEGER)"); // 3. for storing the last message to show in the home screen in the chat cards await db.execute( "CREATE TABLE $chatsTable ($phoneNumberColumn TEXT PRIMARY KEY, $nameColumn TEXT," "$chatIdColumn TEXT, $usernameColumn TEXT, $photoUrlColumn TEXT, $isContactColumn INTEGER, $blockStatusColumn INTEGER," " $msgColumn TEXT, $msgTypeColumn TEXT, " "$timeColumn INTEGER)"); }, ); } Future updateMessageToChatTable( String chatId, String newMsg, String msgType, int currTime) async { final Database db = await database; int numOfUpdates = await db.rawUpdate( "UPDATE $chatsTable SET $msgColumn = ?, $timeColumn = ?, $msgTypeColumn = ? WHERE $chatIdColumn = ?", [newMsg, currTime, msgType, chatId]); print("$numOfUpdates rows were changed in Chats Table"); } Future addNewMessageToMessagesTable(String chatId, String newMsg, String msgType, int currTime, int msgStatus) async { final Database db = await database; await db.insert( messagesTable, { chatIdColumn: chatId, msgColumn: newMsg, msgTypeColumn: msgType, timeColumn: currTime, msgStatusColumn: msgStatus, }, conflictAlgorithm: ConflictAlgorithm.ignore, ); } Future readMessageFromChatTable(String chatId) async { final Database db = await database; List> res = await db.query(chatsTable, columns: [ msgColumn, msgTypeColumn, timeColumn, chatIdColumn, msgStatusColumn ], where: "$chatIdColumn = ?", whereArgs: [chatId]); ChatMessage chatMessage = ChatMessage.fromMap(res[0]); print("Most recent message read From Db :::::::: $chatMessage"); return chatMessage; } Future> readAllMessagesfromMessagesTable( String chatId) async { final Database db = await database; var res = await db .query(messagesTable, where: "$chatIdColumn = ?", whereArgs: [chatId]); return res.map((e) => ChatMessage.fromMap(e)).toList(); } // get all conversations from chat table to show in the home page Future>> getAlConversationsFromChatTable() async { final Database db = await database; var res = await db.query(chatsTable, where: "$msgColumn IS NOT NULL"); List output = List.from(res); return output; } Future updateProfilePicInChatsTable( String photoUrl, String chatId) async { final Database db = await database; await db.rawUpdate( "UPDATE $chatsTable SET $photoUrlColumn = ? WHERE $chatIdColumn = ?", [photoUrl, chatId], ); } Future>> getAllContacts() async { final Database db = await database; var res = await db.query( chatsTable, columns: [ phoneNumberColumn, nameColumn, usernameColumn, photoUrlColumn, chatIdColumn ], where: "$isContactColumn = ?", whereArgs: [1], ); List output = List.from(res); return output; } Future createRow(String phoneNum, String chatId, String contactName, String username, String photoUrl, int isContact) async { final Database db = await database; await db.insert( chatsTable, { phoneNumberColumn: phoneNum, chatIdColumn: chatId, nameColumn: contactName, usernameColumn: username, photoUrlColumn: photoUrl, isContactColumn: isContact, blockStatusColumn: 1, // At first it will be unblocked ofcourse msgColumn: null, msgTypeColumn: null, timeColumn: null, }, conflictAlgorithm: ConflictAlgorithm.ignore, ); } Future deleteContact(String phoneNum) async { final Database db = await database; await db.delete(chatsTable, where: "$phoneNumberColumn = ?", whereArgs: [phoneNum]); } Future checkIfContactExistsInDb(String phoneNumber) async { final Database db = await database; var result = await db.rawQuery( "SELECT COUNT(1) FROM $chatsTable WHERE $phoneNumberColumn = ? LIMIT 1", [phoneNumber], ); return result[0]["COUNT(1)"] == 1 ? true : false; } Future checkIfUsernameExistsInDb(String username) async { final Database db = await database; var result = await db.rawQuery( "SELECT COUNT(1) FROM $chatsTable WHERE $usernameColumn = ? LIMIT 1", [username], ); return result[0]["COUNT(1)"] == 1 ? true : false; } Future deleteTable() async { final Database db = await database; await db.delete(chatsTable, where: '1'); } // check if contact is blocked or unblocked Future isBlocked(String chatId) async { final Database db = await database; var res = await db.query(chatsTable, columns: [blockStatusColumn], where: '$chatIdColumn = ?', whereArgs: [chatId]); return (res[0]["blockStat"] == 1 ? false : true); } Future updateBlockStatus(int newStatus, String chatId) async { final Database db = await database; await db.rawUpdate( "UPDATE $chatsTable SET $blockStatusColumn = ? WHERE $chatIdColumn = ?", [newStatus, chatId], ); } Future updateName(String newName, String chatId) async { final Database db = await database; await db.rawUpdate( "UPDATE $chatsTable SET $nameColumn = ? WHERE $chatIdColumn = ?", [newName, chatId], ); } } ================================================ FILE: lib/managers/mqtt_manager.dart ================================================ import 'dart:convert'; import 'package:coocoo/blocs/chats/chat_bloc.dart'; import 'package:coocoo/blocs/home/home_bloc.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/stateProviders/mqtt_state.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mqtt_client/mqtt_client.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; import 'package:provider/provider.dart'; import 'db_manager.dart'; class MQTTManager { MQTTManager({ @required this.serverAddress, @required this.clientName, @required this.context, }); final String serverAddress; final String clientName; final BuildContext context; MqttServerClient _client; ChatBloc chatBloc; HomeBloc homeBloc; void initializeMQTTClient() { _client = MqttServerClient(serverAddress, clientName); _client.port = 1883; _client.keepAlivePeriod = 43200; _client.logging(on: false); _client.onDisconnected = onDisconnected; _client.onConnected = onConnect; // auto reconnecting when client is disconnected to server _client.autoReconnect = true; final connMess = MqttConnectMessage() .withClientIdentifier(clientName) .keepAliveFor( 43200) // Must agree with the keep alive set above or not set .withWillTopic( 'willtopic') // If you set this you must set a will message .withWillMessage('My Will message') .withWillRetain() .withWillQos(MqttQos.atLeastOnce); _client.connectionMessage = connMess; } void disconnect() { _client.disconnect(); } bool getConnectionStatus() { if (_client.connectionStatus.state == MqttConnectionState.connected) { return true; } else { return false; } } Future connect(String username, String password) async { try { await _client.connect(username, password); } on Exception catch (e) { print('EXAMPLE::client exception - $e'); _client.disconnect(); } } void subscribeTopic(String topic) { _client.subscribe(topic, MqttQos.atLeastOnce); /// The client has a change notifier object(see the Observable class) which we then listen to to get /// notifications of published updates to each subscribed topic. } void unSubscribeTopic(String topic) { _client.unsubscribe(topic); } Stream>> get messageStream => _client.updates; void publish(String myMessage, String topic) { final builder = MqttClientPayloadBuilder(); builder.addString(myMessage); _client.publishMessage(topic, MqttQos.atLeastOnce, builder.payload, retain: true); } void onDisconnected() { print('Client Disconnect'); } void onConnect() { chatBloc = BlocProvider.of(context); homeBloc = BlocProvider.of(context); _client.updates.listen((List> c) async { final MqttPublishMessage recMess = c[0].payload; final String chatId = c[0].topic; final msg = MqttPublishPayload.bytesToStringAsString(recMess.payload.message); print("THIS IS THE MESSAGE BEFORE DECODING"); print(msg); Map parsedMsg = json.decode(msg); final int currTime = DateTime.now().millisecondsSinceEpoch; String myUid = SharedObjects.prefs.getString(Constants.sessionUid); if (parsedMsg["type"] == "service") { // mqtt message if any of my contacts changed his/her profile picture if (parsedMsg["msg"] == Constants.profilePicChangeMsg) { if (parsedMsg["uid"] != myUid) { print("Profile Pic Changed For $chatId"); await DBManager.db.updateProfilePicInChatsTable( parsedMsg["profilePicUrl"], chatId); homeBloc.add(FetchHomeChatsEvent()); } } } else { // save message to local db print(parsedMsg["uid"]); print(myUid); if (parsedMsg["uid"] == myUid) { // if uid of message is same as mine then that means // I sent the message, so save it to db as sent message print("seetting messages to local db 1"); await DBManager.db.addNewMessageToMessagesTable( chatId, parsedMsg["msg"], parsedMsg["type"], currTime, 1); // 1 bcox message is sent } else { print("seetting messages to local db 0"); // save the message to local db as received message await DBManager.db.addNewMessageToMessagesTable( chatId, parsedMsg["msg"], parsedMsg["type"], currTime, 0); // 0 bcox message is received } DBManager.db.updateMessageToChatTable( chatId, parsedMsg["msg"], parsedMsg["type"], currTime, ); //also notify the bloc that a new message is received so that it // may read the last message from the local db chatBloc.add(ReceivedMessageEvent(chatId)); // fetching the chatcards for the homePage homeBloc.add(FetchHomeChatsEvent()); } }); } } ================================================ FILE: lib/models/ChatMessage.dart ================================================ class ChatMessage { final String msg; final int time; final String msgType; // final int msgStatus; final String chatId; final bool isSelf; ChatMessage({this.msg, this.time, this.msgType, this.chatId, this.isSelf}); factory ChatMessage.fromMap(Map map) { return ChatMessage( msg: map["msg"], time: map["time"], msgType: map["msgType"], chatId: map["chatId"], isSelf: map["msgStatus"] == 1 ? true : false, ); } @override String toString() => "msg : $msg, time : $time, " "msgType : $msgType, chatId : $chatId, isSelf: $isSelf"; } ================================================ FILE: lib/models/Conversation.dart ================================================ class Conversation { final String phoneNumber; final String lastMessage; final String msgType; final int time; final String photoUrl; final String chatId; final String name; final String username; Conversation({ this.phoneNumber, this.lastMessage, this.msgType, this.time, this.photoUrl, this.chatId, this.name, this.username, }); factory Conversation.fromMap(Map map) { // here map is the map from the sqflite database return Conversation( phoneNumber: map["phoneNumber"], lastMessage: map["msg"], msgType: map["msgType"], time: map["time"], photoUrl: map["photoUrl"], chatId: map["chatId"], name: map['name'], username: map['username'], ); } @override String toString() => "phoneNumber : $phoneNumber," "lastMessage : $lastMessage, msgType : $msgType, time : $time, photoUrl : $photoUrl, " "chatId : $chatId"; } ================================================ FILE: lib/models/MyContact.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; class MyContact { final String phoneNumber; final String name; final String username; final String photoUrl; final String chatId; final int ind; MyContact( {this.phoneNumber, this.name, this.username, this.photoUrl, this.chatId, this.ind}); factory MyContact.fromFireStore(DocumentSnapshot snapshot) { var data = snapshot.data; return MyContact( phoneNumber: data['phoneNumber'] ?? snapshot.documentID, name: data['name'], photoUrl: data['photoUrl'], username: data['username'], chatId: data['chatId']); } factory MyContact.fromMap(Map data) { return MyContact( phoneNumber: data['phoneNumber'], name: data['name'], photoUrl: data['photoUrl'], username: data['username'], chatId: data['chatId']); } @override String toString() { return "index : ${this.ind}, phoneNumber : ${this.phoneNumber}, " "name : ${this.name} photoUrl : ${this.photoUrl}, " "chatId : ${this.chatId}, username : ${this.username}"; } } ================================================ FILE: lib/models/NonContact.dart ================================================ import 'package:equatable/equatable.dart'; class NonContact extends Equatable { final String name; final String phoneNumber; NonContact(this.phoneNumber, {this.name}); @override List get props => [phoneNumber]; } ================================================ FILE: lib/models/User.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; class User { String documentId; String phoneNumber; String username; String photoUrl; User({this.documentId, this.phoneNumber, this.username, this.photoUrl}); factory User.fromFirestore(DocumentSnapshot doc) { Map data = doc.data; return User( documentId: doc.documentID, phoneNumber: data['phoneNumber'], username: data['username'], photoUrl: data['photoUrl']); } factory User.fromMap(Map data) { return User( documentId: data['uid'], phoneNumber: data['phoneNumber'], username: data['username'], photoUrl: data['photoUrl']); } @override String toString() { return '{ documentId: $documentId, phoneNumb: $phoneNumber, username: $username, photoUrl: $photoUrl }'; } } ================================================ FILE: lib/screens/ContactsHelpPage.dart ================================================ import 'package:coocoo/constants.dart'; import 'package:flutter/material.dart'; class ContactsHelpPage extends StatelessWidget { @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; return Scaffold( appBar: AppBar( title: Text('Contacts Help'), ), body: Container( padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( " If some of your friends don't appear in\n the contacts" " list, we recommend\n the following steps:", style: TextStyle( fontSize: (screenWidth / perfectWidth) * 18.0, fontWeight: FontWeight.w400, ), ), SizedBox(height: 8.0), Text( "1. Click on the Refresh Button on the\n " " Top right hand side of the page and " "\n check for the contact again.", style: TextStyle( fontSize: (screenWidth / perfectWidth) * 18.0, fontWeight: FontWeight.w400, ), ), SizedBox(height: 8.0), Text( "2. Make sure that your friend is using\n HitUp Messenger", style: TextStyle( fontSize: (screenWidth / perfectWidth) * 18.0, fontWeight: FontWeight.w400, ), ), SizedBox(height: 8.0), Text( "3. Make sure that your friend's phone number\n is in your " "address book", style: TextStyle( fontSize: (screenWidth / perfectWidth) * 18.0, fontWeight: FontWeight.w400, ), ), ], ), ), ); } } ================================================ FILE: lib/screens/account_screen.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/screens/profile_screen.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/managers/db_manager.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:coocoo/widgets/DangerCard.dart'; import 'package:coocoo/widgets/SettingsTile.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import '../splashscreen.dart'; class AccountScreen extends StatefulWidget { @override _AccountScreenState createState() => _AccountScreenState(); } class _AccountScreenState extends State { final Firestore _firestore = Firestore.instance; FirebaseAuth _auth = FirebaseAuth.instance; bool loading = false; Future _showDialogBox() async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text('Are you sure?'), content: Text('You want to delete your HitUp account?'), actions: [ FlatButton( onPressed: () => Navigator.of(context).pop(false), child: Text('No'), ), FlatButton( onPressed: () async { setState(() { loading = true; }); String userPhone = SharedObjects.prefs.getString(Constants.sessionUid); try { _firestore .collection(Paths.usersPath) .document(userPhone) .delete(); } catch (e) { print(e); } DBManager.db.deleteTable(); await SharedObjects.prefs.clearAll(); Navigator.push(context, MaterialPageRoute(builder: (context) => SplashScreen())); setState(() { loading = false; }); }, child: Text('Yes Delete'), ), ], ), )) ?? false; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Account'), ), body: !loading ? Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SettingsTile( icon: Icons.account_circle, title: 'My Account', onPress: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ProfileScreen())); }, ), DangerCard(Colors.red, 'Delete Account', () { _showDialogBox(); }), ], ), ) : Center(child: CircularProgressIndicator()), ); } } ================================================ FILE: lib/screens/addFriends_screen.dart ================================================ import 'dart:ui'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/blocs/AddFriends/add_friends_bloc.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/functions/BaseFunctions.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:coocoo/widgets/AddFriendCard.dart'; import 'package:coocoo/widgets/FriendRequestCard.dart'; import 'package:coocoo/widgets/NoRequestsCard.dart'; import 'package:coocoo/widgets/SentRequestCard.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; enum RequestsType { FriendRequests, SentRequests } class AddFriendsScreen extends StatefulWidget { @override _AddFriendsScreenState createState() => _AddFriendsScreenState(); } class _AddFriendsScreenState extends State { final TextEditingController hitupIdController = TextEditingController(); Firestore _firestore = Firestore.instance; String uid = SharedObjects.prefs.getString(Constants.sessionUid); AddFriendsBloc addFriendsBloc; final _formKey = GlobalKey(); Widget _buildRequestsStreamBuilder( double algo, RequestsType requestsType, BuildContext contex) { return StreamBuilder( stream: _firestore .collection(Paths.usersPath) .document(uid) .collection(requestsType == RequestsType.SentRequests ? Paths.sentRequestsPath : Paths.friendRequestsPath) .snapshots(), builder: (BuildContext context, AsyncSnapshot snapshot) { Widget toShow; if (snapshot.hasError) { toShow = HitUpIdTextWidget( algo: algo, text: 'Sorry! Not able to retrieve Data'); } else { switch (snapshot.connectionState) { case ConnectionState.none: toShow = HitUpIdTextWidget( algo: algo, text: 'Sorry! Not able to retrieve Data'); break; case ConnectionState.waiting: toShow = Padding( padding: const EdgeInsets.only(top: 8.0), child: Center( child: SizedBox( child: CircularProgressIndicator(), height: 60, width: 60, ), ), ); break; case ConnectionState.active: case ConnectionState.done: if (snapshot.data.documents.isEmpty) { toShow = NoRequestsCard( algo: algo, text: requestsType == RequestsType.SentRequests ? 'You h' 'ave not sent any Friend Requests' : 'You have No ' 'Friend Requests'); } else { toShow = Column( children: snapshot.data.documents .map((e) => requestsType == RequestsType.SentRequests ? SentRequestCard(MyContact.fromFireStore(e)) : FriendRequestCard(MyContact.fromFireStore(e), addFriendsBloc, contex)) .toList(), ); } break; } } return toShow; }, ); } @override void initState() { addFriendsBloc = BlocProvider.of(context); super.initState(); } @override Widget build(BuildContext context) { double width = MediaQuery.of(context).size.width; double algo = width / perfectWidth; return Form( key: _formKey, child: Padding( padding: EdgeInsets.only(top: 12.0, right: 10.0, left: 10.0), child: ListView( children: [ Row( children: [ GestureDetector( child: Icon( Icons.arrow_back, color: Colors.black, ), onTap: () { Navigator.pop(context); }, ), SizedBox(width: 5.0), Expanded( child: TextFormField( inputFormatters: [ FilteringTextInputFormatter.deny( RegExp(r'\s+')) // no spaces allowed ], validator: (value) { if (value.isEmpty) { return "Please enter a valid username"; } return null; }, controller: hitupIdController, style: TextStyle( fontSize: algo * 18.0, ), autocorrect: false, cursorColor: Colors.blueGrey, decoration: kHitUpIdTextFieldDecoration, )), SizedBox(width: algo * 10.0), GestureDetector( onTap: () { if (_formKey.currentState.validate()) { addFriendsBloc .add(SearchHitUpIdEvent(hitupIdController.text)); } hitupIdController.clear(); }, child: Text( 'Search', style: TextStyle( letterSpacing: 0.5, fontSize: algo * 21.0, fontWeight: FontWeight.bold, ), ), ), ], ), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: BlocBuilder( builder: (context, state) { if (state is SearchingHitUpIdState) { return SearchingCard(algo: algo); } if (state is HitUpIdAlreadyThere) { if (state.hitUpIdLocation == HitUpIdLocation.InLocalDb) { return HitUpIdTextWidget( algo: algo, text: '@${state.hitUpId} already exists in your contacts!'); } else if (state.hitUpIdLocation == HitUpIdLocation.InFriendRequests) { return HitUpIdTextWidget( algo: algo, text: 'You have already received a friend request from @${state.hitUpId}. Please' ' check your Friend Requests List'); } else if (state.hitUpIdLocation == HitUpIdLocation.InSentRequests) { return HitUpIdTextWidget( algo: algo, text: 'You have already sent a friend request to @${state.hitUpId}'); } } if (state is SendingFriendRequestState) { return SearchingCard(algo: algo); } if (state is FriendRequestSentState) { return SentRequestCard(state.friend); } if (state is HitUpIdExistsState) { // return Text(state.friend.name); return AddFriendCard(state.friend, () async { // print(state.friend.phoneNumber); addFriendsBloc .add(AddButtonClickEvent(context, state.friend)); }); } if (state is HitUpIdNotExistsState) { return HitUpIdTextWidget( algo: algo, text: 'HitUp Id @${state.hitupId} not found!', ); } return SizedBox(height: 10.0); }, ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Friend Requests', style: kRequestTitleStyle, ), SizedBox(height: 6.0), BlocBuilder( builder: (context, state) { if (state is AcceptingFriendRequestState || state is DecliningFriendRequestState) { return SearchingCard(algo: algo); } return _buildRequestsStreamBuilder( algo, RequestsType.FriendRequests, context); }, ), ], ), SizedBox(height: 30.0), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Your Sent Requests', style: kRequestTitleStyle, ), SizedBox(height: 6.0), _buildRequestsStreamBuilder( algo, RequestsType.SentRequests, context, ), ], ), ], ), ), ); } @override void dispose() { super.dispose(); hitupIdController.dispose(); // addFriendsBloc.close(); } } class SearchingCard extends StatelessWidget { const SearchingCard({ Key key, @required this.algo, }) : super(key: key); final double algo; @override Widget build(BuildContext context) { return Card( elevation: 5.0, child: Container( padding: EdgeInsets.symmetric(vertical: 3.0), height: algo * 50.0, child: Center( child: CircularProgressIndicator(), ), ), ); } } class HitUpIdTextWidget extends StatelessWidget { final double algo; final String text; HitUpIdTextWidget({ @required this.algo, @required this.text, }); @override Widget build(BuildContext context) { return Card( elevation: 5.0, child: Padding( padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 8.0), child: Center( child: Text(text, textAlign: TextAlign.center, style: TextStyle( fontSize: algo * 20.0, color: Constants.textStuffColor, )), ), ), ); } } ================================================ FILE: lib/screens/chat_screen.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:coocoo/screens/friend_profile_screen.dart'; import 'package:coocoo/blocs/chats/chat_bloc.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/models/ChatMessage.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/stateProviders/profilePicUrlState.dart'; import 'package:coocoo/widgets/ChatItemWidget.dart'; import 'package:coocoo/widgets/GradientSnackBar.dart'; import 'package:coocoo/widgets/ImageFullScreenWidget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_emoji/flutter_emoji.dart' as emj; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; class ChatScreen extends StatefulWidget { final MyContact toContact; ChatScreen(this.toContact); @override _ChatScreenState createState() => _ChatScreenState(); } class _ChatScreenState extends State { var parser = emj.EmojiParser(); TextEditingController chatTextController = TextEditingController(); ChatBloc chatBloc; UserDataFunction userDataFunction = UserDataFunction(); List allMessages = []; ScrollController _scrollController; final picker = ImagePicker(); Widget _buildSendButton(double algo) { return Expanded( child: InkWell( onTap: () { String tempChatId = widget.toContact.chatId; String msgToSend = parser.unemojify(chatTextController.text); chatBloc.add(SendMessageEvent(context, msgToSend, tempChatId)); userDataFunction.sendNotification( toUid: widget.toContact.phoneNumber, title: "${widget.toContact.name}", content: msgToSend, ); chatTextController.clear(); }, child: Neumorphic( style: kCircleNeumorphicStyle, child: CircleAvatar( backgroundColor: Colors.white, radius: algo * 33.0, child: Neumorphic( style: NeumorphicStyle( shadowDarkColor: Colors.blueGrey, boxShape: NeumorphicBoxShape.circle(), depth: 4, intensity: 0.7, surfaceIntensity: 0.6, ), child: CircleAvatar( radius: algo * 23.0, backgroundColor: Colors.white, child: Icon(Icons.send, color: Colors.blueGrey, size: algo * 26.0), ), ), ), ), ), ); } Widget _buildTypeMessageTextField() { return Expanded( flex: 5, child: Row( children: [ Expanded( child: Neumorphic( margin: EdgeInsets.only(left: 6, right: 5, top: 2, bottom: 4), style: NeumorphicStyle( depth: -15, boxShape: NeumorphicBoxShape.stadium(), shadowDarkColorEmboss: Colors.black, shadowLightColor: Colors.white, intensity: 0.6, ), padding: EdgeInsets.symmetric(vertical: 14, horizontal: 18), child: TextField( onTap: () { // scroll to the bottom of the list when keyboard appears Timer( Duration(milliseconds: 200), () => _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 500), curve: Curves.easeIn)); }, focusNode: FocusNode(), cursorColor: Colors.blueGrey, controller: chatTextController, style: TextStyle( fontSize: 21.0, ), decoration: InputDecoration.collapsed(hintText: "Type a message"), ), ), ), ], ), ); } Widget _buildContactProfilePicture(double algo) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageFullScreen( url: widget.toContact.photoUrl, tag: 'dash${widget.toContact.ind}'))); }, child: Hero( tag: 'dash${widget.toContact.ind}', child: Neumorphic( style: kCircleNeumorphicStyle, child: CircleAvatar( radius: algo * 23.5, backgroundImage: CachedNetworkImageProvider(widget.toContact.photoUrl), ), ), ), ); } @override void initState() { super.initState(); chatBloc = BlocProvider.of(context); chatBloc.add(LoadInitialMessagesEvent(widget.toContact.chatId)); _scrollController = ScrollController(); } @override void dispose() { chatTextController.dispose(); _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; final double algo = screenWidth / perfectWidth; return Scaffold( backgroundColor: Colors.grey[200], appBar: AppBar( titleSpacing: 0.0, automaticallyImplyLeading: false, leading: GestureDetector( onTap: () { Navigator.pop(context); }, child: Icon( Icons.arrow_back, color: Colors.blueGrey[700], ), ), backgroundColor: Colors.white, title: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ _buildContactProfilePicture(algo), SizedBox(width: algo * 13.0), GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => FriendProfileScreen(widget.toContact))); }, child: Text( widget.toContact.name, style: TextStyle( color: Colors.blueGrey[700], fontSize: algo * 20.0, fontWeight: FontWeight.w600, ), ), ), ], ), actions: [ GestureDetector( onTap: () { pickImage(); }, child: Icon( Icons.attach_file, color: Colors.blueGrey[700], size: 30.0, ), ), SizedBox(width: 5.0), GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => FriendProfileScreen(widget.toContact))); }, child: Icon( Icons.more_vert_sharp, size: 30.0, ), ), SizedBox(width: algo * 15.0), ], ), body: Container( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: BlocListener( listener: (context, state) { if (state is ReceivedMessageState) { if (state.chatMessages[0].chatId == widget.toContact.chatId) { setState(() { allMessages = state.chatMessages; }); } } if (state is InitialMessagesLoadedState) { setState(() { allMessages = state.chatMessages; }); } // jump to the bottom of the screen when a new message arrives // also using a timer because we need to jump to the bottom // only after the new message is updated in the listview Timer( Duration(milliseconds: 600), () => _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 200), curve: Curves.easeIn)); }, child: Padding( padding: EdgeInsets.symmetric(vertical: algo * 8.0), child: ListView.builder( controller: _scrollController, itemCount: allMessages.length, itemBuilder: (context, index) { return ChatItemWidget(allMessages[index]); }), ), ), ), Row( children: [ _buildTypeMessageTextField(), _buildSendButton(algo), ], ), ], ), ), ); } Future pickImage() async { final pickedFile = await picker.getImage(source: ImageSource.gallery); if (pickedFile != null) { File tempFile = File(pickedFile.path); chatBloc.add(SendImageEvent(context, tempFile, widget.toContact.chatId)); GradientSnackBar.showMessage( context, "Sending Your Beautiful Image...", 2); userDataFunction.sendNotification( toUid: widget.toContact.phoneNumber, title: "You have New Messages", content: "Click To View", ); } } } ================================================ FILE: lib/screens/contacts_screen.dart ================================================ import 'package:animated_bottom_navigation_bar/animated_bottom_navigation_bar.dart'; import 'package:coocoo/screens/ContactsHelpPage.dart'; import 'package:coocoo/screens/addFriends_screen.dart'; import 'package:coocoo/blocs/contacts/contacts_bloc.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/widgets/ContactRowWidget.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import '../constants.dart'; class ContactListPage extends StatefulWidget { @override State createState() => _ContactListPageState(); const ContactListPage(); } class _ContactListPageState extends State with SingleTickerProviderStateMixin { ContactsBloc contactsBloc; final TextEditingController usernameController = TextEditingController(); List contacts = []; UserDataFunction userDataFunction; bool refreshing = false; Color stuffColor = Colors.blueGrey[700]; bool showSearchBar = false; int _selectedIndex = 0; AnimationController _animationController; Animation animation; CurvedAnimation curve; final iconList = [ FontAwesomeIcons.addressBook, // Icons.contacts, // FontAwesomeIcons.solidAddressBook, FontAwesomeIcons.userFriends, ]; @override void dispose() { usernameController.dispose(); _animationController.dispose(); // contactsBloc.close(); super.dispose(); } @override void initState() { userDataFunction = UserDataFunction(); contactsBloc = BlocProvider.of(context); contactsBloc.add(FetchContactsEvent()); super.initState(); final systemTheme = SystemUiOverlayStyle.light.copyWith( systemNavigationBarColor: Colors.black, systemNavigationBarIconBrightness: Brightness.light, ); SystemChrome.setSystemUIOverlayStyle(systemTheme); _animationController = AnimationController( duration: Duration(seconds: 1), vsync: this, ); curve = CurvedAnimation( parent: _animationController, curve: Interval( 0.5, 1.0, curve: Curves.fastOutSlowIn, ), ); animation = Tween( begin: 0, end: 1, ).animate(curve); Future.delayed( Duration(seconds: 1), () => _animationController.forward(), ); } Widget _buildRefreshButton() { return InkWell( child: Icon( Icons.refresh, color: stuffColor, ), onTap: () async { setState(() { refreshing = true; }); await userDataFunction.loadPhoneContactsV2(context); await Future.delayed(Duration(seconds: 8)); contactsBloc.add(FetchContactsEvent()); setState(() { refreshing = false; }); }, ); } Widget _buildRefreshing() { return Container( width: 20.0, padding: EdgeInsets.symmetric(vertical: 18.0), child: CircularProgressIndicator( strokeWidth: 2.5, backgroundColor: Colors.white, ), ); } Widget _buildNormalAppBar(double screenWidth) { return AppBar( automaticallyImplyLeading: false, leading: IconButton( icon: Icon( Icons.arrow_back, color: stuffColor, ), onPressed: () { Navigator.pop(context); }, ), backgroundColor: Colors.white, title: Text( 'Select Contact', style: TextStyle( fontSize: (screenWidth / perfectWidth) * 22.0, color: stuffColor), ), actions: [ refreshing ? _buildRefreshing() : _buildRefreshButton(), SizedBox(width: 25.0), ], ); } void _onItemTapped(int ind) { setState(() { _selectedIndex = ind; }); } @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; return SafeArea( child: Scaffold( backgroundColor: Colors.grey[200], appBar: _selectedIndex == 0 ? _buildNormalAppBar(screenWidth) : null, floatingActionButton: FloatingActionButton( elevation: 8, backgroundColor: Colors.white, child: Icon( Icons.favorite, size: 40.0, color: Colors.black, ), onPressed: () { _animationController.reset(); _animationController.forward(); }, ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: AnimatedBottomNavigationBar( elevation: 30.0, activeColor: Colors.black, splashColor: Colors.blueAccent, backgroundColor: Colors.white, icons: iconList, iconSize: 30.0, activeIndex: _selectedIndex, inactiveColor: Colors.grey, notchAndCornersAnimation: animation, splashSpeedInMilliseconds: 300, notchSmoothness: NotchSmoothness.softEdge, gapLocation: GapLocation.center, leftCornerRadius: 20, rightCornerRadius: 20, onTap: _onItemTapped, ), body: _selectedIndex == 0 ? BlocBuilder( builder: (context, state) { if (state is FetchingContactsState) { print("Fetching Contacts"); return SizedBox( height: (MediaQuery.of(context).size.height), child: Center(child: CircularProgressIndicator()), ); } if (state is FetchedContactsState) { contacts = state.contacts; } return ListView( children: [ Column( children: List.generate( contacts.length, (index) => ContactRowWidget( contact: contacts[index], )), ), Divider(thickness: 1.5), SizedBox(height: (screenWidth / perfectWidth) * 20.0), ListTile( leading: Icon( Icons.share, color: Colors.black, ), title: Text( 'Invite Friends', style: TextStyle( fontSize: (screenWidth / perfectWidth) * 20.0, fontWeight: FontWeight.w400, ), ), onTap: () => userDataFunction.onShare(context), ), ListTile( leading: Icon( Icons.help, color: Colors.black, ), title: Text( 'Contacts Help', style: TextStyle( fontSize: (screenWidth / perfectWidth) * 20.0, fontWeight: FontWeight.w400, ), ), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ContactsHelpPage())); }, ), ], ); }) : AddFriendsScreen(), ), ); } } ================================================ FILE: lib/screens/enter_name_screen.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/screens/update_profile.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:coocoo/widgets/NameTextField.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; class EnterName extends StatefulWidget { @override _EnterNameState createState() => _EnterNameState(); } class _EnterNameState extends State { final TextEditingController firstNameController = TextEditingController(); final TextEditingController lastNameController = TextEditingController(); final _formKey = GlobalKey(); Firestore firestore = Firestore.instance; bool isLoading = false; Future saveFullName(String firstname, String lastname) async { String uid = SharedObjects.prefs.getString(Constants.sessionUid); String fullName = '$firstname $lastname'; DocumentReference ref = firestore.collection(Paths.usersPath).document( uid); //reference of the user's document node in database/users. This node is created using uid var data = {'name': fullName}; await ref.setData(data, merge: true); // set the photourl, age and username await SharedObjects.prefs.setString(Constants.fullName, fullName); } Widget buildLoadingScreen() { return Center( child: CircularProgressIndicator(), ); } Widget buildEnterNameScreen() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 35.0, vertical: 40.0), child: Form( key: _formKey, child: ListView( children: [ Center( child: Text("What's your name?", style: TextStyle( fontSize: 25.0, fontWeight: FontWeight.w600, )), ), SizedBox(height: 8.0), Column( children: [ NameTextField( hintText: 'FIRST NAME', controller: firstNameController, ), NameTextField( hintText: 'LAST NAME', controller: lastNameController, ), SizedBox(height: 10.0), Text("This name will appear when someone searches for you " "on HitUp") ], ), SizedBox(height: 40.0), RaisedButton( padding: EdgeInsets.symmetric(vertical: 8.0), onPressed: () async { if (_formKey.currentState.validate()) { setState(() { isLoading = true; }); await saveFullName( firstNameController.text, lastNameController.text); Navigator.push(context, MaterialPageRoute(builder: (context) => UpdateProfile())); isLoading = false; } }, elevation: 10.0, color: Colors.blueAccent[400], child: Text( "NEXT", style: TextStyle( color: Colors.white, fontSize: 25.0, letterSpacing: 1.0), ), ), ], ), ), ); } @override Widget build(BuildContext context) { return Scaffold( body: isLoading ? buildLoadingScreen() : buildEnterNameScreen(), ); } @override void dispose() { firstNameController.dispose(); lastNameController.dispose(); super.dispose(); } } ================================================ FILE: lib/screens/friend_profile_screen.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:coocoo/blocs/chats/chat_bloc.dart'; import 'package:coocoo/blocs/home/home_bloc.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/managers/db_manager.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/widgets/DangerCard.dart'; import 'package:coocoo/widgets/ImageFullScreenWidget.dart'; import 'package:coocoo/widgets/ListTileProfile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class FriendProfileScreen extends StatefulWidget { final MyContact friend; FriendProfileScreen(this.friend); @override _FriendProfileScreenState createState() => _FriendProfileScreenState(); } class _FriendProfileScreenState extends State { ChatBloc chatBloc; bool isBlocked = false; TextEditingController changeNameController = TextEditingController(); final _formKey = GlobalKey(); HomeBloc homeBloc; Future _showPrompt(BuildContext context) async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text('Are you sure you want to block ${widget.friend.name}?'), content: Text("You won't be able to send or receive any messages from" " ${widget.friend.name}."), actions: [ FlatButton( onPressed: () => Navigator.of(context).pop(false), child: Text( 'NO', style: TextStyle( fontWeight: FontWeight.bold, ), ), ), FlatButton( onPressed: () { chatBloc.add(BlockUserEvent(context, widget.friend.chatId)); Navigator.of(context).pop(false); }, child: Text( 'YES', style: TextStyle( fontWeight: FontWeight.bold, ), ), ), ], ), )) ?? false; } void updateIsBlocked() async { bool temp = await DBManager.db.isBlocked(widget.friend.chatId); setState(() { isBlocked = temp; print(isBlocked); }); } @override void initState() { chatBloc = BlocProvider.of(context); updateIsBlocked(); super.initState(); } @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; double algo = screenWidth / perfectWidth; return Scaffold( appBar: AppBar( title: Text("Profile"), ), body: Container( width: double.infinity, padding: EdgeInsets.symmetric( vertical: algo * 10.0, horizontal: algo * 15.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageFullScreen(url: widget.friend.photoUrl))); }, child: CircleAvatar( radius: algo * 120.0, backgroundColor: Colors.white, backgroundImage: CachedNetworkImageProvider(widget.friend.photoUrl), ), ), Spacer( flex: 2, ), ListTileProfile( iconData: FontAwesomeIcons.book, title: 'Full Name', subTitle: widget.friend.name, trailingWidget: GestureDetector( onTap: () { showModalBottomSheet( context: context, isScrollControlled: true, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20.0))), builder: (BuildContext context) { return Form( key: _formKey, child: Container( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, top: 10.0, left: 15.0, right: 15.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Edit Name', style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.w500, ), ), SizedBox(height: algo * 12.0), TextFormField( validator: (value) { if (value.trim().isEmpty) { return "Please enter a valid Name"; } return null; }, controller: changeNameController, style: TextStyle( fontSize: algo * 18.0, ), autocorrect: false, cursorColor: Colors.blueGrey, decoration: kHitUpIdTextFieldDecoration.copyWith( hintText: 'New Name', ), ), SizedBox(height: algo * 8.0), FlatButton( onPressed: () async { if (_formKey.currentState.validate()) { Navigator.popUntil( context, (route) => route.isFirst); await DBManager.db.updateName( changeNameController.text, widget.friend.chatId); homeBloc = BlocProvider.of(context); homeBloc.add(FetchHomeChatsEvent()); } }, color: Constants.stuffColor, child: Text( 'DONE', style: TextStyle( color: Colors.white, fontSize: algo * 22.0, letterSpacing: 0.5, ), ), ), ], ), ), ); }, ); }, child: Icon( Icons.edit, color: Constants.stuffColor, ), ), ), ListTileProfile( iconData: FontAwesomeIcons.solidUser, title: "Username", subTitle: widget.friend.username, ), Spacer( flex: 5, ), BlocListener( listener: (context, state) { if (state is BlockedUserState) { setState(() { isBlocked = true; }); } else { setState(() { isBlocked = false; }); } }, child: isBlocked ? DangerCard(Colors.green, 'UnBlock', () async { chatBloc .add(UnblockUserEvent(context, widget.friend.chatId)); }) : DangerCard(Colors.red, 'Block', () async { _showPrompt(context); }), ), ], ), ), ); } } ================================================ FILE: lib/screens/help_screen.dart ================================================ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class HelpScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Help"), ), body: Container( padding: const EdgeInsets.symmetric(vertical: 15.0, horizontal: 8.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "For help with any issues, bugs or feature requests please write a review " "on Google Play Store or directly contact us " "at: ", textAlign: TextAlign.center, style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.w400, ), ), Column( children: [ ContactMeCard( iconData: FontAwesomeIcons.envelope, title: 'digantakalita.ai@gmail.com', ), ContactMeCard( iconData: FontAwesomeIcons.instagram, title: '@digantakalita_real', ), ContactMeCard( iconData: FontAwesomeIcons.twitter, title: '@realdiganta', ), ContactMeCard( iconData: FontAwesomeIcons.whatsapp, title: '+91 1111111111', ), SizedBox(height: 10.0), Text( "If your messages are not being sent, just check your " "internet connection or restart the app," " and it will start working fine af", textAlign: TextAlign.center, style: TextStyle( fontSize: 17.0, ), ) ], ), SizedBox(height: 20.0), Text( "We typically reply within 4 hours", style: TextStyle( fontSize: 18.0, color: Colors.teal, fontWeight: FontWeight.bold, ), ), ], ), ), ); } } class ContactMeCard extends StatelessWidget { final IconData iconData; final String title; ContactMeCard({this.iconData, this.title}); @override Widget build(BuildContext context) { return Card( elevation: 5.0, child: ListTile( leading: FaIcon( iconData, color: Colors.pinkAccent, ), title: Text( title, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 19.0, letterSpacing: 0.8, ), ), ), ); } } ================================================ FILE: lib/screens/home_screen.dart ================================================ import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/screens/contacts_screen.dart'; import 'package:coocoo/screens/profile_screen.dart'; import 'package:coocoo/screens/settings_screen.dart'; import 'package:coocoo/blocs/home/home_bloc.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/models/Conversation.dart'; import 'package:coocoo/stateProviders/profilePicUrlState.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:coocoo/widgets/ChatCard.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:onesignal_flutter/onesignal_flutter.dart'; import 'package:provider/provider.dart'; class HomeScreen extends StatefulWidget { @override _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State with WidgetsBindingObserver { HomeBloc homeBloc; List conversations; void setUserNameAndNameIfNull() { Firestore _firestore = Firestore.instance; String uid = SharedObjects.prefs.getString(Constants.sessionUid); String username = SharedObjects.prefs.getString(Constants.sessionUsername); String fullName = SharedObjects.prefs.getString(Constants.fullName); if (username == null || fullName == null) { _firestore.collection(Paths.usersPath).document(uid).get().then((doc) { SharedObjects.prefs .setString(Constants.sessionUsername, doc.data["username"]); SharedObjects.prefs.setString(Constants.fullName, doc.data["name"]); SharedObjects.prefs.setString( Constants.sessionProfilePictureUrl, doc.data["photoUrl"]); }); } } Future _onWillPop() async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text('Are you sure?'), content: Text('Do you want to exit the App'), actions: [ FlatButton( onPressed: () => Navigator.of(context).pop(false), child: Text('No'), ), FlatButton( onPressed: () => SystemChannels.platform.invokeMethod('SystemNavigator.pop'), child: Text('Yes'), ), ], ), )) ?? false; } void initOneSignal() async { await OneSignal.shared.init("98194ba4-9b9a-416b-ab0c-74b851af4f1a", iOSSettings: { OSiOSSettings.autoPrompt: false, OSiOSSettings.inAppLaunchUrl: false }); await OneSignal.shared .setInFocusDisplayType(OSNotificationDisplayType.notification); final String myUid = SharedObjects.prefs.getString(Constants.sessionUid); await OneSignal.shared.setExternalUserId(myUid); OneSignal.shared.addTrigger('update', '1'); } @override void initState() { WidgetsBinding.instance.addObserver(this); initOneSignal(); homeBloc = BlocProvider.of(context); super.initState(); // connecting to the mqtt server in the home page homeBloc.add(ConnectToServerEvent(context)); setUserNameAndNameIfNull(); print("Home Screen Opened"); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused) { homeBloc.add(DisconnectEvent()); } if (state == AppLifecycleState.resumed) { homeBloc.add(ConnectToServerEvent(context)); } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; return WillPopScope( onWillPop: _onWillPop, child: Scaffold( backgroundColor: Colors.grey[200], floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (context) => ContactListPage())); }, backgroundColor: Colors.blueGrey[600], elevation: 15.0, child: Icon( Icons.message, ), ), body: SafeArea( child: Container( padding: EdgeInsets.only(top: 15.0, left: 15.0, right: 15.0), child: Column( children: [ Row( children: [ Text( 'Chats', style: TextStyle( color: Colors.blueGrey[700], fontSize: (screenWidth / perfectWidth) * 30.0, fontWeight: FontWeight.w600, ), ), Spacer( flex: 10, ), GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ProfileScreen())); }, child: Padding( padding: EdgeInsets.symmetric( vertical: 3.0, horizontal: 15.0), child: Hero( tag: 'myProfile', child: Neumorphic( style: kCircleNeumorphicStyle, child: CircleAvatar( backgroundColor: Colors.white, radius: (screenWidth / perfectWidth) * 33.0, backgroundImage: CachedNetworkImageProvider( context .watch() .profilePicUrl), ), ), ), ), ), GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => SettingsScreen())); }, child: Icon( Icons.more_vert, size: (screenWidth / perfectWidth) * 30.0, ), ), ], ), SizedBox(height: 10.0), Expanded( flex: 3, child: BlocBuilder( builder: (context, state) { conversations = state.conversations; if (conversations.length > 0 && conversations.isNotEmpty) { return ListView.builder( itemCount: conversations.length, itemBuilder: (context, index) { return ChatCard(conversations[index], index); }); } else { return Center(child: Text("You have No Messages")); } }, )) ], ), ), ), ), ); } } ================================================ FILE: lib/screens/login_screen.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/screens/otp_screen.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/stateProviders/number_state.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:country_pickers/country.dart'; import 'package:country_pickers/country_pickers.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; class Login extends StatefulWidget { @override _LoginState createState() => _LoginState(); } class _LoginState extends State { final _formKey = GlobalKey(); Firestore _firestore = Firestore.instance; TextEditingController mobileNumberController = TextEditingController(); final FirebaseAuth _auth = FirebaseAuth.instance; UserDataFunction userDataFunction = UserDataFunction(); String countryCode = '91'; // TODO: Handle the exception nicely here instead of just printing out the error final PhoneVerificationFailed _verificationFailed = (AuthException authException) { print(authException.message); }; Future _showContinueDialog() async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text( 'We will be verifying the phone number: +$countryCode ${mobileNumberController.text}'), content: Text('Is this OK, or would you like to edit the number?'), actions: [ FlatButton( onPressed: () => Navigator.of(context).pop(false), child: Text('EDIT'), ), FlatButton( onPressed: () async { context.read().setPhoneNumber( "$countryCode${mobileNumberController.text}"); await SharedObjects.prefs .setString(Constants.sessionCountryCode, countryCode); await userDataFunction.verifyPhoneNumber( context, '+$countryCode' + mobileNumberController.text.toString(), _verificationFailed); Navigator.push(context, MaterialPageRoute(builder: (context) => OTPScreen())); }, child: Text('OK'), ), ], ), )) ?? false; } Widget _buildDropdownItem(Country country) => Container( child: Row( children: [ CountryPickerUtils.getDefaultFlagImage(country), SizedBox( width: 8.0, ), Text("+${country.phoneCode} ${country.isoCode}"), ], ), ); @override void dispose() { mobileNumberController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ChangeNotifierProvider( create: (context) => NumberState(), child: Form( key: _formKey, child: SafeArea( child: Padding( padding: EdgeInsets.symmetric(vertical: 35.0, horizontal: 22.0), child: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( children: [ Text( 'Verify your phone number', style: TextStyle( color: Constants.textStuffColor, fontSize: 22.0, fontWeight: FontWeight.w600, ), ), SizedBox(height: 30.0), Text( 'HitUp Messenger will send and SMS message (carrier charges may apply) to verify' ' your phone number. Enter your phone number.', style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w400, ), textAlign: TextAlign.center, ), SizedBox(height: 20.0), CountryPickerDropdown( initialValue: 'IN', itemBuilder: _buildDropdownItem, // itemFilter: ['AR', 'DE', 'GB', 'CN'].contains(c.isoCode), sortComparator: (Country a, Country b) => a.isoCode.compareTo(b.isoCode), onValuePicked: (Country country) { setState(() { countryCode = country.phoneCode; }); }, ), Padding( padding: EdgeInsets.symmetric(horizontal: 50.0), child: TextFormField( controller: mobileNumberController, maxLengthEnforced: true, maxLength: 10, cursorColor: Constants.stuffColor, style: TextStyle(fontSize: 20.0), keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: InputDecoration( hintText: 'Phone Number', ), inputFormatters: [ FilteringTextInputFormatter.deny( RegExp(r'\s+')) // no spaces allowed ], validator: (value) { if (value.length != 10) { return 'Please enter 10 digits'; } return null; }, ), ), ], ), RaisedButton( padding: EdgeInsets.symmetric( vertical: 8.0, horizontal: 30.0), color: Constants.stuffColor, child: Text( 'NEXT', style: TextStyle( color: Colors.white, fontSize: 18.0, letterSpacing: 1.0, ), ), onPressed: () async { if (_formKey.currentState.validate()) { await _showContinueDialog(); } }, ), ], ), ], ), ), ), ), ), ); } } ================================================ FILE: lib/screens/otp_screen.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/screens/enter_name_screen.dart'; import 'package:coocoo/blocs/timer/timer_bloc.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/functions/MQTTFunction.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/screens/home_screen.dart'; import 'package:coocoo/stateProviders/number_state.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; class OTPScreen extends StatefulWidget { @override _OTPScreenState createState() => _OTPScreenState(); } class _OTPScreenState extends State { final _formKey = GlobalKey(); TextEditingController otpController = TextEditingController(); FirebaseAuth _auth = FirebaseAuth.instance; Firestore _firestore = Firestore.instance; SharedPreferences loginData; bool isVerifying = false; UserDataFunction userDataFunction; MQTTFunction mqttFunction; bool showSpinner = false; TimerBloc timerBLoc; String minutesStr = '00'; String secondsStr = Constants.resendOtpTime.toString(); bool resendOtpSwitch = false; // TODO: Handle the exception nicely here instead of just printing out the error final PhoneVerificationFailed _verificationFailed = (AuthException authException) { print(authException.message); }; Future _showWrongOTPDialog() async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text('The code you entered was not correct'), content: Text('Please enter the correct code!'), actions: [ FlatButton( onPressed: () => Navigator.of(context).pop(false), child: Text('TRY AGAIN'), ), ], ), )) ?? false; } Future _signInWithPhoneNumber(String smsCode) async { AuthCredential _authCredential = PhoneAuthProvider.getCredential( verificationId: context.read().otp, smsCode: smsCode); await _auth.signInWithCredential(_authCredential).catchError((error) { _showWrongOTPDialog(); print(error); }).then((AuthResult _authResult) async { if (_authResult != null) { setState(() { isVerifying = true; }); FirebaseUser currUser = _authResult.user; print(currUser.phoneNumber); String myPhoneNumber = currUser.phoneNumber.substring(1); DocumentReference ref = _firestore.collection('users').document( myPhoneNumber); //reference of the user's document node in database/users. This node is created using uid await SharedObjects.prefs.setBool('login', false); await SharedObjects.prefs .setString(Constants.sessionUid, myPhoneNumber); ref.get().then((doc) async { if (doc.exists) { String profilePhotoUrl = doc.data['photoUrl']; String username = doc.data['username']; SharedObjects.prefs .setString(Constants.sessionProfilePictureUrl, profilePhotoUrl); SharedObjects.prefs.setString(Constants.sessionUsername, username); PermissionStatus currPermission = await userDataFunction.askContactPermissions(); if (currPermission == PermissionStatus.granted) { await userDataFunction.loadPhoneContactsV2(context); isVerifying = false; Navigator.push(context, MaterialPageRoute(builder: (context) => HomeScreen())); } else { setState(() { isVerifying = false; }); } } else { var data = { //add details 'phoneNumber': myPhoneNumber, }; ref.setData(data, merge: true); isVerifying = false; Navigator.push( context, MaterialPageRoute(builder: (context) => EnterName())); } }); } }); } Widget _buildOTPScreen() { return ChangeNotifierProvider( create: (context) => NumberState(), child: Form( key: _formKey, child: SafeArea( child: Padding( padding: EdgeInsets.symmetric(vertical: 35.0, horizontal: 25.0), child: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( children: [ Text( 'Verify +${context.watch().phoneNumber}', style: TextStyle( color: Constants.textStuffColor, fontSize: 22.0, fontWeight: FontWeight.w600, ), ), SizedBox(height: 30.0), Text( "Please enter the 6-digit code sent to +${context.watch().phoneNumber}", style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w400, ), textAlign: TextAlign.center, ), SizedBox(height: 20.0), Padding( padding: EdgeInsets.symmetric(horizontal: 50.0), child: TextFormField( controller: otpController, maxLengthEnforced: true, maxLength: 6, cursorColor: Constants.stuffColor, style: TextStyle(fontSize: 18.0), keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: InputDecoration( hintText: 'Enter 6-digit code', ), ), ), ], ), RaisedButton( padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 30.0), color: Constants.stuffColor, child: Text( 'VERIFY', style: TextStyle( color: Colors.white, fontSize: 18.0, letterSpacing: 1.0, ), ), onPressed: () async { setState(() { showSpinner = true; }); await _signInWithPhoneNumber( otpController.text.toString()); setState(() { showSpinner = false; }); }, ), SizedBox(height: 15.0), Card( elevation: 5.0, child: ListTile( onTap: resendOtpSwitch ? () { timerBLoc.add(StartTimerEvent()); setState(() { resendOtpSwitch = false; }); String phoneNum = context.read().phoneNumber; userDataFunction.verifyPhoneNumber(context, '+$phoneNum', _verificationFailed); } : () {}, leading: Icon(Icons.message, color: resendOtpSwitch ? Colors.black : Colors.grey), title: Text( 'Resend OTP', style: TextStyle( color: resendOtpSwitch ? Colors.black : Colors.grey, ), ), trailing: BlocListener( listener: (context, state) { if (state is TimerRunInProgressState) { setState(() { secondsStr = (state.newTick % 60) .floor() .toString() .padLeft(2, '0'); }); } if (state is TimerStoppedState) { setState(() { resendOtpSwitch = true; }); } }, child: Text( '$minutesStr:$secondsStr', ), )), ), ], ), ], ), ), ), ), ); } Widget _loadingScreen() { return Center( child: CircularProgressIndicator(), ); } @override void initState() { userDataFunction = UserDataFunction(); timerBLoc = BlocProvider.of(context); timerBLoc.add(StartTimerEvent()); super.initState(); } @override void dispose() { otpController.dispose(); timerBLoc.close(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: isVerifying || showSpinner ? _loadingScreen() : _buildOTPScreen(), ); } } ================================================ FILE: lib/screens/profile_screen.dart ================================================ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/functions/ChatFunction.dart'; import 'package:coocoo/stateProviders/profilePicUrlState.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:coocoo/widgets/ListTileProfile.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; class ProfileScreen extends StatefulWidget { @override _ProfileScreenState createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { final picker = ImagePicker(); File profileImageFile; ImageProvider profileImage; FirebaseStorage firebaseStorage = FirebaseStorage.instance; Firestore fireStoreDb = Firestore.instance; bool uploadingDp = false; ChatFunction chatFunction; @override Widget build(BuildContext context) { profileImage = CachedNetworkImageProvider( context.watch().profilePicUrl); double screenWidth = MediaQuery.of(context).size.width; double algo = screenWidth / perfectWidth; return Scaffold( appBar: AppBar( title: Text("Profile"), ), body: Container( width: double.infinity, padding: EdgeInsets.symmetric( vertical: algo * 10.0, horizontal: algo * 15.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Stack( children: [ Opacity( opacity: uploadingDp ? 0.5 : 1.0, child: Hero( tag: 'myProfile', child: CircleAvatar( radius: algo * 120.0, backgroundColor: Colors.white, backgroundImage: profileImage, ), ), ), Positioned.fill( child: Align( alignment: Alignment.center, child: uploadingDp ? CircularProgressIndicator( backgroundColor: Colors.white, ) : Container(), ), ), Positioned( bottom: algo * 5.0, right: algo * 10.0, child: GestureDetector( onTap: () { pickImage(context); }, child: CircleAvatar( backgroundColor: Colors.grey[200], radius: algo * 30.0, child: Icon( Icons.camera_alt, color: Colors.blueGrey, size: algo * 33.0, ), ), ), ) ], ), Spacer( flex: 2, ), ListTileProfile( iconData: FontAwesomeIcons.solidUser, title: 'Username', subTitle: SharedObjects.prefs.getString(Constants.sessionUsername) ?? "", ), ListTileProfile( iconData: FontAwesomeIcons.book, title: 'Full Name', subTitle: SharedObjects.prefs.getString(Constants.fullName), ), ListTileProfile( iconData: Icons.phone, title: 'Phone Number', subTitle: SharedObjects.prefs.getString(Constants.sessionUid), ), Spacer( flex: 9, ), ], ), ), ); } Future pickImage(BuildContext context) async { final pickedFile = await picker.getImage(source: ImageSource.gallery); if (pickedFile != null) { profileImageFile = File(pickedFile.path); setState(() { uploadingDp = true; }); String profileImageUrl = await uploadFile(profileImageFile, Paths.profilePicturePath); await saveProfilePicture(profileImageUrl, context); setState(() { uploadingDp = false; profileImage = FileImage(profileImageFile); }); } } Future saveProfilePicture( String profileImageUrl, BuildContext context) async { String uid = SharedObjects.prefs.getString(Constants.sessionUid); DocumentReference ref = fireStoreDb.collection(Paths.usersPath).document( uid); //reference of the user's document node in database/users. This node is created using uid var data = { 'photoUrl': profileImageUrl, }; await ref.setData(data, merge: true); // set the photourl, age and username await SharedObjects.prefs .setString(Constants.sessionProfilePictureUrl, profileImageUrl); context.read().setProfilePicUrl(profileImageUrl); // notify my contacts devices that I have changed my DP chatFunction = ChatFunction(); ref.get().then((snapshot) { var chatIds = snapshot.data["chats"]; String msgToSend = '{"msg" : "${Constants.profilePicChangeMsg}", "profilePicUrl" : "$profileImageUrl",' '"uid" : "$uid", "type" : "service"}'; chatIds.values.forEach((chatId) { chatFunction.sendServiceMsgToServer(context, msgToSend, chatId); }); }); } // 2. compress file and get file. Future testCompressAndGetFile(File file, String username) async { Directory tempDir = await getTemporaryDirectory(); String tempPath = tempDir.path; var result = await FlutterImageCompress.compressAndGetFile( file.absolute.path, tempPath + "/$username.jpg", minHeight: 500, minWidth: 500, quality: 80, ); return result; } Future uploadFile(File file, String path) async { String username = SharedObjects.prefs.getString(Constants.sessionUsername); file = await testCompressAndGetFile(file, username); String fileName = basename(file.path); final milliSecs = DateTime.now().millisecondsSinceEpoch; StorageReference reference = firebaseStorage.ref().child( '$path/$milliSecs\_$fileName'); // get a reference to the path of the image directory String uploadPath = await reference.getPath(); print('uploading to $uploadPath'); StorageUploadTask uploadTask = reference.putFile(file); // put the file in the path StorageTaskSnapshot result = await uploadTask.onComplete; // wait for the upload to complete String url = await result.ref .getDownloadURL(); //retrieve the download link and return it return url; } } ================================================ FILE: lib/screens/settings_screen.dart ================================================ import 'package:coocoo/screens/account_screen.dart'; import 'package:coocoo/screens/help_screen.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/widgets/SettingsTile.dart'; import 'package:flutter/material.dart'; class SettingsScreen extends StatelessWidget { final UserDataFunction userDataFunction = UserDataFunction(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Settings'), ), body: Column( children: [ SettingsTile( icon: Icons.settings, title: 'Account', onPress: () { Navigator.push(context, MaterialPageRoute(builder: (context) => AccountScreen())); }, ), SettingsTile( icon: Icons.help_outline, title: 'Help', onPress: () { Navigator.push(context, MaterialPageRoute(builder: (context) => HelpScreen())); }, ), SettingsTile( icon: Icons.supervisor_account, title: 'Invite a Friend', onPress: () => userDataFunction.onShare(context), ), ], ), ); } } ================================================ FILE: lib/screens/update_profile.dart ================================================ import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/config/Paths.dart'; import 'package:coocoo/functions/MQTTFunction.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/screens/home_screen.dart'; import 'package:coocoo/stateProviders/profilePicUrlState.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; enum PageState { initial, updating } class UpdateProfile extends StatefulWidget { @override _UpdateProfileState createState() => _UpdateProfileState(); } class _UpdateProfileState extends State { PageState profilePageState = PageState.initial; UserDataFunction userDataFunction; MQTTFunction mqttFunction; final _formKey = GlobalKey(); File profileImageFile; ImageProvider profileImage = AssetImage('images/user.png'); final TextEditingController usernameController = TextEditingController(); FirebaseStorage firebaseStorage = FirebaseStorage.instance; Firestore fireStoreDb = Firestore.instance; final picker = ImagePicker(); String NoDpUrl = "https://firebasestorage.googleapis.com/v0/b/coocoo-private-fc1e0.appspot.com/o/user.png?alt=media&token=0572ebb8-630d-4468-b5e1-91fd4cc9e049"; @override void dispose() { usernameController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Form( key: _formKey, child: buildUpdateProfilePage(context), ), ); } Widget buildUpdateProfilePage(BuildContext context) { if (profilePageState == PageState.initial) { return SafeArea( child: Padding( padding: EdgeInsets.symmetric(vertical: 35.0, horizontal: 15.0), child: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( children: [ Text( 'Profile Info', style: TextStyle( color: Constants.textStuffColor, fontSize: 22.0, fontWeight: FontWeight.w600, ), ), SizedBox(height: 30.0), Text( "Please provide an username and a profile photo", style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w400, ), textAlign: TextAlign.center, ), SizedBox(height: 20.0), Padding( padding: EdgeInsets.symmetric(horizontal: 10.0), child: Row( children: [ GestureDetector( onTap: pickImage, child: CircleAvatar( backgroundImage: profileImage, radius: 35, ), ), SizedBox(width: 10.0), Expanded( child: TextFormField( inputFormatters: [ FilteringTextInputFormatter.deny( RegExp(r'\s+')) // no spaces allowed ], validator: (value) { if (value.isEmpty) { return 'Please enter a valid username'; } return null; }, controller: usernameController, maxLengthEnforced: true, maxLength: 26, cursorColor: Constants.stuffColor, style: TextStyle(fontSize: 18.0), keyboardType: TextInputType.text, decoration: InputDecoration( hintText: 'username', ), ), ), ], ), ), ], ), RaisedButton( padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 30.0), color: Constants.stuffColor, child: Text( 'NEXT', style: TextStyle( color: Colors.white, fontSize: 18.0, letterSpacing: 1.0, ), ), onPressed: () async { if (_formKey.currentState.validate()) { setState(() { profilePageState = PageState.updating; }); // set profile image if (profileImageFile != null) { String profilePictureUrl = await uploadFile( profileImageFile, Paths.profilePicturePath, usernameController.text); await saveProfileDetails(profilePictureUrl, usernameController.text, context); } else { await saveProfileDetails( NoDpUrl, usernameController.text, context); } // get contacts permission and load the phone contacts userDataFunction = UserDataFunction(); PermissionStatus currPermission = await userDataFunction.askContactPermissions(); if (currPermission == PermissionStatus.granted) { await userDataFunction.loadPhoneContactsV2(context); Navigator.push( context, MaterialPageRoute( builder: (context) => HomeScreen())); } else { setState(() { profilePageState = PageState.initial; }); } } }, ), ], ), ], ), ), ); } else if (profilePageState == PageState.updating) { return Center( child: CircularProgressIndicator(), ); } return Center( child: CircularProgressIndicator(), ); } Future saveProfileDetails( String profileImageUrl, String username, BuildContext context) async { String uid = SharedObjects.prefs.getString(Constants.sessionUid); print('Session UID of PROFILE : $uid'); DocumentReference ref = fireStoreDb.collection(Paths.usersPath).document( uid); //reference of the user's document node in database/users. This node is created using uid var data = { 'photoUrl': profileImageUrl, 'username': username, }; await ref.setData(data, merge: true); // set the photourl, age and username await SharedObjects.prefs.setString(Constants.sessionUsername, username); await SharedObjects.prefs .setString(Constants.sessionProfilePictureUrl, profileImageUrl); context.read().setProfilePicUrl(profileImageUrl); } // 2. compress file and get file. Future testCompressAndGetFile(File file, String username) async { Directory tempDir = await getTemporaryDirectory(); String tempPath = tempDir.path; var result = await FlutterImageCompress.compressAndGetFile( file.absolute.path, tempPath + "/$username.jpg", minHeight: 500, minWidth: 500, quality: 80, ); return result; } Future uploadFile(File file, String path, String username) async { file = await testCompressAndGetFile(file, username); String fileName = basename(file.path); final milliSecs = DateTime.now().millisecondsSinceEpoch; StorageReference reference = firebaseStorage.ref().child( '$path/$milliSecs\_$fileName'); // get a reference to the path of the image directory String uploadPath = await reference.getPath(); print('uploading to $uploadPath'); StorageUploadTask uploadTask = reference.putFile(file); // put the file in the path StorageTaskSnapshot result = await uploadTask.onComplete; // wait for the upload to complete String url = await result.ref .getDownloadURL(); //retrieve the download link and return it return url; } Future pickImage() async { final pickedFile = await picker.getImage(source: ImageSource.gallery); print('Picked file path is : ' + pickedFile.path); profileImageFile = File(pickedFile.path); setState(() { profileImage = FileImage(profileImageFile); }); } } ================================================ FILE: lib/splashscreen.dart ================================================ import 'package:coocoo/screens/login_screen.dart'; import 'package:coocoo/functions/MQTTFunction.dart'; import 'package:coocoo/functions/UserDataFunction.dart'; import 'package:coocoo/screens/home_screen.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:permission_handler/permission_handler.dart'; class SplashScreen extends StatefulWidget { @override _SplashScreenState createState() => _SplashScreenState(); } class _SplashScreenState extends State { bool newUser; UserDataFunction userDataFunction; MQTTFunction mqttFunction; String loggedInUser; void checkIfAlreadyLogin(BuildContext context) async { newUser = (SharedObjects.prefs.getBool('login') ?? true); print(newUser); if (newUser == false) { userDataFunction = UserDataFunction(); PermissionStatus currPermission = await userDataFunction.askContactPermissions(); if (currPermission == PermissionStatus.granted) { // load Contacts here itself await userDataFunction.loadPhoneContactsV2(context); // wait a little time for loading await Future.delayed(Duration(seconds: 2)); Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => HomeScreen())); } else { Navigator.push( context, MaterialPageRoute(builder: (context) => Login())); } } else { Navigator.push(context, MaterialPageRoute(builder: (context) => Login())); } } @override void initState() { super.initState(); WidgetsBinding.instance .addPostFrameCallback((_) => checkIfAlreadyLogin(context)); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( body: Container( child: Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 22.0), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), width: 140.0, height: 140.0, decoration: BoxDecoration( boxShadow: [ BoxShadow( color: Colors.blueGrey, offset: Offset(2.5, 3.5), //(x,y) blurRadius: 2.5, ), ], color: Colors.blueGrey[100], borderRadius: BorderRadius.only( topRight: Radius.circular(40.0), bottomLeft: Radius.circular(40.0), ), ), child: Center( child: Icon( Icons.favorite, size: 80.0, )), ), ), ), ), ), ); } } ================================================ FILE: lib/stateProviders/mqtt_state.dart ================================================ import 'package:coocoo/managers/mqtt_manager.dart'; import 'package:flutter/material.dart'; class MQTTState with ChangeNotifier { MQTTManager _manager; Map _lastSenderMap = {}; Map _lastMsgMap = {}; MQTTManager get manager => _manager; String getLastSender(String chatId) { return _lastSenderMap[chatId]; } String getLastMsg(String chatId) { return _lastMsgMap[chatId]; } void setLastSender(String chatId, String newSender) { _lastSenderMap[chatId] = newSender; } void setLastMsg(String chatId, String newMsg) { _lastMsgMap[chatId] = newMsg; } void setNewManager(MQTTManager newManager) { if (_manager == null) { _manager = newManager; } notifyListeners(); } } ================================================ FILE: lib/stateProviders/number_state.dart ================================================ import 'package:flutter/material.dart'; class NumberState with ChangeNotifier { String _otp; String _phoneNumber; String _username; String get otp => _otp; String get phoneNumber => _phoneNumber; String get username => _username; void setOTP(String newValue) { _otp = newValue; notifyListeners(); } void setPhoneNumber(String newphoneNumber) { _phoneNumber = newphoneNumber; notifyListeners(); } } ================================================ FILE: lib/stateProviders/profilePicUrlState.dart ================================================ import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/utils/SharedObjects.dart'; import 'package:flutter/material.dart'; String NoDpUrl = "https://firebasestorage.googleapis.com/v0/b/coocoo-private-fc1e0.appspot.com/o/user.png?alt=media&token=0572ebb8-630d-4468-b5e1-91fd4cc9e049"; class ProfilePicUrlState with ChangeNotifier { String _profilePicUrl = SharedObjects.prefs.getString(Constants.sessionProfilePictureUrl) ?? NoDpUrl; String get profilePicUrl => _profilePicUrl; void setProfilePicUrl(String newUrl) { _profilePicUrl = newUrl; notifyListeners(); } } ================================================ FILE: lib/utils/Exceptions.dart ================================================ abstract class CooCooException implements Exception { String errorMessage(); } class UserNotFoundException extends CooCooException { @override String errorMessage() => 'No user found for provided uid/username'; } class UsernameMappingUndefinedException extends CooCooException { @override String errorMessage() => 'User not found'; } class ContactAlreadyExistsException extends CooCooException { @override String errorMessage() => 'MyContact already exists!'; } ================================================ FILE: lib/utils/SharedObjects.dart ================================================ import 'package:coocoo/config/Constants.dart'; import 'package:shared_preferences/shared_preferences.dart'; class SharedObjects { static CachedSharedPreferences prefs; } class CachedSharedPreferences { static SharedPreferences sharedPreferences; static CachedSharedPreferences instance; static final cachedKeyList = { Constants.firstRun, Constants.sessionUid, Constants.sessionUsername, Constants.fullName, Constants.sessionName, Constants.sessionProfilePictureUrl, Constants.configDarkMode, Constants.sessionCountryCode }; static final sessionKeyList = { Constants.sessionName, Constants.fullName, Constants.sessionUid, Constants.sessionUsername, Constants.sessionProfilePictureUrl, Constants.sessionCountryCode, }; static Map map = Map(); static Future getInstance() async { sharedPreferences = await SharedPreferences.getInstance(); if (sharedPreferences.getBool(Constants.firstRun) == null || sharedPreferences.get(Constants.firstRun)) { // if first run, then set these values await sharedPreferences.setBool(Constants.configDarkMode, false); await sharedPreferences.setBool(Constants.firstRun, false); } for (String key in cachedKeyList) { map[key] = sharedPreferences.get(key); } if (instance == null) instance = CachedSharedPreferences(); return instance; } String getString(String key) { if (cachedKeyList.contains(key)) { return map[key]; } return sharedPreferences.getString(key); } bool getBool(String key) { if (cachedKeyList.contains(key)) { return map[key]; } return sharedPreferences.getBool(key); } Future setString(String key, String value) async { bool result = await sharedPreferences.setString(key, value); if (result) map[key] = value; return result; } Future setBool(String key, bool value) async { bool result = await sharedPreferences.setBool(key, value); if (result) map[key] = value; return result; } Future clearAll() async { await sharedPreferences.clear(); map = Map(); } Future clearSession() async { await sharedPreferences.remove(Constants.sessionProfilePictureUrl); await sharedPreferences.remove(Constants.sessionUsername); await sharedPreferences.remove(Constants.fullName); await sharedPreferences.remove(Constants.sessionUid); await sharedPreferences.remove(Constants.sessionName); await sharedPreferences.remove(Constants.sessionCountryCode); map.removeWhere((k, v) => (sessionKeyList.contains(k))); } } ================================================ FILE: lib/widgets/AddFriendCard.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/widgets/ImageFullScreenWidget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_emoji/flutter_emoji.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class AddFriendCard extends StatelessWidget { final MyContact friend; final Function addFunc; final parser = EmojiParser(); AddFriendCard(this.friend, this.addFunc); @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; double algo = screenWidth / perfectWidth; return Card( elevation: 10.0, child: ListTile( leading: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageFullScreen(url: friend.photoUrl))); }, child: CircleAvatar( backgroundColor: Colors.white, radius: (screenWidth / perfectWidth) * 28.0, backgroundImage: CachedNetworkImageProvider(friend.photoUrl), ), ), title: Text( parser.emojify(friend.name), style: TextStyle( fontWeight: FontWeight.bold, fontSize: (screenWidth / perfectWidth) * 17.0, ), ), subtitle: Text( '@${friend.username}', style: TextStyle( fontSize: (screenWidth / perfectWidth) * 14.0, ), ), trailing: GestureDetector( onTap: addFunc, child: Card( elevation: 5.0, color: Colors.grey[200], child: Padding( padding: EdgeInsets.symmetric( horizontal: algo * 15.0, vertical: algo * 3.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ FaIcon( FontAwesomeIcons.userPlus, size: algo * 20.0, color: Constants.textStuffColor, ), SizedBox( width: algo * 5.0, ), Text( 'Add', style: TextStyle( fontWeight: FontWeight.w500, color: Constants.textStuffColor, fontSize: 18.0, ), ) ], ), )), ), ), ); } } ================================================ FILE: lib/widgets/ChatCard.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/models/Conversation.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/screens/chat_screen.dart'; import 'package:coocoo/widgets/ImageFullScreenWidget.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_emoji/flutter_emoji.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:intl/intl.dart'; class ChatCard extends StatelessWidget { final Conversation conversation; final int ind; final parser = EmojiParser(); ChatCard(this.conversation, this.ind); Widget buildMessage(String msgType, TextStyle chatTextStyle) { if (msgType == 'text') { return Text( parser.emojify(conversation.lastMessage), style: chatTextStyle, ); } else { return Align( alignment: Alignment.centerLeft, child: Icon( Icons.image, color: Colors.blueAccent, size: 30.0, ), ); } } @override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; final TextStyle kChatTextStyle = Theme.of(context).textTheme.bodyText2.copyWith( fontWeight: FontWeight.w400, fontSize: (screenWidth / perfectWidth) * 15.0, ); final TextStyle kTimeTextStyle = Theme.of(context).textTheme.bodyText2.copyWith( fontWeight: FontWeight.w300, fontSize: (screenWidth / perfectWidth) * 13.0, ); final TextStyle kTitleTextStyle = Theme.of(context).textTheme.headline6.copyWith( color: Colors.black, fontWeight: FontWeight.bold, fontSize: (screenWidth / perfectWidth) * 20.0, ); final currTime = DateFormat() .add_MMMd() .add_jm() .format(DateTime.fromMillisecondsSinceEpoch(conversation.time)); return Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Neumorphic( style: NeumorphicStyle( shadowDarkColor: Colors.blueGrey, lightSource: LightSource.topRight, color: Colors.white, depth: 4, intensity: 0.8, ), child: ListTile( contentPadding: EdgeInsets.symmetric(vertical: 3.0, horizontal: 8.0), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ChatScreen(MyContact( phoneNumber: conversation.phoneNumber, name: conversation.name, photoUrl: conversation.photoUrl, chatId: conversation.chatId, username: conversation.username, ind: ind, )))); }, leading: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageFullScreen( url: conversation.photoUrl, tag: "dash$ind"))); }, child: Hero( tag: "dash$ind", child: Neumorphic( style: kChatCircleNeumorphicStyle, child: CircleAvatar( backgroundColor: Colors.white, radius: (screenWidth / perfectWidth) * 28.0, backgroundImage: CachedNetworkImageProvider(conversation.photoUrl), ), ), ), ), title: Text( parser.emojify(conversation.name), style: kTitleTextStyle, ), subtitle: buildMessage(conversation.msgType, kChatTextStyle), trailing: Text( currTime, style: kTimeTextStyle, ), ), ), ); } } ================================================ FILE: lib/widgets/ChatItemWidget.dart ================================================ import 'dart:convert'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/models/ChatMessage.dart'; import 'package:coocoo/widgets/ImageFullScreenWidget.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:flutter_emoji/flutter_emoji.dart' as emj; class ChatItemWidget extends StatelessWidget { final ChatMessage message; final parser = emj.EmojiParser(); ChatItemWidget(this.message); final Color selfMessageColor = Colors.white; final Color otherMessageColor = Colors.black; final Color selfMessageBackgroundColor = Constants.textStuffColor; final Color otherMessageBackgroundColor = Colors.white; @override Widget build(BuildContext context) { return Container( child: Column( children: [ buildMessageContainer( message.isSelf, message.msg, context, message.msgType), buildTimeStamp(context, message.isSelf, message.time.toString()) ], ), ); } Widget buildMessageBody( String msgBody, String msgType, bool isSelf, BuildContext context) { if (msgType == 'text' || msgType == null) { return Text( parser.emojify("${msgBody ?? ''}"), style: TextStyle( color: isSelf ? selfMessageColor : otherMessageColor, fontSize: 15.0, ), ); } else { // it is an image try { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageFullScreen( memoryImage: msgBody, ))); }, child: Container( width: 95.0, height: 25.0, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ FaIcon( FontAwesomeIcons.play, color: isSelf ? Colors.white : Colors.black, size: 22.0, ), SizedBox(width: 8.0), Text( "Photo", style: TextStyle( color: isSelf ? Colors.white : Colors.black, fontSize: 19.0, letterSpacing: 1.0, fontWeight: FontWeight.w500, ), ), ], ), ), ); } catch (e) { return Container(); } } } Row buildMessageContainer( bool isSelf, String msgBody, BuildContext context, String msgType) { double lrEdgeInsets = 15.0; double tbEdgeInsets = 10.0; return Row( children: [ Container( child: buildMessageBody(msgBody, msgType, isSelf, context), padding: EdgeInsets.fromLTRB( lrEdgeInsets, tbEdgeInsets, lrEdgeInsets, tbEdgeInsets), constraints: BoxConstraints(maxWidth: 300.0), decoration: BoxDecoration( color: isSelf ? selfMessageBackgroundColor : otherMessageBackgroundColor, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(8.0), bottomRight: Radius.circular(8.0), topLeft: isSelf ? Radius.circular(8.0) : Radius.zero, topRight: isSelf ? Radius.zero : Radius.circular(8.0), ), border: Border.all(color: Colors.grey)), margin: EdgeInsets.only(right: isSelf ? 5.0 : 0, left: isSelf ? 0 : 5.0), ) ], mainAxisAlignment: isSelf ? MainAxisAlignment.end : MainAxisAlignment.start, // aligns the chatitem to right end ); } Row buildTimeStamp(BuildContext context, bool isSelf, String timeStamp) { final currTime = DateFormat() .add_y() .add_MMMd() .add_jm() .format(DateTime.fromMillisecondsSinceEpoch(int.parse(timeStamp))); return Row( mainAxisAlignment: isSelf ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ Container( child: Text( currTime, style: Theme.of(context).textTheme.caption, ), margin: EdgeInsets.only( left: isSelf ? 5.0 : 4.0, right: isSelf ? 4.0 : 5.0, top: 3.0, bottom: 6.0), ) ]); } } ================================================ FILE: lib/widgets/ContactCard.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:coocoo/constants.dart'; import 'package:flutter/material.dart'; import 'package:flutter_emoji/flutter_emoji.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; class ContactCard extends StatelessWidget { final String name; final String status; final String profilePic; final parser = EmojiParser(); ContactCard({ @required this.name, @required this.profilePic, this.status, }); @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; return ListTile( leading: CircleAvatar( backgroundColor: Colors.white, radius: (screenWidth / perfectWidth) * 18.0, backgroundImage: CachedNetworkImageProvider(profilePic), ), title: Text( parser.emojify(name), style: TextStyle( fontWeight: FontWeight.bold, fontSize: (screenWidth / perfectWidth) * 17.0, ), ), subtitle: Text( status, style: TextStyle( fontSize: (screenWidth / perfectWidth) * 14.0, ), ), ); } } ================================================ FILE: lib/widgets/ContactRowWidget.dart ================================================ import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/screens/chat_screen.dart'; import 'package:coocoo/widgets/ContactCard.dart'; import 'package:flutter/material.dart'; // ignore: must_be_immutable class ContactRowWidget extends StatelessWidget { ContactRowWidget({ Key key, @required this.contact, }) : super(key: key); final MyContact contact; @override Widget build(BuildContext context) { return InkWell( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ChatScreen(contact), )); }, child: ContactCard( name: contact.name, profilePic: contact.photoUrl, status: '@${contact.username ?? contact.phoneNumber}', )); } } ================================================ FILE: lib/widgets/DangerCard.dart ================================================ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class DangerCard extends StatelessWidget { final String title; final Function func; final Color color; DangerCard(this.color, this.title, this.func); @override Widget build(BuildContext context) { return Card( elevation: 5.0, child: ListTile( title: Text( title, style: TextStyle( fontWeight: FontWeight.w600, color: color, fontSize: 15.0, ), ), leading: FaIcon(FontAwesomeIcons.skullCrossbones, color: color), onTap: func, ), ); } } ================================================ FILE: lib/widgets/FriendRequestCard.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:coocoo/blocs/AddFriends/add_friends_bloc.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/widgets/ImageFullScreenWidget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_emoji/flutter_emoji.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class FriendRequestCard extends StatelessWidget { final MyContact friend; final parser = EmojiParser(); final AddFriendsBloc addFriendsBloc; final BuildContext contex; FriendRequestCard(this.friend, this.addFriendsBloc, this.contex); Future _showPrompt(BuildContext context) async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text('Please Confirm'), content: Text( 'Do you want to decline friend request from ${friend.name} ?'), actions: [ FlatButton( onPressed: () => Navigator.of(context).pop(false), child: Text( 'NO', style: TextStyle( fontWeight: FontWeight.bold, ), ), ), FlatButton( onPressed: () { addFriendsBloc .add(DeclineFriendRequestEvent(friend.phoneNumber)); Navigator.of(context).pop(false); }, child: Text( 'YES', style: TextStyle( fontWeight: FontWeight.bold, ), ), ), ], ), )) ?? false; } @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; return Card( elevation: 5.0, child: ListTile( leading: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageFullScreen(url: friend.photoUrl))); }, child: CircleAvatar( backgroundColor: Colors.white, radius: (screenWidth / perfectWidth) * 28.0, backgroundImage: CachedNetworkImageProvider(friend.photoUrl), ), ), title: Text( parser.emojify(friend.name), style: TextStyle( fontWeight: FontWeight.bold, fontSize: (screenWidth / perfectWidth) * 17.0, ), ), subtitle: Text( '@${friend.username}', style: TextStyle( fontSize: (screenWidth / perfectWidth) * 14.0, ), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( onTap: () { addFriendsBloc .add(AcceptFriendRequestEvent(friend.phoneNumber, contex)); }, child: Neumorphic( style: kCircleNeumorphicStyle.copyWith( depth: 5.0, ), child: CircleAvatar( backgroundColor: Colors.green[500], child: FaIcon( FontAwesomeIcons.check, color: Colors.white, ), ), ), ), SizedBox(width: (screenWidth / perfectWidth) * 20.0), GestureDetector( onTap: () async { await _showPrompt(context); }, child: Neumorphic( style: kCircleNeumorphicStyle.copyWith( depth: 5.0, ), child: CircleAvatar( backgroundColor: Colors.red, child: FaIcon( FontAwesomeIcons.times, color: Colors.white, ), ), ), ) ], ), ), ); } } ================================================ FILE: lib/widgets/GradientSnackBar.dart ================================================ import 'package:flushbar/flushbar.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class GradientSnackBar { static void showMessage( BuildContext context, String message, int secondsDuration) { Flushbar( message: message, duration: Duration(seconds: secondsDuration), backgroundGradient: LinearGradient( begin: Alignment.bottomLeft, end: Alignment.bottomRight, colors: [Colors.green, Colors.blueAccent]), backgroundColor: Colors.red, boxShadows: [ BoxShadow( color: Colors.blue[800], offset: Offset(0.0, 2.0), blurRadius: 3.0, ) ], )..show(context); } static void showError(BuildContext context, String error) { Flushbar( message: error, duration: Duration(milliseconds: 1500), backgroundGradient: LinearGradient( begin: Alignment.center, end: Alignment.bottomRight, colors: [ Colors.red[700], Colors.blueGrey, ]), backgroundColor: Colors.red, boxShadows: [ BoxShadow( color: Colors.blue[800], offset: Offset(0.0, 2.0), blurRadius: 3.0, ) ], )..show(context); } } ================================================ FILE: lib/widgets/ImageFullScreenWidget.dart ================================================ import 'dart:convert'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; class ImageFullScreen extends StatelessWidget { final String url; final String tag; final String memoryImage; ImageFullScreen({this.url, this.tag, this.memoryImage}); Widget _buildImage() { if (memoryImage != null) { return Image( image: MemoryImage(base64Decode(memoryImage)), ); } else { return tag != null ? Hero(tag: tag, child: CachedNetworkImage(imageUrl: url)) : CachedNetworkImage( imageUrl: url, ); } } @override Widget build(BuildContext context) { return Stack( children: [ Positioned( left: 12.0, top: 10.0, child: GestureDetector( onTap: () { Navigator.of(context).pop(); }, child: Icon( Icons.arrow_back, color: Colors.white, size: 40.0, ), ), ), Align( alignment: Alignment.center, child: Container( color: Colors.black, child: _buildImage(), ), ), ], ); } } ================================================ FILE: lib/widgets/ListTileProfile.dart ================================================ import 'package:flutter/material.dart'; class ListTileProfile extends StatelessWidget { final IconData iconData; final String title; final String subTitle; final Widget trailingWidget; ListTileProfile( {this.iconData, this.title, this.subTitle, this.trailingWidget}); @override Widget build(BuildContext context) { return Card( elevation: 10.0, child: ListTile( leading: Icon( iconData, color: Colors.blueGrey, ), title: Text( title, style: TextStyle( fontSize: 14.0, color: Colors.grey[600], ), ), subtitle: Text( subTitle, style: TextStyle( fontSize: 20.0, color: Colors.black, ), ), trailing: trailingWidget, ), ); } } ================================================ FILE: lib/widgets/NameTextField.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class NameTextField extends StatelessWidget { final String hintText; final TextEditingController controller; NameTextField({this.hintText, this.controller}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: TextFormField( inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'\s+')) // no spaces allowed ], validator: (value) { if (value.trim().isEmpty) { return 'Please enter a valid $hintText'; } return null; }, controller: controller, style: TextStyle( fontSize: 18.0, ), decoration: InputDecoration( hintText: hintText, contentPadding: EdgeInsets.only(bottom: -15.0), focusedBorder: UnderlineInputBorder( borderSide: BorderSide( color: Colors.black, ), ), ), ), ); } } ================================================ FILE: lib/widgets/NoRequestsCard.dart ================================================ import 'package:coocoo/config/Constants.dart'; import 'package:flutter/material.dart'; class NoRequestsCard extends StatelessWidget { final double algo; final String text; NoRequestsCard({ @required this.algo, @required this.text, }); @override Widget build(BuildContext context) { return Card( elevation: 5.0, child: Padding( padding: const EdgeInsets.symmetric(vertical: 25.0, horizontal: 8.0), child: Center( child: Text(text, textAlign: TextAlign.center, style: TextStyle( fontSize: algo * 20.0, color: Constants.textStuffColor, )), ), ), ); } } ================================================ FILE: lib/widgets/NonContactCard.dart ================================================ import 'package:flutter/material.dart'; import 'package:share/share.dart'; class NonContactCard extends StatelessWidget { final String name; NonContactCard( this.name, ); @override Widget build(BuildContext context) { return ListTile( leading: CircleAvatar( backgroundColor: Colors.white, radius: 18.0, backgroundImage: AssetImage('images/user.png'), ), title: Text( name, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15.0, ), ), trailing: FlatButton( child: Text( 'Invite', style: TextStyle( color: Colors.white, ), ), color: Colors.green, onPressed: () => _onShare(context), ), ); } _onShare(BuildContext context) async { // A builder is used to retrieve the context immediately // surrounding the RaisedButton. // // The context's `findRenderObject` returns the first // RenderObject in its descendent tree when it's not // a RenderObjectWidget. The RaisedButton's RenderObject // has its position and size after it's built. final RenderBox box = context.findRenderObject(); await Share.share( "https://play.google.com/store/apps/details?id=com.digantakalita.coocoo", subject: "Lets have our private chats on" "this New Cool Messenger HitUp from now on. Its way safer than the others.", sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size); } } ================================================ FILE: lib/widgets/SentRequestCard.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:coocoo/config/Constants.dart'; import 'package:coocoo/constants.dart'; import 'package:coocoo/models/MyContact.dart'; import 'package:coocoo/widgets/ImageFullScreenWidget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_emoji/flutter_emoji.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class SentRequestCard extends StatelessWidget { final MyContact friend; final parser = EmojiParser(); SentRequestCard(this.friend); Future _showContinueDialog(BuildContext context) async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text('Friend Request Pending'), content: Text( '${friend.name} has not accepted your friend request yet.'), actions: [ FlatButton( onPressed: () => Navigator.of(context).pop(false), child: Text( 'OK', style: TextStyle( fontWeight: FontWeight.bold, ), ), ), ], ), )) ?? false; } @override Widget build(BuildContext context) { final double screenWidth = MediaQuery.of(context).size.width; double algo = screenWidth / perfectWidth; return Card( elevation: 1.0, child: ListTile( onTap: () async { await _showContinueDialog(context); }, leading: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageFullScreen(url: friend.photoUrl))); }, child: CircleAvatar( backgroundColor: Colors.white, radius: (screenWidth / perfectWidth) * 28.0, backgroundImage: CachedNetworkImageProvider(friend.photoUrl), ), ), title: Text( parser.emojify(friend.name), style: TextStyle( fontWeight: FontWeight.bold, fontSize: (screenWidth / perfectWidth) * 17.0, ), ), subtitle: Text( '@${friend.username}', style: TextStyle( fontSize: (screenWidth / perfectWidth) * 14.0, ), ), trailing: Card( elevation: 2.0, color: Colors.grey[200], child: Padding( padding: EdgeInsets.symmetric( horizontal: algo * 15.0, vertical: algo * 3.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ Stack( clipBehavior: Clip.none, children: [ Positioned( right: algo * 17.0, top: algo * 4.0, child: FaIcon( FontAwesomeIcons.check, size: algo * 10.0, color: Constants.textStuffColor, ), ), FaIcon( FontAwesomeIcons.solidUser, size: algo * 20.0, color: Constants.textStuffColor, ), ], ), SizedBox( width: algo * 5.0, ), Text( 'Sent', style: TextStyle( fontWeight: FontWeight.w500, color: Constants.textStuffColor, fontSize: 18.0, ), ) ], ), )), ), ); } } ================================================ FILE: lib/widgets/SettingsTile.dart ================================================ import 'package:flutter/material.dart'; class SettingsTile extends StatelessWidget { final IconData icon; final String title; final Function onPress; SettingsTile({@required this.icon, @required this.title, this.onPress}); @override Widget build(BuildContext context) { return Card( elevation: 5.0, child: ListTile( title: Text( title, style: TextStyle( fontWeight: FontWeight.w500, ), ), leading: Icon(icon, color: Colors.blueGrey), onTap: onPress, ), ); } } ================================================ FILE: pubspec.yaml ================================================ name: coocoo description: A Private Chat Messenger # The following line prevents the package from being accidentally published to # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # In Android, build-name is used as versionName while build-number used as versionCode. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.2.6+7 environment: sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.3 provider: ^4.1.2 firebase_auth: ^0.16.1 cloud_firestore: ^0.13.7 mqtt_client: ^7.1.0 contacts_service: ^0.4.6 permission_handler: ^5.0.1 image_picker: ^0.6.7+4 firebase_storage: ^3.0.6 shared_preferences: ^0.5.3+4 flutter_bloc: ^6.1.0 equatable: ^1.2.5 cached_network_image: ^2.2.0+1 sqflite: ^1.3.1 path: ^1.7.0 intl: ^0.16.1 flutter_neumorphic: ^3.0.1 country_pickers: ^1.3.0 font_awesome_flutter: ^8.8.1 onesignal_flutter: ^2.6.1 flutter_image_compress: ^0.7.0 path_provider: ^1.6.14 flushbar: ^1.10.4 share: ^0.6.5 flutter_emoji: ^2.2.1+1 animated_bottom_navigation_bar: ^0.1.2+2 dev_dependencies: flutter_test: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: assets: - images/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: NotoColorEmoji # fonts: # - asset: fonts/ # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility that Flutter provides. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:coocoo/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }