├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── harshRajpurohit │ │ │ │ └── we_chat │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── images │ ├── add_image.png │ ├── camera.png │ ├── google.png │ ├── icon.png │ └── logo.png └── lottie │ └── ai.json ├── firebase.json ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── api │ ├── apis.dart │ └── notification_access_token.dart ├── firebase_options.dart ├── helper │ ├── dialogs.dart │ └── my_date_util.dart ├── main.dart ├── models │ ├── chat_user.dart │ └── message.dart ├── screens │ ├── ai_screen.dart │ ├── auth │ │ └── login_screen.dart │ ├── chat_screen.dart │ ├── home_screen.dart │ ├── profile_screen.dart │ ├── splash_screen.dart │ └── view_profile_screen.dart └── widgets │ ├── ai_message_card.dart │ ├── chat_user_card.dart │ ├── dialogs │ └── profile_dialog.dart │ ├── message_card.dart │ └── profile_image.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png └── 8.png └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | <<<<<<< HEAD 8 | revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" 9 | ======= 10 | revision: "68bfaea224880b488c617afe30ab12091ea8fa4e" 11 | >>>>>>> 806dca544752c5c978f39977aeb4005785d5f0ee 12 | channel: "stable" 13 | 14 | project_type: app 15 | 16 | # Tracks metadata for the flutter migrate command 17 | migration: 18 | platforms: 19 | - platform: root 20 | <<<<<<< HEAD 21 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 22 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 23 | - platform: android 24 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 25 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 26 | - platform: ios 27 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 28 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 29 | - platform: linux 30 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 31 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 32 | - platform: web 33 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 34 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 35 | ======= 36 | create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 37 | base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 38 | - platform: android 39 | create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 40 | base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 41 | - platform: ios 42 | create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 43 | base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 44 | - platform: web 45 | create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 46 | base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e 47 | >>>>>>> 806dca544752c5c978f39977aeb4005785d5f0ee 48 | 49 | # User provided section 50 | 51 | # List of Local paths (relative to this file) that should be 52 | # ignored by the migrate tool. 53 | # 54 | # Files that are not part of the templates will be ignored by default. 55 | unmanaged_files: 56 | - 'lib/main.dart' 57 | - 'ios/Runner.xcodeproj/project.pbxproj' 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apna Chat (We Chat) 2 | 3 | ![Status](https://img.shields.io/badge/Status-Active-brightgreen) 4 | ![Dart](https://img.shields.io/badge/dart-100%25-brightgreen) 5 | ![Flutter](https://img.shields.io/badge/Flutter-Cross%20Platform-blue) 6 | 7 | **Simple, Decent & Feature-Rich Chatting Application.** 8 | 9 | --- 10 | 11 | ## 📦 APKs: 12 | 13 | - **Arm APK:** 14 | [Download Arm APK](https://drive.google.com/file/d/1LGytBaUy4mjAXMAzIL36UDYMq5-RtqZL/view?usp=sharing) (~21 MB) 15 | *(Supports ARM v7 and v8 devices)* 16 | 17 | - **Global APK:** 18 | [Download Global APK](https://drive.google.com/file/d/1fAEIvP8CVsSvoOOovkLRv8zF8CxnpNmn/view?usp=sharing) (~53 MB) 19 | *(Supports all devices)* 20 | 21 | --- 22 | 23 | ## 📸 Screenshots: 24 | 25 | **Actual app looks even better! 😃** 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | --- 39 | 40 | ## ✨ Features: 41 | 42 | - Display only known users or contacts. 43 | - Real-time messaging (with Firebase). 44 | - Google Sign-In (no account or mobile number needed). 45 | - Add users by email ID. 46 | - Simple user profiles (with photo, about, last-seen, created-at). 47 | - Message read status. 48 | - Message delete & edit features. 49 | - Message notifications (push notifications). 50 | - AI Chat Bot integration (**Google Gemini**). 51 | - Sleek UI with new Material Widgets. 52 | - **And much more...** 53 | 54 | --- 55 | 56 | ## 🧠 AI Chatbot (Google Gemini) Setup: 57 | 58 | To use the **Gemini AI Chatbot** feature, you need to provide your own API key. 59 | Create an API key by visiting [Google AI Studio](https://aistudio.google.com/app/apikey). 60 | 61 | --- 62 | 63 | ## 🎥 YouTube Course: 64 | 65 | Check out the **YouTube Course** here: 66 | [Watch the YouTube Playlist](https://youtube.com/playlist?list=PL8kbUJtS6hyal7Uw7wTeYmv7yiNPH5kOq) 67 | 68 | **Note:** This project contains more features and optimizations than the one demonstrated in the YouTube course. 69 | 70 | --- 71 | 72 | ## 💬 Feedback and Suggestions: 73 | 74 | I’d love to hear your feedback or suggestions! Feel free to reach out via email: 75 | 📧 [rajpurohitharsh2020@gmail.com](mailto:rajpurohitharsh2020@gmail.com) 76 | 77 | --- 78 | 79 | ## 🏷️ Keywords 80 | 81 | - **Flutter Chat App** 82 | - **Dart Chat Application** 83 | - **Firebase Real-Time Messaging** 84 | - **Google Sign-In Chat App** 85 | - **AI Chatbot in Flutter** 86 | - **Cross-Platform Chat App** 87 | - **Flutter Firebase Integration** 88 | - **Flutter Messaging App** 89 | - **Material Design in Flutter** 90 | - **Google Gemini AI Chat** 91 | 92 | --- 93 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | // START: FlutterFire Configuration 4 | id 'com.google.gms.google-services' 5 | // END: FlutterFire Configuration 6 | id "kotlin-android" 7 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 8 | id "dev.flutter.flutter-gradle-plugin" 9 | } 10 | 11 | android { 12 | namespace = "com.harshRajpurohit.we_chat" 13 | compileSdk = flutter.compileSdkVersion 14 | ndkVersion = flutter.ndkVersion 15 | 16 | compileOptions { 17 | sourceCompatibility = JavaVersion.VERSION_1_8 18 | targetCompatibility = JavaVersion.VERSION_1_8 19 | } 20 | 21 | kotlinOptions { 22 | jvmTarget = JavaVersion.VERSION_1_8 23 | } 24 | 25 | defaultConfig { 26 | applicationId = "com.harshRajpurohit.we_chat" 27 | minSdk = 23 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | signingConfig = signingConfigs.debug 36 | } 37 | } 38 | } 39 | 40 | flutter { 41 | source = "../.." 42 | } 43 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "288842837392", 4 | "project_id": "we-chat-75f13", 5 | "storage_bucket": "we-chat-75f13.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:288842837392:android:1353619813061d867a5715", 11 | "android_client_info": { 12 | "package_name": "com.harshRajpurohit.we_chat" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "288842837392-gse3j6u9fferoav9leh2ik3468asfeup.apps.googleusercontent.com", 18 | "client_type": 1, 19 | "android_info": { 20 | "package_name": "com.harshRajpurohit.we_chat", 21 | "certificate_hash": "6bbd2c69c4f7a04ae23d927546f3c8137dbf4331" 22 | } 23 | }, 24 | { 25 | "client_id": "288842837392-gvt1l790g0t1fmnurc5pmko3oss8b1tq.apps.googleusercontent.com", 26 | "client_type": 1, 27 | "android_info": { 28 | "package_name": "com.harshRajpurohit.we_chat", 29 | "certificate_hash": "aa1014c75d3bcbaeff8097b6786a5633ee826e9b" 30 | } 31 | }, 32 | { 33 | "client_id": "288842837392-tib1k52rlca0mlq4jr3iil7apgvcc5jt.apps.googleusercontent.com", 34 | "client_type": 3 35 | } 36 | ], 37 | "api_key": [ 38 | { 39 | "current_key": "AIzaSyBeszZHLYJNyjUhpujMH9yZ4ol9JqsAmPU" 40 | } 41 | ], 42 | "services": { 43 | "appinvite_service": { 44 | "other_platform_oauth_client": [ 45 | { 46 | "client_id": "288842837392-tib1k52rlca0mlq4jr3iil7apgvcc5jt.apps.googleusercontent.com", 47 | "client_type": 3 48 | }, 49 | { 50 | "client_id": "288842837392-sgib97u6439i4jte3bo19u00fh663euu.apps.googleusercontent.com", 51 | "client_type": 2, 52 | "ios_info": { 53 | "bundle_id": "com.harshRajpurohit.weChat" 54 | } 55 | } 56 | ] 57 | } 58 | } 59 | } 60 | ], 61 | "configuration_version": "1" 62 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/harshRajpurohit/we_chat/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.harshRajpurohit.we_chat 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.1.0" apply false 22 | // START: FlutterFire Configuration 23 | id "com.google.gms.google-services" version "4.3.15" apply false 24 | // END: FlutterFire Configuration 25 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 26 | } 27 | 28 | include ":app" 29 | -------------------------------------------------------------------------------- /assets/images/add_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/assets/images/add_image.png -------------------------------------------------------------------------------- /assets/images/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/assets/images/camera.png -------------------------------------------------------------------------------- /assets/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/assets/images/google.png -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/assets/images/logo.png -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "flutter": { 3 | "platforms": { 4 | "android": { 5 | "default": { 6 | "projectId": "we-chat-75f13", 7 | "appId": "1:288842837392:android:1353619813061d867a5715", 8 | "fileOutput": "android/app/google-services.json" 9 | } 10 | }, 11 | "dart": { 12 | "lib/firebase_options.dart": { 13 | "projectId": "we-chat-75f13", 14 | "configurations": { 15 | "android": "1:288842837392:android:1353619813061d867a5715", 16 | "ios": "1:288842837392:ios:f39c8dc31525a6687a5715", 17 | "web": "1:288842837392:web:6ae5bafc6d7d4f407a5715" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 25 | remoteInfo = Runner; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXCopyFilesBuildPhase section */ 30 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 31 | isa = PBXCopyFilesBuildPhase; 32 | buildActionMask = 2147483647; 33 | dstPath = ""; 34 | dstSubfolderSpec = 10; 35 | files = ( 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 46 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 48 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 49 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 75 | ); 76 | path = RunnerTests; 77 | sourceTree = ""; 78 | }; 79 | 9740EEB11CF90186004384FC /* Flutter */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 83 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 84 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 85 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 86 | ); 87 | name = Flutter; 88 | sourceTree = ""; 89 | }; 90 | 97C146E51CF9000F007C117D = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9740EEB11CF90186004384FC /* Flutter */, 94 | 97C146F01CF9000F007C117D /* Runner */, 95 | 97C146EF1CF9000F007C117D /* Products */, 96 | 331C8082294A63A400263BE5 /* RunnerTests */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 97C146EF1CF9000F007C117D /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 97C146EE1CF9000F007C117D /* Runner.app */, 104 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 97C146F01CF9000F007C117D /* Runner */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 113 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 114 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 115 | 97C147021CF9000F007C117D /* Info.plist */, 116 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 117 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 118 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 119 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 120 | ); 121 | path = Runner; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 130 | buildPhases = ( 131 | 331C807D294A63A400263BE5 /* Sources */, 132 | 331C807F294A63A400263BE5 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 138 | ); 139 | name = RunnerTests; 140 | productName = RunnerTests; 141 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 142 | productType = "com.apple.product-type.bundle.unit-test"; 143 | }; 144 | 97C146ED1CF9000F007C117D /* Runner */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 147 | buildPhases = ( 148 | 9740EEB61CF901F6004384FC /* Run Script */, 149 | 97C146EA1CF9000F007C117D /* Sources */, 150 | 97C146EB1CF9000F007C117D /* Frameworks */, 151 | 97C146EC1CF9000F007C117D /* Resources */, 152 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 153 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Runner; 160 | productName = Runner; 161 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 97C146E61CF9000F007C117D /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | BuildIndependentTargetsInParallel = YES; 171 | LastUpgradeCheck = 1510; 172 | ORGANIZATIONNAME = ""; 173 | TargetAttributes = { 174 | 331C8080294A63A400263BE5 = { 175 | CreatedOnToolsVersion = 14.0; 176 | TestTargetID = 97C146ED1CF9000F007C117D; 177 | }; 178 | 97C146ED1CF9000F007C117D = { 179 | CreatedOnToolsVersion = 7.3.1; 180 | LastSwiftMigration = 1100; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 185 | compatibilityVersion = "Xcode 9.3"; 186 | developmentRegion = en; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | en, 190 | Base, 191 | ); 192 | mainGroup = 97C146E51CF9000F007C117D; 193 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | 97C146ED1CF9000F007C117D /* Runner */, 198 | 331C8080294A63A400263BE5 /* RunnerTests */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | 331C807F294A63A400263BE5 /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 217 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | alwaysOutOfDate = 1; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | inputPaths = ( 232 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 233 | ); 234 | name = "Thin Binary"; 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 240 | }; 241 | 9740EEB61CF901F6004384FC /* Run Script */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | alwaysOutOfDate = 1; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | inputPaths = ( 248 | ); 249 | name = "Run Script"; 250 | outputPaths = ( 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | shellPath = /bin/sh; 254 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 255 | }; 256 | /* End PBXShellScriptBuildPhase section */ 257 | 258 | /* Begin PBXSourcesBuildPhase section */ 259 | 331C807D294A63A400263BE5 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | 97C146EA1CF9000F007C117D /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 272 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin PBXTargetDependency section */ 279 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 280 | isa = PBXTargetDependency; 281 | target = 97C146ED1CF9000F007C117D /* Runner */; 282 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 283 | }; 284 | /* End PBXTargetDependency section */ 285 | 286 | /* Begin PBXVariantGroup section */ 287 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 288 | isa = PBXVariantGroup; 289 | children = ( 290 | 97C146FB1CF9000F007C117D /* Base */, 291 | ); 292 | name = Main.storyboard; 293 | sourceTree = ""; 294 | }; 295 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 296 | isa = PBXVariantGroup; 297 | children = ( 298 | 97C147001CF9000F007C117D /* Base */, 299 | ); 300 | name = LaunchScreen.storyboard; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXVariantGroup section */ 304 | 305 | /* Begin XCBuildConfiguration section */ 306 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 311 | CLANG_ANALYZER_NONNULL = YES; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 338 | ENABLE_NS_ASSERTIONS = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 341 | GCC_C_LANGUAGE_STANDARD = gnu99; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 350 | MTL_ENABLE_DEBUG_INFO = NO; 351 | SDKROOT = iphoneos; 352 | SUPPORTED_PLATFORMS = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | VALIDATE_PRODUCT = YES; 355 | }; 356 | name = Profile; 357 | }; 358 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 359 | isa = XCBuildConfiguration; 360 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CLANG_ENABLE_MODULES = YES; 364 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 365 | ENABLE_BITCODE = NO; 366 | INFOPLIST_FILE = Runner/Info.plist; 367 | LD_RUNPATH_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "@executable_path/Frameworks", 370 | ); 371 | PRODUCT_BUNDLE_IDENTIFIER = com.harshRajpurohit.weChat; 372 | PRODUCT_NAME = "$(TARGET_NAME)"; 373 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 374 | SWIFT_VERSION = 5.0; 375 | VERSIONING_SYSTEM = "apple-generic"; 376 | }; 377 | name = Profile; 378 | }; 379 | 331C8088294A63A400263BE5 /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | BUNDLE_LOADER = "$(TEST_HOST)"; 383 | CODE_SIGN_STYLE = Automatic; 384 | CURRENT_PROJECT_VERSION = 1; 385 | GENERATE_INFOPLIST_FILE = YES; 386 | MARKETING_VERSION = 1.0; 387 | PRODUCT_BUNDLE_IDENTIFIER = com.harshRajpurohit.weChat.RunnerTests; 388 | PRODUCT_NAME = "$(TARGET_NAME)"; 389 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 390 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 391 | SWIFT_VERSION = 5.0; 392 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 393 | }; 394 | name = Debug; 395 | }; 396 | 331C8089294A63A400263BE5 /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | BUNDLE_LOADER = "$(TEST_HOST)"; 400 | CODE_SIGN_STYLE = Automatic; 401 | CURRENT_PROJECT_VERSION = 1; 402 | GENERATE_INFOPLIST_FILE = YES; 403 | MARKETING_VERSION = 1.0; 404 | PRODUCT_BUNDLE_IDENTIFIER = com.harshRajpurohit.weChat.RunnerTests; 405 | PRODUCT_NAME = "$(TARGET_NAME)"; 406 | SWIFT_VERSION = 5.0; 407 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 408 | }; 409 | name = Release; 410 | }; 411 | 331C808A294A63A400263BE5 /* Profile */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | BUNDLE_LOADER = "$(TEST_HOST)"; 415 | CODE_SIGN_STYLE = Automatic; 416 | CURRENT_PROJECT_VERSION = 1; 417 | GENERATE_INFOPLIST_FILE = YES; 418 | MARKETING_VERSION = 1.0; 419 | PRODUCT_BUNDLE_IDENTIFIER = com.harshRajpurohit.weChat.RunnerTests; 420 | PRODUCT_NAME = "$(TARGET_NAME)"; 421 | SWIFT_VERSION = 5.0; 422 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 423 | }; 424 | name = Profile; 425 | }; 426 | 97C147031CF9000F007C117D /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ALWAYS_SEARCH_USER_PATHS = NO; 430 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 431 | CLANG_ANALYZER_NONNULL = YES; 432 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 433 | CLANG_CXX_LIBRARY = "libc++"; 434 | CLANG_ENABLE_MODULES = YES; 435 | CLANG_ENABLE_OBJC_ARC = YES; 436 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 437 | CLANG_WARN_BOOL_CONVERSION = YES; 438 | CLANG_WARN_COMMA = YES; 439 | CLANG_WARN_CONSTANT_CONVERSION = YES; 440 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 448 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 451 | CLANG_WARN_STRICT_PROTOTYPES = YES; 452 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 453 | CLANG_WARN_UNREACHABLE_CODE = YES; 454 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 455 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 456 | COPY_PHASE_STRIP = NO; 457 | DEBUG_INFORMATION_FORMAT = dwarf; 458 | ENABLE_STRICT_OBJC_MSGSEND = YES; 459 | ENABLE_TESTABILITY = YES; 460 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 461 | GCC_C_LANGUAGE_STANDARD = gnu99; 462 | GCC_DYNAMIC_NO_PIC = NO; 463 | GCC_NO_COMMON_BLOCKS = YES; 464 | GCC_OPTIMIZATION_LEVEL = 0; 465 | GCC_PREPROCESSOR_DEFINITIONS = ( 466 | "DEBUG=1", 467 | "$(inherited)", 468 | ); 469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 471 | GCC_WARN_UNDECLARED_SELECTOR = YES; 472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 473 | GCC_WARN_UNUSED_FUNCTION = YES; 474 | GCC_WARN_UNUSED_VARIABLE = YES; 475 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 476 | MTL_ENABLE_DEBUG_INFO = YES; 477 | ONLY_ACTIVE_ARCH = YES; 478 | SDKROOT = iphoneos; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | }; 481 | name = Debug; 482 | }; 483 | 97C147041CF9000F007C117D /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 488 | CLANG_ANALYZER_NONNULL = YES; 489 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 490 | CLANG_CXX_LIBRARY = "libc++"; 491 | CLANG_ENABLE_MODULES = YES; 492 | CLANG_ENABLE_OBJC_ARC = YES; 493 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 494 | CLANG_WARN_BOOL_CONVERSION = YES; 495 | CLANG_WARN_COMMA = YES; 496 | CLANG_WARN_CONSTANT_CONVERSION = YES; 497 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 498 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 499 | CLANG_WARN_EMPTY_BODY = YES; 500 | CLANG_WARN_ENUM_CONVERSION = YES; 501 | CLANG_WARN_INFINITE_RECURSION = YES; 502 | CLANG_WARN_INT_CONVERSION = YES; 503 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 504 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 505 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 506 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 507 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 508 | CLANG_WARN_STRICT_PROTOTYPES = YES; 509 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 510 | CLANG_WARN_UNREACHABLE_CODE = YES; 511 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 512 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 513 | COPY_PHASE_STRIP = NO; 514 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 515 | ENABLE_NS_ASSERTIONS = NO; 516 | ENABLE_STRICT_OBJC_MSGSEND = YES; 517 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 518 | GCC_C_LANGUAGE_STANDARD = gnu99; 519 | GCC_NO_COMMON_BLOCKS = YES; 520 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 521 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 522 | GCC_WARN_UNDECLARED_SELECTOR = YES; 523 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 524 | GCC_WARN_UNUSED_FUNCTION = YES; 525 | GCC_WARN_UNUSED_VARIABLE = YES; 526 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 527 | MTL_ENABLE_DEBUG_INFO = NO; 528 | SDKROOT = iphoneos; 529 | SUPPORTED_PLATFORMS = iphoneos; 530 | SWIFT_COMPILATION_MODE = wholemodule; 531 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 532 | TARGETED_DEVICE_FAMILY = "1,2"; 533 | VALIDATE_PRODUCT = YES; 534 | }; 535 | name = Release; 536 | }; 537 | 97C147061CF9000F007C117D /* Debug */ = { 538 | isa = XCBuildConfiguration; 539 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 540 | buildSettings = { 541 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 542 | CLANG_ENABLE_MODULES = YES; 543 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 544 | ENABLE_BITCODE = NO; 545 | INFOPLIST_FILE = Runner/Info.plist; 546 | LD_RUNPATH_SEARCH_PATHS = ( 547 | "$(inherited)", 548 | "@executable_path/Frameworks", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = com.harshRajpurohit.weChat; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 553 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 554 | SWIFT_VERSION = 5.0; 555 | VERSIONING_SYSTEM = "apple-generic"; 556 | }; 557 | name = Debug; 558 | }; 559 | 97C147071CF9000F007C117D /* Release */ = { 560 | isa = XCBuildConfiguration; 561 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 562 | buildSettings = { 563 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 564 | CLANG_ENABLE_MODULES = YES; 565 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 566 | ENABLE_BITCODE = NO; 567 | INFOPLIST_FILE = Runner/Info.plist; 568 | LD_RUNPATH_SEARCH_PATHS = ( 569 | "$(inherited)", 570 | "@executable_path/Frameworks", 571 | ); 572 | PRODUCT_BUNDLE_IDENTIFIER = com.harshRajpurohit.weChat; 573 | PRODUCT_NAME = "$(TARGET_NAME)"; 574 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 575 | SWIFT_VERSION = 5.0; 576 | VERSIONING_SYSTEM = "apple-generic"; 577 | }; 578 | name = Release; 579 | }; 580 | /* End XCBuildConfiguration section */ 581 | 582 | /* Begin XCConfigurationList section */ 583 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | 331C8088294A63A400263BE5 /* Debug */, 587 | 331C8089294A63A400263BE5 /* Release */, 588 | 331C808A294A63A400263BE5 /* Profile */, 589 | ); 590 | defaultConfigurationIsVisible = 0; 591 | defaultConfigurationName = Release; 592 | }; 593 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 594 | isa = XCConfigurationList; 595 | buildConfigurations = ( 596 | 97C147031CF9000F007C117D /* Debug */, 597 | 97C147041CF9000F007C117D /* Release */, 598 | 249021D3217E4FDB00AE95B9 /* Profile */, 599 | ); 600 | defaultConfigurationIsVisible = 0; 601 | defaultConfigurationName = Release; 602 | }; 603 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 604 | isa = XCConfigurationList; 605 | buildConfigurations = ( 606 | 97C147061CF9000F007C117D /* Debug */, 607 | 97C147071CF9000F007C117D /* Release */, 608 | 249021D4217E4FDB00AE95B9 /* Profile */, 609 | ); 610 | defaultConfigurationIsVisible = 0; 611 | defaultConfigurationName = Release; 612 | }; 613 | /* End XCConfigurationList section */ 614 | }; 615 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 616 | } 617 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | We Chat 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Apna Chat 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | NSPhotoLibraryUsageDescription 51 | App requires access to the photo library for picking pictures. 52 | NSCameraUsageDescription 53 | App requires access to the camera for taking pictures. 54 | 55 | 56 | NSPhotoLibraryAddUsageDescription 57 | This app requires read and write access to the photo library. 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/api/apis.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:developer'; 3 | import 'dart:io'; 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:firebase_auth/firebase_auth.dart'; 7 | import 'package:firebase_messaging/firebase_messaging.dart'; 8 | import 'package:firebase_storage/firebase_storage.dart'; 9 | import 'package:http/http.dart'; 10 | 11 | import '../models/chat_user.dart'; 12 | import '../models/message.dart'; 13 | import 'notification_access_token.dart'; 14 | 15 | class APIs { 16 | // for authentication 17 | static FirebaseAuth get auth => FirebaseAuth.instance; 18 | 19 | // for accessing cloud firestore database 20 | static FirebaseFirestore firestore = FirebaseFirestore.instance; 21 | 22 | // for accessing firebase storage 23 | static FirebaseStorage storage = FirebaseStorage.instance; 24 | 25 | // for storing self information 26 | static ChatUser me = ChatUser( 27 | id: user.uid, 28 | name: user.displayName.toString(), 29 | email: user.email.toString(), 30 | about: "Hey, I'm using We Chat!", 31 | image: user.photoURL.toString(), 32 | createdAt: '', 33 | isOnline: false, 34 | lastActive: '', 35 | pushToken: ''); 36 | 37 | // to return current user 38 | static User get user => auth.currentUser!; 39 | 40 | // for accessing firebase messaging (Push Notification) 41 | static FirebaseMessaging fMessaging = FirebaseMessaging.instance; 42 | 43 | // for getting firebase messaging token 44 | static Future getFirebaseMessagingToken() async { 45 | await fMessaging.requestPermission(); 46 | 47 | await fMessaging.getToken().then((t) { 48 | if (t != null) { 49 | me.pushToken = t; 50 | log('Push Token: $t'); 51 | } 52 | }); 53 | 54 | // for handling foreground messages 55 | // FirebaseMessaging.onMessage.listen((RemoteMessage message) { 56 | // log('Got a message whilst in the foreground!'); 57 | // log('Message data: ${message.data}'); 58 | 59 | // if (message.notification != null) { 60 | // log('Message also contained a notification: ${message.notification}'); 61 | // } 62 | // }); 63 | } 64 | 65 | // for sending push notification (Updated Codes) 66 | static Future sendPushNotification( 67 | ChatUser chatUser, String msg) async { 68 | try { 69 | final body = { 70 | "message": { 71 | "token": chatUser.pushToken, 72 | "notification": { 73 | "title": me.name, //our name should be send 74 | "body": msg, 75 | }, 76 | } 77 | }; 78 | 79 | // Firebase Project > Project Settings > General Tab > Project ID 80 | const projectID = 'we-chat-75f13'; 81 | 82 | // get firebase admin token 83 | final bearerToken = await NotificationAccessToken.getToken; 84 | 85 | log('bearerToken: $bearerToken'); 86 | 87 | // handle null token 88 | if (bearerToken == null) return; 89 | 90 | var res = await post( 91 | Uri.parse( 92 | 'https://fcm.googleapis.com/v1/projects/$projectID/messages:send'), 93 | headers: { 94 | HttpHeaders.contentTypeHeader: 'application/json', 95 | HttpHeaders.authorizationHeader: 'Bearer $bearerToken' 96 | }, 97 | body: jsonEncode(body), 98 | ); 99 | 100 | log('Response status: ${res.statusCode}'); 101 | log('Response body: ${res.body}'); 102 | } catch (e) { 103 | log('\nsendPushNotificationE: $e'); 104 | } 105 | } 106 | 107 | // for checking if user exists or not? 108 | static Future userExists() async { 109 | return (await firestore.collection('users').doc(user.uid).get()).exists; 110 | } 111 | 112 | // for adding an chat user for our conversation 113 | static Future addChatUser(String email) async { 114 | final data = await firestore 115 | .collection('users') 116 | .where('email', isEqualTo: email) 117 | .get(); 118 | 119 | log('data: ${data.docs}'); 120 | 121 | if (data.docs.isNotEmpty && data.docs.first.id != user.uid) { 122 | //user exists 123 | 124 | log('user exists: ${data.docs.first.data()}'); 125 | 126 | firestore 127 | .collection('users') 128 | .doc(user.uid) 129 | .collection('my_users') 130 | .doc(data.docs.first.id) 131 | .set({}); 132 | 133 | return true; 134 | } else { 135 | //user doesn't exists 136 | 137 | return false; 138 | } 139 | } 140 | 141 | // for getting current user info 142 | static Future getSelfInfo() async { 143 | await firestore.collection('users').doc(user.uid).get().then((user) async { 144 | if (user.exists) { 145 | me = ChatUser.fromJson(user.data()!); 146 | await getFirebaseMessagingToken(); 147 | 148 | //for setting user status to active 149 | APIs.updateActiveStatus(true); 150 | log('My Data: ${user.data()}'); 151 | } else { 152 | await createUser().then((value) => getSelfInfo()); 153 | } 154 | }); 155 | } 156 | 157 | // for creating a new user 158 | static Future createUser() async { 159 | final time = DateTime.now().millisecondsSinceEpoch.toString(); 160 | 161 | final chatUser = ChatUser( 162 | id: user.uid, 163 | name: user.displayName.toString(), 164 | email: user.email.toString(), 165 | about: "Hey, I'm using We Chat!", 166 | image: user.photoURL.toString(), 167 | createdAt: time, 168 | isOnline: false, 169 | lastActive: time, 170 | pushToken: ''); 171 | 172 | return await firestore 173 | .collection('users') 174 | .doc(user.uid) 175 | .set(chatUser.toJson()); 176 | } 177 | 178 | // for getting id's of known users from firestore database 179 | static Stream>> getMyUsersId() { 180 | return firestore 181 | .collection('users') 182 | .doc(user.uid) 183 | .collection('my_users') 184 | .snapshots(); 185 | } 186 | 187 | // for getting all users from firestore database 188 | static Stream>> getAllUsers( 189 | List userIds) { 190 | log('\nUserIds: $userIds'); 191 | 192 | return firestore 193 | .collection('users') 194 | .where('id', 195 | whereIn: userIds.isEmpty 196 | ? [''] 197 | : userIds) //because empty list throws an error 198 | // .where('id', isNotEqualTo: user.uid) 199 | .snapshots(); 200 | } 201 | 202 | // for adding an user to my user when first message is send 203 | static Future sendFirstMessage( 204 | ChatUser chatUser, String msg, Type type) async { 205 | await firestore 206 | .collection('users') 207 | .doc(chatUser.id) 208 | .collection('my_users') 209 | .doc(user.uid) 210 | .set({}).then((value) => sendMessage(chatUser, msg, type)); 211 | } 212 | 213 | // for updating user information 214 | static Future updateUserInfo() async { 215 | await firestore.collection('users').doc(user.uid).update({ 216 | 'name': me.name, 217 | 'about': me.about, 218 | }); 219 | } 220 | 221 | // update profile picture of user 222 | static Future updateProfilePicture(File file) async { 223 | //getting image file extension 224 | final ext = file.path.split('.').last; 225 | log('Extension: $ext'); 226 | 227 | //storage file ref with path 228 | final ref = storage.ref().child('profile_pictures/${user.uid}.$ext'); 229 | 230 | //uploading image 231 | await ref 232 | .putFile(file, SettableMetadata(contentType: 'image/$ext')) 233 | .then((p0) { 234 | log('Data Transferred: ${p0.bytesTransferred / 1000} kb'); 235 | }); 236 | 237 | //updating image in firestore database 238 | me.image = await ref.getDownloadURL(); 239 | await firestore 240 | .collection('users') 241 | .doc(user.uid) 242 | .update({'image': me.image}); 243 | } 244 | 245 | // for getting specific user info 246 | static Stream>> getUserInfo( 247 | ChatUser chatUser) { 248 | return firestore 249 | .collection('users') 250 | .where('id', isEqualTo: chatUser.id) 251 | .snapshots(); 252 | } 253 | 254 | // update online or last active status of user 255 | static Future updateActiveStatus(bool isOnline) async { 256 | firestore.collection('users').doc(user.uid).update({ 257 | 'is_online': isOnline, 258 | 'last_active': DateTime.now().millisecondsSinceEpoch.toString(), 259 | 'push_token': me.pushToken, 260 | }); 261 | } 262 | 263 | ///************** Chat Screen Related APIs ************** 264 | 265 | // chats (collection) --> conversation_id (doc) --> messages (collection) --> message (doc) 266 | 267 | // useful for getting conversation id 268 | static String getConversationID(String id) => user.uid.hashCode <= id.hashCode 269 | ? '${user.uid}_$id' 270 | : '${id}_${user.uid}'; 271 | 272 | // for getting all messages of a specific conversation from firestore database 273 | static Stream>> getAllMessages( 274 | ChatUser user) { 275 | return firestore 276 | .collection('chats/${getConversationID(user.id)}/messages/') 277 | .orderBy('sent', descending: true) 278 | .snapshots(); 279 | } 280 | 281 | // for sending message 282 | static Future sendMessage( 283 | ChatUser chatUser, String msg, Type type) async { 284 | //message sending time (also used as id) 285 | final time = DateTime.now().millisecondsSinceEpoch.toString(); 286 | 287 | //message to send 288 | final Message message = Message( 289 | toId: chatUser.id, 290 | msg: msg, 291 | read: '', 292 | type: type, 293 | fromId: user.uid, 294 | sent: time); 295 | 296 | final ref = firestore 297 | .collection('chats/${getConversationID(chatUser.id)}/messages/'); 298 | await ref.doc(time).set(message.toJson()).then((value) => 299 | sendPushNotification(chatUser, type == Type.text ? msg : 'image')); 300 | } 301 | 302 | //update read status of message 303 | static Future updateMessageReadStatus(Message message) async { 304 | firestore 305 | .collection('chats/${getConversationID(message.fromId)}/messages/') 306 | .doc(message.sent) 307 | .update({'read': DateTime.now().millisecondsSinceEpoch.toString()}); 308 | } 309 | 310 | //get only last message of a specific chat 311 | static Stream>> getLastMessage( 312 | ChatUser user) { 313 | return firestore 314 | .collection('chats/${getConversationID(user.id)}/messages/') 315 | .orderBy('sent', descending: true) 316 | .limit(1) 317 | .snapshots(); 318 | } 319 | 320 | //send chat image 321 | static Future sendChatImage(ChatUser chatUser, File file) async { 322 | //getting image file extension 323 | final ext = file.path.split('.').last; 324 | 325 | //storage file ref with path 326 | final ref = storage.ref().child( 327 | 'images/${getConversationID(chatUser.id)}/${DateTime.now().millisecondsSinceEpoch}.$ext'); 328 | 329 | //uploading image 330 | await ref 331 | .putFile(file, SettableMetadata(contentType: 'image/$ext')) 332 | .then((p0) { 333 | log('Data Transferred: ${p0.bytesTransferred / 1000} kb'); 334 | }); 335 | 336 | //updating image in firestore database 337 | final imageUrl = await ref.getDownloadURL(); 338 | await sendMessage(chatUser, imageUrl, Type.image); 339 | } 340 | 341 | //delete message 342 | static Future deleteMessage(Message message) async { 343 | await firestore 344 | .collection('chats/${getConversationID(message.toId)}/messages/') 345 | .doc(message.sent) 346 | .delete(); 347 | 348 | if (message.type == Type.image) { 349 | await storage.refFromURL(message.msg).delete(); 350 | } 351 | } 352 | 353 | //update message 354 | static Future updateMessage(Message message, String updatedMsg) async { 355 | await firestore 356 | .collection('chats/${getConversationID(message.toId)}/messages/') 357 | .doc(message.sent) 358 | .update({'msg': updatedMsg}); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /lib/api/notification_access_token.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:googleapis_auth/auth_io.dart'; 4 | 5 | class NotificationAccessToken { 6 | static String? _token; 7 | 8 | //to generate token only once for an app run 9 | static Future get getToken async => _token ?? await _getAccessToken(); 10 | 11 | // to get admin bearer token 12 | static Future _getAccessToken() async { 13 | try { 14 | const fMessagingScope = 15 | 'https://www.googleapis.com/auth/firebase.messaging'; 16 | 17 | final client = await clientViaServiceAccount( 18 | // To get Admin Json File: Go to Firebase > Project Settings > Service Accounts 19 | // > Click on 'Generate new private key' Btn & Json file will be downloaded 20 | 21 | // Paste Your Generated Json File Content 22 | ServiceAccountCredentials.fromJson({ 23 | "type": "service_account", 24 | "project_id": "we-chat-75f13", 25 | "private_key_id": "1a1621fe6a5374932428ae0f4a05782b7acccb14", 26 | "private_key": 27 | "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6R/L0jOehSyDP\nFX8Sgq+E3HXtgLRppZHy5FQUmDB6pEPCCGG8gOnt/2GzFlIcAI0SSFt//JMwqGoD\n4I5BeV9oKSrmR8YFFllmQrazIL5Ynf9NrNB1pR0+UjAWfgU3O8f5ZclaG5HnFap/\nB93Jt6MatWDKQilnjPnFB+CxyWkXmKls3Qv4W1mot7JC+OoscLz13w9U/+plLZmF\ne9CS/Gynp8F1IlKbWJSEy8ltkHEhpzpm9RDrWD+trBbWHVnhuaOz7rqO4Ydts+TK\npNugK/ksL5B6SFtqZfkvsnZzR2tKh0VtLJLfH5I8tEGWJteli/0RqnRXhBruPrLa\nP4fZo3wxAgMBAAECggEAKa2z8R3+rGKyFEtXu2VEjqpB83IOy615JnqKSnJTtHkA\n1Q7JiWLhlDfF3QszM2M3LR5F5LC3IRWpZLUvNvyISXaei9gGuPgrZ/soxBLhK9Qr\n/W3bVVssXBeI/VtUYOuN/mHVB4VwI19AXrw8liexhWYMIizj9TCAaOJulnC9RAMo\ntxAu2Xt69vE+cIgPTUzf01fY7NXHVnQs3aXvuD3LfQVHQXwbU/BApSNCPn3Df4YQ\nAUQhSXGprEtDmF89d1sCg/v92DxXH22B/nqpUh51iLSzCW6QjUGy4YlwdJ0U/qhj\nRrefq6sC4dJgVcbmJyQkgyAdouZKA2niFfSHeVqXTQKBgQDx+He1kJCWlE2AOFAK\noTNlLSsJxjj6fRhcuxWykZCXvVP2xi+CfYFCN+n+ZDGhU3pi1NDJ8Twh4M7EFCM7\npGmhW7qDgiXqpB3R8lt3huH+3rPqP6z+2zDi+HxCJckpWpb32TorgkIdSRp3eM0s\n03kl0rB6xW59LMNMKBz2/mn5RQKBgQDFFOPp+u6MerJjjuVD9aWTzYuCON0z3DO/\n4bbGIP6vZ4SzmlYtdDHBcZp/ogLIgK4AGYNwWSap6kQVDV/6llxQu9uLtPpwkrza\n3BTKrSQdtu7tvdHWSu5cCqkWIz4hVEDpHWeXoFkg37GDVbK+CZdYOb2y5d2Bel/6\nYY2DoQ5H/QKBgB8Rt1VB5b7f9f+Tu3tR7YZ9QTx1DlXjgCBQCV4vYLCLJ9/U3L7V\nnKZDBbGbbd/4FwvfpZt4dS8obYQxzcBXwRRt8cn3CSVSw1100BfN4vDV6aYXXQAw\nZtuN6m6X6Xd84UubweNaS2D1RQe4JCgwUyrvHaf199TszXrW37k7O6I9AoGAVFxF\n1wEvnXhj5dPj9Xwv/R2N6xcWML3AdRFUIGk9O63vEsYsv1YueiR7wsiBsnvKf4Zs\nSeoPb8o0jGJmRCiaqYBQUPQOA6P8LR7p03vbqtCEY8XODZGTiFiT2kMJtFCRXHfW\nwQPFQxodrR9A3LHUU9KbjflxIJxWeyHI5qBJMa0CgYEAz2iWE5rNtJ1mLrsC/o8j\njDV0RHzhN2f2pAxLlXHJ40nvQ5Nbyk5gCI2llUTN73YPNJXcM6ZKARyzlx0cA0TE\nolbpV91lb5j2L2HLAz1nciOzh1IRCJRk7T7j01TrygOv2n6fyWfUGuHCR5lztaiv\nykRUUKoMSaezOlB88DIqTIQ=\n-----END PRIVATE KEY-----\n", 28 | "client_email": 29 | "firebase-adminsdk-9y8mc@we-chat-75f13.iam.gserviceaccount.com", 30 | "client_id": "115556782220674603906", 31 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 32 | "token_uri": "https://oauth2.googleapis.com/token", 33 | "auth_provider_x509_cert_url": 34 | "https://www.googleapis.com/oauth2/v1/certs", 35 | "client_x509_cert_url": 36 | "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-9y8mc%40we-chat-75f13.iam.gserviceaccount.com", 37 | "universe_domain": "googleapis.com" 38 | }), 39 | [fMessagingScope], 40 | ); 41 | 42 | _token = client.credentials.accessToken.data; 43 | 44 | return _token; 45 | } catch (e) { 46 | log('$e'); 47 | return null; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // File generated by FlutterFire CLI. 2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members 3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 4 | import 'package:flutter/foundation.dart' 5 | show defaultTargetPlatform, kIsWeb, TargetPlatform; 6 | 7 | /// Default [FirebaseOptions] for use with your Firebase apps. 8 | /// 9 | /// Example: 10 | /// ```dart 11 | /// import 'firebase_options.dart'; 12 | /// // ... 13 | /// await Firebase.initializeApp( 14 | /// options: DefaultFirebaseOptions.currentPlatform, 15 | /// ); 16 | /// ``` 17 | class DefaultFirebaseOptions { 18 | static FirebaseOptions get currentPlatform { 19 | if (kIsWeb) { 20 | return web; 21 | } 22 | switch (defaultTargetPlatform) { 23 | case TargetPlatform.android: 24 | return android; 25 | case TargetPlatform.iOS: 26 | return ios; 27 | case TargetPlatform.macOS: 28 | throw UnsupportedError( 29 | 'DefaultFirebaseOptions have not been configured for macos - ' 30 | 'you can reconfigure this by running the FlutterFire CLI again.', 31 | ); 32 | case TargetPlatform.windows: 33 | throw UnsupportedError( 34 | 'DefaultFirebaseOptions have not been configured for windows - ' 35 | 'you can reconfigure this by running the FlutterFire CLI again.', 36 | ); 37 | case TargetPlatform.linux: 38 | throw UnsupportedError( 39 | 'DefaultFirebaseOptions have not been configured for linux - ' 40 | 'you can reconfigure this by running the FlutterFire CLI again.', 41 | ); 42 | default: 43 | throw UnsupportedError( 44 | 'DefaultFirebaseOptions are not supported for this platform.', 45 | ); 46 | } 47 | } 48 | 49 | static const FirebaseOptions android = FirebaseOptions( 50 | apiKey: 'AIzaSyBeszZHLYJNyjUhpujMH9yZ4ol9JqsAmPU', 51 | appId: '1:288842837392:android:1353619813061d867a5715', 52 | messagingSenderId: '288842837392', 53 | projectId: 'we-chat-75f13', 54 | storageBucket: 'we-chat-75f13.appspot.com', 55 | ); 56 | 57 | static const FirebaseOptions ios = FirebaseOptions( 58 | apiKey: 'AIzaSyCCYZoZ5DvuitZJ7qK5iWM3ARHBDoFFriY', 59 | appId: '1:288842837392:ios:f39c8dc31525a6687a5715', 60 | messagingSenderId: '288842837392', 61 | projectId: 'we-chat-75f13', 62 | storageBucket: 'we-chat-75f13.appspot.com', 63 | androidClientId: '288842837392-gse3j6u9fferoav9leh2ik3468asfeup.apps.googleusercontent.com', 64 | iosClientId: '288842837392-sgib97u6439i4jte3bo19u00fh663euu.apps.googleusercontent.com', 65 | iosBundleId: 'com.harshRajpurohit.weChat', 66 | ); 67 | 68 | static const FirebaseOptions web = FirebaseOptions( 69 | apiKey: 'AIzaSyBW-1MjB067GUwQHPnYmWkr_4iMz13UgRs', 70 | appId: '1:288842837392:web:6ae5bafc6d7d4f407a5715', 71 | messagingSenderId: '288842837392', 72 | projectId: 'we-chat-75f13', 73 | authDomain: 'we-chat-75f13.firebaseapp.com', 74 | storageBucket: 'we-chat-75f13.appspot.com', 75 | ); 76 | 77 | } -------------------------------------------------------------------------------- /lib/helper/dialogs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Dialogs { 4 | static void showSnackbar(BuildContext context, String msg) { 5 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 6 | content: Text(msg), 7 | backgroundColor: Colors.blue.withOpacity(.8), 8 | behavior: SnackBarBehavior.floating)); 9 | } 10 | 11 | static void showLoading(BuildContext context) { 12 | showDialog( 13 | context: context, 14 | builder: (_) => 15 | const Center(child: CircularProgressIndicator(strokeWidth: 1))); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/helper/my_date_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class MyDateUtil { 5 | // for getting formatted time from milliSecondsSinceEpochs String 6 | static String getFormattedTime( 7 | {required BuildContext context, required String time}) { 8 | final date = DateTime.fromMillisecondsSinceEpoch(int.parse(time)); 9 | return TimeOfDay.fromDateTime(date).format(context); 10 | } 11 | 12 | // // for getting formatted time for sent & read 13 | // static String getMessageTime( 14 | // {required BuildContext context, required String time}) { 15 | 16 | // final DateTime sent = DateTime.fromMillisecondsSinceEpoch(int.parse(time)); 17 | // final DateTime now = DateTime.now(); 18 | 19 | // final formattedTime = TimeOfDay.fromDateTime(sent).format(context); 20 | // if (now.day == sent.day && 21 | // now.month == sent.month && 22 | // now.year == sent.year) { 23 | // return formattedTime; 24 | // } 25 | 26 | // return now.year == sent.year 27 | // ? '$formattedTime - ${sent.day} ${_getMonth(sent)}' 28 | // : '$formattedTime - ${sent.day} ${_getMonth(sent)} ${sent.year}'; 29 | // } 30 | 31 | // for getting formatted time for sent & read 32 | // [Bux Fix] Avoid bug due to context not mounted when keyboard is open in chat & bottom sheet opens 33 | static String getMessageTime({required String time}) { 34 | final DateTime sent = DateTime.fromMillisecondsSinceEpoch(int.parse(time)); 35 | final DateTime now = DateTime.now(); 36 | 37 | final String formattedTime = DateFormat('h:mm a').format(sent); 38 | 39 | if (now.day == sent.day && 40 | now.month == sent.month && 41 | now.year == sent.year) { 42 | return formattedTime; 43 | } 44 | 45 | final String formattedDate = now.year == sent.year 46 | ? '${sent.day} ${_getMonth(sent)}' 47 | : '${sent.day} ${_getMonth(sent)} ${sent.year}'; 48 | 49 | return '$formattedTime - $formattedDate'; 50 | } 51 | 52 | //get last message time (used in chat user card) 53 | static String getLastMessageTime( 54 | {required BuildContext context, 55 | required String time, 56 | bool showYear = false}) { 57 | final DateTime sent = DateTime.fromMillisecondsSinceEpoch(int.parse(time)); 58 | final DateTime now = DateTime.now(); 59 | 60 | if (now.day == sent.day && 61 | now.month == sent.month && 62 | now.year == sent.year) { 63 | return TimeOfDay.fromDateTime(sent).format(context); 64 | } 65 | 66 | return showYear 67 | ? '${sent.day} ${_getMonth(sent)} ${sent.year}' 68 | : '${sent.day} ${_getMonth(sent)}'; 69 | } 70 | 71 | //get formatted last active time of user in chat screen 72 | static String getLastActiveTime( 73 | {required BuildContext context, required String lastActive}) { 74 | final int i = int.tryParse(lastActive) ?? -1; 75 | 76 | //if time is not available then return below statement 77 | if (i == -1) return 'Last seen not available'; 78 | 79 | DateTime time = DateTime.fromMillisecondsSinceEpoch(i); 80 | DateTime now = DateTime.now(); 81 | 82 | String formattedTime = TimeOfDay.fromDateTime(time).format(context); 83 | if (time.day == now.day && 84 | time.month == now.month && 85 | time.year == time.year) { 86 | return 'Last seen today at $formattedTime'; 87 | } 88 | 89 | if ((now.difference(time).inHours / 24).round() == 1) { 90 | return 'Last seen yesterday at $formattedTime'; 91 | } 92 | 93 | String month = _getMonth(time); 94 | 95 | return 'Last seen on ${time.day} $month on $formattedTime'; 96 | } 97 | 98 | // get month name from month no. or index 99 | static String _getMonth(DateTime date) { 100 | switch (date.month) { 101 | case 1: 102 | return 'Jan'; 103 | case 2: 104 | return 'Feb'; 105 | case 3: 106 | return 'Mar'; 107 | case 4: 108 | return 'Apr'; 109 | case 5: 110 | return 'May'; 111 | case 6: 112 | return 'Jun'; 113 | case 7: 114 | return 'Jul'; 115 | case 8: 116 | return 'Aug'; 117 | case 9: 118 | return 'Sept'; 119 | case 10: 120 | return 'Oct'; 121 | case 11: 122 | return 'Nov'; 123 | case 12: 124 | return 'Dec'; 125 | } 126 | return 'NA'; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:firebase_core/firebase_core.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_notification_channel/flutter_notification_channel.dart'; 7 | import 'package:flutter_notification_channel/notification_importance.dart'; 8 | 9 | import 'firebase_options.dart'; 10 | import 'screens/splash_screen.dart'; 11 | 12 | //global object for accessing device screen size 13 | late Size mq; 14 | 15 | Future main() async { 16 | WidgetsFlutterBinding.ensureInitialized(); 17 | 18 | //enter full-screen 19 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); 20 | 21 | await _initializeFirebase(); 22 | 23 | //for setting orientation to portrait only 24 | SystemChrome.setPreferredOrientations( 25 | [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]) 26 | .then((value) { 27 | runApp(const MyApp()); 28 | }); 29 | } 30 | 31 | class MyApp extends StatelessWidget { 32 | const MyApp({super.key}); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return MaterialApp( 37 | title: 'We Chat', 38 | debugShowCheckedModeBanner: false, 39 | theme: ThemeData( 40 | useMaterial3: false, 41 | appBarTheme: const AppBarTheme( 42 | centerTitle: true, 43 | elevation: 1, 44 | iconTheme: IconThemeData(color: Colors.black), 45 | titleTextStyle: TextStyle( 46 | color: Colors.black, 47 | fontWeight: FontWeight.normal, 48 | fontSize: 19), 49 | backgroundColor: Colors.white, 50 | )), 51 | home: const SplashScreen()); 52 | } 53 | } 54 | 55 | Future _initializeFirebase() async { 56 | await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); 57 | 58 | var result = await FlutterNotificationChannel().registerNotificationChannel( 59 | description: 'For Showing Message Notification', 60 | id: 'chats', 61 | importance: NotificationImportance.IMPORTANCE_HIGH, 62 | name: 'Chats'); 63 | 64 | log('\nNotification Channel Result: $result'); 65 | } 66 | -------------------------------------------------------------------------------- /lib/models/chat_user.dart: -------------------------------------------------------------------------------- 1 | class ChatUser { 2 | ChatUser({ 3 | required this.image, 4 | required this.about, 5 | required this.name, 6 | required this.createdAt, 7 | required this.isOnline, 8 | required this.id, 9 | required this.lastActive, 10 | required this.email, 11 | required this.pushToken, 12 | }); 13 | late String image; 14 | late String about; 15 | late String name; 16 | late String createdAt; 17 | late bool isOnline; 18 | late String id; 19 | late String lastActive; 20 | late String email; 21 | late String pushToken; 22 | 23 | ChatUser.fromJson(Map json) { 24 | image = json['image'] ?? ''; 25 | about = json['about'] ?? ''; 26 | name = json['name'] ?? ''; 27 | createdAt = json['created_at'] ?? ''; 28 | isOnline = json['is_online'] ?? ''; 29 | id = json['id'] ?? ''; 30 | lastActive = json['last_active'] ?? ''; 31 | email = json['email'] ?? ''; 32 | pushToken = json['push_token'] ?? ''; 33 | } 34 | 35 | Map toJson() { 36 | final data = {}; 37 | data['image'] = image; 38 | data['about'] = about; 39 | data['name'] = name; 40 | data['created_at'] = createdAt; 41 | data['is_online'] = isOnline; 42 | data['id'] = id; 43 | data['last_active'] = lastActive; 44 | data['email'] = email; 45 | data['push_token'] = pushToken; 46 | return data; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/models/message.dart: -------------------------------------------------------------------------------- 1 | class Message { 2 | Message({ 3 | required this.toId, 4 | required this.msg, 5 | required this.read, 6 | required this.type, 7 | required this.fromId, 8 | required this.sent, 9 | }); 10 | 11 | late final String toId; 12 | late final String msg; 13 | late final String read; 14 | late final String fromId; 15 | late final String sent; 16 | late final Type type; 17 | 18 | Message.fromJson(Map json) { 19 | toId = json['toId'].toString(); 20 | msg = json['msg'].toString(); 21 | read = json['read'].toString(); 22 | type = json['type'].toString() == Type.image.name ? Type.image : Type.text; 23 | fromId = json['fromId'].toString(); 24 | sent = json['sent'].toString(); 25 | } 26 | 27 | Map toJson() { 28 | final data = {}; 29 | data['toId'] = toId; 30 | data['msg'] = msg; 31 | data['read'] = read; 32 | data['type'] = type.name; 33 | data['fromId'] = fromId; 34 | data['sent'] = sent; 35 | return data; 36 | } 37 | } 38 | 39 | enum Type { text, image } 40 | 41 | 42 | // ai message 43 | class AiMessage { 44 | String msg; 45 | final MessageType msgType; 46 | 47 | AiMessage({required this.msg, required this.msgType}); 48 | } 49 | 50 | enum MessageType { user, bot } -------------------------------------------------------------------------------- /lib/screens/ai_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_generative_ai/google_generative_ai.dart'; 5 | 6 | import '../helper/dialogs.dart'; 7 | import '../main.dart'; 8 | import '../models/message.dart'; 9 | import '../widgets/ai_message_card.dart'; 10 | 11 | class AiScreen extends StatefulWidget { 12 | const AiScreen({super.key}); 13 | 14 | @override 15 | State createState() => _AiScreenState(); 16 | } 17 | 18 | class _AiScreenState extends State { 19 | // final _c = ChatController(); 20 | final _textC = TextEditingController(); 21 | final _scrollC = ScrollController(); 22 | 23 | final _list = [ 24 | AiMessage(msg: 'Hello, How can I help you?', msgType: MessageType.bot) 25 | ]; 26 | 27 | Future _askQuestion() async { 28 | _textC.text = _textC.text.trim(); 29 | 30 | if (_textC.text.isNotEmpty) { 31 | //user 32 | _list.add(AiMessage(msg: _textC.text, msgType: MessageType.user)); 33 | _list.add(AiMessage(msg: '', msgType: MessageType.bot)); 34 | setState(() {}); 35 | 36 | _scrollDown(); 37 | 38 | final res = await _getAnswer(_textC.text); 39 | 40 | //ai bot 41 | _list.removeLast(); 42 | _list.add(AiMessage(msg: res, msgType: MessageType.bot)); 43 | _scrollDown(); 44 | 45 | setState(() {}); 46 | 47 | _textC.text = ''; 48 | return; 49 | } 50 | 51 | Dialogs.showSnackbar(context, 'Ask Something!'); 52 | } 53 | 54 | //for moving to end message 55 | void _scrollDown() { 56 | _scrollC.animateTo(_scrollC.position.maxScrollExtent, 57 | duration: const Duration(milliseconds: 500), curve: Curves.ease); 58 | } 59 | 60 | //get answer from google gemini ai 61 | Future _getAnswer(final String question) async { 62 | try { 63 | //TODO - Google Gemini API Key - https://aistudio.google.com/app/apikey 64 | 65 | const apiKey = ''; 66 | 67 | log('api key: $apiKey'); 68 | 69 | if (apiKey.isEmpty) { 70 | return 'Gemini API Key required\nChange in Ai Screen Codes'; 71 | } 72 | 73 | final model = GenerativeModel( 74 | model: 'gemini-1.5-flash-latest', 75 | apiKey: apiKey, 76 | ); 77 | 78 | final content = [Content.text(question)]; 79 | final res = await model.generateContent(content, safetySettings: [ 80 | SafetySetting(HarmCategory.dangerousContent, HarmBlockThreshold.none), 81 | SafetySetting(HarmCategory.sexuallyExplicit, HarmBlockThreshold.none), 82 | SafetySetting(HarmCategory.harassment, HarmBlockThreshold.none), 83 | SafetySetting(HarmCategory.hateSpeech, HarmBlockThreshold.none), 84 | ]); 85 | 86 | log('res: ${res.text}'); 87 | 88 | return res.text!.trim(); 89 | } catch (e) { 90 | log('getAnswerGeminiE: $e'); 91 | return 'Something went wrong (Try again in sometime)'; 92 | } 93 | } 94 | 95 | @override 96 | void dispose() { 97 | _textC.dispose(); 98 | super.dispose(); 99 | } 100 | 101 | @override 102 | Widget build(BuildContext context) { 103 | return Scaffold( 104 | //app bar 105 | appBar: AppBar( 106 | title: const Text('Your AI Assistant'), 107 | ), 108 | 109 | //send message field & btn 110 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, 111 | floatingActionButton: Padding( 112 | padding: const EdgeInsets.symmetric(horizontal: 8), 113 | child: Row(children: [ 114 | //text input field 115 | Expanded( 116 | child: TextFormField( 117 | controller: _textC, 118 | textAlign: TextAlign.center, 119 | onTapOutside: (e) => FocusScope.of(context).unfocus(), 120 | decoration: InputDecoration( 121 | fillColor: Theme.of(context).scaffoldBackgroundColor, 122 | filled: true, 123 | isDense: true, 124 | hintText: 'Ask me anything you want...', 125 | hintStyle: const TextStyle(fontSize: 14), 126 | border: const OutlineInputBorder( 127 | borderRadius: BorderRadius.all(Radius.circular(50)))), 128 | )), 129 | 130 | //for adding some space 131 | const SizedBox(width: 8), 132 | 133 | //send button 134 | CircleAvatar( 135 | radius: 24, 136 | backgroundColor: Colors.blue, 137 | child: IconButton( 138 | onPressed: _askQuestion, 139 | icon: const Icon(Icons.rocket_launch_rounded, 140 | color: Colors.white, size: 28), 141 | ), 142 | ) 143 | ]), 144 | ), 145 | 146 | //body 147 | body: ListView( 148 | physics: const BouncingScrollPhysics(), 149 | controller: _scrollC, 150 | padding: EdgeInsets.only(top: mq.height * .02, bottom: mq.height * .1), 151 | children: _list.map((e) => AiMessageCard(message: e)).toList(), 152 | ), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/screens/auth/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'dart:io'; 3 | 4 | import 'package:firebase_auth/firebase_auth.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:google_sign_in/google_sign_in.dart'; 7 | 8 | import '../../api/apis.dart'; 9 | import '../../helper/dialogs.dart'; 10 | import '../../main.dart'; 11 | import '../home_screen.dart'; 12 | 13 | //login screen -- implements google sign in or sign up feature for app 14 | class LoginScreen extends StatefulWidget { 15 | const LoginScreen({super.key}); 16 | 17 | @override 18 | State createState() => _LoginScreenState(); 19 | } 20 | 21 | class _LoginScreenState extends State { 22 | bool _isAnimate = false; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | 28 | //for auto triggering animation 29 | Future.delayed(const Duration(milliseconds: 500), () { 30 | setState(() => _isAnimate = true); 31 | }); 32 | } 33 | 34 | // handles google login button click 35 | _handleGoogleBtnClick() { 36 | //for showing progress bar 37 | Dialogs.showLoading(context); 38 | 39 | _signInWithGoogle().then((user) async { 40 | //for hiding progress bar 41 | Navigator.pop(context); 42 | 43 | if (user != null) { 44 | log('\nUser: ${user.user}'); 45 | log('\nUserAdditionalInfo: ${user.additionalUserInfo}'); 46 | 47 | if (await APIs.userExists() && mounted) { 48 | Navigator.pushReplacement( 49 | context, MaterialPageRoute(builder: (_) => const HomeScreen())); 50 | } else { 51 | await APIs.createUser().then((value) { 52 | Navigator.pushReplacement( 53 | context, MaterialPageRoute(builder: (_) => const HomeScreen())); 54 | }); 55 | } 56 | } 57 | }); 58 | } 59 | 60 | Future _signInWithGoogle() async { 61 | try { 62 | await InternetAddress.lookup('google.com'); 63 | // Trigger the authentication flow 64 | final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn(); 65 | 66 | // Obtain the auth details from the request 67 | final GoogleSignInAuthentication? googleAuth = 68 | await googleUser?.authentication; 69 | 70 | // Create a new credential 71 | final credential = GoogleAuthProvider.credential( 72 | accessToken: googleAuth?.accessToken, 73 | idToken: googleAuth?.idToken, 74 | ); 75 | 76 | // Once signed in, return the UserCredential 77 | return await APIs.auth.signInWithCredential(credential); 78 | } catch (e) { 79 | log('\n_signInWithGoogle: $e'); 80 | 81 | if (mounted) { 82 | Dialogs.showSnackbar(context, 'Something Went Wrong (Check Internet!)'); 83 | } 84 | 85 | return null; 86 | } 87 | } 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | //initializing media query (for getting device screen size) 92 | mq = MediaQuery.sizeOf(context); 93 | 94 | return Scaffold( 95 | //app bar 96 | appBar: AppBar( 97 | automaticallyImplyLeading: false, 98 | title: const Text('Welcome to We Chat'), 99 | ), 100 | 101 | //body 102 | body: Stack(children: [ 103 | //app logo 104 | AnimatedPositioned( 105 | top: mq.height * .15, 106 | right: _isAnimate ? mq.width * .25 : -mq.width * .5, 107 | width: mq.width * .5, 108 | duration: const Duration(seconds: 1), 109 | child: Image.asset('assets/images/icon.png')), 110 | 111 | //google login button 112 | Positioned( 113 | bottom: mq.height * .15, 114 | left: mq.width * .05, 115 | width: mq.width * .9, 116 | height: mq.height * .06, 117 | child: ElevatedButton.icon( 118 | style: ElevatedButton.styleFrom( 119 | backgroundColor: const Color.fromARGB(255, 223, 255, 187), 120 | shape: const StadiumBorder(), 121 | elevation: 1), 122 | 123 | // on tap 124 | onPressed: _handleGoogleBtnClick, 125 | 126 | //google icon 127 | icon: Image.asset('assets/images/google.png', 128 | height: mq.height * .03), 129 | 130 | //login with google label 131 | label: RichText( 132 | text: const TextSpan( 133 | style: TextStyle(color: Colors.black, fontSize: 16), 134 | children: [ 135 | TextSpan(text: 'Login with '), 136 | TextSpan( 137 | text: 'Google', 138 | style: TextStyle(fontWeight: FontWeight.w500)), 139 | ]), 140 | ))), 141 | ]), 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/screens/chat_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'dart:io'; 3 | 4 | import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:image_picker/image_picker.dart'; 7 | 8 | import '../api/apis.dart'; 9 | import '../helper/my_date_util.dart'; 10 | import '../main.dart'; 11 | import '../models/chat_user.dart'; 12 | import '../models/message.dart'; 13 | import '../widgets/message_card.dart'; 14 | import '../widgets/profile_image.dart'; 15 | import 'view_profile_screen.dart'; 16 | 17 | class ChatScreen extends StatefulWidget { 18 | final ChatUser user; 19 | 20 | const ChatScreen({super.key, required this.user}); 21 | 22 | @override 23 | State createState() => _ChatScreenState(); 24 | } 25 | 26 | class _ChatScreenState extends State { 27 | //for storing all messages 28 | List _list = []; 29 | 30 | //for handling message text changes 31 | final _textController = TextEditingController(); 32 | 33 | //showEmoji -- for storing value of showing or hiding emoji 34 | //isUploading -- for checking if image is uploading or not? 35 | bool _showEmoji = false, _isUploading = false; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return GestureDetector( 40 | onTap: FocusScope.of(context).unfocus, 41 | child: PopScope( 42 | // onWillPop: () { 43 | // if (_showEmoji) { 44 | // setState(() => _showEmoji = !_showEmoji); 45 | // return Future.value(false); 46 | // } else { 47 | // return Future.value(true); 48 | // } 49 | // }, 50 | 51 | //if emojis are shown & back button is pressed then hide emojis 52 | //or else simple close current screen on back button click 53 | canPop: false, 54 | 55 | onPopInvokedWithResult: (_, __) { 56 | if (_showEmoji) { 57 | setState(() => _showEmoji = !_showEmoji); 58 | return; 59 | } 60 | 61 | // some delay before pop 62 | Future.delayed(const Duration(milliseconds: 300), () { 63 | try { 64 | if (Navigator.canPop(context)) Navigator.pop(context); 65 | } catch (e) { 66 | log('ErrorPop: $e'); 67 | } 68 | }); 69 | }, 70 | 71 | // 72 | child: Scaffold( 73 | //app bar 74 | appBar: AppBar( 75 | automaticallyImplyLeading: false, 76 | flexibleSpace: _appBar(), 77 | ), 78 | 79 | backgroundColor: const Color.fromARGB(255, 234, 248, 255), 80 | 81 | //body 82 | body: SafeArea( 83 | child: Column( 84 | children: [ 85 | Expanded( 86 | child: StreamBuilder( 87 | stream: APIs.getAllMessages(widget.user), 88 | builder: (context, snapshot) { 89 | switch (snapshot.connectionState) { 90 | //if data is loading 91 | case ConnectionState.waiting: 92 | case ConnectionState.none: 93 | return const SizedBox(); 94 | 95 | //if some or all data is loaded then show it 96 | case ConnectionState.active: 97 | case ConnectionState.done: 98 | final data = snapshot.data?.docs; 99 | _list = data 100 | ?.map((e) => Message.fromJson(e.data())) 101 | .toList() ?? 102 | []; 103 | 104 | if (_list.isNotEmpty) { 105 | return ListView.builder( 106 | reverse: true, 107 | itemCount: _list.length, 108 | padding: EdgeInsets.only(top: mq.height * .01), 109 | physics: const BouncingScrollPhysics(), 110 | itemBuilder: (context, index) { 111 | return MessageCard(message: _list[index]); 112 | }); 113 | } else { 114 | return const Center( 115 | child: Text('Say Hii! 👋', 116 | style: TextStyle(fontSize: 20)), 117 | ); 118 | } 119 | } 120 | }, 121 | ), 122 | ), 123 | 124 | //progress indicator for showing uploading 125 | if (_isUploading) 126 | const Align( 127 | alignment: Alignment.centerRight, 128 | child: Padding( 129 | padding: 130 | EdgeInsets.symmetric(vertical: 8, horizontal: 20), 131 | child: CircularProgressIndicator(strokeWidth: 2))), 132 | 133 | //chat input filed 134 | _chatInput(), 135 | 136 | //show emojis on keyboard emoji button click & vice versa 137 | if (_showEmoji) 138 | SizedBox( 139 | height: mq.height * .35, 140 | child: EmojiPicker( 141 | textEditingController: _textController, 142 | config: const Config(), 143 | ), 144 | ) 145 | ], 146 | ), 147 | ), 148 | ), 149 | ), 150 | ); 151 | } 152 | 153 | // app bar widget 154 | Widget _appBar() { 155 | return SafeArea( 156 | child: InkWell( 157 | onTap: () { 158 | Navigator.push( 159 | context, 160 | MaterialPageRoute( 161 | builder: (_) => ViewProfileScreen(user: widget.user))); 162 | }, 163 | child: StreamBuilder( 164 | stream: APIs.getUserInfo(widget.user), 165 | builder: (context, snapshot) { 166 | final data = snapshot.data?.docs; 167 | final list = 168 | data?.map((e) => ChatUser.fromJson(e.data())).toList() ?? 169 | []; 170 | 171 | return Row( 172 | children: [ 173 | //back button 174 | IconButton( 175 | onPressed: () => Navigator.pop(context), 176 | icon: const Icon(Icons.arrow_back, 177 | color: Colors.black54)), 178 | 179 | //user profile picture 180 | ProfileImage( 181 | size: mq.height * .05, 182 | url: list.isNotEmpty ? list[0].image : widget.user.image, 183 | ), 184 | 185 | //for adding some space 186 | const SizedBox(width: 10), 187 | 188 | //user name & last seen time 189 | Column( 190 | mainAxisAlignment: MainAxisAlignment.center, 191 | crossAxisAlignment: CrossAxisAlignment.start, 192 | children: [ 193 | //user name 194 | Text(list.isNotEmpty ? list[0].name : widget.user.name, 195 | style: const TextStyle( 196 | fontSize: 16, 197 | color: Colors.black87, 198 | fontWeight: FontWeight.w500)), 199 | 200 | //for adding some space 201 | const SizedBox(height: 2), 202 | 203 | //last seen time of user 204 | Text( 205 | list.isNotEmpty 206 | ? list[0].isOnline 207 | ? 'Online' 208 | : MyDateUtil.getLastActiveTime( 209 | context: context, 210 | lastActive: list[0].lastActive) 211 | : MyDateUtil.getLastActiveTime( 212 | context: context, 213 | lastActive: widget.user.lastActive), 214 | style: const TextStyle( 215 | fontSize: 13, color: Colors.black54)), 216 | ], 217 | ) 218 | ], 219 | ); 220 | })), 221 | ); 222 | } 223 | 224 | // bottom chat input field 225 | Widget _chatInput() { 226 | return Padding( 227 | padding: EdgeInsets.symmetric( 228 | vertical: mq.height * .01, horizontal: mq.width * .025), 229 | child: Row( 230 | children: [ 231 | //input field & buttons 232 | Expanded( 233 | child: Card( 234 | shape: const RoundedRectangleBorder( 235 | borderRadius: BorderRadius.all(Radius.circular(15))), 236 | child: Row( 237 | children: [ 238 | //emoji button 239 | IconButton( 240 | onPressed: () { 241 | FocusScope.of(context).unfocus(); 242 | setState(() => _showEmoji = !_showEmoji); 243 | }, 244 | icon: const Icon(Icons.emoji_emotions, 245 | color: Colors.blueAccent, size: 25)), 246 | 247 | Expanded( 248 | child: TextField( 249 | controller: _textController, 250 | keyboardType: TextInputType.multiline, 251 | maxLines: null, 252 | onTap: () { 253 | if (_showEmoji) setState(() => _showEmoji = !_showEmoji); 254 | }, 255 | decoration: const InputDecoration( 256 | hintText: 'Type Something...', 257 | hintStyle: TextStyle(color: Colors.blueAccent), 258 | border: InputBorder.none), 259 | )), 260 | 261 | //pick image from gallery button 262 | IconButton( 263 | onPressed: () async { 264 | final ImagePicker picker = ImagePicker(); 265 | 266 | // Picking multiple images 267 | final List images = 268 | await picker.pickMultiImage(imageQuality: 70); 269 | 270 | // uploading & sending image one by one 271 | for (var i in images) { 272 | log('Image Path: ${i.path}'); 273 | setState(() => _isUploading = true); 274 | await APIs.sendChatImage(widget.user, File(i.path)); 275 | setState(() => _isUploading = false); 276 | } 277 | }, 278 | icon: const Icon(Icons.image, 279 | color: Colors.blueAccent, size: 26)), 280 | 281 | //take image from camera button 282 | IconButton( 283 | onPressed: () async { 284 | final ImagePicker picker = ImagePicker(); 285 | 286 | // Pick an image 287 | final XFile? image = await picker.pickImage( 288 | source: ImageSource.camera, imageQuality: 70); 289 | if (image != null) { 290 | log('Image Path: ${image.path}'); 291 | setState(() => _isUploading = true); 292 | 293 | await APIs.sendChatImage( 294 | widget.user, File(image.path)); 295 | setState(() => _isUploading = false); 296 | } 297 | }, 298 | icon: const Icon(Icons.camera_alt_rounded, 299 | color: Colors.blueAccent, size: 26)), 300 | 301 | //adding some space 302 | SizedBox(width: mq.width * .02), 303 | ], 304 | ), 305 | ), 306 | ), 307 | 308 | //send message button 309 | MaterialButton( 310 | onPressed: () { 311 | if (_textController.text.isNotEmpty) { 312 | if (_list.isEmpty) { 313 | //on first message (add user to my_user collection of chat user) 314 | APIs.sendFirstMessage( 315 | widget.user, _textController.text, Type.text); 316 | } else { 317 | //simply send message 318 | APIs.sendMessage( 319 | widget.user, _textController.text, Type.text); 320 | } 321 | _textController.text = ''; 322 | } 323 | }, 324 | minWidth: 0, 325 | padding: 326 | const EdgeInsets.only(top: 10, bottom: 10, right: 5, left: 10), 327 | shape: const CircleBorder(), 328 | color: Colors.green, 329 | child: const Icon(Icons.send, color: Colors.white, size: 28), 330 | ) 331 | ], 332 | ), 333 | ); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:lottie/lottie.dart'; 7 | 8 | import '../api/apis.dart'; 9 | import '../helper/dialogs.dart'; 10 | import '../main.dart'; 11 | import '../models/chat_user.dart'; 12 | import '../widgets/chat_user_card.dart'; 13 | import '../widgets/profile_image.dart'; 14 | import 'ai_screen.dart'; 15 | import 'profile_screen.dart'; 16 | 17 | //home screen -- where all available contacts are shown 18 | class HomeScreen extends StatefulWidget { 19 | const HomeScreen({super.key}); 20 | 21 | @override 22 | State createState() => _HomeScreenState(); 23 | } 24 | 25 | class _HomeScreenState extends State { 26 | // for storing all users 27 | List _list = []; 28 | 29 | // for storing searched items 30 | final List _searchList = []; 31 | // for storing search status 32 | bool _isSearching = false; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | APIs.getSelfInfo(); 38 | 39 | //for updating user active status according to lifecycle events 40 | //resume -- active or online 41 | //pause -- inactive or offline 42 | SystemChannels.lifecycle.setMessageHandler((message) { 43 | log('Message: $message'); 44 | 45 | if (APIs.auth.currentUser != null) { 46 | if (message.toString().contains('resume')) { 47 | APIs.updateActiveStatus(true); 48 | } 49 | if (message.toString().contains('pause')) { 50 | APIs.updateActiveStatus(false); 51 | } 52 | } 53 | 54 | return Future.value(message); 55 | }); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return GestureDetector( 61 | //for hiding keyboard when a tap is detected on screen 62 | onTap: FocusScope.of(context).unfocus, 63 | child: PopScope( 64 | // onWillPop: () { 65 | // if (_isSearching) { 66 | // setState(() { 67 | // _isSearching = !_isSearching; 68 | // }); 69 | // return Future.value(false); 70 | // } else { 71 | // return Future.value(true); 72 | // } 73 | // }, 74 | 75 | //if search is on & back button is pressed then close search 76 | //or else simple close current screen on back button click 77 | canPop: false, 78 | onPopInvokedWithResult: (_, __) { 79 | if (_isSearching) { 80 | setState(() => _isSearching = !_isSearching); 81 | return; 82 | } 83 | 84 | // some delay before pop 85 | Future.delayed( 86 | const Duration(milliseconds: 300), SystemNavigator.pop); 87 | }, 88 | 89 | // 90 | child: Scaffold( 91 | //app bar 92 | appBar: AppBar( 93 | //view profile 94 | leading: IconButton( 95 | tooltip: 'View Profile', 96 | onPressed: () { 97 | Navigator.push( 98 | context, 99 | MaterialPageRoute( 100 | builder: (_) => ProfileScreen(user: APIs.me))); 101 | }, 102 | icon: const ProfileImage(size: 32), 103 | ), 104 | 105 | //title 106 | title: _isSearching 107 | ? TextField( 108 | decoration: const InputDecoration( 109 | border: InputBorder.none, hintText: 'Name, Email, ...'), 110 | autofocus: true, 111 | style: const TextStyle(fontSize: 17, letterSpacing: 0.5), 112 | //when search text changes then updated search list 113 | onChanged: (val) { 114 | //search logic 115 | _searchList.clear(); 116 | 117 | val = val.toLowerCase(); 118 | 119 | for (var i in _list) { 120 | if (i.name.toLowerCase().contains(val) || 121 | i.email.toLowerCase().contains(val)) { 122 | _searchList.add(i); 123 | } 124 | } 125 | setState(() => _searchList); 126 | }, 127 | ) 128 | : const Text('We Chat'), 129 | actions: [ 130 | //search user button 131 | IconButton( 132 | tooltip: 'Search', 133 | onPressed: () => setState(() => _isSearching = !_isSearching), 134 | icon: Icon(_isSearching 135 | ? CupertinoIcons.clear_circled_solid 136 | : CupertinoIcons.search)), 137 | 138 | //add new user 139 | IconButton( 140 | tooltip: 'Add User', 141 | padding: const EdgeInsets.only(right: 8), 142 | onPressed: _addChatUserDialog, 143 | icon: const Icon(CupertinoIcons.person_add, size: 25)) 144 | ], 145 | ), 146 | 147 | //floating button to add new user 148 | floatingActionButton: Padding( 149 | padding: const EdgeInsets.only(bottom: 10), 150 | child: FloatingActionButton( 151 | backgroundColor: Colors.white, 152 | onPressed: () { 153 | Navigator.push(context, 154 | MaterialPageRoute(builder: (_) => const AiScreen())); 155 | }, 156 | child: Lottie.asset('assets/lottie/ai.json', width: 40)), 157 | ), 158 | 159 | //body 160 | body: StreamBuilder( 161 | stream: APIs.getMyUsersId(), 162 | 163 | //get id of only known users 164 | builder: (context, snapshot) { 165 | switch (snapshot.connectionState) { 166 | //if data is loading 167 | case ConnectionState.waiting: 168 | case ConnectionState.none: 169 | return const Center(child: CircularProgressIndicator()); 170 | 171 | //if some or all data is loaded then show it 172 | case ConnectionState.active: 173 | case ConnectionState.done: 174 | return StreamBuilder( 175 | stream: APIs.getAllUsers( 176 | snapshot.data?.docs.map((e) => e.id).toList() ?? []), 177 | 178 | //get only those user, who's ids are provided 179 | builder: (context, snapshot) { 180 | switch (snapshot.connectionState) { 181 | //if data is loading 182 | case ConnectionState.waiting: 183 | case ConnectionState.none: 184 | // return const Center( 185 | // child: CircularProgressIndicator()); 186 | 187 | //if some or all data is loaded then show it 188 | case ConnectionState.active: 189 | case ConnectionState.done: 190 | final data = snapshot.data?.docs; 191 | _list = data 192 | ?.map((e) => ChatUser.fromJson(e.data())) 193 | .toList() ?? 194 | []; 195 | 196 | if (_list.isNotEmpty) { 197 | return ListView.builder( 198 | itemCount: _isSearching 199 | ? _searchList.length 200 | : _list.length, 201 | padding: EdgeInsets.only(top: mq.height * .01), 202 | physics: const BouncingScrollPhysics(), 203 | itemBuilder: (context, index) { 204 | return ChatUserCard( 205 | user: _isSearching 206 | ? _searchList[index] 207 | : _list[index]); 208 | }); 209 | } else { 210 | return const Center( 211 | child: Text('No Connections Found!', 212 | style: TextStyle(fontSize: 20)), 213 | ); 214 | } 215 | } 216 | }, 217 | ); 218 | } 219 | }, 220 | ), 221 | ), 222 | ), 223 | ); 224 | } 225 | 226 | // for adding new chat user 227 | void _addChatUserDialog() { 228 | String email = ''; 229 | 230 | showDialog( 231 | context: context, 232 | builder: (_) => AlertDialog( 233 | contentPadding: const EdgeInsets.only( 234 | left: 24, right: 24, top: 20, bottom: 10), 235 | 236 | shape: const RoundedRectangleBorder( 237 | borderRadius: BorderRadius.all(Radius.circular(15))), 238 | 239 | //title 240 | title: const Row( 241 | children: [ 242 | Icon( 243 | Icons.person_add, 244 | color: Colors.blue, 245 | size: 28, 246 | ), 247 | Text(' Add User') 248 | ], 249 | ), 250 | 251 | //content 252 | content: TextFormField( 253 | maxLines: null, 254 | onChanged: (value) => email = value, 255 | decoration: const InputDecoration( 256 | hintText: 'Email Id', 257 | prefixIcon: Icon(Icons.email, color: Colors.blue), 258 | border: OutlineInputBorder( 259 | borderRadius: BorderRadius.all(Radius.circular(15)))), 260 | ), 261 | 262 | //actions 263 | actions: [ 264 | //cancel button 265 | MaterialButton( 266 | onPressed: () { 267 | //hide alert dialog 268 | Navigator.pop(context); 269 | }, 270 | child: const Text('Cancel', 271 | style: TextStyle(color: Colors.blue, fontSize: 16))), 272 | 273 | //add button 274 | MaterialButton( 275 | onPressed: () async { 276 | //hide alert dialog 277 | Navigator.pop(context); 278 | if (email.trim().isNotEmpty) { 279 | await APIs.addChatUser(email).then((value) { 280 | if (!value) { 281 | Dialogs.showSnackbar( 282 | context, 'User does not Exists!'); 283 | } 284 | }); 285 | } 286 | }, 287 | child: const Text( 288 | 'Add', 289 | style: TextStyle(color: Colors.blue, fontSize: 16), 290 | )) 291 | ], 292 | )); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /lib/screens/profile_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:google_sign_in/google_sign_in.dart'; 6 | import 'package:image_picker/image_picker.dart'; 7 | 8 | import '../../api/apis.dart'; 9 | import '../../helper/dialogs.dart'; 10 | import '../../main.dart'; 11 | import '../../models/chat_user.dart'; 12 | import '../widgets/profile_image.dart'; 13 | import 'auth/login_screen.dart'; 14 | 15 | //profile screen -- to show signed in user info 16 | class ProfileScreen extends StatefulWidget { 17 | final ChatUser user; 18 | 19 | const ProfileScreen({super.key, required this.user}); 20 | 21 | @override 22 | State createState() => _ProfileScreenState(); 23 | } 24 | 25 | class _ProfileScreenState extends State { 26 | final _formKey = GlobalKey(); 27 | String? _image; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return GestureDetector( 32 | // for hiding keyboard 33 | onTap: FocusScope.of(context).unfocus, 34 | child: Scaffold( 35 | //app bar 36 | appBar: AppBar(title: const Text('Profile Screen')), 37 | 38 | //floating button to log out 39 | floatingActionButton: Padding( 40 | padding: const EdgeInsets.only(bottom: 10), 41 | child: FloatingActionButton.extended( 42 | backgroundColor: Colors.redAccent, 43 | onPressed: () async { 44 | //for showing progress dialog 45 | Dialogs.showLoading(context); 46 | 47 | await APIs.updateActiveStatus(false); 48 | 49 | //sign out from app 50 | await APIs.auth.signOut().then((value) async { 51 | await GoogleSignIn().signOut().then((value) { 52 | //for hiding progress dialog 53 | Navigator.pop(context); 54 | 55 | //for moving to home screen 56 | Navigator.pop(context); 57 | 58 | // APIs.auth = FirebaseAuth.instance; 59 | 60 | //replacing home screen with login screen 61 | Navigator.pushReplacement( 62 | context, 63 | MaterialPageRoute( 64 | builder: (_) => const LoginScreen())); 65 | }); 66 | }); 67 | }, 68 | icon: const Icon(Icons.logout), 69 | label: const Text('Logout')), 70 | ), 71 | 72 | //body 73 | body: Form( 74 | key: _formKey, 75 | child: Padding( 76 | padding: EdgeInsets.symmetric(horizontal: mq.width * .05), 77 | child: SingleChildScrollView( 78 | child: Column( 79 | children: [ 80 | // for adding some space 81 | SizedBox(width: mq.width, height: mq.height * .03), 82 | 83 | //user profile picture 84 | Stack( 85 | children: [ 86 | //profile picture 87 | _image != null 88 | ? 89 | 90 | //local image 91 | ClipRRect( 92 | borderRadius: BorderRadius.all( 93 | Radius.circular(mq.height * .1)), 94 | child: Image.file(File(_image!), 95 | width: mq.height * .2, 96 | height: mq.height * .2, 97 | fit: BoxFit.cover)) 98 | : 99 | 100 | //image from server 101 | ProfileImage( 102 | size: mq.height * .2, 103 | url: widget.user.image, 104 | ), 105 | 106 | //edit image button 107 | Positioned( 108 | bottom: 0, 109 | right: 0, 110 | child: MaterialButton( 111 | elevation: 1, 112 | onPressed: () { 113 | _showBottomSheet(); 114 | }, 115 | shape: const CircleBorder(), 116 | color: Colors.white, 117 | child: const Icon(Icons.edit, color: Colors.blue), 118 | ), 119 | ) 120 | ], 121 | ), 122 | 123 | // for adding some space 124 | SizedBox(height: mq.height * .03), 125 | 126 | // user email label 127 | Text(widget.user.email, 128 | style: const TextStyle( 129 | color: Colors.black54, fontSize: 16)), 130 | 131 | // for adding some space 132 | SizedBox(height: mq.height * .05), 133 | 134 | // name input field 135 | TextFormField( 136 | initialValue: widget.user.name, 137 | onSaved: (val) => APIs.me.name = val ?? '', 138 | validator: (val) => val != null && val.isNotEmpty 139 | ? null 140 | : 'Required Field', 141 | decoration: const InputDecoration( 142 | prefixIcon: Icon(Icons.person, color: Colors.blue), 143 | border: OutlineInputBorder( 144 | borderRadius: BorderRadius.all(Radius.circular(12)), 145 | ), 146 | hintText: 'eg. Happy Singh', 147 | label: Text('Name')), 148 | ), 149 | 150 | // for adding some space 151 | SizedBox(height: mq.height * .02), 152 | 153 | // about input field 154 | TextFormField( 155 | initialValue: widget.user.about, 156 | onSaved: (val) => APIs.me.about = val ?? '', 157 | validator: (val) => val != null && val.isNotEmpty 158 | ? null 159 | : 'Required Field', 160 | decoration: const InputDecoration( 161 | prefixIcon: 162 | Icon(Icons.info_outline, color: Colors.blue), 163 | border: OutlineInputBorder( 164 | borderRadius: BorderRadius.all(Radius.circular(12)), 165 | ), 166 | hintText: 'eg. Feeling Happy', 167 | label: Text('About')), 168 | ), 169 | 170 | // for adding some space 171 | SizedBox(height: mq.height * .05), 172 | 173 | // update profile button 174 | ElevatedButton.icon( 175 | style: ElevatedButton.styleFrom( 176 | shape: const StadiumBorder(), 177 | minimumSize: Size(mq.width * .5, mq.height * .06)), 178 | onPressed: () { 179 | if (_formKey.currentState!.validate()) { 180 | _formKey.currentState!.save(); 181 | APIs.updateUserInfo().then((value) { 182 | Dialogs.showSnackbar( 183 | context, 'Profile Updated Successfully!'); 184 | }); 185 | } 186 | }, 187 | icon: const Icon(Icons.edit, size: 28), 188 | label: 189 | const Text('UPDATE', style: TextStyle(fontSize: 16)), 190 | ) 191 | ], 192 | ), 193 | ), 194 | ), 195 | )), 196 | ); 197 | } 198 | 199 | // bottom sheet for picking a profile picture for user 200 | void _showBottomSheet() { 201 | showModalBottomSheet( 202 | context: context, 203 | shape: const RoundedRectangleBorder( 204 | borderRadius: BorderRadius.only( 205 | topLeft: Radius.circular(20), topRight: Radius.circular(20))), 206 | builder: (_) { 207 | return ListView( 208 | shrinkWrap: true, 209 | padding: 210 | EdgeInsets.only(top: mq.height * .03, bottom: mq.height * .05), 211 | children: [ 212 | //pick profile picture label 213 | const Text('Pick Profile Picture', 214 | textAlign: TextAlign.center, 215 | style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), 216 | 217 | //for adding some space 218 | SizedBox(height: mq.height * .02), 219 | 220 | //buttons 221 | Row( 222 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 223 | children: [ 224 | //pick from gallery button 225 | ElevatedButton( 226 | style: ElevatedButton.styleFrom( 227 | backgroundColor: Colors.white, 228 | shape: const CircleBorder(), 229 | fixedSize: Size(mq.width * .3, mq.height * .15)), 230 | onPressed: () async { 231 | final ImagePicker picker = ImagePicker(); 232 | 233 | // Pick an image 234 | final XFile? image = await picker.pickImage( 235 | source: ImageSource.gallery, imageQuality: 80); 236 | if (image != null) { 237 | log('Image Path: ${image.path}'); 238 | setState(() { 239 | _image = image.path; 240 | }); 241 | 242 | APIs.updateProfilePicture(File(_image!)); 243 | 244 | // for hiding bottom sheet 245 | if (mounted) Navigator.pop(context); 246 | } 247 | }, 248 | child: Image.asset('assets/images/add_image.png')), 249 | 250 | //take picture from camera button 251 | ElevatedButton( 252 | style: ElevatedButton.styleFrom( 253 | backgroundColor: Colors.white, 254 | shape: const CircleBorder(), 255 | fixedSize: Size(mq.width * .3, mq.height * .15)), 256 | onPressed: () async { 257 | final ImagePicker picker = ImagePicker(); 258 | 259 | // Pick an image 260 | final XFile? image = await picker.pickImage( 261 | source: ImageSource.camera, imageQuality: 80); 262 | if (image != null) { 263 | log('Image Path: ${image.path}'); 264 | setState(() { 265 | _image = image.path; 266 | }); 267 | 268 | APIs.updateProfilePicture(File(_image!)); 269 | 270 | // for hiding bottom sheet 271 | if (mounted) Navigator.pop(context); 272 | } 273 | }, 274 | child: Image.asset('assets/images/camera.png')), 275 | ], 276 | ) 277 | ], 278 | ); 279 | }); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /lib/screens/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | import '../../../main.dart'; 7 | import '../../api/apis.dart'; 8 | import 'auth/login_screen.dart'; 9 | import 'home_screen.dart'; 10 | 11 | //splash screen 12 | class SplashScreen extends StatefulWidget { 13 | const SplashScreen({super.key}); 14 | 15 | @override 16 | State createState() => _SplashScreenState(); 17 | } 18 | 19 | class _SplashScreenState extends State { 20 | @override 21 | void initState() { 22 | super.initState(); 23 | Future.delayed(const Duration(seconds: 2), () { 24 | //exit full-screen 25 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 26 | SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( 27 | systemNavigationBarColor: Colors.white, 28 | statusBarColor: Colors.white)); 29 | 30 | log('\nUser: ${APIs.auth.currentUser}'); 31 | 32 | //navigate 33 | Navigator.pushReplacement( 34 | context, 35 | MaterialPageRoute( 36 | builder: (_) => APIs.auth.currentUser != null 37 | ? const HomeScreen() 38 | : const LoginScreen(), 39 | )); 40 | }); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | //initializing media query (for getting device screen size) 46 | mq = MediaQuery.sizeOf(context); 47 | 48 | return Scaffold( 49 | //body 50 | body: Stack(children: [ 51 | //app logo 52 | Positioned( 53 | top: mq.height * .15, 54 | right: mq.width * .25, 55 | width: mq.width * .5, 56 | child: Image.asset('assets/images/icon.png')), 57 | 58 | //google login button 59 | Positioned( 60 | bottom: mq.height * .15, 61 | width: mq.width, 62 | child: const Text('MADE IN INDIA WITH ❤️', 63 | textAlign: TextAlign.center, 64 | style: TextStyle( 65 | fontSize: 16, color: Colors.black87, letterSpacing: .5))), 66 | ]), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/screens/view_profile_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../helper/my_date_util.dart'; 4 | import '../../main.dart'; 5 | import '../../models/chat_user.dart'; 6 | import '../widgets/profile_image.dart'; 7 | 8 | //view profile screen -- to view profile of user 9 | class ViewProfileScreen extends StatefulWidget { 10 | final ChatUser user; 11 | 12 | const ViewProfileScreen({super.key, required this.user}); 13 | 14 | @override 15 | State createState() => _ViewProfileScreenState(); 16 | } 17 | 18 | class _ViewProfileScreenState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return GestureDetector( 22 | // for hiding keyboard 23 | onTap: FocusScope.of(context).unfocus, 24 | child: Scaffold( 25 | //app bar 26 | appBar: AppBar(title: Text(widget.user.name)), 27 | 28 | //user about 29 | floatingActionButton: Row( 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | children: [ 32 | const Text( 33 | 'Joined On: ', 34 | style: TextStyle( 35 | color: Colors.black87, 36 | fontWeight: FontWeight.w500, 37 | fontSize: 15), 38 | ), 39 | Text( 40 | MyDateUtil.getLastMessageTime( 41 | context: context, 42 | time: widget.user.createdAt, 43 | showYear: true), 44 | style: const TextStyle(color: Colors.black54, fontSize: 15)), 45 | ], 46 | ), 47 | 48 | //body 49 | body: Padding( 50 | padding: EdgeInsets.symmetric(horizontal: mq.width * .05), 51 | child: SingleChildScrollView( 52 | child: Column( 53 | children: [ 54 | // for adding some space 55 | SizedBox(width: mq.width, height: mq.height * .03), 56 | 57 | //user profile picture 58 | ProfileImage( 59 | size: mq.height * .2, 60 | url: widget.user.image, 61 | ), 62 | 63 | // for adding some space 64 | SizedBox(height: mq.height * .03), 65 | 66 | // user email label 67 | Text(widget.user.email, 68 | style: 69 | const TextStyle(color: Colors.black87, fontSize: 16)), 70 | 71 | // for adding some space 72 | SizedBox(height: mq.height * .02), 73 | 74 | //user about 75 | Row( 76 | mainAxisAlignment: MainAxisAlignment.center, 77 | children: [ 78 | const Text( 79 | 'About: ', 80 | style: TextStyle( 81 | color: Colors.black87, 82 | fontWeight: FontWeight.w500, 83 | fontSize: 15), 84 | ), 85 | Text(widget.user.about, 86 | style: const TextStyle( 87 | color: Colors.black54, fontSize: 15)), 88 | ], 89 | ), 90 | ], 91 | ), 92 | ), 93 | )), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/widgets/ai_message_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:lottie/lottie.dart'; 2 | 3 | import '../main.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import '../models/message.dart'; 7 | import 'profile_image.dart'; 8 | 9 | class AiMessageCard extends StatelessWidget { 10 | final AiMessage message; 11 | 12 | const AiMessageCard({super.key, required this.message}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | const r = Radius.circular(15); 17 | 18 | return message.msgType == MessageType.bot 19 | 20 | //bot 21 | ? Row(children: [ 22 | const SizedBox(width: 6), 23 | 24 | CircleAvatar( 25 | radius: 18, 26 | backgroundColor: Colors.white, 27 | child: Image.asset('assets/images/logo.png', width: 24), 28 | ), 29 | 30 | // 31 | Container( 32 | constraints: BoxConstraints(maxWidth: mq.width * .6), 33 | margin: EdgeInsets.only( 34 | bottom: mq.height * .02, left: mq.width * .02), 35 | padding: EdgeInsets.symmetric( 36 | vertical: mq.height * .01, horizontal: mq.width * .02), 37 | decoration: BoxDecoration( 38 | border: Border.all(color: Colors.blue), 39 | borderRadius: const BorderRadius.only( 40 | topLeft: r, topRight: r, bottomRight: r)), 41 | child: message.msg.isEmpty 42 | ? Lottie.asset('assets/lottie/ai.json', width: 35) 43 | : Text(message.msg, textAlign: TextAlign.center), 44 | ) 45 | ]) 46 | 47 | //user 48 | : Row(mainAxisAlignment: MainAxisAlignment.end, children: [ 49 | // 50 | Container( 51 | constraints: BoxConstraints(maxWidth: mq.width * .6), 52 | margin: EdgeInsets.only( 53 | bottom: mq.height * .02, right: mq.width * .02), 54 | padding: EdgeInsets.symmetric( 55 | vertical: mq.height * .01, horizontal: mq.width * .02), 56 | decoration: BoxDecoration( 57 | border: Border.all(color: Colors.green), 58 | borderRadius: const BorderRadius.only( 59 | topLeft: r, topRight: r, bottomLeft: r)), 60 | child: Text( 61 | message.msg, 62 | textAlign: TextAlign.center, 63 | )), 64 | 65 | const ProfileImage(size: 35), 66 | 67 | const SizedBox(width: 6), 68 | ]); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/widgets/chat_user_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../api/apis.dart'; 4 | import '../helper/my_date_util.dart'; 5 | import '../main.dart'; 6 | import '../models/chat_user.dart'; 7 | import '../models/message.dart'; 8 | import '../screens/chat_screen.dart'; 9 | import 'dialogs/profile_dialog.dart'; 10 | import 'profile_image.dart'; 11 | 12 | //card to represent a single user in home screen 13 | class ChatUserCard extends StatefulWidget { 14 | final ChatUser user; 15 | 16 | const ChatUserCard({super.key, required this.user}); 17 | 18 | @override 19 | State createState() => _ChatUserCardState(); 20 | } 21 | 22 | class _ChatUserCardState extends State { 23 | //last message info (if null --> no message) 24 | Message? _message; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Card( 29 | margin: EdgeInsets.symmetric(horizontal: mq.width * .04, vertical: 4), 30 | // color: Colors.blue.shade100, 31 | elevation: 0.5, 32 | shape: const RoundedRectangleBorder( 33 | borderRadius: BorderRadius.all(Radius.circular(15))), 34 | child: InkWell( 35 | borderRadius: const BorderRadius.all(Radius.circular(15)), 36 | onTap: () { 37 | //for navigating to chat screen 38 | Navigator.push( 39 | context, 40 | MaterialPageRoute( 41 | builder: (_) => ChatScreen(user: widget.user))); 42 | }, 43 | child: StreamBuilder( 44 | stream: APIs.getLastMessage(widget.user), 45 | builder: (context, snapshot) { 46 | final data = snapshot.data?.docs; 47 | final list = 48 | data?.map((e) => Message.fromJson(e.data())).toList() ?? []; 49 | if (list.isNotEmpty) _message = list[0]; 50 | 51 | return ListTile( 52 | //user profile picture 53 | leading: InkWell( 54 | onTap: () { 55 | showDialog( 56 | context: context, 57 | builder: (_) => ProfileDialog(user: widget.user)); 58 | }, 59 | child: ProfileImage( 60 | size: mq.height * .055, url: widget.user.image), 61 | ), 62 | 63 | //user name 64 | title: Text(widget.user.name), 65 | 66 | //last message 67 | subtitle: Text( 68 | _message != null 69 | ? _message!.type == Type.image 70 | ? 'image' 71 | : _message!.msg 72 | : widget.user.about, 73 | maxLines: 1), 74 | 75 | //last message time 76 | trailing: _message == null 77 | ? null //show nothing when no message is sent 78 | : _message!.read.isEmpty && 79 | _message!.fromId != APIs.user.uid 80 | ? 81 | //show for unread message 82 | const SizedBox( 83 | width: 15, 84 | height: 15, 85 | child: DecoratedBox( 86 | decoration: BoxDecoration( 87 | color: Color.fromARGB(255, 0, 230, 119), 88 | borderRadius: 89 | BorderRadius.all(Radius.circular(10))), 90 | ), 91 | ) 92 | : 93 | //message sent time 94 | Text( 95 | MyDateUtil.getLastMessageTime( 96 | context: context, time: _message!.sent), 97 | style: const TextStyle(color: Colors.black54), 98 | ), 99 | ); 100 | }, 101 | )), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/widgets/dialogs/profile_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../main.dart'; 4 | import '../../models/chat_user.dart'; 5 | import '../../screens/view_profile_screen.dart'; 6 | import '../profile_image.dart'; 7 | 8 | class ProfileDialog extends StatelessWidget { 9 | const ProfileDialog({super.key, required this.user}); 10 | 11 | final ChatUser user; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return AlertDialog( 16 | contentPadding: EdgeInsets.zero, 17 | backgroundColor: Colors.white.withOpacity(.9), 18 | shape: const RoundedRectangleBorder( 19 | borderRadius: BorderRadius.all(Radius.circular(15))), 20 | content: SizedBox( 21 | width: mq.width * .6, 22 | height: mq.height * .35, 23 | child: Stack( 24 | children: [ 25 | //user profile picture 26 | Positioned( 27 | top: mq.height * .075, 28 | left: mq.width * .1, 29 | child: ProfileImage(size: mq.width * .5, url: user.image), 30 | ), 31 | 32 | //user name 33 | Positioned( 34 | left: mq.width * .04, 35 | top: mq.height * .02, 36 | width: mq.width * .55, 37 | child: Text(user.name, 38 | style: const TextStyle( 39 | fontSize: 18, fontWeight: FontWeight.w500)), 40 | ), 41 | 42 | //info button 43 | Positioned( 44 | right: 8, 45 | top: 6, 46 | child: MaterialButton( 47 | onPressed: () { 48 | //for hiding image dialog 49 | Navigator.pop(context); 50 | 51 | //move to view profile screen 52 | Navigator.push( 53 | context, 54 | MaterialPageRoute( 55 | builder: (_) => ViewProfileScreen(user: user))); 56 | }, 57 | minWidth: 0, 58 | padding: const EdgeInsets.all(0), 59 | shape: const CircleBorder(), 60 | child: const Icon(Icons.info_outline, 61 | color: Colors.blue, size: 30), 62 | )) 63 | ], 64 | )), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/widgets/message_card.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:gallery_saver_plus/gallery_saver.dart'; 7 | 8 | import '../api/apis.dart'; 9 | import '../helper/dialogs.dart'; 10 | import '../helper/my_date_util.dart'; 11 | import '../main.dart'; 12 | import '../models/message.dart'; 13 | 14 | // for showing single message details 15 | class MessageCard extends StatefulWidget { 16 | const MessageCard({super.key, required this.message}); 17 | 18 | final Message message; 19 | 20 | @override 21 | State createState() => _MessageCardState(); 22 | } 23 | 24 | class _MessageCardState extends State { 25 | @override 26 | Widget build(BuildContext context) { 27 | bool isMe = APIs.user.uid == widget.message.fromId; 28 | return InkWell( 29 | onLongPress: () => _showBottomSheet(isMe), 30 | child: isMe ? _greenMessage() : _blueMessage()); 31 | } 32 | 33 | // sender or another user message 34 | Widget _blueMessage() { 35 | //update last read message if sender and receiver are different 36 | if (widget.message.read.isEmpty) { 37 | APIs.updateMessageReadStatus(widget.message); 38 | } 39 | 40 | return Row( 41 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 42 | children: [ 43 | //message content 44 | Flexible( 45 | child: Container( 46 | padding: EdgeInsets.all(widget.message.type == Type.image 47 | ? mq.width * .03 48 | : mq.width * .04), 49 | margin: EdgeInsets.symmetric( 50 | horizontal: mq.width * .04, vertical: mq.height * .01), 51 | decoration: BoxDecoration( 52 | color: const Color.fromARGB(255, 221, 245, 255), 53 | border: Border.all(color: Colors.lightBlue), 54 | //making borders curved 55 | borderRadius: const BorderRadius.only( 56 | topLeft: Radius.circular(30), 57 | topRight: Radius.circular(30), 58 | bottomRight: Radius.circular(30))), 59 | child: widget.message.type == Type.text 60 | ? 61 | //show text 62 | Text( 63 | widget.message.msg, 64 | style: const TextStyle(fontSize: 15, color: Colors.black87), 65 | ) 66 | : 67 | //show image 68 | ClipRRect( 69 | borderRadius: const BorderRadius.all(Radius.circular(15)), 70 | child: CachedNetworkImage( 71 | imageUrl: widget.message.msg, 72 | fit: BoxFit.cover, 73 | placeholder: (context, url) => const Padding( 74 | padding: EdgeInsets.all(8.0), 75 | child: CircularProgressIndicator(strokeWidth: 2), 76 | ), 77 | errorWidget: (context, url, error) => 78 | const Icon(Icons.image, size: 70), 79 | ), 80 | ), 81 | ), 82 | ), 83 | 84 | //message time 85 | Padding( 86 | padding: EdgeInsets.only(right: mq.width * .04), 87 | child: Text( 88 | MyDateUtil.getFormattedTime( 89 | context: context, time: widget.message.sent), 90 | style: const TextStyle(fontSize: 13, color: Colors.black54), 91 | ), 92 | ), 93 | ], 94 | ); 95 | } 96 | 97 | // our or user message 98 | Widget _greenMessage() { 99 | return Row( 100 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 101 | children: [ 102 | //message time 103 | Row( 104 | children: [ 105 | //for adding some space 106 | SizedBox(width: mq.width * .04), 107 | 108 | //double tick blue icon for message read 109 | if (widget.message.read.isNotEmpty) 110 | const Icon(Icons.done_all_rounded, color: Colors.blue, size: 20), 111 | 112 | //for adding some space 113 | const SizedBox(width: 2), 114 | 115 | //sent time 116 | Text( 117 | MyDateUtil.getFormattedTime( 118 | context: context, time: widget.message.sent), 119 | style: const TextStyle(fontSize: 13, color: Colors.black54), 120 | ), 121 | ], 122 | ), 123 | 124 | //message content 125 | Flexible( 126 | child: Container( 127 | padding: EdgeInsets.all(widget.message.type == Type.image 128 | ? mq.width * .03 129 | : mq.width * .04), 130 | margin: EdgeInsets.symmetric( 131 | horizontal: mq.width * .04, vertical: mq.height * .01), 132 | decoration: BoxDecoration( 133 | color: const Color.fromARGB(255, 218, 255, 176), 134 | border: Border.all(color: Colors.lightGreen), 135 | //making borders curved 136 | borderRadius: const BorderRadius.only( 137 | topLeft: Radius.circular(30), 138 | topRight: Radius.circular(30), 139 | bottomLeft: Radius.circular(30))), 140 | child: widget.message.type == Type.text 141 | ? 142 | //show text 143 | Text( 144 | widget.message.msg, 145 | style: const TextStyle(fontSize: 15, color: Colors.black87), 146 | ) 147 | : 148 | //show image 149 | ClipRRect( 150 | borderRadius: const BorderRadius.all(Radius.circular(15)), 151 | child: CachedNetworkImage( 152 | imageUrl: widget.message.msg, 153 | placeholder: (context, url) => const Padding( 154 | padding: EdgeInsets.all(8.0), 155 | child: CircularProgressIndicator(strokeWidth: 2), 156 | ), 157 | errorWidget: (context, url, error) => 158 | const Icon(Icons.image, size: 70), 159 | ), 160 | ), 161 | ), 162 | ), 163 | ], 164 | ); 165 | } 166 | 167 | // bottom sheet for modifying message details 168 | void _showBottomSheet(bool isMe) { 169 | showModalBottomSheet( 170 | context: context, 171 | shape: const RoundedRectangleBorder( 172 | borderRadius: BorderRadius.only( 173 | topLeft: Radius.circular(20), topRight: Radius.circular(20))), 174 | builder: (_) { 175 | return ListView( 176 | shrinkWrap: true, 177 | children: [ 178 | //black divider 179 | Container( 180 | height: 4, 181 | margin: EdgeInsets.symmetric( 182 | vertical: mq.height * .015, horizontal: mq.width * .4), 183 | decoration: const BoxDecoration( 184 | color: Colors.grey, 185 | borderRadius: BorderRadius.all(Radius.circular(8))), 186 | ), 187 | 188 | widget.message.type == Type.text 189 | ? 190 | //copy option 191 | _OptionItem( 192 | icon: const Icon(Icons.copy_all_rounded, 193 | color: Colors.blue, size: 26), 194 | name: 'Copy Text', 195 | onTap: (ctx) async { 196 | await Clipboard.setData( 197 | ClipboardData(text: widget.message.msg)) 198 | .then((value) { 199 | if (ctx.mounted) { 200 | //for hiding bottom sheet 201 | Navigator.pop(ctx); 202 | 203 | Dialogs.showSnackbar(ctx, 'Text Copied!'); 204 | } 205 | }); 206 | }) 207 | : 208 | //save option 209 | _OptionItem( 210 | icon: const Icon(Icons.download_rounded, 211 | color: Colors.blue, size: 26), 212 | name: 'Save Image', 213 | onTap: (ctx) async { 214 | try { 215 | log('Image Url: ${widget.message.msg}'); 216 | await GallerySaver.saveImage(widget.message.msg, 217 | albumName: 'We Chat') 218 | .then((success) { 219 | if (ctx.mounted) { 220 | //for hiding bottom sheet 221 | Navigator.pop(ctx); 222 | if (success != null && success) { 223 | Dialogs.showSnackbar( 224 | ctx, 'Image Successfully Saved!'); 225 | } 226 | } 227 | }); 228 | } catch (e) { 229 | log('ErrorWhileSavingImg: $e'); 230 | } 231 | }), 232 | 233 | //separator or divider 234 | if (isMe) 235 | Divider( 236 | color: Colors.black54, 237 | endIndent: mq.width * .04, 238 | indent: mq.width * .04, 239 | ), 240 | 241 | //edit option 242 | if (widget.message.type == Type.text && isMe) 243 | _OptionItem( 244 | icon: const Icon(Icons.edit, color: Colors.blue, size: 26), 245 | name: 'Edit Message', 246 | onTap: (ctx) { 247 | if (ctx.mounted) { 248 | _showMessageUpdateDialog(ctx); 249 | 250 | //for hiding bottom sheet 251 | // Navigator.pop(ctx); 252 | } 253 | }), 254 | 255 | //delete option 256 | if (isMe) 257 | _OptionItem( 258 | icon: const Icon(Icons.delete_forever, 259 | color: Colors.red, size: 26), 260 | name: 'Delete Message', 261 | onTap: (ctx) async { 262 | await APIs.deleteMessage(widget.message).then((value) { 263 | //for hiding bottom sheet 264 | if (ctx.mounted) Navigator.pop(ctx); 265 | }); 266 | }), 267 | 268 | //separator or divider 269 | Divider( 270 | color: Colors.black54, 271 | endIndent: mq.width * .04, 272 | indent: mq.width * .04, 273 | ), 274 | 275 | //sent time 276 | _OptionItem( 277 | icon: const Icon(Icons.remove_red_eye, color: Colors.blue), 278 | name: 279 | 'Sent At: ${MyDateUtil.getMessageTime(time: widget.message.sent)}', 280 | onTap: (_) {}), 281 | 282 | //read time 283 | _OptionItem( 284 | icon: const Icon(Icons.remove_red_eye, color: Colors.green), 285 | name: widget.message.read.isEmpty 286 | ? 'Read At: Not seen yet' 287 | : 'Read At: ${MyDateUtil.getMessageTime(time: widget.message.read)}', 288 | onTap: (_) {}), 289 | ], 290 | ); 291 | }); 292 | } 293 | 294 | //dialog for updating message content 295 | void _showMessageUpdateDialog(final BuildContext ctx) { 296 | String updatedMsg = widget.message.msg; 297 | 298 | showDialog( 299 | context: ctx, 300 | builder: (_) => AlertDialog( 301 | contentPadding: const EdgeInsets.only( 302 | left: 24, right: 24, top: 20, bottom: 10), 303 | 304 | shape: const RoundedRectangleBorder( 305 | borderRadius: BorderRadius.all(Radius.circular(20))), 306 | 307 | //title 308 | title: const Row( 309 | children: [ 310 | Icon( 311 | Icons.message, 312 | color: Colors.blue, 313 | size: 28, 314 | ), 315 | Text(' Update Message') 316 | ], 317 | ), 318 | 319 | //content 320 | content: TextFormField( 321 | initialValue: updatedMsg, 322 | maxLines: null, 323 | onChanged: (value) => updatedMsg = value, 324 | decoration: const InputDecoration( 325 | border: OutlineInputBorder( 326 | borderRadius: BorderRadius.all(Radius.circular(15)))), 327 | ), 328 | 329 | //actions 330 | actions: [ 331 | //cancel button 332 | MaterialButton( 333 | onPressed: () { 334 | //hide alert dialog 335 | Navigator.pop(ctx); 336 | }, 337 | child: const Text( 338 | 'Cancel', 339 | style: TextStyle(color: Colors.blue, fontSize: 16), 340 | )), 341 | 342 | //update button 343 | MaterialButton( 344 | onPressed: () { 345 | APIs.updateMessage(widget.message, updatedMsg); 346 | //hide alert dialog 347 | Navigator.pop(ctx); 348 | 349 | //for hiding bottom sheet 350 | Navigator.pop(ctx); 351 | }, 352 | child: const Text( 353 | 'Update', 354 | style: TextStyle(color: Colors.blue, fontSize: 16), 355 | )) 356 | ], 357 | )); 358 | } 359 | } 360 | 361 | //custom options card (for copy, edit, delete, etc.) 362 | class _OptionItem extends StatelessWidget { 363 | final Icon icon; 364 | final String name; 365 | final Function(BuildContext) onTap; 366 | 367 | const _OptionItem( 368 | {required this.icon, required this.name, required this.onTap}); 369 | 370 | @override 371 | Widget build(BuildContext context) { 372 | return InkWell( 373 | onTap: () => onTap(context), 374 | child: Padding( 375 | padding: EdgeInsets.only( 376 | left: mq.width * .05, 377 | top: mq.height * .015, 378 | bottom: mq.height * .015), 379 | child: Row(children: [ 380 | icon, 381 | Flexible( 382 | child: Text(' $name', 383 | style: const TextStyle( 384 | fontSize: 15, 385 | color: Colors.black54, 386 | letterSpacing: 0.5))) 387 | ]), 388 | )); 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /lib/widgets/profile_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import '../api/apis.dart'; 5 | 6 | class ProfileImage extends StatelessWidget { 7 | final double size; 8 | final String? url; 9 | 10 | const ProfileImage({super.key, required this.size, this.url}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return ClipRRect( 15 | borderRadius: BorderRadius.all(Radius.circular(size)), 16 | child: CachedNetworkImage( 17 | width: size, 18 | height: size, 19 | fit: BoxFit.cover, 20 | imageUrl: url ?? APIs.user.photoURL.toString(), 21 | errorWidget: (context, url, error) => 22 | const CircleAvatar(child: Icon(CupertinoIcons.person)), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "we_chat") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "com.harshRajpurohit.we_chat") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | 90 | # Generated plugin build rules, which manage building the plugins and adding 91 | # them to the application. 92 | include(flutter/generated_plugins.cmake) 93 | 94 | 95 | # === Installation === 96 | # By default, "installing" just makes a relocatable bundle in the build 97 | # directory. 98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 101 | endif() 102 | 103 | # Start with a clean build bundle directory every time. 104 | install(CODE " 105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 106 | " COMPONENT Runtime) 107 | 108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 110 | 111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 112 | COMPONENT Runtime) 113 | 114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 115 | COMPONENT Runtime) 116 | 117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 118 | COMPONENT Runtime) 119 | 120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 121 | install(FILES "${bundled_library}" 122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 123 | COMPONENT Runtime) 124 | endforeach(bundled_library) 125 | 126 | # Copy the native assets provided by the build.dart from all packages. 127 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 128 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 129 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 130 | COMPONENT Runtime) 131 | 132 | # Fully re-copy the assets directory on each build to avoid having stale files 133 | # from a previous install. 134 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 135 | install(CODE " 136 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 137 | " COMPONENT Runtime) 138 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 139 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 140 | 141 | # Install the AOT library on non-Debug builds only. 142 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 143 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 144 | COMPONENT Runtime) 145 | endif() 146 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin"); 15 | emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar); 16 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 18 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | emoji_picker_flutter 7 | file_selector_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "we_chat"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "we_chat"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GApplication::startup. 85 | static void my_application_startup(GApplication* application) { 86 | //MyApplication* self = MY_APPLICATION(object); 87 | 88 | // Perform any actions required at application startup. 89 | 90 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application); 91 | } 92 | 93 | // Implements GApplication::shutdown. 94 | static void my_application_shutdown(GApplication* application) { 95 | //MyApplication* self = MY_APPLICATION(object); 96 | 97 | // Perform any actions required at application shutdown. 98 | 99 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); 100 | } 101 | 102 | // Implements GObject::dispose. 103 | static void my_application_dispose(GObject* object) { 104 | MyApplication* self = MY_APPLICATION(object); 105 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 106 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 107 | } 108 | 109 | static void my_application_class_init(MyApplicationClass* klass) { 110 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 111 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 112 | G_APPLICATION_CLASS(klass)->startup = my_application_startup; 113 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | return MY_APPLICATION(g_object_new(my_application_get_type(), 121 | "application-id", APPLICATION_ID, 122 | "flags", G_APPLICATION_NON_UNIQUE, 123 | nullptr)); 124 | } 125 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: we_chat 2 | description: "A Chatting App Developed by Harsh H. Rajpurohit (Google Me)" 3 | publish_to: "none" 4 | version: 1.0.2+2 5 | 6 | environment: 7 | sdk: ^3.5.4 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | # For using cupertino icons 14 | cupertino_icons: ^1.0.8 15 | 16 | # For integrating firebase 17 | firebase_core: ^3.9.0 18 | 19 | # For handling firebase authentication 20 | firebase_auth: ^5.3.4 21 | 22 | # For handling google sign in 23 | google_sign_in: ^6.2.2 24 | 25 | # For accessing cloud firestore database 26 | cloud_firestore: ^5.6.0 27 | 28 | # For showing network images 29 | cached_network_image: ^3.4.1 30 | 31 | # For picking images 32 | image_picker: ^1.1.2 33 | 34 | # For accessing Firebase Storage (for uploading files) 35 | firebase_storage: ^12.3.7 36 | 37 | # For showing Emojis 38 | emoji_picker_flutter: ^3.1.0 39 | 40 | # For accessing firebase messaging (Push Notification) 41 | firebase_messaging: ^15.1.6 42 | 43 | # Bearer Token for Push Notification 44 | googleapis_auth: ^1.6.0 45 | 46 | # For calling RestAPIs 47 | http: ^1.2.2 48 | 49 | # For creating notification channel 50 | flutter_notification_channel: ^3.1.0 51 | 52 | # For storing images into gallery 53 | # gallery_saver_updated: ^3.0.0 54 | gallery_saver_plus: ^3.2.1 55 | 56 | # For Playing Lottie Animation 57 | lottie: ^3.2.0 58 | 59 | # Google Gemini Ai Integration 60 | google_generative_ai: ^0.4.3 61 | 62 | # Date Time Formatting 63 | intl: ^0.20.1 64 | 65 | dev_dependencies: 66 | flutter_test: 67 | sdk: flutter 68 | 69 | # flutter_launcher_icons: "^0.10.0" 70 | 71 | # flutter_icons: 72 | # android: "ic_launcher" 73 | # ios: true 74 | # image_path: "assets/images/icon.png" 75 | # min_sdk_android: 21 76 | # remove_alpha_ios: true 77 | 78 | # For recommended coding practices 79 | flutter_lints: ^5.0.0 80 | 81 | flutter: 82 | # For enabling material design 83 | uses-material-design: true 84 | 85 | # App Assets 86 | assets: 87 | - assets/ 88 | - assets/images/ 89 | - assets/lottie/ 90 | -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/4.png -------------------------------------------------------------------------------- /screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/5.png -------------------------------------------------------------------------------- /screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/6.png -------------------------------------------------------------------------------- /screenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/7.png -------------------------------------------------------------------------------- /screenshots/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/screenshots/8.png -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarshAndroid/ApnaChat-Realtime-Chat-App-In-Flutter-Firebase/030ae76f96c866946e0ed7bda43d4980d51dde76/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | we_chat 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "we_chat", 3 | "short_name": "we_chat", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | --------------------------------------------------------------------------------