├── functions
├── .gitignore
├── .eslintrc.js
├── index.js
└── package.json
├── ios
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── GoogleService-Info.plist
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ └── project.pbxproj
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── firebase_app_id_file.json
└── .gitignore
├── .firebaserc
├── web
├── favicon.png
├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
├── manifest.json
└── index.html
├── assets
└── images
│ ├── circle.png
│ ├── doodle_bg.png
│ ├── splash_dark.png
│ └── splash_light.png
├── android
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── whatsapp_messenger
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
└── build.gradle
├── firebase.json
├── lib
├── feature
│ ├── contact
│ │ ├── controller
│ │ │ └── contacts_controller.dart
│ │ ├── widget
│ │ │ └── contact_card.dart
│ │ ├── repository
│ │ │ └── contacts_repository.dart
│ │ └── pages
│ │ │ └── contact_page.dart
│ ├── home
│ │ └── pages
│ │ │ ├── call_home_page.dart
│ │ │ ├── status_home_page.dart
│ │ │ ├── home_page.dart
│ │ │ └── chat_home_page.dart
│ ├── chat
│ │ ├── widgets
│ │ │ ├── show_date_card.dart
│ │ │ ├── custom_list_tile.dart
│ │ │ ├── yellow_card.dart
│ │ │ ├── message_card.dart
│ │ │ └── chat_text_field.dart
│ │ ├── controller
│ │ │ └── chat_controller.dart
│ │ ├── repository
│ │ │ └── chat_repository.dart
│ │ └── pages
│ │ │ ├── chat_page.dart
│ │ │ └── profile_page.dart
│ ├── welcome
│ │ ├── widgets
│ │ │ ├── privacy_and_terms.dart
│ │ │ └── language_button.dart
│ │ └── pages
│ │ │ └── welcome_page.dart
│ └── auth
│ │ ├── widgets
│ │ └── custom_text_field.dart
│ │ ├── controller
│ │ └── auth_controller.dart
│ │ ├── pages
│ │ ├── image_picker_page.dart
│ │ ├── verification_page.dart
│ │ ├── login_page.dart
│ │ └── user_info_page.dart
│ │ └── repository
│ │ └── auth_repository.dart
├── common
│ ├── utils
│ │ └── coloors.dart
│ ├── enum
│ │ └── message_type.dart
│ ├── widgets
│ │ ├── custom_elevated_button.dart
│ │ ├── short_h_bar.dart
│ │ └── custom_icon_button.dart
│ ├── helper
│ │ ├── last_seen_message.dart
│ │ ├── show_alert_dialog.dart
│ │ └── show_loading_dialog.dart
│ ├── repository
│ │ └── firebase_storage_repository.dart
│ ├── models
│ │ ├── last_message_model.dart
│ │ ├── user_model.dart
│ │ └── message_model.dart
│ ├── theme
│ │ ├── light_theme.dart
│ │ └── dark_theme.dart
│ ├── routes
│ │ └── routes.dart
│ └── extension
│ │ └── custom_theme_extension.dart
└── main.dart
├── README.md
├── .gitignore
├── test
└── widget_test.dart
├── .metadata
├── analysis_options.yaml
└── pubspec.yaml
/functions/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "whatsapp-chat-app-id"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/web/favicon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/assets/images/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/assets/images/circle.png
--------------------------------------------------------------------------------
/assets/images/doodle_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/assets/images/doodle_bg.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/assets/images/splash_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/assets/images/splash_dark.png
--------------------------------------------------------------------------------
/assets/images/splash_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/assets/images/splash_light.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YonasAlem/whatsapp_messenger/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/whatsapp_messenger/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatsapp_messenger
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/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-7.4-all.zip
6 |
--------------------------------------------------------------------------------
/functions/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | es6: true,
5 | node: true,
6 | },
7 | extends: [
8 | "eslint:recommended",
9 | "google",
10 | ],
11 | rules: {
12 | quotes: ["error", "double"],
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/firebase_app_id_file.json:
--------------------------------------------------------------------------------
1 | {
2 | "file_generated_by": "FlutterFire CLI",
3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
4 | "GOOGLE_APP_ID": "1:157371465900:ios:870c21c7bcdcca35c99f09",
5 | "FIREBASE_PROJECT_ID": "whatsapp-chat-app-id",
6 | "GCM_SENDER_ID": "157371465900"
7 | }
--------------------------------------------------------------------------------
/ios/Runner.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/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": [
3 | {
4 | "source": "functions",
5 | "codebase": "default",
6 | "ignore": [
7 | "node_modules",
8 | ".git",
9 | "firebase-debug.log",
10 | "firebase-debug.*.log"
11 | ],
12 | "predeploy": [
13 | "npm --prefix \"%RESOURCE_DIR%\" run lint"
14 | ]
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/lib/feature/contact/controller/contacts_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_riverpod/flutter_riverpod.dart';
2 | import 'package:whatsapp_messenger/feature/contact/repository/contacts_repository.dart';
3 |
4 | final contactsControllerProvider = FutureProvider(
5 | (ref) {
6 | final contactsRepository = ref.watch(contactsRepositoryProvider);
7 | return contactsRepository.getAllContacts();
8 | },
9 | );
10 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
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 |
--------------------------------------------------------------------------------
/lib/feature/home/pages/call_home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CallHomePage extends StatelessWidget {
4 | const CallHomePage({super.key});
5 |
6 | @override
7 | Widget build(BuildContext context) {
8 | return Scaffold(
9 | body: const Center(
10 | child: Text('Call Home Page'),
11 | ),
12 | floatingActionButton: FloatingActionButton(
13 | onPressed: () {},
14 | child: const Icon(Icons.call),
15 | ),
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/lib/feature/home/pages/status_home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class StatusHomePage extends StatelessWidget {
4 | const StatusHomePage({super.key});
5 |
6 | @override
7 | Widget build(BuildContext context) {
8 | return Scaffold(
9 | body: const Center(
10 | child: Text('Status Home Page'),
11 | ),
12 | floatingActionButton: FloatingActionButton(
13 | onPressed: () {},
14 | child: const Icon(Icons.edit),
15 | ),
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/functions/index.js:
--------------------------------------------------------------------------------
1 | const functions = require("firebase-functions");
2 | const admin = require('firebase-admin');
3 | admin.initializeApp();
4 |
5 | const firestore = admin.firestore();
6 |
7 | exports.onUserStateChange = functions.database.ref('/{uid}/active').onUpdate(
8 | async (change, context) => {
9 | const isActive = change.after.val();
10 | const firestoreRef = firestore.doc(`users/${context.params.uid}`);
11 |
12 | return firestoreRef.update({
13 | active: isActive,
14 | lastSeen: Date.now(),
15 | });
16 | }
17 | );
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # whatsapp_messenger
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/lib/common/utils/coloors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Coloors {
4 | Coloors._();
5 |
6 | static const Color greenDark = Color(0xFF00A884);
7 | static const Color greenLight = Color(0xFF008069);
8 | static const Color blueDark = Color(0xFF53BDEB);
9 | static const Color blueLight = Color(0xFF027EB5);
10 | static const Color greyDark = Color(0xFF8696A0);
11 | static const Color greyLight = Color(0xFF667781);
12 | static const Color backgroundDark = Color(0xFF111B21);
13 | static const Color backgroundLight = Color(0xFFFFFFFF);
14 | static const Color greyBackground = Color(0xFF202C33);
15 | }
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/common/enum/message_type.dart:
--------------------------------------------------------------------------------
1 | enum MessageType {
2 | text('text'),
3 | image('image'),
4 | audio('audio'),
5 | video('video'),
6 | gif('gif');
7 |
8 | final String type;
9 |
10 | const MessageType(this.type);
11 | }
12 |
13 | extension ConvertMessage on String {
14 | MessageType toEnum() {
15 | switch (this) {
16 | case 'text':
17 | return MessageType.text;
18 | case 'image':
19 | return MessageType.image;
20 | case 'audio':
21 | return MessageType.audio;
22 | case 'video':
23 | return MessageType.video;
24 | case 'gif':
25 | return MessageType.gif;
26 |
27 | default:
28 | return MessageType.text;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.1.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/lib/common/widgets/custom_elevated_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomElevatedButton extends StatelessWidget {
4 | const CustomElevatedButton({
5 | Key? key,
6 | this.buttonWidth,
7 | required this.onPressed,
8 | required this.text,
9 | }) : super(key: key);
10 |
11 | final double? buttonWidth;
12 | final VoidCallback onPressed;
13 | final String text;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return SizedBox(
18 | height: 42,
19 | width: buttonWidth ?? MediaQuery.of(context).size.width - 100,
20 | child: ElevatedButton(
21 | onPressed: onPressed,
22 | child: Text(text),
23 | ),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "Cloud Functions for Firebase",
4 | "scripts": {
5 | "lint": "eslint",
6 | "serve": "firebase emulators:start --only functions",
7 | "shell": "firebase functions:shell",
8 | "start": "npm run shell",
9 | "deploy": "firebase deploy --only functions",
10 | "logs": "firebase functions:log"
11 | },
12 | "engines": {
13 | "node": "16"
14 | },
15 | "main": "index.js",
16 | "dependencies": {
17 | "firebase-admin": "^10.0.2",
18 | "firebase-functions": "^3.18.0"
19 | },
20 | "devDependencies": {
21 | "eslint": "^8.9.0",
22 | "eslint-config-google": "^0.14.0",
23 | "firebase-functions-test": "^0.2.0"
24 | },
25 | "private": true
26 | }
27 |
--------------------------------------------------------------------------------
/lib/common/helper/last_seen_message.dart:
--------------------------------------------------------------------------------
1 | String lastSeenMessage(lastSeen) {
2 | DateTime now = DateTime.now();
3 | Duration differenceDuration = now.difference(
4 | DateTime.fromMillisecondsSinceEpoch(lastSeen),
5 | );
6 |
7 | String finalMessage = differenceDuration.inSeconds > 59
8 | ? differenceDuration.inMinutes > 59
9 | ? differenceDuration.inHours > 23
10 | ? "${differenceDuration.inDays} ${differenceDuration.inDays == 1 ? 'day' : 'days'}"
11 | : "${differenceDuration.inHours} ${differenceDuration.inHours == 1 ? 'hour' : 'hours'}"
12 | : "${differenceDuration.inMinutes} ${differenceDuration.inMinutes == 1 ? 'minute' : 'minutes'}"
13 | : 'few moments';
14 |
15 | return finalMessage;
16 | }
17 |
--------------------------------------------------------------------------------
/lib/common/widgets/short_h_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 |
4 | class ShortHBar extends StatelessWidget {
5 | const ShortHBar({
6 | super.key,
7 | this.height,
8 | this.width,
9 | this.color,
10 | });
11 |
12 | final double? height;
13 | final double? width;
14 | final Color? color;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Container(
19 | height: height ?? 4,
20 | width: width ?? 25,
21 | margin: const EdgeInsets.symmetric(vertical: 5),
22 | decoration: BoxDecoration(
23 | color: color ?? context.theme.greyColor!.withOpacity(.2),
24 | borderRadius: BorderRadius.circular(5),
25 | ),
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/feature/chat/widgets/show_date_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:intl/intl.dart';
3 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
4 |
5 | class ShowDateCard extends StatelessWidget {
6 | const ShowDateCard({Key? key, required this.date}) : super(key: key);
7 |
8 | final DateTime date;
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Container(
13 | padding: const EdgeInsets.symmetric(
14 | horizontal: 10,
15 | vertical: 5,
16 | ),
17 | margin: const EdgeInsets.symmetric(vertical: 10),
18 | decoration: BoxDecoration(
19 | color: context.theme.receiverChatCardBg,
20 | borderRadius: BorderRadius.circular(10),
21 | ),
22 | child: Text(
23 | DateFormat.yMMMd().format(date),
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.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 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
--------------------------------------------------------------------------------
/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 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/common/repository/firebase_storage_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:typed_data';
3 |
4 | import 'package:firebase_storage/firebase_storage.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 |
7 | final firebaseStorageRepositoryProvider = Provider(
8 | (ref) {
9 | return FirebaseStorageRepository(firebaseStorage: FirebaseStorage.instance);
10 | },
11 | );
12 |
13 | class FirebaseStorageRepository {
14 | final FirebaseStorage firebaseStorage;
15 |
16 | FirebaseStorageRepository({required this.firebaseStorage});
17 |
18 | storeFileToFirebase(String ref, var file) async {
19 | UploadTask? uploadTask;
20 | if (file is File) {
21 | uploadTask = firebaseStorage.ref().child(ref).putFile(file);
22 | }
23 | if (file is Uint8List) {
24 | uploadTask = firebaseStorage.ref().child(ref).putData(file);
25 | }
26 |
27 | TaskSnapshot snapshot = await uploadTask!;
28 | String imageUrl = await snapshot.ref.getDownloadURL();
29 | return imageUrl;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/common/helper/show_alert_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 |
4 | showAlertDialog({
5 | required BuildContext context,
6 | required String message,
7 | String? btnText,
8 | }) {
9 | return showDialog(
10 | context: context,
11 | builder: (context) {
12 | return AlertDialog(
13 | content: Text(
14 | message,
15 | style: TextStyle(
16 | color: context.theme.greyColor,
17 | fontSize: 15,
18 | ),
19 | ),
20 | contentPadding: const EdgeInsets.fromLTRB(20, 20, 20, 0),
21 | actions: [
22 | TextButton(
23 | onPressed: () => Navigator.pop(context),
24 | child: Text(
25 | btnText ?? "OK",
26 | style: TextStyle(
27 | color: context.theme.circleImageColor,
28 | ),
29 | ),
30 | ),
31 | ],
32 | );
33 | },
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/lib/feature/chat/widgets/custom_list_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 |
4 | class CustomListTile extends StatelessWidget {
5 | const CustomListTile({
6 | super.key,
7 | required this.title,
8 | required this.leading,
9 | this.subTitle,
10 | this.trailing,
11 | });
12 |
13 | final String title;
14 | final IconData leading;
15 | final String? subTitle;
16 | final Widget? trailing;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return ListTile(
21 | onTap: () {},
22 | contentPadding: const EdgeInsets.fromLTRB(25, 5, 10, 5),
23 | title: Text(title),
24 | subtitle: subTitle != null
25 | ? Text(
26 | subTitle!,
27 | style: TextStyle(
28 | color: context.theme.greyColor,
29 | ),
30 | )
31 | : null,
32 | leading: Icon(leading),
33 | trailing: trailing,
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/feature/chat/widgets/yellow_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 |
4 | class YellowCard extends StatelessWidget {
5 | const YellowCard({
6 | Key? key,
7 | }) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Container(
12 | margin: const EdgeInsets.symmetric(
13 | vertical: 10,
14 | horizontal: 30,
15 | ),
16 | padding: const EdgeInsets.all(10),
17 | decoration: BoxDecoration(
18 | color: context.theme.yellowCardBgColor,
19 | borderRadius: BorderRadius.circular(10),
20 | ),
21 | child: Text(
22 | 'Message and calls are end-to-end encrypted. No one outside of this chat, not even WhatsApp, can read or listen to them. Tap to learn more.',
23 | textAlign: TextAlign.center,
24 | style: TextStyle(
25 | fontSize: 13,
26 | color: context.theme.yellowCardTextColor,
27 | ),
28 | ),
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "whatsapp_messenger",
3 | "short_name": "whatsapp_messenger",
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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/common/models/last_message_model.dart:
--------------------------------------------------------------------------------
1 | class LastMessageModel {
2 | final String username;
3 | final String profileImageUrl;
4 | final String contactId;
5 | final DateTime timeSent;
6 | final String lastMessage;
7 |
8 | LastMessageModel({
9 | required this.username,
10 | required this.profileImageUrl,
11 | required this.contactId,
12 | required this.timeSent,
13 | required this.lastMessage,
14 | });
15 |
16 | Map toMap() {
17 | return {
18 | 'username': username,
19 | 'profileImageUrl': profileImageUrl,
20 | 'contactId': contactId,
21 | 'timeSent': timeSent.millisecondsSinceEpoch,
22 | 'lastMessage': lastMessage,
23 | };
24 | }
25 |
26 | factory LastMessageModel.fromMap(Map map) {
27 | return LastMessageModel(
28 | username: map['username'] ?? '',
29 | profileImageUrl: map['profileImageUrl'] ?? '',
30 | contactId: map['contactId'] ?? '',
31 | timeSent: DateTime.fromMillisecondsSinceEpoch(map['timeSent']),
32 | lastMessage: map['lastMessage'] ?? '',
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:whatsapp_messenger/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/lib/common/models/user_model.dart:
--------------------------------------------------------------------------------
1 | class UserModel {
2 | final String username;
3 | final String uid;
4 | final String profileImageUrl;
5 | final bool active;
6 | final int lastSeen;
7 | final String phoneNumber;
8 | final List groupId;
9 |
10 | UserModel({
11 | required this.username,
12 | required this.uid,
13 | required this.profileImageUrl,
14 | required this.active,
15 | required this.lastSeen,
16 | required this.phoneNumber,
17 | required this.groupId,
18 | });
19 |
20 | Map toMap() {
21 | return {
22 | 'username': username,
23 | 'uid': uid,
24 | 'profileImageUrl': profileImageUrl,
25 | 'active': active,
26 | 'lastSeen': lastSeen,
27 | 'phoneNumber': phoneNumber,
28 | 'groupId': groupId,
29 | };
30 | }
31 |
32 | factory UserModel.fromMap(Map map) {
33 | return UserModel(
34 | username: map['username'] ?? '',
35 | uid: map['uid'] ?? '',
36 | profileImageUrl: map['profileImageUrl'] ?? '',
37 | active: map['active'] ?? false,
38 | lastSeen: map['lastSeen'] ?? 0,
39 | phoneNumber: map['phoneNumber'] ?? '',
40 | groupId: List.from(map['groupId']),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/common/helper/show_loading_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
4 |
5 | showLoadingDialog({
6 | required BuildContext context,
7 | required String message,
8 | }) async {
9 | return await showDialog(
10 | context: context,
11 | barrierDismissible: false,
12 | builder: (context) {
13 | return AlertDialog(
14 | content: Column(
15 | mainAxisSize: MainAxisSize.min,
16 | children: [
17 | Row(
18 | children: [
19 | const CircularProgressIndicator(
20 | color: Coloors.greenDark,
21 | ),
22 | const SizedBox(width: 20),
23 | Expanded(
24 | child: Text(
25 | message,
26 | style: TextStyle(
27 | fontSize: 15,
28 | color: context.theme.greyColor,
29 | height: 1.5,
30 | ),
31 | ),
32 | ),
33 | ],
34 | ),
35 | ],
36 | ),
37 | );
38 | },
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/lib/feature/welcome/widgets/privacy_and_terms.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 |
4 | class PrivacyAndTerms extends StatelessWidget {
5 | const PrivacyAndTerms({Key? key}) : super(key: key);
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Padding(
10 | padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
11 | child: RichText(
12 | textAlign: TextAlign.center,
13 | text: TextSpan(
14 | text: 'Read our ',
15 | style: TextStyle(
16 | color: context.theme.greyColor,
17 | height: 1.5,
18 | ),
19 | children: [
20 | TextSpan(
21 | text: 'Privacy Policy. ',
22 | style: TextStyle(
23 | color: context.theme.blueColor,
24 | ),
25 | ),
26 | const TextSpan(text: 'Tap "Agree and continue" to accept the '),
27 | TextSpan(
28 | text: 'Terms of Services.',
29 | style: TextStyle(
30 | color: context.theme.blueColor,
31 | ),
32 | ),
33 | ],
34 | ),
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
17 | base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
18 | - platform: android
19 | create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
20 | base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
21 | - platform: ios
22 | create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
23 | base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
24 | - platform: web
25 | create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
26 | base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
27 |
28 | # User provided section
29 |
30 | # List of Local paths (relative to this file) that should be
31 | # ignored by the migrate tool.
32 | #
33 | # Files that are not part of the templates will be ignored by default.
34 | unmanaged_files:
35 | - 'lib/main.dart'
36 | - 'ios/Runner.xcodeproj/project.pbxproj'
37 |
--------------------------------------------------------------------------------
/lib/common/models/message_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:whatsapp_messenger/common/enum/message_type.dart';
2 |
3 | class MessageModel {
4 | final String senderId;
5 | final String receiverId;
6 | final String textMessage;
7 | final MessageType type;
8 | final DateTime timeSent;
9 | final String messageId;
10 | final bool isSeen;
11 |
12 | MessageModel({
13 | required this.senderId,
14 | required this.receiverId,
15 | required this.textMessage,
16 | required this.type,
17 | required this.timeSent,
18 | required this.messageId,
19 | required this.isSeen,
20 | });
21 |
22 | factory MessageModel.fromMap(Map map) {
23 | return MessageModel(
24 | senderId: map["senderId"],
25 | receiverId: map["receiverId"],
26 | textMessage: map["textMessage"],
27 | type: (map["type"] as String).toEnum(),
28 | timeSent: DateTime.fromMillisecondsSinceEpoch(map["timeSent"]),
29 | messageId: map["messageId"],
30 | isSeen: map["isSeen"] ?? false,
31 | );
32 | }
33 |
34 | Map toMap() {
35 | return {
36 | "senderId": senderId,
37 | "receiverId": receiverId,
38 | "textMessage": textMessage,
39 | "type": type.type,
40 | "timeSent": timeSent.millisecondsSinceEpoch,
41 | "messageId": messageId,
42 | "isSeen": isSeen,
43 | };
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ios/Runner/GoogleService-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CLIENT_ID
6 | 157371465900-5d92glon5cq0mh0ccg5qc6ijgohlbo8m.apps.googleusercontent.com
7 | REVERSED_CLIENT_ID
8 | com.googleusercontent.apps.157371465900-5d92glon5cq0mh0ccg5qc6ijgohlbo8m
9 | API_KEY
10 | AIzaSyCGBNVkNILtkXf9-VuNZZ51IBB18hZngF4
11 | GCM_SENDER_ID
12 | 157371465900
13 | PLIST_VERSION
14 | 1
15 | BUNDLE_ID
16 | com.example.whatsappMessenger
17 | PROJECT_ID
18 | whatsapp-chat-app-id
19 | STORAGE_BUCKET
20 | whatsapp-chat-app-id.appspot.com
21 | IS_ADS_ENABLED
22 |
23 | IS_ANALYTICS_ENABLED
24 |
25 | IS_APPINVITE_ENABLED
26 |
27 | IS_GCM_ENABLED
28 |
29 | IS_SIGNIN_ENABLED
30 |
31 | GOOGLE_APP_ID
32 | 1:157371465900:ios:118c8435475ccecec99f09
33 | DATABASE_URL
34 | https://whatsapp-chat-app-id-default-rtdb.firebaseio.com
35 |
36 |
--------------------------------------------------------------------------------
/lib/common/widgets/custom_icon_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomIconButton extends StatelessWidget {
4 | const CustomIconButton({
5 | Key? key,
6 | required this.onPressed,
7 | required this.icon,
8 | this.iconColor,
9 | this.iconSize,
10 | this.minWidth,
11 | this.background,
12 | this.border,
13 | }) : super(key: key);
14 |
15 | final VoidCallback onPressed;
16 | final IconData icon;
17 | final Color? iconColor;
18 | final double? iconSize;
19 | final double? minWidth;
20 | final Color? background;
21 | final BoxBorder? border;
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return Container(
26 | decoration: BoxDecoration(
27 | color: background,
28 | shape: BoxShape.circle,
29 | border: border,
30 | ),
31 | child: IconButton(
32 | onPressed: onPressed,
33 | splashColor: Colors.transparent,
34 | splashRadius: (minWidth ?? 45) - 25,
35 | iconSize: iconSize ?? 22,
36 | padding: EdgeInsets.zero,
37 | constraints: BoxConstraints(
38 | minWidth: minWidth ?? 45,
39 | minHeight: minWidth ?? 45,
40 | ),
41 | icon: Icon(
42 | icon,
43 | color: iconColor ?? Theme.of(context).appBarTheme.iconTheme?.color,
44 | ),
45 | ),
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/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 | Whatsapp Messenger
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | whatsapp_messenger
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 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/lib/feature/auth/widgets/custom_text_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
4 |
5 | class CustomTextField extends StatelessWidget {
6 | const CustomTextField({
7 | super.key,
8 | this.controller,
9 | this.hintText,
10 | this.readOnly,
11 | this.textAlign,
12 | this.keyboardType,
13 | this.prefixText,
14 | this.onTap,
15 | this.suffixIcon,
16 | this.onChanged,
17 | this.fontSize,
18 | this.autoFocus,
19 | });
20 |
21 | final TextEditingController? controller;
22 | final String? hintText;
23 | final bool? readOnly;
24 | final TextAlign? textAlign;
25 | final TextInputType? keyboardType;
26 | final String? prefixText;
27 | final VoidCallback? onTap;
28 | final Widget? suffixIcon;
29 | final Function(String)? onChanged;
30 | final double? fontSize;
31 | final bool? autoFocus;
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return TextFormField(
36 | onTap: onTap,
37 | controller: controller,
38 | readOnly: readOnly ?? false,
39 | textAlign: textAlign ?? TextAlign.center,
40 | keyboardType: keyboardType,
41 | onChanged: onChanged,
42 | style: TextStyle(fontSize: fontSize),
43 | autofocus: autoFocus ?? false,
44 | decoration: InputDecoration(
45 | isDense: true,
46 | prefixText: prefixText,
47 | suffix: suffixIcon,
48 | hintText: hintText,
49 | hintStyle: TextStyle(color: context.theme.greyColor),
50 | enabledBorder: const UnderlineInputBorder(
51 | borderSide: BorderSide(
52 | color: Coloors.greenDark,
53 | ),
54 | ),
55 | focusedBorder: const UnderlineInputBorder(
56 | borderSide: BorderSide(
57 | color: Coloors.greenDark,
58 | width: 2,
59 | ),
60 | ),
61 | ),
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
10 |
18 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
33 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | whatsapp_messenger
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_core/firebase_core.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_native_splash/flutter_native_splash.dart';
4 | import 'package:flutter_riverpod/flutter_riverpod.dart';
5 | import 'package:whatsapp_messenger/common/routes/routes.dart';
6 | import 'package:whatsapp_messenger/common/theme/dark_theme.dart';
7 | import 'package:whatsapp_messenger/common/theme/light_theme.dart';
8 | import 'package:whatsapp_messenger/feature/auth/controller/auth_controller.dart';
9 | import 'package:whatsapp_messenger/feature/home/pages/home_page.dart';
10 | import 'package:whatsapp_messenger/feature/welcome/pages/welcome_page.dart';
11 | import 'package:whatsapp_messenger/firebase_options.dart';
12 |
13 | void main() async {
14 | WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
15 | FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
16 | await Firebase.initializeApp(
17 | options: DefaultFirebaseOptions.currentPlatform,
18 | );
19 | runApp(
20 | const ProviderScope(
21 | child: MyApp(),
22 | ),
23 | );
24 | }
25 |
26 | class MyApp extends ConsumerWidget {
27 | const MyApp({super.key});
28 |
29 | @override
30 | Widget build(BuildContext context, WidgetRef ref) {
31 | return MaterialApp(
32 | debugShowCheckedModeBanner: false,
33 | title: 'WhatsApp Me',
34 | theme: lightTheme(),
35 | darkTheme: darkTheme(),
36 | home: ref.watch(userInfoAuthProvider).when(
37 | data: (user) {
38 | FlutterNativeSplash.remove();
39 | if (user == null) return const WelcomePage();
40 | return const HomePage();
41 | },
42 | error: (error, trace) {
43 | return const Scaffold(
44 | body: Center(
45 | child: Text('Something wrong happened'),
46 | ),
47 | );
48 | },
49 | loading: () => const SizedBox(),
50 | ),
51 | onGenerateRoute: Routes.onGenerateRoute,
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/feature/welcome/pages/welcome_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 | import 'package:whatsapp_messenger/common/routes/routes.dart';
4 |
5 | import '../../../common/widgets/custom_elevated_button.dart';
6 | import '../widgets/language_button.dart';
7 | import '../widgets/privacy_and_terms.dart';
8 |
9 | class WelcomePage extends StatelessWidget {
10 | const WelcomePage({super.key});
11 |
12 | navigateToLoginPage(context) {
13 | Navigator.pushNamedAndRemoveUntil(
14 | context,
15 | Routes.login,
16 | (route) => false,
17 | );
18 | }
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return Scaffold(
23 | body: Column(
24 | children: [
25 | Expanded(
26 | child: Align(
27 | alignment: Alignment.bottomCenter,
28 | child: Padding(
29 | padding: const EdgeInsets.symmetric(
30 | horizontal: 50,
31 | vertical: 10,
32 | ),
33 | child: Image.asset(
34 | 'assets/images/circle.png',
35 | color: context.theme.circleImageColor,
36 | ),
37 | ),
38 | ),
39 | ),
40 | const SizedBox(height: 40),
41 | Expanded(
42 | child: Column(
43 | children: [
44 | const Text(
45 | 'Welcome to WhatsApp',
46 | style: TextStyle(
47 | fontSize: 22,
48 | fontWeight: FontWeight.bold,
49 | ),
50 | ),
51 | const PrivacyAndTerms(),
52 | CustomElevatedButton(
53 | onPressed: () => navigateToLoginPage(context),
54 | text: 'AGREE AND CONTINUE',
55 | ),
56 | const SizedBox(height: 50),
57 | const LanguageButton(),
58 | ],
59 | ))
60 | ],
61 | ),
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/feature/chat/controller/chat_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_riverpod/flutter_riverpod.dart';
3 | import 'package:whatsapp_messenger/common/enum/message_type.dart';
4 | import 'package:whatsapp_messenger/common/models/last_message_model.dart';
5 | import 'package:whatsapp_messenger/common/models/message_model.dart';
6 | import 'package:whatsapp_messenger/feature/auth/controller/auth_controller.dart';
7 | import 'package:whatsapp_messenger/feature/chat/repository/chat_repository.dart';
8 |
9 | final chatControllerProvider = Provider((ref) {
10 | final chatRepository = ref.watch(chatRepositoryProvider);
11 | return ChatController(
12 | chatRepository: chatRepository,
13 | ref: ref,
14 | );
15 | });
16 |
17 | class ChatController {
18 | final ChatRepository chatRepository;
19 | final ProviderRef ref;
20 |
21 | ChatController({required this.chatRepository, required this.ref});
22 |
23 | void sendFileMessage(
24 | BuildContext context,
25 | var file,
26 | String receiverId,
27 | MessageType messageType,
28 | ) {
29 | ref.read(userInfoAuthProvider).whenData((senderData) {
30 | return chatRepository.sendFileMessage(
31 | file: file,
32 | context: context,
33 | receiverId: receiverId,
34 | senderData: senderData!,
35 | ref: ref,
36 | messageType: messageType,
37 | );
38 | });
39 | }
40 |
41 | Stream> getAllOneToOneMessage(String receiverId) {
42 | return chatRepository.getAllOneToOneMessage(receiverId);
43 | }
44 |
45 | Stream> getAllLastMessageList() {
46 | return chatRepository.getAllLastMessageList();
47 | }
48 |
49 | void sendTextMessage({
50 | required BuildContext context,
51 | required String textMessage,
52 | required String receiverId,
53 | }) {
54 | ref.read(userInfoAuthProvider).whenData(
55 | (value) => chatRepository.sendTextMessage(
56 | context: context,
57 | textMessage: textMessage,
58 | receiverId: receiverId,
59 | senderData: value!,
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/feature/auth/controller/auth_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_riverpod/flutter_riverpod.dart';
3 | import 'package:whatsapp_messenger/feature/auth/repository/auth_repository.dart';
4 |
5 | import '../../../common/models/user_model.dart';
6 |
7 | final authControllerProvider = Provider((ref) {
8 | final authRepository = ref.watch(authRepositoryProvider);
9 | return AuthController(authRepository: authRepository, ref: ref);
10 | });
11 |
12 | final userInfoAuthProvider = FutureProvider(
13 | (ref) {
14 | final authController = ref.watch(authControllerProvider);
15 | return authController.getCurrentUserInfo();
16 | },
17 | );
18 |
19 | class AuthController {
20 | final AuthRepository authRepository;
21 | final ProviderRef ref;
22 |
23 | AuthController({required this.authRepository, required this.ref});
24 |
25 | Stream getUserPresenceStatus({required String uid}) {
26 | return authRepository.getUserPresenceStatus(uid: uid);
27 | }
28 |
29 | void updateUserPresence() {
30 | authRepository.updateUserPresence();
31 | }
32 |
33 | Future getCurrentUserInfo() async {
34 | UserModel? user = await authRepository.getCurrentUserInfo();
35 | return user;
36 | }
37 |
38 | void saveUserInfoToFirestore({
39 | required String username,
40 | required var profileImage,
41 | required BuildContext context,
42 | required bool mounted,
43 | }) {
44 | authRepository.saveUserInfoToFirestore(
45 | username: username,
46 | profileImage: profileImage,
47 | ref: ref,
48 | context: context,
49 | mounted: mounted,
50 | );
51 | }
52 |
53 | void verifySmsCode({
54 | required BuildContext context,
55 | required String smsCodeId,
56 | required String smsCode,
57 | required bool mounted,
58 | }) {
59 | authRepository.verifySmsCode(
60 | context: context,
61 | smsCodeId: smsCodeId,
62 | smsCode: smsCode,
63 | mounted: mounted,
64 | );
65 | }
66 |
67 | void sendSmsCode({
68 | required BuildContext context,
69 | required String phoneNumber,
70 | }) {
71 | authRepository.sendSmsCode(context: context, phoneNumber: phoneNumber);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/feature/contact/widget/contact_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
4 | import 'package:whatsapp_messenger/common/models/user_model.dart';
5 |
6 | import '../../../common/utils/coloors.dart';
7 |
8 | class ContactCard extends StatelessWidget {
9 | const ContactCard({
10 | Key? key,
11 | required this.contactSource,
12 | required this.onTap,
13 | }) : super(key: key);
14 |
15 | final UserModel contactSource;
16 | final VoidCallback onTap;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return ListTile(
21 | onTap: onTap,
22 | contentPadding: const EdgeInsets.only(
23 | left: 20,
24 | right: 10,
25 | ),
26 | leading: CircleAvatar(
27 | backgroundColor: context.theme.greyColor!.withOpacity(.3),
28 | radius: 20,
29 | backgroundImage: contactSource.profileImageUrl.isNotEmpty
30 | ? CachedNetworkImageProvider(contactSource.profileImageUrl)
31 | : null,
32 | child: contactSource.profileImageUrl.isEmpty
33 | ? const Icon(
34 | Icons.person,
35 | size: 30,
36 | color: Colors.white,
37 | )
38 | : null,
39 | ),
40 | title: Text(
41 | contactSource.username,
42 | style: const TextStyle(
43 | fontSize: 16,
44 | fontWeight: FontWeight.w500,
45 | ),
46 | ),
47 | subtitle: contactSource.uid.isNotEmpty
48 | ? Text(
49 | "Hey there! I'm using WhatsApp",
50 | style: TextStyle(
51 | color: context.theme.greyColor,
52 | fontWeight: FontWeight.w600,
53 | ),
54 | )
55 | : null,
56 | trailing: contactSource.uid.isEmpty
57 | ? TextButton(
58 | onPressed: onTap,
59 | style: TextButton.styleFrom(
60 | foregroundColor: Coloors.greenDark,
61 | ),
62 | child: const Text('INVITE'),
63 | )
64 | : null,
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/feature/contact/repository/contacts_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 |
3 | import 'package:cloud_firestore/cloud_firestore.dart';
4 | import 'package:flutter_contacts/flutter_contacts.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 | import 'package:whatsapp_messenger/common/models/user_model.dart';
7 |
8 | final contactsRepositoryProvider = Provider(
9 | (ref) {
10 | return ContactsRepository(firestore: FirebaseFirestore.instance);
11 | },
12 | );
13 |
14 | class ContactsRepository {
15 | final FirebaseFirestore firestore;
16 |
17 | ContactsRepository({required this.firestore});
18 |
19 | Future>> getAllContacts() async {
20 | List firebaseContacts = [];
21 | List phoneContacts = [];
22 |
23 | try {
24 | if (await FlutterContacts.requestPermission()) {
25 | final userCollection = await firestore.collection('users').get();
26 | final allContactsInThePhone = await FlutterContacts.getContacts(
27 | withProperties: true,
28 | );
29 |
30 | bool isContactFound = false;
31 |
32 | for (var contact in allContactsInThePhone) {
33 | for (var firebaseContactData in userCollection.docs) {
34 | var firebaseContact = UserModel.fromMap(firebaseContactData.data());
35 | if (contact.phones[0].number.replaceAll(' ', '') ==
36 | firebaseContact.phoneNumber) {
37 | firebaseContacts.add(firebaseContact);
38 | isContactFound = true;
39 | break;
40 | }
41 | }
42 | if (!isContactFound) {
43 | phoneContacts.add(
44 | UserModel(
45 | username: contact.displayName,
46 | uid: '',
47 | profileImageUrl: '',
48 | active: false,
49 | lastSeen: 0,
50 | phoneNumber: contact.phones[0].number.replaceAll(' ', ''),
51 | groupId: [],
52 | ),
53 | );
54 | }
55 |
56 | isContactFound = false;
57 | }
58 | }
59 | } catch (e) {
60 | log(e.toString());
61 | }
62 | return [firebaseContacts, phoneContacts];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/feature/home/pages/home_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_riverpod/flutter_riverpod.dart';
5 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
6 | import 'package:whatsapp_messenger/feature/auth/controller/auth_controller.dart';
7 | import 'package:whatsapp_messenger/feature/home/pages/call_home_page.dart';
8 | import 'package:whatsapp_messenger/feature/home/pages/chat_home_page.dart';
9 | import 'package:whatsapp_messenger/feature/home/pages/status_home_page.dart';
10 |
11 | class HomePage extends ConsumerStatefulWidget {
12 | const HomePage({super.key});
13 |
14 | @override
15 | ConsumerState createState() => _HomePageState();
16 | }
17 |
18 | class _HomePageState extends ConsumerState {
19 | late Timer timer;
20 |
21 | updateUserPresence() {
22 | ref.read(authControllerProvider).updateUserPresence();
23 | }
24 |
25 | @override
26 | void initState() {
27 | updateUserPresence();
28 | timer = Timer.periodic(
29 | const Duration(minutes: 1),
30 | (timer) => setState(() {}),
31 | );
32 | super.initState();
33 | }
34 |
35 | @override
36 | void dispose() {
37 | timer.cancel();
38 | super.dispose();
39 | }
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | return DefaultTabController(
44 | length: 3,
45 | child: Scaffold(
46 | appBar: AppBar(
47 | title: const Text(
48 | 'WhatsApp',
49 | style: TextStyle(letterSpacing: 1),
50 | ),
51 | elevation: 1,
52 | actions: [
53 | CustomIconButton(onPressed: () {}, icon: Icons.search),
54 | CustomIconButton(onPressed: () {}, icon: Icons.more_vert),
55 | ],
56 | bottom: const TabBar(
57 | indicatorWeight: 3,
58 | labelStyle: TextStyle(fontWeight: FontWeight.bold),
59 | splashFactory: NoSplash.splashFactory,
60 | tabs: [
61 | Tab(text: 'CHATS'),
62 | Tab(text: 'STATUS'),
63 | Tab(text: 'CALLS'),
64 | ],
65 | ),
66 | ),
67 | body: const TabBarView(
68 | children: [
69 | ChatHomePage(),
70 | StatusHomePage(),
71 | CallHomePage(),
72 | ],
73 | ),
74 | ),
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 33
30 | ndkVersion flutter.ndkVersion
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 |
41 | sourceSets {
42 | main.java.srcDirs += 'src/main/kotlin'
43 | }
44 |
45 | defaultConfig {
46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
47 | applicationId "com.example.whatsapp_messenger"
48 | // You can update the following values to match your application needs.
49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
50 | minSdkVersion 19
51 | targetSdkVersion flutter.targetSdkVersion
52 | versionCode flutterVersionCode.toInteger()
53 | versionName flutterVersionName
54 | multiDexEnabled true
55 | }
56 |
57 | buildTypes {
58 | release {
59 | // TODO: Add your own signing config for the release build.
60 | // Signing with the debug keys for now, so `flutter run --release` works.
61 | signingConfig signingConfigs.debug
62 | }
63 | }
64 | }
65 |
66 | flutter {
67 | source '../..'
68 | }
69 |
70 | dependencies {
71 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
72 | }
73 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/common/theme/light_theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
4 |
5 | import '../extension/custom_theme_extension.dart';
6 |
7 | ThemeData lightTheme() {
8 | final ThemeData base = ThemeData.light();
9 | return base.copyWith(
10 | backgroundColor: Coloors.backgroundLight,
11 | scaffoldBackgroundColor: Coloors.backgroundLight,
12 | extensions: [CustomThemeExtension.lightMode],
13 | appBarTheme: const AppBarTheme(
14 | backgroundColor: Coloors.greenLight,
15 | titleTextStyle: TextStyle(
16 | fontSize: 18,
17 | fontWeight: FontWeight.w600,
18 | ),
19 | systemOverlayStyle: SystemUiOverlayStyle(
20 | statusBarColor: Colors.transparent,
21 | statusBarIconBrightness: Brightness.dark,
22 | ),
23 | iconTheme: IconThemeData(
24 | color: Colors.white,
25 | ),
26 | ),
27 | tabBarTheme: const TabBarTheme(
28 | indicator: UnderlineTabIndicator(
29 | borderSide: BorderSide(
30 | color: Colors.white,
31 | width: 2,
32 | ),
33 | ),
34 | ),
35 | elevatedButtonTheme: ElevatedButtonThemeData(
36 | style: ElevatedButton.styleFrom(
37 | backgroundColor: Coloors.greenLight,
38 | foregroundColor: Coloors.backgroundLight,
39 | splashFactory: NoSplash.splashFactory,
40 | elevation: 0,
41 | shadowColor: Colors.transparent,
42 | ),
43 | ),
44 | bottomSheetTheme: const BottomSheetThemeData(
45 | backgroundColor: Coloors.backgroundLight,
46 | modalBackgroundColor: Coloors.backgroundLight,
47 | shape: RoundedRectangleBorder(
48 | borderRadius: BorderRadius.vertical(
49 | top: Radius.circular(20),
50 | ),
51 | ),
52 | ),
53 | dialogBackgroundColor: Coloors.backgroundLight,
54 | dialogTheme: DialogTheme(
55 | shape: RoundedRectangleBorder(
56 | borderRadius: BorderRadius.circular(10),
57 | ),
58 | ),
59 | floatingActionButtonTheme: const FloatingActionButtonThemeData(
60 | backgroundColor: Coloors.greenDark,
61 | foregroundColor: Colors.white,
62 | ),
63 | listTileTheme: const ListTileThemeData(
64 | iconColor: Coloors.greyDark,
65 | tileColor: Coloors.backgroundLight,
66 | ),
67 | switchTheme: const SwitchThemeData(
68 | thumbColor: MaterialStatePropertyAll(Color(0xFF83939C)),
69 | trackColor: MaterialStatePropertyAll(Color(0xFFDADFE2)),
70 | ),
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/lib/common/theme/dark_theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
4 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
5 |
6 | ThemeData darkTheme() {
7 | final ThemeData base = ThemeData.dark();
8 | return base.copyWith(
9 | backgroundColor: Coloors.backgroundDark,
10 | scaffoldBackgroundColor: Coloors.backgroundDark,
11 | extensions: [CustomThemeExtension.darkMode],
12 | appBarTheme: const AppBarTheme(
13 | backgroundColor: Coloors.greyBackground,
14 | titleTextStyle: TextStyle(
15 | fontSize: 18,
16 | fontWeight: FontWeight.w600,
17 | color: Coloors.greyDark,
18 | ),
19 | systemOverlayStyle: SystemUiOverlayStyle(
20 | statusBarColor: Colors.transparent,
21 | statusBarIconBrightness: Brightness.light,
22 | ),
23 | iconTheme: IconThemeData(
24 | color: Coloors.greyDark,
25 | ),
26 | ),
27 | tabBarTheme: const TabBarTheme(
28 | indicator: UnderlineTabIndicator(
29 | borderSide: BorderSide(
30 | color: Coloors.greenDark,
31 | width: 2,
32 | ),
33 | ),
34 | unselectedLabelColor: Coloors.greyDark,
35 | labelColor: Coloors.greenDark,
36 | ),
37 | elevatedButtonTheme: ElevatedButtonThemeData(
38 | style: ElevatedButton.styleFrom(
39 | backgroundColor: Coloors.greenDark,
40 | foregroundColor: Coloors.backgroundDark,
41 | splashFactory: NoSplash.splashFactory,
42 | elevation: 0,
43 | shadowColor: Colors.transparent,
44 | ),
45 | ),
46 | bottomSheetTheme: const BottomSheetThemeData(
47 | backgroundColor: Coloors.greyBackground,
48 | modalBackgroundColor: Coloors.greyBackground,
49 | shape: RoundedRectangleBorder(
50 | borderRadius: BorderRadius.vertical(
51 | top: Radius.circular(20),
52 | ),
53 | ),
54 | ),
55 | dialogBackgroundColor: Coloors.greyBackground,
56 | dialogTheme: DialogTheme(
57 | shape: RoundedRectangleBorder(
58 | borderRadius: BorderRadius.circular(10),
59 | ),
60 | ),
61 | floatingActionButtonTheme: const FloatingActionButtonThemeData(
62 | backgroundColor: Coloors.greenDark,
63 | foregroundColor: Colors.white,
64 | ),
65 | listTileTheme: const ListTileThemeData(
66 | iconColor: Coloors.greyDark,
67 | tileColor: Coloors.backgroundDark,
68 | ),
69 | switchTheme: const SwitchThemeData(
70 | thumbColor: MaterialStatePropertyAll(Coloors.greyDark),
71 | trackColor: MaterialStatePropertyAll(Color(0xFF344047)),
72 | ),
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/common/routes/routes.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:page_transition/page_transition.dart';
3 | import 'package:whatsapp_messenger/common/models/user_model.dart';
4 | import 'package:whatsapp_messenger/feature/auth/pages/login_page.dart';
5 | import 'package:whatsapp_messenger/feature/auth/pages/user_info_page.dart';
6 | import 'package:whatsapp_messenger/feature/auth/pages/verification_page.dart';
7 | import 'package:whatsapp_messenger/feature/chat/pages/chat_page.dart';
8 | import 'package:whatsapp_messenger/feature/chat/pages/profile_page.dart';
9 | import 'package:whatsapp_messenger/feature/contact/pages/contact_page.dart';
10 | import 'package:whatsapp_messenger/feature/home/pages/home_page.dart';
11 | import 'package:whatsapp_messenger/feature/welcome/pages/welcome_page.dart';
12 |
13 | class Routes {
14 | static const String welcome = 'welcome';
15 | static const String login = 'login';
16 | static const String verification = 'verification';
17 | static const String userInfo = 'user-info';
18 | static const String home = 'home';
19 | static const String contact = 'contact';
20 | static const String chat = 'chat';
21 | static const String profile = 'profile';
22 |
23 | static Route onGenerateRoute(RouteSettings settings) {
24 | switch (settings.name) {
25 | case welcome:
26 | return MaterialPageRoute(
27 | builder: (context) => const WelcomePage(),
28 | );
29 | case login:
30 | return MaterialPageRoute(
31 | builder: (context) => const LoginPage(),
32 | );
33 | case verification:
34 | final Map args = settings.arguments as Map;
35 | return MaterialPageRoute(
36 | builder: (context) => VerificationPage(
37 | smsCodeId: args['smsCodeId'],
38 | phoneNumber: args['phoneNumber'],
39 | ),
40 | );
41 | case userInfo:
42 | final String? profileImageUrl = settings.arguments as String?;
43 | return MaterialPageRoute(
44 | builder: (context) => UserInfoPage(
45 | profileImageUrl: profileImageUrl,
46 | ),
47 | );
48 | case home:
49 | return MaterialPageRoute(
50 | builder: (context) => const HomePage(),
51 | );
52 | case contact:
53 | return MaterialPageRoute(
54 | builder: (context) => const ContactPage(),
55 | );
56 | case chat:
57 | final UserModel user = settings.arguments as UserModel;
58 | return MaterialPageRoute(
59 | builder: (context) => ChatPage(user: user),
60 | );
61 | case profile:
62 | final UserModel user = settings.arguments as UserModel;
63 | return PageTransition(
64 | child: ProfilePage(user: user),
65 | type: PageTransitionType.fade,
66 | duration: const Duration(milliseconds: 800),
67 | );
68 | default:
69 | return MaterialPageRoute(
70 | builder: (context) {
71 | return const Scaffold(
72 | body: Center(
73 | child: Text('No Page Route Provided'),
74 | ),
75 | );
76 | },
77 | );
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/lib/feature/home/pages/chat_home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 | import 'package:intl/intl.dart';
5 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
6 | import 'package:whatsapp_messenger/common/models/last_message_model.dart';
7 | import 'package:whatsapp_messenger/common/models/user_model.dart';
8 | import 'package:whatsapp_messenger/common/routes/routes.dart';
9 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
10 | import 'package:whatsapp_messenger/feature/chat/controller/chat_controller.dart';
11 |
12 | class ChatHomePage extends ConsumerWidget {
13 | const ChatHomePage({super.key});
14 |
15 | @override
16 | Widget build(BuildContext context, WidgetRef ref) {
17 | return Scaffold(
18 | body: StreamBuilder>(
19 | stream: ref.watch(chatControllerProvider).getAllLastMessageList(),
20 | builder: (_, snapshot) {
21 | if (snapshot.connectionState == ConnectionState.waiting) {
22 | return const Center(
23 | child: CircularProgressIndicator(
24 | color: Coloors.greenDark,
25 | ),
26 | );
27 | }
28 | return ListView.builder(
29 | itemCount: snapshot.data!.length,
30 | shrinkWrap: true,
31 | itemBuilder: (context, index) {
32 | final lastMessageData = snapshot.data![index];
33 | return ListTile(
34 | onTap: () {
35 | Navigator.pushNamed(
36 | context,
37 | Routes.chat,
38 | arguments: UserModel(
39 | username: lastMessageData.username,
40 | uid: lastMessageData.contactId,
41 | profileImageUrl: lastMessageData.profileImageUrl,
42 | active: true,
43 | lastSeen: 0,
44 | phoneNumber: '0',
45 | groupId: [],
46 | ),
47 | );
48 | },
49 | title: Row(
50 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
51 | children: [
52 | Text(lastMessageData.username),
53 | Text(
54 | DateFormat.Hm().format(lastMessageData.timeSent),
55 | style: TextStyle(
56 | fontSize: 13,
57 | color: context.theme.greyColor,
58 | ),
59 | ),
60 | ],
61 | ),
62 | subtitle: Padding(
63 | padding: const EdgeInsets.only(top: 3),
64 | child: Text(
65 | lastMessageData.lastMessage,
66 | maxLines: 1,
67 | overflow: TextOverflow.ellipsis,
68 | style: TextStyle(color: context.theme.greyColor),
69 | ),
70 | ),
71 | leading: CircleAvatar(
72 | backgroundImage: CachedNetworkImageProvider(
73 | lastMessageData.profileImageUrl,
74 | ),
75 | radius: 24,
76 | ),
77 | );
78 | },
79 | );
80 | },
81 | ),
82 | floatingActionButton: FloatingActionButton(
83 | onPressed: () {
84 | Navigator.pushNamed(context, Routes.contact);
85 | },
86 | child: const Icon(Icons.chat),
87 | ),
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/feature/welcome/widgets/language_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
3 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
4 |
5 | import '../../../common/utils/coloors.dart';
6 |
7 | class LanguageButton extends StatelessWidget {
8 | const LanguageButton({Key? key}) : super(key: key);
9 |
10 | showBottomSheet(context) {
11 | return showModalBottomSheet(
12 | context: context,
13 | builder: (context) {
14 | return Padding(
15 | padding: const EdgeInsets.symmetric(vertical: 10),
16 | child: Column(
17 | mainAxisSize: MainAxisSize.min,
18 | children: [
19 | Container(
20 | height: 4,
21 | width: 30,
22 | decoration: BoxDecoration(
23 | color: context.theme.greyColor!.withOpacity(.4),
24 | borderRadius: BorderRadius.circular(5),
25 | ),
26 | ),
27 | const SizedBox(height: 20),
28 | Row(
29 | children: [
30 | const SizedBox(width: 20),
31 | CustomIconButton(
32 | onPressed: () => Navigator.pop(context),
33 | icon: Icons.close,
34 | ),
35 | const SizedBox(width: 10),
36 | const Text(
37 | 'App Language',
38 | style: TextStyle(
39 | fontSize: 20,
40 | fontWeight: FontWeight.w500,
41 | ),
42 | ),
43 | ],
44 | ),
45 | const SizedBox(height: 10),
46 | Divider(
47 | thickness: .5,
48 | color: context.theme.greyColor!.withOpacity(.3),
49 | ),
50 | RadioListTile(
51 | value: true,
52 | groupValue: true,
53 | onChanged: (value) {},
54 | activeColor: Coloors.greenDark,
55 | title: const Text('English'),
56 | subtitle: Text(
57 | "(Phone's language)",
58 | style: TextStyle(
59 | color: context.theme.greyColor,
60 | ),
61 | ),
62 | ),
63 | RadioListTile(
64 | value: true,
65 | groupValue: false,
66 | onChanged: (value) {},
67 | activeColor: Coloors.greenDark,
68 | title: const Text('አማርኛ'),
69 | subtitle: Text(
70 | "Amharic",
71 | style: TextStyle(
72 | color: context.theme.greyColor,
73 | ),
74 | ),
75 | ),
76 | ],
77 | ),
78 | );
79 | },
80 | );
81 | }
82 |
83 | @override
84 | Widget build(BuildContext context) {
85 | return Material(
86 | color: context.theme.langBgColor,
87 | borderRadius: BorderRadius.circular(20),
88 | child: InkWell(
89 | onTap: () => showBottomSheet(context),
90 | borderRadius: BorderRadius.circular(20),
91 | splashFactory: NoSplash.splashFactory,
92 | highlightColor: context.theme.langHightlightColor,
93 | child: Padding(
94 | padding: const EdgeInsets.symmetric(
95 | horizontal: 16,
96 | vertical: 8.0,
97 | ),
98 | child: Row(
99 | mainAxisSize: MainAxisSize.min,
100 | children: const [
101 | Icon(
102 | Icons.language,
103 | color: Coloors.greenDark,
104 | ),
105 | SizedBox(width: 10),
106 | Text(
107 | 'English',
108 | style: TextStyle(
109 | color: Coloors.greenDark,
110 | ),
111 | ),
112 | SizedBox(width: 10),
113 | Icon(
114 | Icons.keyboard_arrow_down,
115 | color: Coloors.greenDark,
116 | ),
117 | ],
118 | ),
119 | ),
120 | ),
121 | );
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/lib/feature/auth/pages/image_picker_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:photo_manager/photo_manager.dart';
5 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
6 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
7 |
8 | class ImagePickerPage extends StatefulWidget {
9 | const ImagePickerPage({super.key});
10 |
11 | @override
12 | State createState() => _ImagePickerPageState();
13 | }
14 |
15 | class _ImagePickerPageState extends State {
16 | List imageList = [];
17 | int currentPage = 0;
18 | int? lastPage;
19 |
20 | handleScrollEvent(ScrollNotification scroll) {
21 | if (scroll.metrics.pixels / scroll.metrics.maxScrollExtent <= .33) return;
22 | if (currentPage == lastPage) return;
23 | fetchAllImages();
24 | }
25 |
26 | fetchAllImages() async {
27 | lastPage = currentPage;
28 | final permission = await PhotoManager.requestPermissionExtend();
29 | if (!permission.isAuth) return PhotoManager.openSetting();
30 |
31 | List albums = await PhotoManager.getAssetPathList(
32 | type: RequestType.image,
33 | onlyAll: true,
34 | );
35 |
36 | List photos = await albums[0].getAssetListPaged(
37 | page: currentPage,
38 | size: 24,
39 | );
40 |
41 | List temp = [];
42 |
43 | for (var asset in photos) {
44 | temp.add(
45 | FutureBuilder(
46 | future: asset.thumbnailDataWithSize(
47 | const ThumbnailSize(200, 200),
48 | ),
49 | builder: (context, snapshot) {
50 | if (snapshot.connectionState == ConnectionState.done) {
51 | return ClipRRect(
52 | borderRadius: BorderRadius.circular(5),
53 | child: InkWell(
54 | onTap: () => Navigator.pop(context, snapshot.data),
55 | borderRadius: BorderRadius.circular(5),
56 | splashFactory: NoSplash.splashFactory,
57 | child: Container(
58 | margin: const EdgeInsets.all(2),
59 | decoration: BoxDecoration(
60 | border: Border.all(
61 | color: context.theme.greyColor!.withOpacity(.4),
62 | width: 1,
63 | ),
64 | image: DecorationImage(
65 | image: MemoryImage(snapshot.data as Uint8List),
66 | fit: BoxFit.cover,
67 | ),
68 | borderRadius: BorderRadius.circular(5),
69 | ),
70 | ),
71 | ),
72 | );
73 | }
74 | return const SizedBox();
75 | },
76 | ),
77 | );
78 | }
79 |
80 | setState(() {
81 | imageList.addAll(temp);
82 | currentPage++;
83 | });
84 | }
85 |
86 | @override
87 | void initState() {
88 | fetchAllImages();
89 | super.initState();
90 | }
91 |
92 | @override
93 | Widget build(BuildContext context) {
94 | return Scaffold(
95 | appBar: AppBar(
96 | backgroundColor: Theme.of(context).backgroundColor,
97 | leading: CustomIconButton(
98 | onPressed: () => Navigator.pop(context),
99 | icon: Icons.arrow_back,
100 | ),
101 | title: Text(
102 | 'WhatsApp',
103 | style: TextStyle(
104 | color: context.theme.authAppbarTextColor,
105 | ),
106 | ),
107 | actions: [
108 | CustomIconButton(
109 | onPressed: () {},
110 | icon: Icons.more_vert,
111 | ),
112 | ],
113 | ),
114 | body: Padding(
115 | padding: const EdgeInsets.all(5.0),
116 | child: NotificationListener(
117 | onNotification: (ScrollNotification scroll) {
118 | handleScrollEvent(scroll);
119 | return true;
120 | },
121 | child: GridView.builder(
122 | itemCount: imageList.length,
123 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
124 | crossAxisCount: 3,
125 | ),
126 | itemBuilder: (_, index) {
127 | return imageList[index];
128 | },
129 | ),
130 | ),
131 | ),
132 | );
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/lib/feature/auth/pages/verification_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_riverpod/flutter_riverpod.dart';
3 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
4 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
5 | import 'package:whatsapp_messenger/feature/auth/controller/auth_controller.dart';
6 | import 'package:whatsapp_messenger/feature/auth/widgets/custom_text_field.dart';
7 |
8 | class VerificationPage extends ConsumerWidget {
9 | const VerificationPage({
10 | super.key,
11 | required this.smsCodeId,
12 | required this.phoneNumber,
13 | });
14 |
15 | final String smsCodeId;
16 | final String phoneNumber;
17 |
18 | void verifySmsCode(
19 | BuildContext context,
20 | WidgetRef ref,
21 | String smsCode,
22 | ) {
23 | ref.read(authControllerProvider).verifySmsCode(
24 | context: context,
25 | smsCodeId: smsCodeId,
26 | smsCode: smsCode,
27 | mounted: true,
28 | );
29 | }
30 |
31 | @override
32 | Widget build(BuildContext context, WidgetRef ref) {
33 | return Scaffold(
34 | appBar: AppBar(
35 | backgroundColor: Theme.of(context).scaffoldBackgroundColor,
36 | elevation: 0,
37 | centerTitle: true,
38 | title: Text(
39 | 'Verify your number',
40 | style: TextStyle(
41 | color: context.theme.authAppbarTextColor,
42 | ),
43 | ),
44 | actions: [
45 | CustomIconButton(
46 | onPressed: () {},
47 | icon: Icons.more_vert,
48 | ),
49 | ],
50 | ),
51 | body: SingleChildScrollView(
52 | padding: const EdgeInsets.symmetric(horizontal: 20),
53 | child: Column(
54 | children: [
55 | Padding(
56 | padding: const EdgeInsets.symmetric(horizontal: 10),
57 | child: RichText(
58 | textAlign: TextAlign.center,
59 | text: TextSpan(
60 | style: TextStyle(color: context.theme.greyColor),
61 | children: [
62 | const TextSpan(
63 | text:
64 | "You've tried to register +251935838471. before requesting an SMS or Call with your code.",
65 | ),
66 | TextSpan(
67 | text: "Wrong number?",
68 | style: TextStyle(
69 | color: context.theme.blueColor,
70 | ),
71 | ),
72 | ],
73 | ),
74 | ),
75 | ),
76 | const SizedBox(height: 20),
77 | Container(
78 | padding: const EdgeInsets.symmetric(horizontal: 80),
79 | child: CustomTextField(
80 | hintText: "- - - - - -",
81 | fontSize: 30,
82 | autoFocus: true,
83 | keyboardType: TextInputType.number,
84 | onChanged: (value) {
85 | if (value.length == 6) {
86 | return verifySmsCode(context, ref, value);
87 | }
88 | },
89 | ),
90 | ),
91 | const SizedBox(height: 20),
92 | Text(
93 | 'Enter 6-digit code',
94 | style: TextStyle(color: context.theme.greyColor),
95 | ),
96 | const SizedBox(height: 30),
97 | Row(
98 | children: [
99 | Icon(Icons.message, color: context.theme.greyColor),
100 | const SizedBox(width: 20),
101 | Text(
102 | 'Resend SMS',
103 | style: TextStyle(
104 | color: context.theme.greyColor,
105 | ),
106 | ),
107 | ],
108 | ),
109 | const SizedBox(height: 10),
110 | Divider(
111 | color: context.theme.greyColor!.withOpacity(.2),
112 | ),
113 | const SizedBox(height: 10),
114 | Row(
115 | children: [
116 | Icon(Icons.phone, color: context.theme.greyColor),
117 | const SizedBox(width: 20),
118 | Text(
119 | 'Call Me',
120 | style: TextStyle(
121 | color: context.theme.greyColor,
122 | ),
123 | ),
124 | ],
125 | ),
126 | ],
127 | ),
128 | ),
129 | );
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: whatsapp_messenger
2 | description: A new Flutter project.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | # In Windows, build-name is used as the major, minor, and patch parts
19 | # of the product and file versions while build-number is used as the build suffix.
20 | version: 1.0.0+1
21 |
22 | environment:
23 | sdk: '>=2.18.4 <3.0.0'
24 |
25 | # Dependencies specify other packages that your package needs in order to work.
26 | # To automatically upgrade your package dependencies to the latest versions
27 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
28 | # dependencies can be manually updated by changing the version numbers below to
29 | # the latest version available on pub.dev. To see which dependencies have newer
30 | # versions available, run `flutter pub outdated`.
31 | dependencies:
32 | flutter:
33 | sdk: flutter
34 |
35 |
36 | # The following adds the Cupertino Icons font to your application.
37 | # Use with the CupertinoIcons class for iOS style icons.
38 | cupertino_icons: ^1.0.2
39 | flutter_native_splash: ^2.2.14
40 | country_picker: ^2.0.17
41 | firebase_core: ^2.3.0
42 | firebase_auth: ^4.1.3
43 | cloud_firestore: ^4.1.0
44 | firebase_storage: ^11.0.6
45 | flutter_riverpod: ^2.1.1
46 | image_picker: ^0.8.6
47 | photo_manager: ^2.5.0
48 | flutter_contacts: ^1.1.5+1
49 | url_launcher: ^6.1.7
50 | firebase_database: ^10.0.6
51 | page_transition: ^2.0.9
52 | cached_network_image: ^3.2.3
53 | intl: ^0.17.0
54 | uuid: ^3.0.7
55 | custom_clippers: ^2.0.0
56 | shimmer: ^2.0.0
57 |
58 | flutter_native_splash:
59 | color: "#FFFFFF"
60 | color_dark: "#111B21"
61 | image: assets/images/splash_light.png
62 | image_dark: assets/images/splash_dark.png
63 | android_12:
64 | color: "#FFFFFF"
65 | color_dark: "#111B21"
66 | image: assets/images/splash_light.png
67 | image_dark: assets/images/splash_dark.png
68 | icon_background_color: "#FFFFFF"
69 | icon_background_color_dark: "#111B21"
70 |
71 | dev_dependencies:
72 | flutter_test:
73 | sdk: flutter
74 |
75 | # The "flutter_lints" package below contains a set of recommended lints to
76 | # encourage good coding practices. The lint set provided by the package is
77 | # activated in the `analysis_options.yaml` file located at the root of your
78 | # package. See that file for information about deactivating specific lint
79 | # rules and activating additional ones.
80 | flutter_lints: ^2.0.0
81 |
82 | # For information on the generic Dart part of this file, see the
83 | # following page: https://dart.dev/tools/pub/pubspec
84 |
85 | # The following section is specific to Flutter packages.
86 | flutter:
87 |
88 | # The following line ensures that the Material Icons font is
89 | # included with your application, so that you can use the icons in
90 | # the material Icons class.
91 | uses-material-design: true
92 |
93 | # To add assets to your application, add an assets section, like this:
94 | assets:
95 | - assets/images/
96 | # - images/a_dot_ham.jpeg
97 |
98 | # An image asset can refer to one or more resolution-specific "variants", see
99 | # https://flutter.dev/assets-and-images/#resolution-aware
100 |
101 | # For details regarding adding assets from package dependencies, see
102 | # https://flutter.dev/assets-and-images/#from-packages
103 |
104 | # To add custom fonts to your application, add a fonts section here,
105 | # in this "flutter" section. Each entry in this list should have a
106 | # "family" key with the font family name, and a "fonts" key with a
107 | # list giving the asset and other descriptors for the font. For
108 | # example:
109 | # fonts:
110 | # - family: Schyler
111 | # fonts:
112 | # - asset: fonts/Schyler-Regular.ttf
113 | # - asset: fonts/Schyler-Italic.ttf
114 | # style: italic
115 | # - family: Trajan Pro
116 | # fonts:
117 | # - asset: fonts/TrajanPro.ttf
118 | # - asset: fonts/TrajanPro_Bold.ttf
119 | # weight: 700
120 | #
121 | # For details regarding fonts from package dependencies,
122 | # see https://flutter.dev/custom-fonts/#from-packages
123 |
--------------------------------------------------------------------------------
/lib/feature/chat/widgets/message_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:custom_clippers/custom_clippers.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:intl/intl.dart';
5 | import 'package:whatsapp_messenger/common/enum/message_type.dart' as my_type;
6 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
7 | import 'package:whatsapp_messenger/common/models/message_model.dart';
8 |
9 | class MessageCard extends StatelessWidget {
10 | const MessageCard({
11 | Key? key,
12 | required this.isSender,
13 | required this.haveNip,
14 | required this.message,
15 | }) : super(key: key);
16 |
17 | final bool isSender;
18 | final bool haveNip;
19 | final MessageModel message;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Container(
24 | alignment: isSender ? Alignment.centerRight : Alignment.centerLeft,
25 | margin: EdgeInsets.only(
26 | top: 4,
27 | bottom: 4,
28 | left: isSender
29 | ? 80
30 | : haveNip
31 | ? 10
32 | : 15,
33 | right: isSender
34 | ? haveNip
35 | ? 10
36 | : 15
37 | : 80,
38 | ),
39 | child: ClipPath(
40 | clipper: haveNip
41 | ? UpperNipMessageClipperTwo(
42 | isSender ? MessageType.send : MessageType.receive,
43 | nipWidth: 8,
44 | nipHeight: 10,
45 | bubbleRadius: haveNip ? 12 : 0,
46 | )
47 | : null,
48 | child: Stack(
49 | children: [
50 | Container(
51 | decoration: BoxDecoration(
52 | color: isSender ? context.theme.senderChatCardBg : context.theme.receiverChatCardBg,
53 | borderRadius: haveNip ? null : BorderRadius.circular(12),
54 | boxShadow: const [
55 | BoxShadow(color: Colors.black38),
56 | ],
57 | ),
58 | child: Padding(
59 | padding: const EdgeInsets.only(bottom: 5),
60 | child: message.type == my_type.MessageType.image
61 | ? Padding(
62 | padding: const EdgeInsets.only(right: 3, top: 3, left: 3),
63 | child: ClipRRect(
64 | borderRadius: BorderRadius.circular(12),
65 | child: Image(
66 | image: CachedNetworkImageProvider(message.textMessage),
67 | ),
68 | ),
69 | )
70 | : Padding(
71 | padding: EdgeInsets.only(
72 | top: 8,
73 | bottom: 8,
74 | left: isSender ? 10 : 15,
75 | right: isSender ? 15 : 10,
76 | ),
77 | child: Text(
78 | "${message.textMessage} ",
79 | style: const TextStyle(fontSize: 16),
80 | ),
81 | ),
82 | ),
83 | ),
84 | Positioned(
85 | bottom: message.type == my_type.MessageType.text ? 8 : 4,
86 | right: message.type == my_type.MessageType.text
87 | ? isSender
88 | ? 15
89 | : 10
90 | : 4,
91 | child: message.type == my_type.MessageType.text
92 | ? Text(
93 | DateFormat.Hm().format(message.timeSent),
94 | style: TextStyle(
95 | fontSize: 11,
96 | color: context.theme.greyColor,
97 | ),
98 | )
99 | : Container(
100 | padding: const EdgeInsets.only(left: 90, right: 10, bottom: 10, top: 14),
101 | decoration: BoxDecoration(
102 | gradient: LinearGradient(
103 | begin: const Alignment(0, -1),
104 | end: const Alignment(1, 1),
105 | colors: [
106 | context.theme.greyColor!.withOpacity(0),
107 | context.theme.greyColor!.withOpacity(.5),
108 | ],
109 | ),
110 | borderRadius: const BorderRadius.only(
111 | topLeft: Radius.circular(300),
112 | bottomRight: Radius.circular(100),
113 | ),
114 | ),
115 | child: Text(
116 | DateFormat.Hm().format(message.timeSent),
117 | style: const TextStyle(
118 | fontSize: 11,
119 | color: Colors.white,
120 | ),
121 | ),
122 | ),
123 | )
124 | ],
125 | ),
126 | ),
127 | );
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/lib/feature/auth/repository/auth_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:firebase_auth/firebase_auth.dart';
3 | import 'package:firebase_database/firebase_database.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 | import 'package:whatsapp_messenger/common/helper/show_alert_dialog.dart';
7 | import 'package:whatsapp_messenger/common/helper/show_loading_dialog.dart';
8 | import 'package:whatsapp_messenger/common/models/user_model.dart';
9 | import 'package:whatsapp_messenger/common/repository/firebase_storage_repository.dart';
10 | import 'package:whatsapp_messenger/common/routes/routes.dart';
11 |
12 | final authRepositoryProvider = Provider((ref) {
13 | return AuthRepository(
14 | auth: FirebaseAuth.instance,
15 | firestore: FirebaseFirestore.instance,
16 | realtime: FirebaseDatabase.instance,
17 | );
18 | });
19 |
20 | class AuthRepository {
21 | final FirebaseAuth auth;
22 | final FirebaseFirestore firestore;
23 | final FirebaseDatabase realtime;
24 |
25 | AuthRepository({
26 | required this.auth,
27 | required this.firestore,
28 | required this.realtime,
29 | });
30 |
31 | Stream getUserPresenceStatus({required String uid}) {
32 | return firestore
33 | .collection('users')
34 | .doc(uid)
35 | .snapshots()
36 | .map((event) => UserModel.fromMap(event.data()!));
37 | }
38 |
39 | void updateUserPresence() {
40 | Map online = {
41 | 'active': true,
42 | 'lastSeen': DateTime.now().millisecondsSinceEpoch,
43 | };
44 | Map offline = {
45 | 'active': false,
46 | 'lastSeen': DateTime.now().millisecondsSinceEpoch,
47 | };
48 |
49 | final connectedRef = realtime.ref('.info/connected');
50 |
51 | connectedRef.onValue.listen((event) async {
52 | final isConnected = event.snapshot.value as bool? ?? false;
53 | if (isConnected) {
54 | await realtime.ref().child(auth.currentUser!.uid).update(online);
55 | } else {
56 | realtime
57 | .ref()
58 | .child(auth.currentUser!.uid)
59 | .onDisconnect()
60 | .update(offline);
61 | }
62 | });
63 | }
64 |
65 | Future getCurrentUserInfo() async {
66 | UserModel? user;
67 | final userInfo =
68 | await firestore.collection('users').doc(auth.currentUser?.uid).get();
69 |
70 | if (userInfo.data() == null) return user;
71 | user = UserModel.fromMap(userInfo.data()!);
72 | return user;
73 | }
74 |
75 | void saveUserInfoToFirestore({
76 | required String username,
77 | required var profileImage,
78 | required ProviderRef ref,
79 | required BuildContext context,
80 | required bool mounted,
81 | }) async {
82 | try {
83 | showLoadingDialog(
84 | context: context,
85 | message: "Saving user info ... ",
86 | );
87 | String uid = auth.currentUser!.uid;
88 | String profileImageUrl = profileImage is String ? profileImage : '';
89 | if (profileImage != null && profileImage is! String) {
90 | profileImageUrl = await ref
91 | .read(firebaseStorageRepositoryProvider)
92 | .storeFileToFirebase('profileImage/$uid', profileImage);
93 | }
94 |
95 | UserModel user = UserModel(
96 | username: username,
97 | uid: uid,
98 | profileImageUrl: profileImageUrl,
99 | active: true,
100 | lastSeen: DateTime.now().millisecondsSinceEpoch,
101 | phoneNumber: auth.currentUser!.phoneNumber!,
102 | groupId: [],
103 | );
104 |
105 | await firestore.collection('users').doc(uid).set(user.toMap());
106 | if (!mounted) return;
107 |
108 | Navigator.pushNamedAndRemoveUntil(
109 | context,
110 | Routes.home,
111 | (route) => false,
112 | );
113 | } catch (e) {
114 | Navigator.pop(context);
115 | showAlertDialog(context: context, message: e.toString());
116 | }
117 | }
118 |
119 | void verifySmsCode({
120 | required BuildContext context,
121 | required String smsCodeId,
122 | required String smsCode,
123 | required bool mounted,
124 | }) async {
125 | try {
126 | showLoadingDialog(
127 | context: context,
128 | message: 'Verifiying code ... ',
129 | );
130 | final credential = PhoneAuthProvider.credential(
131 | verificationId: smsCodeId,
132 | smsCode: smsCode,
133 | );
134 | await auth.signInWithCredential(credential);
135 | UserModel? user = await getCurrentUserInfo();
136 | if (!mounted) return;
137 | Navigator.pushNamedAndRemoveUntil(
138 | context,
139 | Routes.userInfo,
140 | (route) => false,
141 | arguments: user?.profileImageUrl,
142 | );
143 | } on FirebaseAuthException catch (e) {
144 | Navigator.pop(context);
145 | showAlertDialog(context: context, message: e.toString());
146 | }
147 | }
148 |
149 | void sendSmsCode({
150 | required BuildContext context,
151 | required String phoneNumber,
152 | }) async {
153 | try {
154 | showLoadingDialog(
155 | context: context,
156 | message: "Sending a verification code to $phoneNumber",
157 | );
158 | await auth.verifyPhoneNumber(
159 | phoneNumber: phoneNumber,
160 | verificationCompleted: (PhoneAuthCredential credential) async {
161 | await auth.signInWithCredential(credential);
162 | },
163 | verificationFailed: (e) {
164 | showAlertDialog(context: context, message: e.toString());
165 | },
166 | codeSent: (smsCodeId, resendSmsCodeId) {
167 | Navigator.pushNamedAndRemoveUntil(
168 | context,
169 | Routes.verification,
170 | (route) => false,
171 | arguments: {
172 | 'phoneNumber': phoneNumber,
173 | 'smsCodeId': smsCodeId,
174 | },
175 | );
176 | },
177 | codeAutoRetrievalTimeout: (String smsCodeId) {},
178 | );
179 | } on FirebaseAuthException catch (e) {
180 | Navigator.pop(context);
181 | showAlertDialog(context: context, message: e.toString());
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/lib/common/extension/custom_theme_extension.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
3 |
4 | extension ExtendedTheme on BuildContext {
5 | CustomThemeExtension get theme {
6 | return Theme.of(this).extension()!;
7 | }
8 | }
9 |
10 | class CustomThemeExtension extends ThemeExtension {
11 | final Color? circleImageColor;
12 | final Color? greyColor;
13 | final Color? blueColor;
14 | final Color? langBgColor;
15 | final Color? langHightlightColor;
16 | final Color? authAppbarTextColor;
17 | final Color? photoIconBgColor;
18 | final Color? photoIconColor;
19 | final Color? profilePageBg;
20 | final Color? chatTextFieldBg;
21 | final Color? chatPageBgColor;
22 | final Color? chatPageDoodleColor;
23 | final Color? senderChatCardBg;
24 | final Color? receiverChatCardBg;
25 | final Color? yellowCardBgColor;
26 | final Color? yellowCardTextColor;
27 |
28 | const CustomThemeExtension({
29 | this.circleImageColor,
30 | this.greyColor,
31 | this.blueColor,
32 | this.langBgColor,
33 | this.langHightlightColor,
34 | this.authAppbarTextColor,
35 | this.photoIconBgColor,
36 | this.photoIconColor,
37 | this.profilePageBg,
38 | this.chatTextFieldBg,
39 | this.chatPageBgColor,
40 | this.chatPageDoodleColor,
41 | this.senderChatCardBg,
42 | this.receiverChatCardBg,
43 | this.yellowCardBgColor,
44 | this.yellowCardTextColor,
45 | });
46 |
47 | static const lightMode = CustomThemeExtension(
48 | circleImageColor: Color(0xFF25D366),
49 | greyColor: Coloors.greyLight,
50 | blueColor: Coloors.blueLight,
51 | langBgColor: Color(0xFFF7F8FA),
52 | langHightlightColor: Color(0xFFE8E8ED),
53 | authAppbarTextColor: Coloors.greenLight,
54 | photoIconBgColor: Color(0xFFF1F1F1),
55 | photoIconColor: Color(0xFF9DAAB3),
56 | profilePageBg: Color(0xFFF7F8FA),
57 | chatTextFieldBg: Colors.white,
58 | chatPageBgColor: Color(0xFFEFE7DE),
59 | chatPageDoodleColor: Colors.white70,
60 | senderChatCardBg: Color(0xFFE7FFDB),
61 | receiverChatCardBg: Color(0xFFFFFFFF),
62 | yellowCardBgColor: Color(0xFFFFEECC),
63 | yellowCardTextColor: Color(0xFF13191C),
64 | );
65 |
66 | static const darkMode = CustomThemeExtension(
67 | circleImageColor: Coloors.greenDark,
68 | greyColor: Coloors.greyDark,
69 | blueColor: Coloors.blueDark,
70 | langBgColor: Color(0xFF182229),
71 | langHightlightColor: Color(0xFF09141A),
72 | authAppbarTextColor: Color(0xFFE9EDEF),
73 | photoIconBgColor: Color(0xFF283339),
74 | photoIconColor: Color(0xFF61717B),
75 | profilePageBg: Color(0xFF0B141A),
76 | chatTextFieldBg: Coloors.greyBackground,
77 | chatPageBgColor: Color(0xFF081419),
78 | chatPageDoodleColor: Color(0xFF172428),
79 | senderChatCardBg: Color(0xFF005C4B),
80 | receiverChatCardBg: Coloors.greyBackground,
81 | yellowCardBgColor: Color(0xFF222E35),
82 | yellowCardTextColor: Color(0xFFFFD279),
83 | );
84 |
85 | @override
86 | ThemeExtension copyWith({
87 | Color? circleImageColor,
88 | Color? greyColor,
89 | Color? blueColor,
90 | Color? langBgColor,
91 | Color? langHightlightColor,
92 | Color? authAppbarTextColor,
93 | Color? photoIconBgColor,
94 | Color? photoIconColor,
95 | Color? profilePageBg,
96 | Color? chatTextFieldBg,
97 | Color? chatPageBgColor,
98 | Color? chatPageDoodleColor,
99 | Color? senderChatCardBg,
100 | Color? receiverChatCardBg,
101 | Color? yellowCardBgColor,
102 | Color? yellowCardTextColor,
103 | }) {
104 | return CustomThemeExtension(
105 | circleImageColor: circleImageColor ?? this.circleImageColor,
106 | greyColor: greyColor ?? this.greyColor,
107 | blueColor: blueColor ?? this.blueColor,
108 | langBgColor: langBgColor ?? this.langBgColor,
109 | langHightlightColor: langHightlightColor ?? this.langHightlightColor,
110 | authAppbarTextColor: authAppbarTextColor ?? this.authAppbarTextColor,
111 | photoIconBgColor: photoIconBgColor ?? this.photoIconBgColor,
112 | photoIconColor: photoIconColor ?? this.photoIconColor,
113 | profilePageBg: profilePageBg ?? this.profilePageBg,
114 | chatTextFieldBg: chatTextFieldBg ?? this.chatTextFieldBg,
115 | chatPageBgColor: chatPageBgColor ?? this.chatPageBgColor,
116 | chatPageDoodleColor: chatPageDoodleColor ?? this.chatPageDoodleColor,
117 | senderChatCardBg: senderChatCardBg ?? this.senderChatCardBg,
118 | receiverChatCardBg: receiverChatCardBg ?? this.receiverChatCardBg,
119 | yellowCardBgColor: yellowCardBgColor ?? this.yellowCardBgColor,
120 | yellowCardTextColor: yellowCardTextColor ?? this.yellowCardTextColor,
121 | );
122 | }
123 |
124 | @override
125 | ThemeExtension lerp(
126 | ThemeExtension? other, double t) {
127 | if (other is! CustomThemeExtension) return this;
128 | return CustomThemeExtension(
129 | circleImageColor: Color.lerp(circleImageColor, other.circleImageColor, t),
130 | greyColor: Color.lerp(greyColor, other.greyColor, t),
131 | blueColor: Color.lerp(blueColor, other.blueColor, t),
132 | langBgColor: Color.lerp(langBgColor, other.langBgColor, t),
133 | langHightlightColor:
134 | Color.lerp(langHightlightColor, other.langHightlightColor, t),
135 | authAppbarTextColor:
136 | Color.lerp(authAppbarTextColor, other.authAppbarTextColor, t),
137 | photoIconBgColor: Color.lerp(photoIconBgColor, other.photoIconBgColor, t),
138 | photoIconColor: Color.lerp(photoIconColor, other.photoIconColor, t),
139 | profilePageBg: Color.lerp(profilePageBg, other.profilePageBg, t),
140 | chatTextFieldBg: Color.lerp(chatTextFieldBg, other.chatTextFieldBg, t),
141 | chatPageBgColor: Color.lerp(chatPageBgColor, other.chatPageBgColor, t),
142 | senderChatCardBg: Color.lerp(senderChatCardBg, other.senderChatCardBg, t),
143 | yellowCardBgColor:
144 | Color.lerp(yellowCardBgColor, other.yellowCardBgColor, t),
145 | yellowCardTextColor:
146 | Color.lerp(yellowCardTextColor, other.yellowCardTextColor, t),
147 | receiverChatCardBg:
148 | Color.lerp(receiverChatCardBg, other.receiverChatCardBg, t),
149 | chatPageDoodleColor:
150 | Color.lerp(chatPageDoodleColor, other.chatPageDoodleColor, t),
151 | );
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/lib/feature/contact/pages/contact_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_riverpod/flutter_riverpod.dart';
3 | import 'package:url_launcher/url_launcher.dart';
4 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
5 | import 'package:whatsapp_messenger/common/models/user_model.dart';
6 | import 'package:whatsapp_messenger/common/routes/routes.dart';
7 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
8 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
9 | import 'package:whatsapp_messenger/feature/contact/controller/contacts_controller.dart';
10 |
11 | import '../widget/contact_card.dart';
12 |
13 | class ContactPage extends ConsumerWidget {
14 | const ContactPage({super.key});
15 |
16 | shareSmsLink(phoneNumber) async {
17 | Uri sms = Uri.parse(
18 | "sms:$phoneNumber?body=Let's chat on WhatsApp! it's a fast, simple, and secure app we can call each other for free. Get it at https://whatsapp.com/dl/",
19 | );
20 | if (await launchUrl(sms)) {
21 | } else {}
22 | }
23 |
24 | @override
25 | Widget build(BuildContext context, WidgetRef ref) {
26 | return Scaffold(
27 | appBar: AppBar(
28 | leading: const BackButton(),
29 | title: Column(
30 | crossAxisAlignment: CrossAxisAlignment.start,
31 | children: [
32 | const Text(
33 | 'Select contact',
34 | style: TextStyle(
35 | color: Colors.white,
36 | ),
37 | ),
38 | const SizedBox(height: 3),
39 | ref.watch(contactsControllerProvider).when(
40 | data: (allContacts) {
41 | return Text(
42 | "${allContacts[0].length} contact${allContacts[0].length == 1 ? '' : 's'}",
43 | style: const TextStyle(fontSize: 12),
44 | );
45 | },
46 | error: (e, t) {
47 | return const SizedBox();
48 | },
49 | loading: () {
50 | return const Text(
51 | 'counting...',
52 | style: TextStyle(fontSize: 12),
53 | );
54 | },
55 | ),
56 | ],
57 | ),
58 | actions: [
59 | CustomIconButton(onPressed: () {}, icon: Icons.search),
60 | CustomIconButton(onPressed: () {}, icon: Icons.more_vert),
61 | ],
62 | ),
63 | body: ref.watch(contactsControllerProvider).when(
64 | data: (allContacts) {
65 | return ListView.builder(
66 | itemCount: allContacts[0].length + allContacts[1].length,
67 | itemBuilder: (context, index) {
68 | late UserModel firebaseContacts;
69 | late UserModel phoneContacts;
70 |
71 | if (index < allContacts[0].length) {
72 | firebaseContacts = allContacts[0][index];
73 | } else {
74 | phoneContacts = allContacts[1][index - allContacts[0].length];
75 | }
76 | return index < allContacts[0].length
77 | ? Column(
78 | crossAxisAlignment: CrossAxisAlignment.start,
79 | children: [
80 | if (index == 0)
81 | Column(
82 | crossAxisAlignment: CrossAxisAlignment.start,
83 | children: [
84 | myListTile(
85 | leading: Icons.group,
86 | text: 'New group',
87 | ),
88 | myListTile(
89 | leading: Icons.contacts,
90 | text: 'New contact',
91 | trailing: Icons.qr_code,
92 | ),
93 | Padding(
94 | padding: const EdgeInsets.symmetric(
95 | horizontal: 20,
96 | vertical: 10,
97 | ),
98 | child: Text(
99 | 'Contacts on WhatsApp',
100 | style: TextStyle(
101 | fontWeight: FontWeight.w600,
102 | color: context.theme.greyColor,
103 | ),
104 | ),
105 | ),
106 | ],
107 | ),
108 | ContactCard(
109 | onTap: () {
110 | Navigator.pushNamed(
111 | context,
112 | Routes.chat,
113 | arguments: firebaseContacts,
114 | );
115 | },
116 | contactSource: firebaseContacts,
117 | ),
118 | ],
119 | )
120 | : Column(
121 | crossAxisAlignment: CrossAxisAlignment.start,
122 | children: [
123 | if (index == allContacts[0].length)
124 | Padding(
125 | padding: const EdgeInsets.symmetric(
126 | horizontal: 20,
127 | vertical: 10,
128 | ),
129 | child: Text(
130 | 'Invite to WhatsApp',
131 | style: TextStyle(
132 | fontWeight: FontWeight.w600,
133 | color: context.theme.greyColor,
134 | ),
135 | ),
136 | ),
137 | ContactCard(
138 | contactSource: phoneContacts,
139 | onTap: () => shareSmsLink(phoneContacts.phoneNumber),
140 | )
141 | ],
142 | );
143 | },
144 | );
145 | },
146 | error: (e, t) {
147 | return null;
148 | },
149 | loading: () {
150 | return Center(
151 | child: CircularProgressIndicator(
152 | color: context.theme.authAppbarTextColor,
153 | ),
154 | );
155 | },
156 | ),
157 | );
158 | }
159 |
160 | ListTile myListTile({
161 | required IconData leading,
162 | required String text,
163 | IconData? trailing,
164 | }) {
165 | return ListTile(
166 | contentPadding: const EdgeInsets.only(top: 10, left: 20, right: 10),
167 | leading: CircleAvatar(
168 | radius: 20,
169 | backgroundColor: Coloors.greenDark,
170 | child: Icon(
171 | leading,
172 | color: Colors.white,
173 | ),
174 | ),
175 | title: Text(
176 | text,
177 | style: const TextStyle(
178 | fontSize: 16,
179 | fontWeight: FontWeight.w500,
180 | ),
181 | ),
182 | trailing: Icon(
183 | trailing,
184 | color: Coloors.greyDark,
185 | ),
186 | );
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/lib/feature/auth/pages/login_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:country_picker/country_picker.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
5 | import 'package:whatsapp_messenger/common/helper/show_alert_dialog.dart';
6 | import 'package:whatsapp_messenger/common/widgets/custom_elevated_button.dart';
7 | import 'package:whatsapp_messenger/feature/auth/controller/auth_controller.dart';
8 | import 'package:whatsapp_messenger/feature/auth/widgets/custom_text_field.dart';
9 |
10 | import '../../../common/utils/coloors.dart';
11 | import '../../../common/widgets/custom_icon_button.dart';
12 |
13 | class LoginPage extends ConsumerStatefulWidget {
14 | const LoginPage({super.key});
15 |
16 | @override
17 | ConsumerState createState() => _LoginPageState();
18 | }
19 |
20 | class _LoginPageState extends ConsumerState {
21 | late TextEditingController countryNameController;
22 | late TextEditingController countryCodeController;
23 | late TextEditingController phoneNumberController;
24 |
25 | sendCodeToPhone() {
26 | final phoneNumber = phoneNumberController.text;
27 | final countryName = countryNameController.text;
28 | final countryCode = countryCodeController.text;
29 |
30 | if (phoneNumber.isEmpty) {
31 | return showAlertDialog(
32 | context: context,
33 | message: "Please enter your phone number",
34 | );
35 | } else if (phoneNumber.length < 9) {
36 | return showAlertDialog(
37 | context: context,
38 | message:
39 | 'The phone number you entered is too short for the country: $countryName\n\nInclude your area code if you haven\'t',
40 | );
41 | } else if (phoneNumber.length > 10) {
42 | return showAlertDialog(
43 | context: context,
44 | message:
45 | "The phone number you entered is too long for the country: $countryName",
46 | );
47 | }
48 |
49 | ref.read(authControllerProvider).sendSmsCode(
50 | context: context,
51 | phoneNumber: "+$countryCode$phoneNumber",
52 | );
53 | }
54 |
55 | showCountryPickerBottomSheet() {
56 | showCountryPicker(
57 | context: context,
58 | showPhoneCode: true,
59 | favorite: ['ET'],
60 | countryListTheme: CountryListThemeData(
61 | bottomSheetHeight: 600,
62 | backgroundColor: Theme.of(context).backgroundColor,
63 | flagSize: 22,
64 | borderRadius: BorderRadius.circular(20),
65 | textStyle: TextStyle(color: context.theme.greyColor),
66 | inputDecoration: InputDecoration(
67 | labelStyle: TextStyle(color: context.theme.greyColor),
68 | prefixIcon: const Icon(
69 | Icons.language,
70 | color: Coloors.greenDark,
71 | ),
72 | hintText: 'Search country by code or name',
73 | enabledBorder: UnderlineInputBorder(
74 | borderSide: BorderSide(
75 | color: context.theme.greyColor!.withOpacity(.2),
76 | ),
77 | ),
78 | focusedBorder: const UnderlineInputBorder(
79 | borderSide: BorderSide(
80 | color: Coloors.greenDark,
81 | ),
82 | ),
83 | ),
84 | ),
85 | onSelect: (country) {
86 | countryNameController.text = country.name;
87 | countryCodeController.text = country.phoneCode;
88 | },
89 | );
90 | }
91 |
92 | @override
93 | void initState() {
94 | countryNameController = TextEditingController(text: 'Ethiopia');
95 | countryCodeController = TextEditingController(text: '251');
96 | phoneNumberController = TextEditingController();
97 | super.initState();
98 | }
99 |
100 | @override
101 | void dispose() {
102 | countryNameController.dispose();
103 | countryCodeController.dispose();
104 | phoneNumberController.dispose();
105 | super.dispose();
106 | }
107 |
108 | @override
109 | Widget build(BuildContext context) {
110 | return Scaffold(
111 | appBar: AppBar(
112 | backgroundColor: Theme.of(context).scaffoldBackgroundColor,
113 | elevation: 0,
114 | title: Text(
115 | 'Enter your phone number',
116 | style: TextStyle(
117 | color: context.theme.authAppbarTextColor,
118 | ),
119 | ),
120 | centerTitle: true,
121 | actions: [
122 | CustomIconButton(
123 | onPressed: () {},
124 | icon: Icons.more_vert,
125 | ),
126 | ],
127 | ),
128 | body: Column(
129 | children: [
130 | Padding(
131 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
132 | child: RichText(
133 | textAlign: TextAlign.center,
134 | text: TextSpan(
135 | text: 'WhatsApp will need to verify your number. ',
136 | style: TextStyle(
137 | color: context.theme.greyColor,
138 | height: 1.5,
139 | ),
140 | children: [
141 | TextSpan(
142 | text: "What's my number?",
143 | style: TextStyle(
144 | color: context.theme.blueColor,
145 | ),
146 | ),
147 | ],
148 | ),
149 | ),
150 | ),
151 | const SizedBox(height: 10),
152 | Padding(
153 | padding: const EdgeInsets.symmetric(horizontal: 50),
154 | child: CustomTextField(
155 | onTap: showCountryPickerBottomSheet,
156 | controller: countryNameController,
157 | readOnly: true,
158 | suffixIcon: const Icon(
159 | Icons.arrow_drop_down,
160 | color: Coloors.greenDark,
161 | size: 22,
162 | ),
163 | ),
164 | ),
165 | const SizedBox(height: 10),
166 | Padding(
167 | padding: const EdgeInsets.symmetric(horizontal: 50),
168 | child: Row(
169 | children: [
170 | SizedBox(
171 | width: 70,
172 | child: CustomTextField(
173 | onTap: showCountryPickerBottomSheet,
174 | controller: countryCodeController,
175 | prefixText: '+',
176 | readOnly: true,
177 | ),
178 | ),
179 | const SizedBox(width: 10),
180 | Expanded(
181 | child: CustomTextField(
182 | controller: phoneNumberController,
183 | hintText: 'phone number',
184 | textAlign: TextAlign.left,
185 | keyboardType: TextInputType.number,
186 | ),
187 | ),
188 | ],
189 | ),
190 | ),
191 | const SizedBox(height: 20),
192 | Text(
193 | 'Carrier charges may apply',
194 | style: TextStyle(
195 | color: context.theme.greyColor,
196 | ),
197 | ),
198 | ],
199 | ),
200 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
201 | floatingActionButton: CustomElevatedButton(
202 | onPressed: sendCodeToPhone,
203 | text: 'NEXT',
204 | buttonWidth: 90,
205 | ),
206 | );
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/lib/feature/chat/repository/chat_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:firebase_auth/firebase_auth.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_riverpod/flutter_riverpod.dart';
5 | import 'package:uuid/uuid.dart';
6 | import 'package:whatsapp_messenger/common/enum/message_type.dart';
7 | import 'package:whatsapp_messenger/common/helper/show_alert_dialog.dart';
8 | import 'package:whatsapp_messenger/common/models/last_message_model.dart';
9 | import 'package:whatsapp_messenger/common/models/message_model.dart';
10 | import 'package:whatsapp_messenger/common/models/user_model.dart';
11 | import 'package:whatsapp_messenger/common/repository/firebase_storage_repository.dart';
12 |
13 | final chatRepositoryProvider = Provider((ref) {
14 | return ChatRepository(
15 | firestore: FirebaseFirestore.instance,
16 | auth: FirebaseAuth.instance,
17 | );
18 | });
19 |
20 | class ChatRepository {
21 | final FirebaseFirestore firestore;
22 | final FirebaseAuth auth;
23 |
24 | ChatRepository({required this.firestore, required this.auth});
25 |
26 | void sendFileMessage({
27 | required var file,
28 | required BuildContext context,
29 | required String receiverId,
30 | required UserModel senderData,
31 | required Ref ref,
32 | required MessageType messageType,
33 | }) async {
34 | try {
35 | final timeSent = DateTime.now();
36 | final messageId = const Uuid().v1();
37 |
38 | final imageUrl = await ref.read(firebaseStorageRepositoryProvider).storeFileToFirebase(
39 | 'chats/${messageType.type}/${senderData.uid}/$receiverId/$messageId',
40 | file,
41 | );
42 | final userMap = await firestore.collection('users').doc(receiverId).get();
43 | final receverUserData = UserModel.fromMap(userMap.data()!);
44 |
45 | String lastMessage;
46 |
47 | switch (messageType) {
48 | case MessageType.image:
49 | lastMessage = '📸 Photo message';
50 | break;
51 | case MessageType.audio:
52 | lastMessage = '📸 Voice message';
53 | break;
54 | case MessageType.video:
55 | lastMessage = '📸 Video message';
56 | break;
57 | case MessageType.gif:
58 | lastMessage = '📸 GIF message';
59 | break;
60 | default:
61 | lastMessage = '📦 GIF message';
62 | break;
63 | }
64 |
65 | saveToMessageCollection(
66 | receiverId: receiverId,
67 | textMessage: imageUrl,
68 | timeSent: timeSent,
69 | textMessageId: messageId,
70 | senderUsername: senderData.username,
71 | receiverUsername: receverUserData.username,
72 | messageType: messageType,
73 | );
74 |
75 | saveAsLastMessage(
76 | senderUserData: senderData,
77 | receiverUserData: receverUserData,
78 | lastMessage: lastMessage,
79 | timeSent: timeSent,
80 | receiverId: receiverId,
81 | );
82 | } catch (e) {
83 | showAlertDialog(context: context, message: e.toString());
84 | }
85 | }
86 |
87 | Stream> getAllOneToOneMessage(String receiverId) {
88 | return firestore
89 | .collection('users')
90 | .doc(auth.currentUser!.uid)
91 | .collection('chats')
92 | .doc(receiverId)
93 | .collection('messages')
94 | .orderBy('timeSent')
95 | .snapshots()
96 | .map((event) {
97 | List messages = [];
98 | for (var message in event.docs) {
99 | messages.add(MessageModel.fromMap(message.data()));
100 | }
101 | return messages;
102 | });
103 | }
104 |
105 | Stream> getAllLastMessageList() {
106 | return firestore
107 | .collection('users')
108 | .doc(auth.currentUser!.uid)
109 | .collection('chats')
110 | .snapshots()
111 | .asyncMap((event) async {
112 | List contacts = [];
113 | for (var document in event.docs) {
114 | final lastMessage = LastMessageModel.fromMap(document.data());
115 | final userData = await firestore.collection('users').doc(lastMessage.contactId).get();
116 | final user = UserModel.fromMap(userData.data()!);
117 | contacts.add(
118 | LastMessageModel(
119 | username: user.username,
120 | profileImageUrl: user.profileImageUrl,
121 | contactId: lastMessage.contactId,
122 | timeSent: lastMessage.timeSent,
123 | lastMessage: lastMessage.lastMessage,
124 | ),
125 | );
126 | }
127 | return contacts;
128 | });
129 | }
130 |
131 | void sendTextMessage({
132 | required BuildContext context,
133 | required String textMessage,
134 | required String receiverId,
135 | required UserModel senderData,
136 | }) async {
137 | try {
138 | final timeSent = DateTime.now();
139 | final receiverDataMap = await firestore.collection('users').doc(receiverId).get();
140 | final receiverData = UserModel.fromMap(receiverDataMap.data()!);
141 | final textMessageId = const Uuid().v1();
142 |
143 | saveToMessageCollection(
144 | receiverId: receiverId,
145 | textMessage: textMessage,
146 | timeSent: timeSent,
147 | textMessageId: textMessageId,
148 | senderUsername: senderData.username,
149 | receiverUsername: receiverData.username,
150 | messageType: MessageType.text,
151 | );
152 |
153 | saveAsLastMessage(
154 | senderUserData: senderData,
155 | receiverUserData: receiverData,
156 | lastMessage: textMessage,
157 | timeSent: timeSent,
158 | receiverId: receiverId,
159 | );
160 | } catch (e) {
161 | showAlertDialog(context: context, message: e.toString());
162 | }
163 | }
164 |
165 | void saveToMessageCollection({
166 | required String receiverId,
167 | required String textMessage,
168 | required DateTime timeSent,
169 | required String textMessageId,
170 | required String senderUsername,
171 | required String receiverUsername,
172 | required MessageType messageType,
173 | }) async {
174 | final message = MessageModel(
175 | senderId: auth.currentUser!.uid,
176 | receiverId: receiverId,
177 | textMessage: textMessage,
178 | type: messageType,
179 | timeSent: timeSent,
180 | messageId: textMessageId,
181 | isSeen: false,
182 | );
183 |
184 | // sender
185 | await firestore
186 | .collection('users')
187 | .doc(auth.currentUser!.uid)
188 | .collection('chats')
189 | .doc(receiverId)
190 | .collection('messages')
191 | .doc(textMessageId)
192 | .set(message.toMap());
193 |
194 | // receiver
195 | await firestore
196 | .collection('users')
197 | .doc(receiverId)
198 | .collection('chats')
199 | .doc(auth.currentUser!.uid)
200 | .collection('messages')
201 | .doc(textMessageId)
202 | .set(message.toMap());
203 | }
204 |
205 | void saveAsLastMessage({
206 | required UserModel senderUserData,
207 | required UserModel receiverUserData,
208 | required String lastMessage,
209 | required DateTime timeSent,
210 | required String receiverId,
211 | }) async {
212 | final receiverLastMessage = LastMessageModel(
213 | username: senderUserData.username,
214 | profileImageUrl: senderUserData.profileImageUrl,
215 | contactId: senderUserData.uid,
216 | timeSent: timeSent,
217 | lastMessage: lastMessage,
218 | );
219 |
220 | await firestore
221 | .collection('users')
222 | .doc(receiverId)
223 | .collection('chats')
224 | .doc(auth.currentUser!.uid)
225 | .set(receiverLastMessage.toMap());
226 |
227 | final senderLastMessage = LastMessageModel(
228 | username: receiverUserData.username,
229 | profileImageUrl: receiverUserData.profileImageUrl,
230 | contactId: receiverUserData.uid,
231 | timeSent: timeSent,
232 | lastMessage: lastMessage,
233 | );
234 |
235 | await firestore
236 | .collection('users')
237 | .doc(auth.currentUser!.uid)
238 | .collection('chats')
239 | .doc(receiverId)
240 | .set(senderLastMessage.toMap());
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/lib/feature/auth/pages/user_info_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 | import 'dart:io';
3 | import 'dart:typed_data';
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 | import 'package:image_picker/image_picker.dart';
8 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
9 | import 'package:whatsapp_messenger/common/helper/show_alert_dialog.dart';
10 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
11 | import 'package:whatsapp_messenger/common/widgets/custom_elevated_button.dart';
12 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
13 | import 'package:whatsapp_messenger/common/widgets/short_h_bar.dart';
14 | import 'package:whatsapp_messenger/feature/auth/controller/auth_controller.dart';
15 | import 'package:whatsapp_messenger/feature/auth/pages/image_picker_page.dart';
16 | import 'package:whatsapp_messenger/feature/auth/widgets/custom_text_field.dart';
17 |
18 | class UserInfoPage extends ConsumerStatefulWidget {
19 | const UserInfoPage({super.key, this.profileImageUrl});
20 |
21 | final String? profileImageUrl;
22 |
23 | @override
24 | ConsumerState createState() => _UserInfoPageState();
25 | }
26 |
27 | class _UserInfoPageState extends ConsumerState {
28 | File? imageCamera;
29 | Uint8List? imageGallery;
30 |
31 | late TextEditingController usernameController;
32 |
33 | saveUserDataToFirebase() {
34 | String username = usernameController.text;
35 | if (username.isEmpty) {
36 | return showAlertDialog(
37 | context: context,
38 | message: 'Please provide a username',
39 | );
40 | } else if (username.length < 3 || username.length > 20) {
41 | return showAlertDialog(
42 | context: context,
43 | message: 'A username length should be between 3-20',
44 | );
45 | }
46 |
47 | ref.read(authControllerProvider).saveUserInfoToFirestore(
48 | username: username,
49 | profileImage:
50 | imageCamera ?? imageGallery ?? widget.profileImageUrl ?? '',
51 | context: context,
52 | mounted: mounted,
53 | );
54 | }
55 |
56 | imagePickerTypeBottomSheet() {
57 | return showModalBottomSheet(
58 | context: context,
59 | builder: (context) {
60 | return Column(
61 | mainAxisSize: MainAxisSize.min,
62 | children: [
63 | const ShortHBar(),
64 | Row(
65 | children: [
66 | const SizedBox(width: 20),
67 | const Text(
68 | 'Profile photo',
69 | style: TextStyle(
70 | fontSize: 20,
71 | fontWeight: FontWeight.w500,
72 | ),
73 | ),
74 | const Spacer(),
75 | CustomIconButton(
76 | onPressed: () => Navigator.pop(context),
77 | icon: Icons.close,
78 | ),
79 | const SizedBox(width: 15),
80 | ],
81 | ),
82 | Divider(
83 | color: context.theme.greyColor!.withOpacity(.3),
84 | ),
85 | const SizedBox(height: 5),
86 | Row(
87 | children: [
88 | const SizedBox(width: 20),
89 | imagePickerIcon(
90 | onTap: pickmageFromCamera,
91 | icon: Icons.camera_alt_rounded,
92 | text: 'Camera',
93 | ),
94 | const SizedBox(width: 15),
95 | imagePickerIcon(
96 | onTap: () async {
97 | log('hello');
98 | Navigator.pop(context);
99 | final image = await Navigator.push(
100 | context,
101 | MaterialPageRoute(
102 | builder: (context) => const ImagePickerPage(),
103 | ),
104 | );
105 | if (image == null) return;
106 | setState(() {
107 | imageGallery = image;
108 | imageCamera = null;
109 | });
110 | },
111 | icon: Icons.photo_camera_back_rounded,
112 | text: 'Gallery',
113 | ),
114 | ],
115 | ),
116 | const SizedBox(height: 15),
117 | ],
118 | );
119 | },
120 | );
121 | }
122 |
123 | pickmageFromCamera() async {
124 | try {
125 | Navigator.pop(context);
126 | final image = await ImagePicker().pickImage(source: ImageSource.camera);
127 | setState(() {
128 | imageCamera = File(image!.path);
129 | imageGallery = null;
130 | });
131 | } catch (e) {
132 | showAlertDialog(context: context, message: e.toString());
133 | }
134 | }
135 |
136 | imagePickerIcon({
137 | required VoidCallback onTap,
138 | required IconData icon,
139 | required String text,
140 | }) {
141 | return Column(
142 | children: [
143 | CustomIconButton(
144 | onPressed: onTap,
145 | icon: icon,
146 | iconColor: Coloors.greenDark,
147 | minWidth: 50,
148 | border: Border.all(
149 | color: context.theme.greyColor!.withOpacity(.2),
150 | width: 1,
151 | ),
152 | ),
153 | const SizedBox(height: 5),
154 | Text(
155 | text,
156 | style: TextStyle(
157 | color: context.theme.greyColor,
158 | ),
159 | ),
160 | ],
161 | );
162 | }
163 |
164 | @override
165 | void initState() {
166 | usernameController = TextEditingController();
167 | super.initState();
168 | }
169 |
170 | @override
171 | void dispose() {
172 | usernameController.dispose();
173 | super.dispose();
174 | }
175 |
176 | @override
177 | Widget build(BuildContext context) {
178 | return Scaffold(
179 | appBar: AppBar(
180 | backgroundColor: Theme.of(context).backgroundColor,
181 | elevation: 0,
182 | title: Text(
183 | 'Profile info',
184 | style: TextStyle(
185 | color: context.theme.authAppbarTextColor,
186 | ),
187 | ),
188 | centerTitle: true,
189 | ),
190 | body: SingleChildScrollView(
191 | padding: const EdgeInsets.symmetric(horizontal: 20),
192 | child: Column(
193 | children: [
194 | Text(
195 | 'Please provide your name and an optional profile photo',
196 | textAlign: TextAlign.center,
197 | style: TextStyle(
198 | color: context.theme.greyColor,
199 | ),
200 | ),
201 | const SizedBox(height: 40),
202 | GestureDetector(
203 | onTap: imagePickerTypeBottomSheet,
204 | child: Container(
205 | padding: const EdgeInsets.all(26),
206 | decoration: BoxDecoration(
207 | shape: BoxShape.circle,
208 | color: context.theme.photoIconBgColor,
209 | border: Border.all(
210 | color: imageCamera == null && imageGallery == null
211 | ? Colors.transparent
212 | : context.theme.greyColor!.withOpacity(.4),
213 | ),
214 | image: imageCamera != null ||
215 | imageGallery != null ||
216 | widget.profileImageUrl != null
217 | ? DecorationImage(
218 | fit: BoxFit.cover,
219 | image: imageGallery != null
220 | ? MemoryImage(imageGallery!)
221 | : widget.profileImageUrl != null
222 | ? NetworkImage(widget.profileImageUrl!)
223 | : FileImage(imageCamera!) as ImageProvider,
224 | )
225 | : null,
226 | ),
227 | child: Padding(
228 | padding: const EdgeInsets.only(bottom: 3, right: 3),
229 | child: Icon(
230 | Icons.add_a_photo_rounded,
231 | size: 48,
232 | color: imageCamera == null &&
233 | imageGallery == null &&
234 | widget.profileImageUrl == null
235 | ? context.theme.photoIconColor
236 | : Colors.transparent,
237 | ),
238 | ),
239 | ),
240 | ),
241 | const SizedBox(height: 40),
242 | Row(
243 | children: [
244 | const SizedBox(width: 20),
245 | Expanded(
246 | child: CustomTextField(
247 | controller: usernameController,
248 | hintText: 'Type your name here',
249 | textAlign: TextAlign.start,
250 | autoFocus: true,
251 | ),
252 | ),
253 | const SizedBox(width: 10),
254 | Icon(
255 | Icons.emoji_emotions_outlined,
256 | color: context.theme.photoIconColor,
257 | ),
258 | const SizedBox(width: 10),
259 | ],
260 | ),
261 | ],
262 | ),
263 | ),
264 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
265 | floatingActionButton: CustomElevatedButton(
266 | onPressed: saveUserDataToFirebase,
267 | text: 'NEXT',
268 | buttonWidth: 90,
269 | ),
270 | );
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/lib/feature/chat/pages/chat_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:cached_network_image/cached_network_image.dart';
4 | import 'package:custom_clippers/custom_clippers.dart';
5 | import 'package:firebase_auth/firebase_auth.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_riverpod/flutter_riverpod.dart';
8 | import 'package:shimmer/shimmer.dart';
9 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
10 | import 'package:whatsapp_messenger/common/models/user_model.dart';
11 | import 'package:whatsapp_messenger/common/routes/routes.dart';
12 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
13 | import 'package:whatsapp_messenger/feature/auth/controller/auth_controller.dart';
14 | import 'package:whatsapp_messenger/feature/chat/controller/chat_controller.dart';
15 | import 'package:whatsapp_messenger/feature/chat/widgets/chat_text_field.dart';
16 | import 'package:whatsapp_messenger/feature/chat/widgets/message_card.dart';
17 | import 'package:whatsapp_messenger/feature/chat/widgets/show_date_card.dart';
18 | import 'package:whatsapp_messenger/feature/chat/widgets/yellow_card.dart';
19 |
20 | import '../../../common/helper/last_seen_message.dart';
21 |
22 | final pageStorageBucket = PageStorageBucket();
23 |
24 | class ChatPage extends ConsumerWidget {
25 | ChatPage({super.key, required this.user});
26 |
27 | final UserModel user;
28 | final ScrollController scrollController = ScrollController();
29 |
30 | @override
31 | Widget build(BuildContext context, WidgetRef ref) {
32 | return Scaffold(
33 | backgroundColor: context.theme.chatPageBgColor,
34 | appBar: AppBar(
35 | leading: InkWell(
36 | onTap: () {
37 | Navigator.pop(context);
38 | },
39 | borderRadius: BorderRadius.circular(20),
40 | child: Row(
41 | children: [
42 | const Icon(Icons.arrow_back),
43 | Hero(
44 | tag: 'profile',
45 | child: Container(
46 | width: 32,
47 | decoration: BoxDecoration(
48 | shape: BoxShape.circle,
49 | image: DecorationImage(
50 | image: CachedNetworkImageProvider(user.profileImageUrl),
51 | ),
52 | ),
53 | ),
54 | ),
55 | ],
56 | ),
57 | ),
58 | title: InkWell(
59 | onTap: () {
60 | Navigator.pushNamed(
61 | context,
62 | Routes.profile,
63 | arguments: user,
64 | );
65 | },
66 | child: Padding(
67 | padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 5),
68 | child: Column(
69 | crossAxisAlignment: CrossAxisAlignment.start,
70 | children: [
71 | Text(
72 | user.username,
73 | style: const TextStyle(
74 | fontSize: 18,
75 | color: Colors.white,
76 | ),
77 | ),
78 | const SizedBox(height: 3),
79 | StreamBuilder(
80 | stream: ref.read(authControllerProvider).getUserPresenceStatus(uid: user.uid),
81 | builder: (_, snapshot) {
82 | if (snapshot.connectionState != ConnectionState.active) {
83 | return const SizedBox();
84 | }
85 |
86 | final singleUserModel = snapshot.data!;
87 | final lastMessage = lastSeenMessage(singleUserModel.lastSeen);
88 |
89 | return Text(
90 | singleUserModel.active ? 'online' : "last seen $lastMessage ago",
91 | style: const TextStyle(
92 | fontSize: 12,
93 | color: Colors.white,
94 | ),
95 | );
96 | },
97 | ),
98 | ],
99 | ),
100 | ),
101 | ),
102 | actions: [
103 | CustomIconButton(
104 | onPressed: () {},
105 | icon: Icons.video_call,
106 | iconColor: Colors.white,
107 | ),
108 | CustomIconButton(
109 | onPressed: () {},
110 | icon: Icons.call,
111 | iconColor: Colors.white,
112 | ),
113 | CustomIconButton(
114 | onPressed: () {},
115 | icon: Icons.more_vert,
116 | iconColor: Colors.white,
117 | ),
118 | ],
119 | ),
120 | body: Stack(
121 | children: [
122 | // chat background image
123 | Image(
124 | height: double.maxFinite,
125 | width: double.maxFinite,
126 | image: const AssetImage('assets/images/doodle_bg.png'),
127 | fit: BoxFit.cover,
128 | color: context.theme.chatPageDoodleColor,
129 | ),
130 | // Stream of Chat
131 | Padding(
132 | padding: const EdgeInsets.only(bottom: 60),
133 | child: StreamBuilder(
134 | stream: ref.watch(chatControllerProvider).getAllOneToOneMessage(user.uid),
135 | builder: (context, snapshot) {
136 | if (snapshot.connectionState != ConnectionState.active) {
137 | return ListView.builder(
138 | itemCount: 15,
139 | itemBuilder: (_, index) {
140 | final random = Random().nextInt(14);
141 | return Container(
142 | alignment: random.isEven ? Alignment.centerRight : Alignment.centerLeft,
143 | margin: EdgeInsets.only(
144 | top: 5,
145 | bottom: 5,
146 | left: random.isEven ? 150 : 15,
147 | right: random.isEven ? 15 : 150,
148 | ),
149 | child: ClipPath(
150 | clipper: UpperNipMessageClipperTwo(
151 | random.isEven ? MessageType.send : MessageType.receive,
152 | nipWidth: 8,
153 | nipHeight: 10,
154 | bubbleRadius: 12,
155 | ),
156 | child: Shimmer.fromColors(
157 | baseColor: random.isEven
158 | ? context.theme.greyColor!.withOpacity(.3)
159 | : context.theme.greyColor!.withOpacity(.2),
160 | highlightColor: random.isEven
161 | ? context.theme.greyColor!.withOpacity(.4)
162 | : context.theme.greyColor!.withOpacity(.3),
163 | child: Container(
164 | height: 40,
165 | width: 170 +
166 | double.parse(
167 | (random * 2).toString(),
168 | ),
169 | color: Colors.red,
170 | ),
171 | ),
172 | ),
173 | );
174 | },
175 | );
176 | }
177 |
178 | return PageStorage(
179 | bucket: pageStorageBucket,
180 | child: ListView.builder(
181 | key: const PageStorageKey('chat_page_list'),
182 | itemCount: snapshot.data!.length,
183 | shrinkWrap: true,
184 | controller: scrollController,
185 | itemBuilder: (_, index) {
186 | final message = snapshot.data![index];
187 | final isSender = message.senderId == FirebaseAuth.instance.currentUser!.uid;
188 |
189 | final haveNip = (index == 0) ||
190 | (index == snapshot.data!.length - 1 &&
191 | message.senderId != snapshot.data![index - 1].senderId) ||
192 | (message.senderId != snapshot.data![index - 1].senderId &&
193 | message.senderId == snapshot.data![index + 1].senderId) ||
194 | (message.senderId != snapshot.data![index - 1].senderId &&
195 | message.senderId != snapshot.data![index + 1].senderId);
196 | final isShowDateCard = (index == 0) ||
197 | ((index == snapshot.data!.length - 1) &&
198 | (message.timeSent.day > snapshot.data![index - 1].timeSent.day)) ||
199 | (message.timeSent.day > snapshot.data![index - 1].timeSent.day &&
200 | message.timeSent.day <= snapshot.data![index + 1].timeSent.day);
201 |
202 | return Column(
203 | children: [
204 | if (index == 0) const YellowCard(),
205 | if (isShowDateCard) ShowDateCard(date: message.timeSent),
206 | MessageCard(
207 | isSender: isSender,
208 | haveNip: haveNip,
209 | message: message,
210 | ),
211 | ],
212 | );
213 | },
214 | ),
215 | );
216 | },
217 | ),
218 | ),
219 | Container(
220 | alignment: const Alignment(0, 1),
221 | child: ChatTextField(
222 | receiverId: user.uid,
223 | scrollController: scrollController,
224 | ),
225 | ),
226 | ],
227 | ),
228 | );
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/lib/feature/chat/widgets/chat_text_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/scheduler.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 | import 'package:whatsapp_messenger/common/enum/message_type.dart';
5 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
6 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
7 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
8 | import 'package:whatsapp_messenger/feature/auth/pages/image_picker_page.dart';
9 | import 'package:whatsapp_messenger/feature/chat/controller/chat_controller.dart';
10 |
11 | class ChatTextField extends ConsumerStatefulWidget {
12 | const ChatTextField({
13 | super.key,
14 | required this.receiverId,
15 | required this.scrollController,
16 | });
17 |
18 | final String receiverId;
19 | final ScrollController scrollController;
20 |
21 | @override
22 | ConsumerState createState() => _ChatTextFieldState();
23 | }
24 |
25 | class _ChatTextFieldState extends ConsumerState {
26 | late TextEditingController messageController;
27 |
28 | bool isMessageIconEnabled = false;
29 | double cardHeight = 0;
30 |
31 | void sendImageMessageFromGallery() async {
32 | final image = await Navigator.push(
33 | context,
34 | MaterialPageRoute(
35 | builder: (_) => const ImagePickerPage(),
36 | ));
37 |
38 | if (image != null) {
39 | sendFileMessage(image, MessageType.image);
40 | setState(() => cardHeight = 0);
41 | }
42 | }
43 |
44 | void sendFileMessage(var file, MessageType messageType) async {
45 | ref.read(chatControllerProvider).sendFileMessage(
46 | context,
47 | file,
48 | widget.receiverId,
49 | messageType,
50 | );
51 | await Future.delayed(const Duration(milliseconds: 500));
52 | SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
53 | widget.scrollController.animateTo(
54 | widget.scrollController.position.maxScrollExtent,
55 | duration: const Duration(milliseconds: 300),
56 | curve: Curves.easeOut,
57 | );
58 | });
59 | }
60 |
61 | void sendTextMessage() async {
62 | if (isMessageIconEnabled) {
63 | ref.read(chatControllerProvider).sendTextMessage(
64 | context: context,
65 | textMessage: messageController.text,
66 | receiverId: widget.receiverId,
67 | );
68 | messageController.clear();
69 | }
70 |
71 | await Future.delayed(const Duration(milliseconds: 100));
72 | SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
73 | widget.scrollController.animateTo(
74 | widget.scrollController.position.maxScrollExtent,
75 | duration: const Duration(milliseconds: 300),
76 | curve: Curves.easeOut,
77 | );
78 | });
79 | }
80 |
81 | iconWithText({
82 | required VoidCallback onPressed,
83 | required IconData icon,
84 | required String text,
85 | required Color background,
86 | }) {
87 | return Column(
88 | children: [
89 | CustomIconButton(
90 | onPressed: onPressed,
91 | icon: icon,
92 | background: background,
93 | minWidth: 50,
94 | iconColor: Colors.white,
95 | border: Border.all(
96 | color: context.theme.greyColor!.withOpacity(.2),
97 | width: 1,
98 | ),
99 | ),
100 | const SizedBox(height: 5),
101 | Text(
102 | text,
103 | style: TextStyle(
104 | color: context.theme.greyColor,
105 | ),
106 | ),
107 | ],
108 | );
109 | }
110 |
111 | @override
112 | void initState() {
113 | messageController = TextEditingController();
114 | super.initState();
115 | }
116 |
117 | @override
118 | void dispose() {
119 | messageController.dispose();
120 | super.dispose();
121 | }
122 |
123 | @override
124 | Widget build(BuildContext context) {
125 | return Column(
126 | mainAxisSize: MainAxisSize.min,
127 | children: [
128 | AnimatedContainer(
129 | duration: const Duration(milliseconds: 200),
130 | height: cardHeight,
131 | width: double.maxFinite,
132 | margin: const EdgeInsets.symmetric(horizontal: 10),
133 | decoration: BoxDecoration(
134 | color: context.theme.receiverChatCardBg,
135 | borderRadius: BorderRadius.circular(20),
136 | ),
137 | child: Center(
138 | child: SingleChildScrollView(
139 | child: Column(
140 | children: [
141 | Row(
142 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
143 | children: [
144 | iconWithText(
145 | onPressed: () {},
146 | icon: Icons.book,
147 | text: 'File',
148 | background: const Color(0xFF7F66FE),
149 | ),
150 | iconWithText(
151 | onPressed: () {},
152 | icon: Icons.camera_alt,
153 | text: 'Camera',
154 | background: const Color(0xFFFE2E74),
155 | ),
156 | iconWithText(
157 | onPressed: sendImageMessageFromGallery,
158 | icon: Icons.photo,
159 | text: 'Gallery',
160 | background: const Color(0xFFC861F9),
161 | ),
162 | ],
163 | ),
164 | const SizedBox(height: 20),
165 | Row(
166 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
167 | children: [
168 | iconWithText(
169 | onPressed: () {},
170 | icon: Icons.headphones,
171 | text: 'Audio',
172 | background: const Color(0xFFF96533),
173 | ),
174 | iconWithText(
175 | onPressed: () {},
176 | icon: Icons.location_on,
177 | text: 'Location',
178 | background: const Color(0xFF1FA855),
179 | ),
180 | iconWithText(
181 | onPressed: () {},
182 | icon: Icons.person,
183 | text: 'Contact',
184 | background: const Color(0xFF009DE1),
185 | ),
186 | ],
187 | ),
188 | ],
189 | ),
190 | ),
191 | ),
192 | ),
193 | Padding(
194 | padding: const EdgeInsets.all(5.0),
195 | child: Row(
196 | children: [
197 | Expanded(
198 | child: TextFormField(
199 | controller: messageController,
200 | maxLines: 4,
201 | minLines: 1,
202 | onChanged: (value) {
203 | value.isEmpty
204 | ? setState(() => isMessageIconEnabled = false)
205 | : setState(() => isMessageIconEnabled = true);
206 | },
207 | decoration: InputDecoration(
208 | hintText: 'Message',
209 | hintStyle: TextStyle(color: context.theme.greyColor),
210 | filled: true,
211 | fillColor: context.theme.chatTextFieldBg,
212 | isDense: true,
213 | border: OutlineInputBorder(
214 | borderSide: const BorderSide(
215 | style: BorderStyle.none,
216 | width: 0,
217 | ),
218 | borderRadius: BorderRadius.circular(30),
219 | ),
220 | prefixIcon: Material(
221 | color: Colors.transparent,
222 | child: CustomIconButton(
223 | onPressed: () {},
224 | icon: Icons.emoji_emotions_outlined,
225 | iconColor: Theme.of(context).listTileTheme.iconColor,
226 | ),
227 | ),
228 | suffixIcon: Row(
229 | mainAxisSize: MainAxisSize.min,
230 | children: [
231 | RotatedBox(
232 | quarterTurns: 45,
233 | child: CustomIconButton(
234 | onPressed: () => setState(
235 | () => cardHeight == 0 ? cardHeight = 220 : cardHeight = 0,
236 | ),
237 | icon: cardHeight == 0 ? Icons.attach_file : Icons.close,
238 | iconColor: Theme.of(context).listTileTheme.iconColor,
239 | ),
240 | ),
241 | CustomIconButton(
242 | onPressed: () {},
243 | icon: Icons.camera_alt_outlined,
244 | iconColor: Theme.of(context).listTileTheme.iconColor,
245 | ),
246 | ],
247 | ),
248 | ),
249 | ),
250 | ),
251 | const SizedBox(width: 5),
252 | CustomIconButton(
253 | onPressed: sendTextMessage,
254 | icon: isMessageIconEnabled ? Icons.send_outlined : Icons.mic_none_outlined,
255 | background: Coloors.greenDark,
256 | iconColor: Colors.white,
257 | ),
258 | ],
259 | ),
260 | ),
261 | ],
262 | );
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/lib/feature/chat/pages/profile_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:whatsapp_messenger/common/extension/custom_theme_extension.dart';
4 | import 'package:whatsapp_messenger/common/helper/last_seen_message.dart';
5 | import 'package:whatsapp_messenger/common/models/user_model.dart';
6 | import 'package:whatsapp_messenger/common/utils/coloors.dart';
7 | import 'package:whatsapp_messenger/common/widgets/custom_icon_button.dart';
8 | import 'package:whatsapp_messenger/feature/chat/widgets/custom_list_tile.dart';
9 |
10 | class ProfilePage extends StatelessWidget {
11 | const ProfilePage({super.key, required this.user});
12 |
13 | final UserModel user;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Scaffold(
18 | backgroundColor: context.theme.profilePageBg,
19 | body: CustomScrollView(
20 | slivers: [
21 | SliverPersistentHeader(
22 | delegate: SliverPersistentDelegate(user),
23 | pinned: true,
24 | ),
25 | // lets create a long list to make the content scrollable
26 | SliverToBoxAdapter(
27 | child: Column(
28 | children: [
29 | Container(
30 | color: Theme.of(context).backgroundColor,
31 | child: Column(
32 | children: [
33 | Text(
34 | user.username,
35 | style: const TextStyle(fontSize: 24),
36 | ),
37 | const SizedBox(height: 10),
38 | Text(
39 | user.phoneNumber,
40 | style: TextStyle(
41 | fontSize: 20,
42 | color: context.theme.greyColor,
43 | ),
44 | ),
45 | const SizedBox(height: 10),
46 | Text(
47 | "last seen ${lastSeenMessage(user.lastSeen)} ago",
48 | style: TextStyle(color: context.theme.greyColor),
49 | ),
50 | Row(
51 | mainAxisAlignment: MainAxisAlignment.center,
52 | children: [
53 | iconWithText(icon: Icons.call, text: 'Call'),
54 | iconWithText(icon: Icons.video_call, text: 'Video'),
55 | iconWithText(icon: Icons.search, text: 'Search'),
56 | ],
57 | ),
58 | ],
59 | ),
60 | ),
61 | const SizedBox(height: 20),
62 | ListTile(
63 | contentPadding: const EdgeInsets.only(left: 30),
64 | title: const Text('Hey there! I am using WhatsApp'),
65 | subtitle: Text(
66 | '17th February',
67 | style: TextStyle(
68 | color: context.theme.greyColor,
69 | ),
70 | ),
71 | ),
72 | const SizedBox(height: 20),
73 | CustomListTile(
74 | title: 'Mute notification',
75 | leading: Icons.notifications,
76 | trailing: Switch(
77 | value: false,
78 | onChanged: (value) {},
79 | ),
80 | ),
81 | const CustomListTile(
82 | title: 'Custom notification',
83 | leading: Icons.music_note,
84 | ),
85 | CustomListTile(
86 | title: 'Media visibility',
87 | leading: Icons.photo,
88 | trailing: Switch(
89 | value: false,
90 | onChanged: (value) {},
91 | ),
92 | ),
93 | const SizedBox(height: 20),
94 | const CustomListTile(
95 | title: 'Encryption',
96 | subTitle:
97 | 'Messages and calls are end-to-end encrypted, Tap to verify.',
98 | leading: Icons.lock,
99 | ),
100 | const CustomListTile(
101 | title: 'Disappearing messages',
102 | subTitle: 'Off',
103 | leading: Icons.timer,
104 | ),
105 | const SizedBox(height: 20),
106 | ListTile(
107 | leading: CustomIconButton(
108 | onPressed: () {},
109 | icon: Icons.group,
110 | background: Coloors.greenDark,
111 | iconColor: Colors.white,
112 | ),
113 | title: Text('Create group with ${user.username}'),
114 | ),
115 | const SizedBox(height: 20),
116 | ListTile(
117 | contentPadding: const EdgeInsets.only(left: 25, right: 10),
118 | leading: const Icon(
119 | Icons.block,
120 | color: Color(0xFFF15C6D),
121 | ),
122 | title: Text(
123 | 'Block ${user.username}',
124 | style: const TextStyle(
125 | color: Color(0xFFF15C6D),
126 | ),
127 | ),
128 | ),
129 | ListTile(
130 | contentPadding: const EdgeInsets.only(left: 25, right: 10),
131 | leading: const Icon(
132 | Icons.thumb_down,
133 | color: Color(0xFFF15C6D),
134 | ),
135 | title: Text(
136 | 'Report ${user.username}',
137 | style: const TextStyle(
138 | color: Color(0xFFF15C6D),
139 | ),
140 | ),
141 | ),
142 | const SizedBox(height: 40),
143 | ],
144 | ),
145 | ),
146 | ],
147 | ),
148 | );
149 | }
150 |
151 | iconWithText({required IconData icon, required String text}) {
152 | return Padding(
153 | padding: const EdgeInsets.all(20),
154 | child: Column(
155 | mainAxisSize: MainAxisSize.min,
156 | children: [
157 | Icon(
158 | icon,
159 | size: 30,
160 | color: Coloors.greenDark,
161 | ),
162 | const SizedBox(height: 10),
163 | Text(
164 | text,
165 | style: const TextStyle(color: Coloors.greenDark),
166 | ),
167 | ],
168 | ),
169 | );
170 | }
171 | }
172 |
173 | class SliverPersistentDelegate extends SliverPersistentHeaderDelegate {
174 | final UserModel user;
175 |
176 | final double maxHeaderHeight = 180;
177 | final double minHeaderHeight = kToolbarHeight + 20;
178 | final double maxImageSize = 130;
179 | final double minImageSize = 40;
180 |
181 | SliverPersistentDelegate(this.user);
182 |
183 | @override
184 | Widget build(
185 | BuildContext context,
186 | double shrinkOffset,
187 | bool overlapsContent,
188 | ) {
189 | final size = MediaQuery.of(context).size;
190 | final percent = shrinkOffset / (maxHeaderHeight - 35);
191 | final percent2 = shrinkOffset / (maxHeaderHeight);
192 | final currentImageSize = (maxImageSize * (1 - percent)).clamp(
193 | minImageSize,
194 | maxImageSize,
195 | );
196 | final currentImagePosition = ((size.width / 2 - 65) * (1 - percent)).clamp(
197 | minImageSize,
198 | maxImageSize,
199 | );
200 | return Container(
201 | color: Theme.of(context).backgroundColor,
202 | child: Container(
203 | color: Theme.of(context)
204 | .appBarTheme
205 | .backgroundColor!
206 | .withOpacity(percent2 * 2 < 1 ? percent2 * 2 : 1),
207 | child: Stack(
208 | children: [
209 | Positioned(
210 | top: MediaQuery.of(context).viewPadding.top + 15,
211 | left: currentImagePosition + 50,
212 | child: Text(
213 | user.username,
214 | style: TextStyle(
215 | fontSize: 20,
216 | color: Colors.white.withOpacity(percent2),
217 | ),
218 | ),
219 | ),
220 | Positioned(
221 | left: 0,
222 | top: MediaQuery.of(context).viewPadding.top + 5,
223 | child: BackButton(
224 | color:
225 | percent2 > .3 ? Colors.white.withOpacity(percent2) : null,
226 | ),
227 | ),
228 | Positioned(
229 | right: 0,
230 | top: MediaQuery.of(context).viewPadding.top + 5,
231 | child: CustomIconButton(
232 | onPressed: () {},
233 | icon: Icons.more_vert,
234 | iconColor: percent2 > .3
235 | ? Colors.white.withOpacity(percent2)
236 | : Theme.of(context).textTheme.bodyText2!.color,
237 | ),
238 | ),
239 | Positioned(
240 | left: currentImagePosition,
241 | top: MediaQuery.of(context).viewPadding.top + 5,
242 | bottom: 0,
243 | child: Hero(
244 | tag: 'profile',
245 | child: Container(
246 | width: currentImageSize,
247 | decoration: BoxDecoration(
248 | shape: BoxShape.circle,
249 | image: DecorationImage(
250 | image: CachedNetworkImageProvider(user.profileImageUrl),
251 | ),
252 | ),
253 | ),
254 | ),
255 | ),
256 | ],
257 | ),
258 | ),
259 | );
260 | }
261 |
262 | @override
263 | double get maxExtent => maxHeaderHeight;
264 |
265 | @override
266 | double get minExtent => minHeaderHeight;
267 |
268 | @override
269 | bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
270 | return false;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
97 | );
98 | path = Runner;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 97C146ED1CF9000F007C117D /* Runner */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 | buildPhases = (
108 | 9740EEB61CF901F6004384FC /* Run Script */,
109 | 97C146EA1CF9000F007C117D /* Sources */,
110 | 97C146EB1CF9000F007C117D /* Frameworks */,
111 | 97C146EC1CF9000F007C117D /* Resources */,
112 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
114 | );
115 | buildRules = (
116 | );
117 | dependencies = (
118 | );
119 | name = Runner;
120 | productName = Runner;
121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | /* End PBXNativeTarget section */
125 |
126 | /* Begin PBXProject section */
127 | 97C146E61CF9000F007C117D /* Project object */ = {
128 | isa = PBXProject;
129 | attributes = {
130 | LastUpgradeCheck = 1300;
131 | ORGANIZATIONNAME = "";
132 | TargetAttributes = {
133 | 97C146ED1CF9000F007C117D = {
134 | CreatedOnToolsVersion = 7.3.1;
135 | LastSwiftMigration = 1100;
136 | };
137 | };
138 | };
139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
140 | compatibilityVersion = "Xcode 9.3";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = 97C146E51CF9000F007C117D;
148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 97C146ED1CF9000F007C117D /* Runner */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 97C146EC1CF9000F007C117D /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXResourcesBuildPhase section */
170 |
171 | /* Begin PBXShellScriptBuildPhase section */
172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 | isa = PBXShellScriptBuildPhase;
174 | buildActionMask = 2147483647;
175 | files = (
176 | );
177 | inputPaths = (
178 | );
179 | name = "Thin Binary";
180 | outputPaths = (
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | shellPath = /bin/sh;
184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
185 | };
186 | 9740EEB61CF901F6004384FC /* Run Script */ = {
187 | isa = PBXShellScriptBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | inputPaths = (
192 | );
193 | name = "Run Script";
194 | outputPaths = (
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | shellPath = /bin/sh;
198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
199 | };
200 | /* End PBXShellScriptBuildPhase section */
201 |
202 | /* Begin PBXSourcesBuildPhase section */
203 | 97C146EA1CF9000F007C117D /* Sources */ = {
204 | isa = PBXSourcesBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
209 | );
210 | runOnlyForDeploymentPostprocessing = 0;
211 | };
212 | /* End PBXSourcesBuildPhase section */
213 |
214 | /* Begin PBXVariantGroup section */
215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
216 | isa = PBXVariantGroup;
217 | children = (
218 | 97C146FB1CF9000F007C117D /* Base */,
219 | );
220 | name = Main.storyboard;
221 | sourceTree = "";
222 | };
223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
224 | isa = PBXVariantGroup;
225 | children = (
226 | 97C147001CF9000F007C117D /* Base */,
227 | );
228 | name = LaunchScreen.storyboard;
229 | sourceTree = "";
230 | };
231 | /* End PBXVariantGroup section */
232 |
233 | /* Begin XCBuildConfiguration section */
234 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
235 | isa = XCBuildConfiguration;
236 | buildSettings = {
237 | ALWAYS_SEARCH_USER_PATHS = NO;
238 | CLANG_ANALYZER_NONNULL = YES;
239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
240 | CLANG_CXX_LIBRARY = "libc++";
241 | CLANG_ENABLE_MODULES = YES;
242 | CLANG_ENABLE_OBJC_ARC = YES;
243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
244 | CLANG_WARN_BOOL_CONVERSION = YES;
245 | CLANG_WARN_COMMA = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
249 | CLANG_WARN_EMPTY_BODY = YES;
250 | CLANG_WARN_ENUM_CONVERSION = YES;
251 | CLANG_WARN_INFINITE_RECURSION = YES;
252 | CLANG_WARN_INT_CONVERSION = YES;
253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
258 | CLANG_WARN_STRICT_PROTOTYPES = YES;
259 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
260 | CLANG_WARN_UNREACHABLE_CODE = YES;
261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
263 | COPY_PHASE_STRIP = NO;
264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
265 | ENABLE_NS_ASSERTIONS = NO;
266 | ENABLE_STRICT_OBJC_MSGSEND = YES;
267 | GCC_C_LANGUAGE_STANDARD = gnu99;
268 | GCC_NO_COMMON_BLOCKS = YES;
269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
271 | GCC_WARN_UNDECLARED_SELECTOR = YES;
272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
273 | GCC_WARN_UNUSED_FUNCTION = YES;
274 | GCC_WARN_UNUSED_VARIABLE = YES;
275 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
276 | MTL_ENABLE_DEBUG_INFO = NO;
277 | SDKROOT = iphoneos;
278 | SUPPORTED_PLATFORMS = iphoneos;
279 | TARGETED_DEVICE_FAMILY = "1,2";
280 | VALIDATE_PRODUCT = YES;
281 | };
282 | name = Profile;
283 | };
284 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
285 | isa = XCBuildConfiguration;
286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
287 | buildSettings = {
288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 | CLANG_ENABLE_MODULES = YES;
290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
291 | ENABLE_BITCODE = NO;
292 | INFOPLIST_FILE = Runner/Info.plist;
293 | LD_RUNPATH_SEARCH_PATHS = (
294 | "$(inherited)",
295 | "@executable_path/Frameworks",
296 | );
297 | PRODUCT_BUNDLE_IDENTIFIER = com.example.whatsappMessenger;
298 | PRODUCT_NAME = "$(TARGET_NAME)";
299 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
300 | SWIFT_VERSION = 5.0;
301 | VERSIONING_SYSTEM = "apple-generic";
302 | };
303 | name = Profile;
304 | };
305 | 97C147031CF9000F007C117D /* Debug */ = {
306 | isa = XCBuildConfiguration;
307 | buildSettings = {
308 | ALWAYS_SEARCH_USER_PATHS = NO;
309 | CLANG_ANALYZER_NONNULL = YES;
310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
311 | CLANG_CXX_LIBRARY = "libc++";
312 | CLANG_ENABLE_MODULES = YES;
313 | CLANG_ENABLE_OBJC_ARC = YES;
314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
315 | CLANG_WARN_BOOL_CONVERSION = YES;
316 | CLANG_WARN_COMMA = YES;
317 | CLANG_WARN_CONSTANT_CONVERSION = YES;
318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
320 | CLANG_WARN_EMPTY_BODY = YES;
321 | CLANG_WARN_ENUM_CONVERSION = YES;
322 | CLANG_WARN_INFINITE_RECURSION = YES;
323 | CLANG_WARN_INT_CONVERSION = YES;
324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
329 | CLANG_WARN_STRICT_PROTOTYPES = YES;
330 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
331 | CLANG_WARN_UNREACHABLE_CODE = YES;
332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
334 | COPY_PHASE_STRIP = NO;
335 | DEBUG_INFORMATION_FORMAT = dwarf;
336 | ENABLE_STRICT_OBJC_MSGSEND = YES;
337 | ENABLE_TESTABILITY = YES;
338 | GCC_C_LANGUAGE_STANDARD = gnu99;
339 | GCC_DYNAMIC_NO_PIC = NO;
340 | GCC_NO_COMMON_BLOCKS = YES;
341 | GCC_OPTIMIZATION_LEVEL = 0;
342 | GCC_PREPROCESSOR_DEFINITIONS = (
343 | "DEBUG=1",
344 | "$(inherited)",
345 | );
346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
348 | GCC_WARN_UNDECLARED_SELECTOR = YES;
349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
350 | GCC_WARN_UNUSED_FUNCTION = YES;
351 | GCC_WARN_UNUSED_VARIABLE = YES;
352 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
353 | MTL_ENABLE_DEBUG_INFO = YES;
354 | ONLY_ACTIVE_ARCH = YES;
355 | SDKROOT = iphoneos;
356 | TARGETED_DEVICE_FAMILY = "1,2";
357 | };
358 | name = Debug;
359 | };
360 | 97C147041CF9000F007C117D /* Release */ = {
361 | isa = XCBuildConfiguration;
362 | buildSettings = {
363 | ALWAYS_SEARCH_USER_PATHS = NO;
364 | CLANG_ANALYZER_NONNULL = YES;
365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
366 | CLANG_CXX_LIBRARY = "libc++";
367 | CLANG_ENABLE_MODULES = YES;
368 | CLANG_ENABLE_OBJC_ARC = YES;
369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
370 | CLANG_WARN_BOOL_CONVERSION = YES;
371 | CLANG_WARN_COMMA = YES;
372 | CLANG_WARN_CONSTANT_CONVERSION = YES;
373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
375 | CLANG_WARN_EMPTY_BODY = YES;
376 | CLANG_WARN_ENUM_CONVERSION = YES;
377 | CLANG_WARN_INFINITE_RECURSION = YES;
378 | CLANG_WARN_INT_CONVERSION = YES;
379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
384 | CLANG_WARN_STRICT_PROTOTYPES = YES;
385 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
386 | CLANG_WARN_UNREACHABLE_CODE = YES;
387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
389 | COPY_PHASE_STRIP = NO;
390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
391 | ENABLE_NS_ASSERTIONS = NO;
392 | ENABLE_STRICT_OBJC_MSGSEND = YES;
393 | GCC_C_LANGUAGE_STANDARD = gnu99;
394 | GCC_NO_COMMON_BLOCKS = YES;
395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
397 | GCC_WARN_UNDECLARED_SELECTOR = YES;
398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
399 | GCC_WARN_UNUSED_FUNCTION = YES;
400 | GCC_WARN_UNUSED_VARIABLE = YES;
401 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
402 | MTL_ENABLE_DEBUG_INFO = NO;
403 | SDKROOT = iphoneos;
404 | SUPPORTED_PLATFORMS = iphoneos;
405 | SWIFT_COMPILATION_MODE = wholemodule;
406 | SWIFT_OPTIMIZATION_LEVEL = "-O";
407 | TARGETED_DEVICE_FAMILY = "1,2";
408 | VALIDATE_PRODUCT = YES;
409 | };
410 | name = Release;
411 | };
412 | 97C147061CF9000F007C117D /* Debug */ = {
413 | isa = XCBuildConfiguration;
414 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
415 | buildSettings = {
416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
417 | CLANG_ENABLE_MODULES = YES;
418 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
419 | ENABLE_BITCODE = NO;
420 | INFOPLIST_FILE = Runner/Info.plist;
421 | LD_RUNPATH_SEARCH_PATHS = (
422 | "$(inherited)",
423 | "@executable_path/Frameworks",
424 | );
425 | PRODUCT_BUNDLE_IDENTIFIER = com.example.whatsappMessenger;
426 | PRODUCT_NAME = "$(TARGET_NAME)";
427 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
428 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
429 | SWIFT_VERSION = 5.0;
430 | VERSIONING_SYSTEM = "apple-generic";
431 | };
432 | name = Debug;
433 | };
434 | 97C147071CF9000F007C117D /* Release */ = {
435 | isa = XCBuildConfiguration;
436 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
437 | buildSettings = {
438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
439 | CLANG_ENABLE_MODULES = YES;
440 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
441 | ENABLE_BITCODE = NO;
442 | INFOPLIST_FILE = Runner/Info.plist;
443 | LD_RUNPATH_SEARCH_PATHS = (
444 | "$(inherited)",
445 | "@executable_path/Frameworks",
446 | );
447 | PRODUCT_BUNDLE_IDENTIFIER = com.example.whatsappMessenger;
448 | PRODUCT_NAME = "$(TARGET_NAME)";
449 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
450 | SWIFT_VERSION = 5.0;
451 | VERSIONING_SYSTEM = "apple-generic";
452 | };
453 | name = Release;
454 | };
455 | /* End XCBuildConfiguration section */
456 |
457 | /* Begin XCConfigurationList section */
458 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
459 | isa = XCConfigurationList;
460 | buildConfigurations = (
461 | 97C147031CF9000F007C117D /* Debug */,
462 | 97C147041CF9000F007C117D /* Release */,
463 | 249021D3217E4FDB00AE95B9 /* Profile */,
464 | );
465 | defaultConfigurationIsVisible = 0;
466 | defaultConfigurationName = Release;
467 | };
468 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
469 | isa = XCConfigurationList;
470 | buildConfigurations = (
471 | 97C147061CF9000F007C117D /* Debug */,
472 | 97C147071CF9000F007C117D /* Release */,
473 | 249021D4217E4FDB00AE95B9 /* Profile */,
474 | );
475 | defaultConfigurationIsVisible = 0;
476 | defaultConfigurationName = Release;
477 | };
478 | /* End XCConfigurationList section */
479 | };
480 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
481 | }
482 |
--------------------------------------------------------------------------------