Repository: mogohary99/Flutter-WhatsApp-Clone-Clean-Architecture Branch: master Commit: 29d469a24f89 Files: 210 Total size: 364.8 KB Directory structure: gitextract_ue6w1k4a/ ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android/ │ ├── .gitignore │ ├── app/ │ │ ├── build.gradle │ │ ├── google-services.json │ │ └── src/ │ │ ├── debug/ │ │ │ └── AndroidManifest.xml │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin/ │ │ │ │ └── mogohary99/ │ │ │ │ └── whatsapp_flutter_clone/ │ │ │ │ └── MainActivity.kt │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21/ │ │ │ │ └── launch_background.xml │ │ │ ├── values/ │ │ │ │ └── styles.xml │ │ │ └── values-night/ │ │ │ └── styles.xml │ │ └── profile/ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ └── settings.gradle ├── ios/ │ ├── .gitignore │ ├── Flutter/ │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.imageset/ │ │ │ ├── Contents.json │ │ │ └── README.md │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ ├── Runner.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── Runner.xcscheme │ └── Runner.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── lib/ │ ├── config/ │ │ └── agora_config.dart │ ├── core/ │ │ ├── enums/ │ │ │ └── messge_type.dart │ │ ├── error/ │ │ │ ├── exceptions.dart │ │ │ └── failure.dart │ │ ├── extensions/ │ │ │ ├── extensions.dart │ │ │ └── time_extension.dart │ │ ├── functions/ │ │ │ ├── app_dialogs.dart │ │ │ ├── date_converter.dart │ │ │ └── navigator.dart │ │ ├── network/ │ │ │ └── cache_helper.dart │ │ ├── services/ │ │ │ └── services_locator.dart │ │ ├── shared/ │ │ │ ├── bloc_observer.dart │ │ │ ├── commen.dart │ │ │ ├── message_replay.dart │ │ │ └── pop_up_menu_item_model.dart │ │ ├── usecase/ │ │ │ └── base_use_case.dart │ │ └── utils/ │ │ ├── constants/ │ │ │ ├── assets_manager.dart │ │ │ ├── font_manager.dart │ │ │ ├── strings_manager.dart │ │ │ └── values_manager.dart │ │ ├── routes/ │ │ │ └── routes_manager.dart │ │ └── thems/ │ │ ├── my_colors.dart │ │ ├── styles_manager.dart │ │ └── theme_manager.dart │ ├── features/ │ │ ├── data/ │ │ │ ├── data_source/ │ │ │ │ ├── auth/ │ │ │ │ │ ├── local/ │ │ │ │ │ │ └── auth_local_data_source.dart │ │ │ │ │ └── remote/ │ │ │ │ │ └── auth_remote_data_source.dart │ │ │ │ ├── call/ │ │ │ │ │ └── call_data_source.dart │ │ │ │ ├── chat/ │ │ │ │ │ ├── local/ │ │ │ │ │ │ └── chat_local_data_source.dart │ │ │ │ │ └── remote/ │ │ │ │ │ └── chat_remote_data_source.dart │ │ │ │ ├── firebase_storage_remote_data_source.dart │ │ │ │ └── select_contact/ │ │ │ │ ├── local/ │ │ │ │ │ └── get_contacts_local_data_source.dart │ │ │ │ └── remote/ │ │ │ │ └── select_contact_remote_data_source.dart │ │ │ ├── models/ │ │ │ │ ├── call_model.dart │ │ │ │ ├── contact_chat_model.dart │ │ │ │ ├── message_model.dart │ │ │ │ └── user_model.dart │ │ │ └── repository/ │ │ │ ├── auth_repository.dart │ │ │ ├── call_repository.dart │ │ │ ├── chat_repository.dart │ │ │ └── select_contact_Repository.dart │ │ ├── domain/ │ │ │ ├── entities/ │ │ │ │ ├── call.dart │ │ │ │ ├── contact_chat.dart │ │ │ │ ├── message.dart │ │ │ │ └── user.dart │ │ │ ├── repository/ │ │ │ │ ├── base_auth_repository.dart │ │ │ │ ├── base_call_repository.dart │ │ │ │ ├── base_chat_repository.dart │ │ │ │ └── base_select_contact_repository.dart │ │ │ └── usecases/ │ │ │ ├── auth/ │ │ │ │ ├── get_cached_local_current_uid_usecase.dart │ │ │ │ ├── get_current_uid_usecase.dart │ │ │ │ ├── get_current_user_usecase.dart │ │ │ │ ├── get_user_by_id_usecase.dart │ │ │ │ ├── save_userdata_to_firebase_usecase.dart │ │ │ │ ├── set_user_state_usecase.dart │ │ │ │ ├── sign_in_with_phone_number_usecase.dart │ │ │ │ ├── signout_usecase.dart │ │ │ │ ├── update_profile_pic_usecase.dart │ │ │ │ └── verify_otp_usecase.dart │ │ │ ├── call/ │ │ │ │ ├── call_stream_usecase.dart │ │ │ │ ├── end_call_usecase.dart │ │ │ │ └── make_call_usecase.dart │ │ │ ├── chat/ │ │ │ │ ├── get_chat_messages_usecase.dart │ │ │ │ ├── get_contacts_chat_usecase.dart │ │ │ │ ├── get_num_of_message_not_seen_usecase.dart │ │ │ │ ├── send_file_message_usecase.dart │ │ │ │ ├── send_gif_message_usecase.dart │ │ │ │ ├── send_text_message_usecase.dart │ │ │ │ └── set_chat_message_seen_usecase.dart │ │ │ └── select_contact/ │ │ │ ├── get_all_contacts_usecase.dart │ │ │ ├── get_contact_name_usecase.dart │ │ │ ├── get_contacts_not_on_whats_usecase.dart │ │ │ └── get_contacts_on_whats_usecase.dart │ │ └── presentation/ │ │ ├── components/ │ │ │ ├── contact_profile_pic_dialog.dart │ │ │ ├── custom_list_tile.dart │ │ │ ├── custom_network_image.dart │ │ │ ├── custom_pop_up_menu_button.dart │ │ │ ├── custom_text.dart │ │ │ ├── default_button.dart │ │ │ ├── loader.dart │ │ │ ├── my_cached_net_image.dart │ │ │ └── update_profile_pic_model_bottom_sheet.dart │ │ ├── controllers/ │ │ │ ├── auth_cubit/ │ │ │ │ ├── auth_cubit.dart │ │ │ │ └── auth_state.dart │ │ │ ├── bottom_chat_cubit/ │ │ │ │ ├── bottom_chat_cubit.dart │ │ │ │ └── bottom_chat_state.dart │ │ │ ├── call_cubit/ │ │ │ │ ├── call_cubit.dart │ │ │ │ └── call_state.dart │ │ │ ├── chat_background_cubit/ │ │ │ │ ├── chat_background_cubit.dart │ │ │ │ └── chat_background_state.dart │ │ │ ├── chat_cubit/ │ │ │ │ ├── chat_cubit.dart │ │ │ │ └── chat_state.dart │ │ │ └── select_contact_cubit/ │ │ │ ├── select_contact_cubit.dart │ │ │ └── select_contact_state.dart │ │ └── views/ │ │ ├── calls/ │ │ │ ├── call_pickup_screen.dart │ │ │ ├── call_screen.dart │ │ │ └── calls_page.dart │ │ ├── camera/ │ │ │ ├── camera_screen.dart │ │ │ ├── components/ │ │ │ │ ├── camera_appbar.dart │ │ │ │ ├── image_view_top_row_icons.dart │ │ │ │ ├── select_image_from_gallery_button.dart │ │ │ │ ├── sending_image_video_bottom_roww_widget.dart │ │ │ │ └── video_view_top_row_widget.dart │ │ │ ├── sending_image_view_page.dart │ │ │ └── sending_video_view_page.dart │ │ ├── chat/ │ │ │ ├── chat_screen.dart │ │ │ └── components/ │ │ │ ├── bottom_field/ │ │ │ │ ├── attchment_pop_up.dart │ │ │ │ ├── bottom_chat_field.dart │ │ │ │ ├── bottom_chat_with_icon.dart │ │ │ │ ├── emoji_picker_widget.dart │ │ │ │ ├── recording_mic.dart │ │ │ │ ├── recording_mic_widget.dart │ │ │ │ └── timer.dart │ │ │ ├── chat_appbar.dart │ │ │ ├── message/ │ │ │ │ ├── first_message_small_curved_bubble.dart │ │ │ │ ├── message_replay_card.dart │ │ │ │ ├── message_replay_preview.dart │ │ │ │ ├── messages_list.dart │ │ │ │ ├── my_message_card.dart │ │ │ │ └── sender_message_card.dart │ │ │ └── message_content/ │ │ │ ├── audio_player_widget.dart │ │ │ ├── image_widget.dart │ │ │ ├── message_content.dart │ │ │ ├── text_widget.dart │ │ │ ├── time_sent_widget.dart │ │ │ └── video_palyer_widget.dart │ │ ├── contacts_chat/ │ │ │ ├── components/ │ │ │ │ └── chat_contact_card.dart │ │ │ └── contacts_chat_page.dart │ │ ├── login/ │ │ │ ├── components/ │ │ │ │ ├── landing_image.dart │ │ │ │ ├── login_appbar.dart │ │ │ │ ├── login_profile_pic.dart │ │ │ │ ├── privacy_policy_link_and_terms_of_service.dart │ │ │ │ └── sign_out_button.dart │ │ │ ├── login_landing_screen.dart │ │ │ ├── login_loading_screen.dart │ │ │ ├── login_otp_screen.dart │ │ │ ├── login_profile_info_screen.dart │ │ │ └── login_screen.dart │ │ ├── main_layout/ │ │ │ ├── components/ │ │ │ │ ├── fab.dart │ │ │ │ └── sliver_appbar_actions.dart │ │ │ └── main_layout_screen.dart │ │ ├── select_contact/ │ │ │ ├── components/ │ │ │ │ ├── contacts_not_on_whatsapp_list.dart │ │ │ │ ├── contacts_on_whatsapp_list.dart │ │ │ │ ├── new_group_contact_community_buttons_List.dart │ │ │ │ └── select_contact_appbar.dart │ │ │ └── select_contact_screen.dart │ │ ├── sender_profile/ │ │ │ ├── components/ │ │ │ │ ├── body.dart │ │ │ │ ├── encryption_container.dart │ │ │ │ ├── notification_container.dart │ │ │ │ ├── phone_and_name.dart │ │ │ │ ├── sender_profile_icons.dart │ │ │ │ ├── sender_user_profile_appbar.dart │ │ │ │ └── status_container.dart │ │ │ └── sender_profile_page.dart │ │ ├── settings/ │ │ │ ├── components/ │ │ │ │ ├── about_card.dart │ │ │ │ ├── name_card.dart │ │ │ │ ├── phone_card.dart │ │ │ │ ├── profile_card.dart │ │ │ │ ├── profile_pic_circle_card.dart │ │ │ │ ├── setting_bottom_text.dart │ │ │ │ └── settings_item_card.dart │ │ │ ├── profile_screen.dart │ │ │ └── settings_screen.dart │ │ ├── splash/ │ │ │ ├── components/ │ │ │ │ ├── bottom_text.dart │ │ │ │ └── splash_icon.dart │ │ │ └── splash_screen.dart │ │ ├── status/ │ │ │ └── status_page.dart │ │ └── wallpaper/ │ │ └── wallpaper_screen.dart │ ├── info.dart │ └── main.dart ├── pubspec.yaml └── test/ └── widget_test.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled. version: revision: e5c8614e789b1d3f7ef8d9a77295b884b5b396e0 channel: master project_type: app # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: e5c8614e789b1d3f7ef8d9a77295b884b5b396e0 base_revision: e5c8614e789b1d3f7ef8d9a77295b884b5b396e0 - platform: android create_revision: e5c8614e789b1d3f7ef8d9a77295b884b5b396e0 base_revision: e5c8614e789b1d3f7ef8d9a77295b884b5b396e0 - platform: ios create_revision: e5c8614e789b1d3f7ef8d9a77295b884b5b396e0 base_revision: e5c8614e789b1d3f7ef8d9a77295b884b5b396e0 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: README.md ================================================ # Flutter-WhatsApp-Clone-Clean-Architecture WhatsApp Clone with Flutter, Firebase, Bloc, Clean Code & Clean Architecture🔥🔥❤️ ## [Watch it on YouTube](https://www.youtube.com/watch?v=INxbhrOeiuk) ## Some of the cool features applied in the project: - Flutter bloc(cubit) - Enhanced clean architecture. - Firebase Firestore, Firebase Storage, and Firebase Auth. - Real-Time Changes from Firebase using Streams. - Phone Number Authentication - 1-1 Chatting with Contacts Only - Text, Image, GIF, Audio(Recording), Video & Emoji Sharing - Online/Offline Status - Seen Message - Replying to Messages - Auto Scroll on New Messages - Custom gallery & camera & video display Now, I am working on the status and video calling. ## Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. ================================================ FILE: analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties **/*.keystore **/*.jks ================================================ FILE: android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply plugin: 'com.google.gms.google-services' android { // compileSdkVersion flutter.compileSdkVersion compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "mogohary99.whatsapp_flutter_clone" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion 21 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:multidex:2.0.1' implementation platform('com.google.firebase:firebase-bom:31.0.2') } ================================================ FILE: android/app/google-services.json ================================================ { "project_info": { "project_number": "528108582188", "project_id": "whatsapp-8d793", "storage_bucket": "whatsapp-8d793.appspot.com" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:528108582188:android:6544c1493d3433f4f26889", "android_client_info": { "package_name": "mogohary99.whatsapp_flutter_clone" } }, "oauth_client": [ { "client_id": "528108582188-0d3v7bqfv5vtdhc08th823se96cgsqk0.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "mogohary99.whatsapp_flutter_clone", "certificate_hash": "09199a03d91f7c6cd3061042942deb2c3f3a93d0" } }, { "client_id": "528108582188-nveih6hahfhv7sh0cep1kdj8rjgm1loa.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyBa-_MafiWD6xzy9aAgoK1hfQ6yjp3u_zg" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [ { "client_id": "528108582188-nveih6hahfhv7sh0cep1kdj8rjgm1loa.apps.googleusercontent.com", "client_type": 3 } ] } } } ], "configuration_version": "1" } ================================================ FILE: android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: android/app/src/main/kotlin/mogohary99/whatsapp_flutter_clone/MainActivity.kt ================================================ package mogohary99.whatsapp_flutter_clone import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ================================================ FILE: android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.3.13' } } allprojects { repositories { google() mavenCentral() maven { url 'https://jitpack.io' } } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip ================================================ FILE: android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: android/settings.gradle ================================================ include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() assert localPropertiesFile.exists() localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" ================================================ FILE: ios/.gitignore ================================================ **/dgph *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. ================================================ FILE: ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Whatsapp Flutter Clone CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName whatsapp_flutter_clone CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = mogohary99.whatsappFlutterClone; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = mogohary99.whatsappFlutterClone; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = mogohary99.whatsappFlutterClone; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: lib/config/agora_config.dart ================================================ class AgoraConfig{ static const String token = ''; static const String appId = '97bc1546a8174ed0a7a7c9acdfca61f5'; static const String appCertificate = '56cae9b6d2854e0a833f11c5ccc61cbe'; } ================================================ FILE: lib/core/enums/messge_type.dart ================================================ enum MessageType{ text('text'), image('image'), gif('gif'), video('video'), audio('audio'), location('location'), contact('contact'), poll('poll'); const MessageType(this.type); final String type; } extension ConvertMessage on String{ MessageType toEnum(){ switch(this){ case 'text': return MessageType.text; case 'image': return MessageType.image; case 'gif': return MessageType.gif; case 'video': return MessageType.video; case 'audio': return MessageType.audio; case 'location': return MessageType.location; case 'contact': return MessageType.contact; case 'poll': return MessageType.poll; default: return MessageType.text; } } } ================================================ FILE: lib/core/error/exceptions.dart ================================================ class LocalDatabaseException implements Exception { final String message; const LocalDatabaseException({ required this.message, }); } class CachedException implements Exception { final String message; const CachedException({ required this.message, }); } ================================================ FILE: lib/core/error/failure.dart ================================================ import 'package:equatable/equatable.dart'; abstract class Failure extends Equatable { final String message; const Failure(this.message); @override List get props => [message]; } class ServerFailure extends Failure{ const ServerFailure(super.message); } class LocalDatabaseFailure extends Failure{ const LocalDatabaseFailure(super.message); } class CachedFailure extends Failure{ const CachedFailure(super.message); } ================================================ FILE: lib/core/extensions/extensions.dart ================================================ import 'package:flutter/material.dart'; extension ContextExtension on BuildContext { MediaQueryData get mediaQuery => MediaQuery.of(this); TextTheme get textTheme => Theme.of(this).textTheme; ThemeData get theme => Theme.of(this); ColorScheme get colorScheme => Theme.of(this).colorScheme; } extension MediaQueryExtension on BuildContext { Size get size => mediaQuery.size; double height(double value) => mediaQuery.size.height * value; double width(double value) => mediaQuery.size.width * value; } extension TextThemeExtension on BuildContext { TextStyle? get displayLarge => textTheme.displayLarge; TextStyle? get displayMedium => textTheme.displayMedium; TextStyle? get displaySmall => textTheme.displaySmall; TextStyle? get headlineLarge => textTheme.headlineLarge; TextStyle? get headlineMedium => textTheme.headlineMedium; TextStyle? get headlineSmall => textTheme.headlineSmall; TextStyle? get titleLarge => textTheme.titleLarge; TextStyle? get titleMedium => textTheme.titleMedium; TextStyle? get titleSmall => textTheme.titleSmall; TextStyle? get bodyLarge => textTheme.bodyLarge; TextStyle? get bodyMedium => textTheme.bodyMedium; TextStyle? get bodySmall => textTheme.bodySmall; TextStyle? get labelLarge => textTheme.labelLarge; TextStyle? get labelSmall => textTheme.labelSmall; } ================================================ FILE: lib/core/extensions/time_extension.dart ================================================ import '/core/functions/date_converter.dart'; extension DateUtil on DateTime { String get lastSeen { return 'last seen ${DateConverter.getLastSeenDayTime(this)} at ${DateConverter.dateConverterHoursAmPmMode(this)}'; } String get amPmMode{ return DateConverter.dateConverterHoursAmPmMode(this); } String get chatDayTime{ return DateConverter.getChatDayTime(this); } String get chatContactTime{ return DateConverter.getChatContactTime(this); } bool isSameDay(DateTime day2){ return DateConverter.isSameDay(this, day2); } } ================================================ FILE: lib/core/functions/app_dialogs.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../utils/constants/strings_manager.dart'; import '../utils/constants/values_manager.dart'; import 'navigator.dart'; class AppDialogs { static Future permissionDialog( BuildContext context, { required VoidCallback onContinuePressed, required String contentText, }) { return _showMyDialog( context: context, contentPadding: EdgeInsets.zero, borderRadius: AppSize.s8, content: Column( children: [ Container( width: double.infinity, height: AppSize.s120, decoration: BoxDecoration( //color: AppColors.primary, color: context.colorScheme.secondaryContainer, borderRadius: const BorderRadius.only( topLeft: Radius.circular(AppSize.s8), topRight: Radius.circular(AppSize.s8), ), ), child: const Icon( Icons.phone, color: Colors.white, size: 50, ), ), Padding( padding: const EdgeInsets.symmetric( horizontal: AppPadding.p28, vertical: AppPadding.p28, ), child: Text( contentText, //style: Theme.of(context).textTheme.bodyLarge, style: context.titleSmall!.copyWith( height: 1.5, ), ), ) ], ), actions: [ TextButton( child: const Text( 'Not Now', ), onPressed: () { navigatePop(context); }, ), TextButton( onPressed: onContinuePressed, child: const Text( 'Continue', ), ), ], ); } static Future submitPhoneDialog({ required BuildContext context, required String phoneNumber, required VoidCallback okPressed, }) { return _showMyDialog( context: context, actionSpacer: true, borderRadius: 3, contentPadding: const EdgeInsets.only( top: AppPadding.p20, right: AppPadding.p20, left: AppPadding.p20, ), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text(AppStrings.youEnteredThePhoneNum), Padding( padding: const EdgeInsets.symmetric( vertical: AppPadding.p20, ), child: Text( phoneNumber, style: context.titleMedium, ), ), const Text(AppStrings.isThisOk), ], ), actions: [ TextButton( child: Text( AppStrings.edit, style: TextStyle( color: context.colorScheme.secondary, fontWeight: FontWeight.w500, ), ), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( onPressed: okPressed, child: Text( AppStrings.ok, style: TextStyle( color: context.colorScheme.secondary, fontWeight: FontWeight.w500, ), ), ), ], ); } } Future _showMyDialog({ required BuildContext context, double borderRadius = 0, required Widget content, required List actions, bool actionSpacer = false, EdgeInsetsGeometry? contentPadding, }) async { return showDialog( context: context, barrierDismissible: false, // user must tap button! builder: (BuildContext context) { return AlertDialog( contentPadding: contentPadding, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(borderRadius), ), ), content: SingleChildScrollView( child: content, ), actions: actions, actionsAlignment: actionSpacer ? MainAxisAlignment.spaceBetween : MainAxisAlignment.end, ); }, ); } ================================================ FILE: lib/core/functions/date_converter.dart ================================================ class DateConverter{ static String getChatContactTime(DateTime dateTime){ DateTime now = DateTime.now(); DateTime yesterday = now.subtract(const Duration(days: 1)); DateTime localDateTime = dateTime.toLocal(); if (localDateTime.day == now.day && localDateTime.month == now.month && localDateTime.year == now.year) { return dateConverterHoursAmPmMode(dateTime); } else if (localDateTime.day == yesterday.day && localDateTime.month == yesterday.month && localDateTime.year == yesterday.year) { return 'Yesterday'; }else{ return dateConverterMonthNum(dateTime.toString()); } } static String getChatDayTime(DateTime dateTime){ DateTime now = DateTime.now(); DateTime yesterday = now.subtract(const Duration(days: 1)); DateTime localDateTime = dateTime.toLocal(); if (localDateTime.day == now.day && localDateTime.month == now.month && localDateTime.year == now.year) { return 'Today'; } else if (localDateTime.day == yesterday.day && localDateTime.month == yesterday.month && localDateTime.year == yesterday.year) { return 'Yesterday'; }else{ return dateConverterMonth(dateTime.toString()); } } static String getLastSeenDayTime(DateTime dateTime){ DateTime now = DateTime.now(); DateTime yesterday = now.subtract(const Duration(days: 1)); DateTime localDateTime = dateTime.toLocal(); if (localDateTime.day == now.day && localDateTime.month == now.month && localDateTime.year == now.year) { return 'today'; } else if (localDateTime.day == yesterday.day && localDateTime.month == yesterday.month && localDateTime.year == yesterday.year) { return 'yesterday'; }else{ return dateConverterMonthNum(dateTime.toString()); } } static bool isSameDay(DateTime nowTime,DateTime priviesTime){ DateTime now = nowTime.toLocal(); DateTime privies = priviesTime.toLocal(); if(now.day ==privies.day && now.month == privies.month && now.year == privies.year){ return true; } return false; } //2022-12-07 12:01:17.696 static String dateConverterHoursAmPmMode(DateTime dateTime){ String string = dateTime.toString(); int i = 0; String s = ""; int mode = int.parse(string[11] + string[12]); List duration = ['am', 'pm']; if(mode > 11) { if(mode != 12) mode -= 12; mode < 10 ? s += '0$mode' : s += '$mode' ; for (i = 13; i < 16; ++i) { s += string.split('')[i]; } s += ' ${duration[1]}'; } else{ for (i = 11; i < 16; ++i) { s += string.split('')[i]; } s += ' ${duration[0]}'; } return s; } //2022-12-07 12:01:17.696 static String dateConverterMonth(String string){ int i = 0; String s = ""; String monthNum = string.split('')[5] + string.split('')[6]; String dayNum = string.split('')[8] + string.split('')[9]; List m = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; List mN = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']; for (i = 0; i < 12; ++i) { if (monthNum == mN[i]) { s = '$dayNum ${m[i]} '; break; } } for (i = 0; i < 4; ++i) { s += string.split('')[i]; } return s; } static String dateConverterMonthNum(String string){ int i = 0; String s = ""; String monthNum = string.split('')[5] + string.split('')[6]; String dayNum = string.split('')[8] + string.split('')[9]; List m = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; List mN = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']; for (i = 0; i < 12; ++i) { if (monthNum == mN[i]) { s = '$dayNum/${mN[i]}/'; break; } } for (i = 2; i < 4; ++i) { s += string.split('')[i]; } return s; } static String dateConverterOnly(String string) { String s = ""; s = string.split("T")[0]; return s; } static String dateConverterSince(String string){ String s = ""; int apiDay = int.parse(string.split("")[8] + string.split("")[9]); int currentDay = DateTime.now().day; if(currentDay == apiDay) s = "Today"; else if(currentDay == apiDay + 1) s = "Yesterday"; else if(currentDay > apiDay + 1) s = "Since ${currentDay - apiDay} days"; return s; } static String dateConverterHours24Mode(String string){ int i = 0; String s = ""; for (i = 11; i < 16; ++i) { s += string.split('')[i]; } return s; } } ================================================ FILE: lib/core/functions/navigator.dart ================================================ import 'package:flutter/material.dart'; void navigatePop( BuildContext context, ) { Navigator.of(context).pop(); } void navigateAndRemove( BuildContext context, String routeName, { Object? arguments, }) { Navigator.of(context).pushNamedAndRemoveUntil( routeName, arguments: arguments, (route) => false, ); } Future navigateTo( BuildContext context, String routeName, { Object? arguments, }) { return Navigator.of(context).pushNamed( routeName, arguments: arguments, ); } void navigateAndReplace( BuildContext context, String routeName, { Object? arguments, }) { Navigator.of(context).pushReplacementNamed( routeName, arguments: arguments, ); } ================================================ FILE: lib/core/network/cache_helper.dart ================================================ import 'package:shared_preferences/shared_preferences.dart'; class CacheHelper { final SharedPreferences _sharedPreferences; CacheHelper(this._sharedPreferences); dynamic getData({required String key}) { return _sharedPreferences.get(key); } Future saveData({ required String key, required dynamic value, }) async { if (value is String) return await _sharedPreferences.setString(key, value); if (value is double) return await _sharedPreferences.setDouble(key, value); if (value is bool) return await _sharedPreferences.setBool(key, value); return await _sharedPreferences.setInt(key, value); } void removeData({required String key})async{ await _sharedPreferences.remove(key); } } ================================================ FILE: lib/core/services/services_locator.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:whatsapp_flutter_clone/features/data/data_source/call/call_data_source.dart'; import 'package:whatsapp_flutter_clone/features/data/repository/call_repository.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_call_repository.dart'; import 'package:whatsapp_flutter_clone/features/domain/usecases/call/call_stream_usecase.dart'; import 'package:whatsapp_flutter_clone/features/domain/usecases/call/end_call_usecase.dart'; import 'package:whatsapp_flutter_clone/features/domain/usecases/call/make_call_usecase.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/call_cubit/call_cubit.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/chat_background_cubit/chat_background_cubit.dart'; import '../../features/data/data_source/auth/local/auth_local_data_source.dart'; import '../../features/data/data_source/auth/remote/auth_remote_data_source.dart'; import '../../features/data/data_source/chat/remote/chat_remote_data_source.dart'; import '../../features/data/data_source/select_contact/local/get_contacts_local_data_source.dart'; import '../../features/data/data_source/select_contact/remote/select_contact_remote_data_source.dart'; import '../../features/data/repository/auth_repository.dart'; import '../../features/data/repository/chat_repository.dart'; import '../../features/data/repository/select_contact_Repository.dart'; import '../../features/domain/repository/base_auth_repository.dart'; import '../../features/domain/repository/base_chat_repository.dart'; import '../../features/domain/repository/base_select_contact_repository.dart'; import '../../features/domain/usecases/auth/get_cached_local_current_uid_usecase.dart'; import '../../features/domain/usecases/auth/get_current_uid_usecase.dart'; import '../../features/domain/usecases/auth/get_current_user_usecase.dart'; import '../../features/domain/usecases/auth/get_user_by_id_usecase.dart'; import '../../features/domain/usecases/auth/save_userdata_to_firebase_usecase.dart'; import '../../features/domain/usecases/auth/set_user_state_usecase.dart'; import '../../features/domain/usecases/auth/sign_in_with_phone_number_usecase.dart'; import '../../features/domain/usecases/auth/signout_usecase.dart'; import '../../features/domain/usecases/auth/update_profile_pic_usecase.dart'; import '../../features/domain/usecases/auth/verify_otp_usecase.dart'; import '../../features/domain/usecases/chat/get_chat_messages_usecase.dart'; import '../../features/domain/usecases/chat/get_contacts_chat_usecase.dart'; import '../../features/domain/usecases/chat/get_num_of_message_not_seen_usecase.dart'; import '../../features/domain/usecases/chat/send_file_message_usecase.dart'; import '../../features/domain/usecases/chat/send_gif_message_usecase.dart'; import '../../features/domain/usecases/chat/send_text_message_usecase.dart'; import '../../features/domain/usecases/chat/set_chat_message_seen_usecase.dart'; import '../../features/domain/usecases/select_contact/get_all_contacts_usecase.dart'; import '../../features/domain/usecases/select_contact/get_contacts_not_on_whats_usecase.dart'; import '../../features/domain/usecases/select_contact/get_contacts_on_whats_usecase.dart'; import '../../features/presentation/controllers/auth_cubit/auth_cubit.dart'; import '../../features/presentation/controllers/bottom_chat_cubit/bottom_chat_cubit.dart'; import '../../features/presentation/controllers/chat_cubit/chat_cubit.dart'; import '../../features/presentation/controllers/select_contact_cubit/select_contact_cubit.dart'; import '../network/cache_helper.dart'; final sl = GetIt.instance; Future init() async { ///shared prefs final sharedPref = await SharedPreferences.getInstance(); sl.registerLazySingleton(() => sharedPref); sl.registerLazySingleton(() => CacheHelper(sl())); ///cubit sl.registerFactory( () => AuthCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl())); sl.registerFactory(() => SelectContactCubit(sl(), sl(), sl())); sl.registerFactory(() => ChatCubit(sl(), sl(), sl(), sl(), sl(), sl(),sl())); sl.registerLazySingleton(() => BottomChatCubit()); sl.registerLazySingleton(() => ChatBackgroundCubit()); sl.registerLazySingleton(() => CallCubit(sl(), sl(), sl())); ///use case /////////auth sl.registerLazySingleton(() => SignInWithPhoneNumberUseCase(sl())); sl.registerLazySingleton(() => VerifyOtpUseCase(sl())); sl.registerLazySingleton(() => SaveUserDataToFirebaseUseCase(sl())); sl.registerLazySingleton(() => GetCurrentUidUseCase(sl())); sl.registerLazySingleton(() => SignOutUseCase(sl())); sl.registerLazySingleton(() => GetCachedLocalCurrentUidUseCase(sl())); sl.registerLazySingleton(() => GetUserByIdUseCase(sl())); sl.registerLazySingleton(() => SetUserStateUseCase(sl())); sl.registerLazySingleton(() => GetCurrentUserUseCase(sl())); sl.registerLazySingleton(() => UpdateProfilePicUseCase(sl())); ////////select contact sl.registerLazySingleton(() => GetAllContactsUseCase(sl())); sl.registerLazySingleton(() => GetContactsOnWhatsUseCase(sl())); sl.registerLazySingleton(() => GetContactsNotOnWhatsUseCase(sl())); ///////chat sl.registerLazySingleton(() => SendTextMessageUseCase(sl())); sl.registerLazySingleton(() => GetContactsChatUseCase(sl())); sl.registerLazySingleton(() => GetChatMessagesUseCase(sl())); sl.registerLazySingleton(() => SetChatMessageSeenUseCase(sl())); sl.registerLazySingleton(() => SendGifMessageUseCase(sl())); sl.registerLazySingleton(() => SendFileMessageUseCase(sl())); sl.registerLazySingleton(() => GetNumberOfMessageNotSeenUseCase(sl())); // sl.registerLazySingleton(() => GetContactNameUseCase(sl())); /////call sl.registerLazySingleton(() => CallStreamUseCase(sl())); sl.registerLazySingleton(() => EndCallUseCase(sl())); sl.registerLazySingleton(() => MakeCallUseCase(sl())); ///repository sl.registerLazySingleton( () => AuthRepository(localDataSource: sl(), remoteDataSource: sl()), ); sl.registerLazySingleton( () => SelectContactRepository(sl(), sl()), ); sl.registerLazySingleton(() => ChatRepository(sl())); sl.registerLazySingleton(() => CallRepository(sl())); ///remote data source sl.registerLazySingleton( () => AuthRemoteDataSource( auth: sl(), firestore: sl(), firebaseStorage: sl(), ), ); sl.registerLazySingleton( () => SelectContactsRemoteDataSource(sl(), sl()), ); sl.registerLazySingleton( () => ChatRemoteDataSource(sl(), sl(), sl()), ); sl.registerLazySingleton(() => CallDataSource(sl(), sl())); /// local data source sl.registerLazySingleton( () => AuthLocalDataSource(sharedPreferences: sl()), ); sl.registerLazySingleton( () => SelectContactsLocalDataSource(), ); //External final auth = FirebaseAuth.instance; final firestore = FirebaseFirestore.instance; final firebaseStorage = FirebaseStorage.instance; sl.registerLazySingleton(() => auth); sl.registerLazySingleton(() => firestore); sl.registerLazySingleton(() => firebaseStorage); } ================================================ FILE: lib/core/shared/bloc_observer.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class MyBlocObserver extends BlocObserver { @override void onCreate(BlocBase bloc) { super.onCreate(bloc); if (kDebugMode) { print('onCreate -- ${bloc.runtimeType}'); } } @override void onChange(BlocBase bloc, Change change) { super.onChange(bloc, change); if (kDebugMode) { print('onChange -- ${bloc.runtimeType}, $change'); } } @override void onError(BlocBase bloc, Object error, StackTrace stackTrace) { if (kDebugMode) { print('onError -- ${bloc.runtimeType}, $error'); } super.onError(bloc, error, stackTrace); } @override void onClose(BlocBase bloc) { super.onClose(bloc); if (kDebugMode) { print('onClose -- ${bloc.runtimeType}'); } } } ================================================ FILE: lib/core/shared/commen.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:giphy_get/giphy_get.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; void showSnackBar({ required BuildContext context, required String content, }) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(content), ), ); } Future pickImageFromGallery(BuildContext context) async { File? image; try { final pickedImage = await ImagePicker().pickImage(source: ImageSource.gallery); if (pickedImage != null) { image = File(pickedImage.path); } } catch (e) { showSnackBar( context: context, content: e.toString(), ); } return image; } Future pickGif(BuildContext context) async { GiphyGif? gif; try { gif = await GiphyGet.getGif( context: context, apiKey: 'GmpyPrPF9RVZ1LJY1iAe6O8MTWrBpNob', ); } catch (e) { showSnackBar(context: context, content: e.toString()); } return gif; } Future cropImage(String path)async{ return ImageCropper().cropImage( sourcePath: path, aspectRatioPresets: Platform.isAndroid ? [ CropAspectRatioPreset.square, CropAspectRatioPreset.ratio3x2, CropAspectRatioPreset.original, CropAspectRatioPreset.ratio4x3, CropAspectRatioPreset.ratio16x9 ] : [ CropAspectRatioPreset.original, CropAspectRatioPreset.square, CropAspectRatioPreset.ratio3x2, CropAspectRatioPreset.ratio4x3, CropAspectRatioPreset.ratio5x3, CropAspectRatioPreset.ratio5x4, CropAspectRatioPreset.ratio7x5, CropAspectRatioPreset.ratio16x9 ], aspectRatio: const CropAspectRatio( ratioX: 1.0, ratioY: 1.0, ), compressQuality: 100, maxWidth: 400, maxHeight: 400, compressFormat: ImageCompressFormat.jpg, cropStyle: CropStyle.rectangle, uiSettings: [ AndroidUiSettings( toolbarColor: Colors.teal, toolbarTitle: "Profile Image", statusBarColor: Colors.teal, backgroundColor: Colors.white, hideBottomControls: true, lockAspectRatio: false, initAspectRatio: CropAspectRatioPreset.square, toolbarWidgetColor: Colors.white ), ], ); } ================================================ FILE: lib/core/shared/message_replay.dart ================================================ import 'package:equatable/equatable.dart'; import '/core/enums/messge_type.dart'; class MessageReplay extends Equatable { final String message; final bool isMe; final MessageType messageType; final String repliedTo; const MessageReplay( { required this.message, required this.isMe, required this.messageType, required this.repliedTo, }); @override List get props => [ message, isMe, messageType, repliedTo, ]; } ================================================ FILE: lib/core/shared/pop_up_menu_item_model.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter/animation.dart'; class PopUpMenuItemModel extends Equatable{ final String name; final VoidCallback onTap; const PopUpMenuItemModel({required this.name, required this.onTap,}); @override List get props => [name,onTap]; } ================================================ FILE: lib/core/usecase/base_use_case.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import '../error/failure.dart'; abstract class BaseUseCase{ Future> call(Parameters parameters); } abstract class StreamBaseUseCase{ Stream call(Parameters parameters); } class NoParameters extends Equatable{ const NoParameters(); @override List get props => []; } ================================================ FILE: lib/core/utils/constants/assets_manager.dart ================================================ const String imgPath = 'assets/images'; class AppImage{ static const String landingImg = '$imgPath/bg.png'; static const String genericProfileImage = '$imgPath/user3.png'; static const String qrCode = '$imgPath/qr-code.png'; static const String chatBackground = '$imgPath/chat-bg.png'; static const String chatBackground2 = '$imgPath/wall1.jpg'; static const String chatBackground3 = '$imgPath/wall2.jpg'; static const String chatBackgroundDark = '$imgPath/chat-bg-dark.png'; static const String splashLightImg = '$imgPath/splash_light.png'; static const String trashContainerImg = '$imgPath/trash_container.png'; static const String trashCoverImg = '$imgPath/trash_cover.png'; } ================================================ FILE: lib/core/utils/constants/font_manager.dart ================================================ import 'package:flutter/material.dart'; class FontConstants { static const String fontFamily = "Montserrat"; } class FontWeightManager { static const FontWeight light = FontWeight.w300; static const FontWeight regular = FontWeight.w400; static const FontWeight medium = FontWeight.w500; static const FontWeight semiBold = FontWeight.w600; static const FontWeight bold = FontWeight.w700; } class FontSize { static const double s12 = 12.0; static const double s14 = 14.0; static const double s16 = 16.0; static const double s17 = 17.0; static const double s18 = 18.0; static const double s20 = 20.0; static const double s22 = 22.0; static const double s26 = 26.0; static const double s28 = 28.0; } ================================================ FILE: lib/core/utils/constants/strings_manager.dart ================================================ class AppStrings { static const String appName = "WhatsApp "; static const String noRouteFound = "noRouteFound"; //landing screen static const String welcomeToWhatsApp = 'Welcome to WhatsApp'; static const String readOur = 'Read our '; static const String privacyPolicy = 'Privacy Policy'; static const String tapAgreeAnd = '. Tap "Agree And Continue" to accept the '; static const String termsOfService = 'Terms of Service'; static const String agreeAndContinue = 'AGREE AND CONTINUE'; //login screen static const String enterYourPhoneNumber = 'Enter your phone number'; static const String whatsAppWillNeed = 'WhatsApp will need to verify your phone number.'; static const String whatIsMyNumber = 'What\'s my number?'; static const String phoneNumber = 'Phone number'; static const String carrierChargesMayApply = 'Carrier charges may apply'; static const String next = 'NEXT'; static const String pickCountry = 'Choose a country'; //alert dialog static const String toRetrieveYourPhone = 'To retrieve your phone number, WhatsApp needs permissions to make and manage your calls. Without this permission, WhatsApp will be unable to retrieve your phone number from the SIM.'; static const String youEnteredThePhoneNum = 'You entered the phone number:'; static const String isThisOk = 'Is this OK, or would you like to edit the number?'; static const String edit = 'EDIT'; static const String ok = 'OK'; //otp screen static const String verifyingYourNumber = 'Verifying Your Number'; static const String waitingToDetectSms = 'Waiting to automatically detect an sms sent to'; static const String wrongNumber = 'Wrong number?'; static const String enter6DigitCode = 'Enter 6-digit code'; static const String resendSms = 'Resend SMS'; //login profile info static const String profileInfo = 'Profile info'; static const String pleaseProvideYourName = 'Please provide your name and an optional profile photo'; static const String typeYourNameHere = 'Type your name here'; static const String profilePhoto = 'Profile photo'; static const String camera = 'Camera'; static const String gallery = 'Gallery'; //login loading screen static const String initializing = 'Initializing...'; static const String pleaseWaitAMoment = 'Please wait a moment'; ///// static const String heyThere = 'Hey there! I am using WhatsApp.'; // select contact static const String selectContact = 'Select contact'; static const String contacts = 'contacts'; static const String newGroup = 'New group'; static const String newContact = 'New contact'; static const String newCommunity = 'New Community'; static const String contactsOnWhatsApp = 'Contacts on WhatsApp'; static const String inviteToWhatsApp = 'Invite to WhatsApp'; static const String inviteAFriend = 'Invite a friend'; static const String refresh = 'Refresh'; static const String help = 'Help'; static const String invite = 'Invite'; /// Main Layout screen static const String chats = 'CHATS'; static const String calls = 'Calls'; static const String status = 'Status'; static const String newBroadcast = 'New broadcast'; static const String linkedDevices = 'Linked devices'; static const String starredMessage = 'Starred message'; static const String settings = 'Settings'; //chat static const String viewContact = 'View contact'; static const String mediaLinksAndDocs = 'Media, links, and docs'; static const String search = 'Search'; static const String muteNotifications = 'Mute notifications'; static const String disappearingMessages = 'Disappearing messages'; static const String wallpaper = 'Wallpaper'; static const String more = 'More'; static const String report = 'Report'; static const String block = 'Block'; static const String clearChat = 'Clear chat'; //status static const String statusPrivacy = 'Status privacy'; //call static const String clearCallLog = 'Clear call log'; //setting and profile static const String profile = 'Profile'; static const String name = 'Name'; static const String phone = 'Phone'; static const String about = 'About'; static const String thisIsNotYourUser = 'This is not your username or pin. This name will be visible to your WhatsApp contacts.'; //sender profile static const String muteNotification = 'Mute Notifications'; static const String customNotification = 'Custom Notifications'; static const String mediaVisibility = 'Media visibility'; static const String encryption = 'Encryption'; static const String messagesAndCallsAre = 'Messages and calls are end-to-end encrypted. Tap to verify.'; static const String disappearingMessage = 'Disappearing Messages'; static const String off = 'Off'; static const String call = 'Call'; static const String video = 'Video'; } ================================================ FILE: lib/core/utils/constants/values_manager.dart ================================================ class AppMargin { static const double m8 = 8.0; static const double m12 = 12.0; static const double m14 = 14.0; static const double m16 = 16.0; static const double m18 = 18.0; static const double m20 = 20.0; } class AppPadding { static const double p2 = 2.0; static const double p8 = 8.0; static const double p10 = 10.0; static const double p12 = 12.0; static const double p14 = 14.0; static const double p16 = 16.0; static const double p18 = 18.0; static const double p20 = 20.0; static const double p28 = 28.0; static const double p100 = 100.0; } class AppSize { static const double s0 = 0; static const double s1 = 1; static const int s2 = 2; static const double s1_5 = 1.5; static const double s4 = 4.0; static const double s8 = 8.0; static const double s12 = 12.0; static const double s14 = 14.0; static const double s16 = 16.0; static const double s18 = 18.0; static const double s20 = 20.0; static const double s28 = 28.0; static const double s40 = 40.0; static const double s60 = 60.0; static const double s80 = 80.0; static const double s90 = 90.0; static const double s100 = 100.0; static const double s120 = 120.0; static const double s140 = 140.0; static const double s160 = 160.0; static const double s190 = 190.0; } ================================================ FILE: lib/core/utils/routes/routes_manager.dart ================================================ import 'package:flutter/material.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/call.dart'; import 'package:whatsapp_flutter_clone/features/presentation/views/calls/call_screen.dart'; import 'package:whatsapp_flutter_clone/features/presentation/views/wallpaper/wallpaper_screen.dart'; import '../../../features/domain/entities/user.dart'; import '../../../features/presentation/views/camera/camera_screen.dart'; import '../../../features/presentation/views/camera/sending_image_view_page.dart'; import '../../../features/presentation/views/camera/sending_video_view_page.dart'; import '../../../features/presentation/views/chat/chat_screen.dart'; import '../../../features/presentation/views/login/login_landing_screen.dart'; import '../../../features/presentation/views/login/login_loading_screen.dart'; import '../../../features/presentation/views/login/login_otp_screen.dart'; import '../../../features/presentation/views/login/login_profile_info_screen.dart'; import '../../../features/presentation/views/login/login_screen.dart'; import '../../../features/presentation/views/main_layout/main_layout_screen.dart'; import '../../../features/presentation/views/select_contact/select_contact_screen.dart'; import '../../../features/presentation/views/sender_profile/sender_profile_page.dart'; import '../../../features/presentation/views/settings/profile_screen.dart'; import '../../../features/presentation/views/settings/settings_screen.dart'; import '../../../features/presentation/views/splash/splash_screen.dart'; import '../constants/strings_manager.dart'; class Routes { static const String splashRoute = '/'; static const String landingRoute = '/landing'; static const String loginRoute = '/login'; static const String otpRoute = '/otp'; static const String loginProfileInfoRoute = '/login-profile'; static const String loginLoadingRoute = '/login-loading'; static const String mainLayoutRoute = '/main-layout'; static const String selectContactRoute = '/select-contact'; static const String settingsRoute = '/settings'; static const String chatRoute = '/chat'; static const String cameraRoute = '/camera'; static const String sendingImageViewRoute = '/sending-image-view'; static const String sendingVideoViewRoute = '/sending-video-view'; static const String profileRoute = '/profile'; static const String senderUserProfileRoute = '/sender-profile'; static const String wallpaperRoute = '/wallpaper'; static const String callRoute = '/call-route'; } class AppRoutes { static Route onGenerateRoute(RouteSettings settings) { switch (settings.name) { case Routes.splashRoute: return MaterialPageRoute( builder: (_) => const SplashScreen(), ); case Routes.landingRoute: return MaterialPageRoute( builder: (_) => const LandingScreen(), ); case Routes.loginRoute: return MaterialPageRoute( builder: (_) => const LoginScreen(), ); case Routes.otpRoute: final phoneNumber = settings.arguments as String; return MaterialPageRoute( builder: (_) => OtpScreen(phoneNumber: phoneNumber), ); case Routes.loginProfileInfoRoute: return MaterialPageRoute( builder: (_) => const LoginProfileInfoScreen(), ); case Routes.loginLoadingRoute: return MaterialPageRoute( builder: (_) => const LoginLoadingScreen(), ); case Routes.mainLayoutRoute: return MaterialPageRoute( builder: (_) => const MainLayoutScreen(), ); case Routes.selectContactRoute: return MaterialPageRoute( builder: (_) => const SelectContactScreen(), ); case Routes.settingsRoute: return MaterialPageRoute( builder: (_) => SettingsScreen(), ); case Routes.chatRoute: final arguments =settings.arguments as Map; final String name =arguments['name']; final String uId = arguments['uId']; return MaterialPageRoute( builder: (_) => ChatScreen(name: name,uId: uId,), ); case Routes.cameraRoute: final arguments =settings.arguments as Map; //final String name =arguments['name']; final String uId = arguments['uId']; return MaterialPageRoute( builder: (_) => CameraScreen(receiverId: uId), ); case Routes.sendingImageViewRoute: final arguments =settings.arguments as Map; //final String name =arguments['name']; final String uId = arguments['uId']; final String path = arguments['path']; return MaterialPageRoute( builder: (_) => SendingImageViewPage(path: path,receiverId: uId), ); case Routes.sendingVideoViewRoute: final arguments =settings.arguments as Map; //final String name =arguments['name']; final String uId = arguments['uId']; final String path = arguments['path']; return MaterialPageRoute( builder: (_) => SendingVideoViewPage(path: path,receiverId: uId), ); case Routes.profileRoute: final argument = settings.arguments as UserEntity; return MaterialPageRoute( builder: (_) => ProfileScreen(user: argument), ); case Routes.senderUserProfileRoute: final argument = settings.arguments as UserEntity; return MaterialPageRoute( builder: (_) => SenderUserProfilePage(user: argument), ); case Routes.wallpaperRoute: return MaterialPageRoute( builder: (_) => const WallpaperScreen(), ); case Routes.callRoute: final arguments =settings.arguments as Map; final Call call = arguments['call']; final String channelId = arguments['channelId']; return MaterialPageRoute( builder: (_) => CallScreen(call: call,channelId: channelId), ); default: return unDefinedRoute(); } } static Route unDefinedRoute() { return MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar( title: const Text( AppStrings.noRouteFound, ), ), body: const Center( child: Text( AppStrings.noRouteFound, ), ), ), ); } } ================================================ FILE: lib/core/utils/thems/my_colors.dart ================================================ import 'package:flutter/material.dart'; class MyColors { /// Base Colors final Color black = Colors.black; final Color black1 = const Color(0xFF121212); final Color black2 = const Color(0xFF18171d); // 900 final Color black3 = const Color(0xFF242329); // 900 final Color onyx = const Color(0xFF45444B); // 900 final Color textBlack = const Color(0XFF4F5054); final Color grey = Colors.grey; final Color abbey = const Color(0xFF464648); final Color dimGray = const Color(0xFF666666); final Color textIconColorGray = Colors.grey[300]!; final Color white = Colors.white; final Color white1 = const Color(0xFFFAFAFA); final Color white2 = const Color(0xFFECECF4); final Color transparent = Colors.transparent; final Color shadow = const Color.fromRGBO(33, 22, 156, 0.1); final Color blackLight= const Color(0XFF646464); final Color secondary = const Color(0XFF128C7E); //primary final Color primary = const Color(0XFF008069); //primary2 final Color primary1 = const Color(0XFF1EBEA5); //primaryLight1 final Color lightGreen = const Color(0XFF25D366); final Color teaGreen = const Color(0XFFDCF8C6); final Color checkMarkBlue = const Color(0XFF34B7F1); final Color round = const Color(0XFF25D167);//coloo final Color timeBackgroundColor = const Color(0XFFF0F2F4); } abstract class IColors { MyColors get _colors; Color? scaffoldBackgroundColor; Color? appBarColor; Color? primaryColor; //Color? tabBarColor; //Color? tapBarSelectedColor; //Color? tapBarNormalColor; Brightness? brightness; ColorScheme? colorScheme; } class LightColors implements IColors { @override final MyColors _colors = MyColors(); @override ColorScheme? colorScheme; @override Color? appBarColor; @override Color? scaffoldBackgroundColor; @override Brightness? brightness; @override Color? primaryColor; LightColors() { appBarColor = _colors.white; scaffoldBackgroundColor = _colors.white; primaryColor = _colors.primary; colorScheme = ColorScheme( brightness: Brightness.light, primary: _colors.primary, //to appbar onPrimary: _colors.white1, //to text on appbar primaryContainer: _colors.white2, // to other text on appbar,action icon secondary: _colors.secondary, secondaryContainer: _colors.primary1, //to fab onSecondary: _colors.white1, error: _colors.black, onError: _colors.white1, background: _colors.white1, onBackground: _colors.black1, onSecondaryContainer: _colors.lightGreen,//landing image surface: _colors.teaGreen, onSurface: _colors.dimGray, surfaceVariant: _colors.timeBackgroundColor, onSurfaceVariant: _colors.blackLight, onInverseSurface: _colors.grey, onTertiary: _colors.checkMarkBlue, onTertiaryContainer: _colors.onyx, onPrimaryContainer: _colors.round ); brightness = Brightness.light; } } ================================================ FILE: lib/core/utils/thems/styles_manager.dart ================================================ import 'package:flutter/material.dart'; import '../constants/font_manager.dart'; TextStyle _getTextStyle(double fontSize, FontWeight fontWeight, Color color) { return TextStyle( fontSize: fontSize, fontFamily: FontConstants.fontFamily, color: color, fontWeight: fontWeight, ); } // regular style TextStyle getRegularStyle({ double fontSize = FontSize.s12, required Color color, }) { return _getTextStyle( fontSize, FontWeightManager.regular, color, ); } // medium style TextStyle getMediumStyle({ double fontSize = FontSize.s12, required Color color, }) { return _getTextStyle( fontSize, FontWeightManager.medium, color, ); } // medium style TextStyle getLightStyle({ double fontSize = FontSize.s12, required Color color, }) { return _getTextStyle( fontSize, FontWeightManager.light, color, ); } // bold style TextStyle getBoldStyle({ double fontSize = FontSize.s12, required Color color, }) { return _getTextStyle( fontSize, FontWeightManager.bold, color, ); } // semibold style TextStyle getSemiBoldStyle({ double fontSize = FontSize.s12, required Color color, }) { return _getTextStyle( fontSize, FontWeightManager.semiBold, color, ); } ================================================ FILE: lib/core/utils/thems/theme_manager.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '/core/utils/thems/styles_manager.dart'; import 'my_colors.dart'; import '../constants/font_manager.dart'; import '../constants/values_manager.dart'; ThemeData createTheme(IColors iColors) => ThemeData( scaffoldBackgroundColor: iColors.scaffoldBackgroundColor, primaryColor: iColors.primaryColor, colorScheme: iColors.colorScheme, brightness: iColors.brightness, textTheme: _textTheme(iColors), appBarTheme: _appBarTheme(iColors), popupMenuTheme: _popupMenuThemeData(iColors), tabBarTheme: tabBarTheme(), textButtonTheme: _textButtonThemeData(iColors), elevatedButtonTheme: _elevatedButtonThemeData(iColors), inputDecorationTheme: _inputDecorationTheme(iColors), dividerColor: iColors.colorScheme!.secondaryContainer, progressIndicatorTheme: ProgressIndicatorThemeData( color: iColors.primaryColor, ), ); TextTheme _textTheme(IColors iColors) { return TextTheme( displayLarge: getSemiBoldStyle( color: iColors.colorScheme!.secondary, fontSize: FontSize.s20, ), //for appbar headlineLarge: getBoldStyle( color: iColors.colorScheme!.onPrimary, fontSize: 18, ), bodySmall: getRegularStyle( color: iColors.colorScheme!.primaryContainer, fontSize: 13 ), //for list tile title headlineMedium: getMediumStyle( //color: AppColors.textBlack, color: iColors.colorScheme!.onSurface, fontSize: FontSize.s17, ), labelLarge: getBoldStyle( color: iColors.colorScheme!.onPrimary, fontSize: 14, ), //for list tile subtitle bodyMedium: getMediumStyle( color: iColors.colorScheme!.onInverseSurface, fontSize: FontSize.s14, ), //for time card displaySmall: TextStyle( color: iColors.colorScheme!.onSurfaceVariant, fontSize: 16, fontWeight: FontWeight.w500 ), //////////////////////////////////////////////login titleLarge: getMediumStyle( color: iColors.colorScheme!.onSurface, fontSize: FontSize.s17, ), titleMedium: getMediumStyle( color: iColors.colorScheme!.onInverseSurface, fontSize: FontSize.s14, ), titleSmall: getSemiBoldStyle( //color: AppColors.blackLight, color: iColors.colorScheme!.onSurfaceVariant, fontSize: FontSize.s14, ) ); } AppBarTheme _appBarTheme(IColors colors) { return AppBarTheme( color: colors.colorScheme!.primary, elevation: 0, systemOverlayStyle: SystemUiOverlayStyle( statusBarColor: colors.colorScheme!.primary, statusBarIconBrightness: Brightness.light, ), titleTextStyle: TextStyle( color: colors.colorScheme!.onPrimary, fontSize: AppSize.s20, fontWeight: FontWeightManager.semiBold, ), actionsIconTheme: IconThemeData( color: colors.colorScheme!.primaryContainer, size: FontSize.s26, ), ); } TabBarTheme tabBarTheme() { return const TabBarTheme( labelStyle: TextStyle( fontSize: FontSize.s14, fontWeight: FontWeightManager.bold, ), ); } TextButtonThemeData _textButtonThemeData(IColors colors) { return TextButtonThemeData( style: TextButton.styleFrom( textStyle: TextStyle( color: colors.colorScheme!.secondary, fontWeight: FontWeightManager.medium, ), ), ); } PopupMenuThemeData _popupMenuThemeData(IColors iColors) { return PopupMenuThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(3), ), textStyle: TextStyle( color: iColors.colorScheme!.onSurface, fontWeight: FontWeightManager.medium, fontSize: FontSize.s17, ), ); } InputDecorationTheme _inputDecorationTheme(IColors iColors) { return InputDecorationTheme( hintStyle: TextStyle( color: iColors.colorScheme!.onInverseSurface, ), focusedBorder: UnderlineInputBorder( borderSide: BorderSide( color: iColors.colorScheme!.secondary, width: 2, ), ), enabledBorder: UnderlineInputBorder( borderSide: BorderSide( color: iColors.colorScheme!.secondary, width: 2, ), ), ); } ElevatedButtonThemeData _elevatedButtonThemeData(IColors iColors) { return ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: iColors.colorScheme!.secondary, textStyle: TextStyle( color: iColors.colorScheme!.onSecondary, fontWeight: FontWeightManager.medium, ), padding: const EdgeInsets.symmetric( horizontal: AppSize.s20, vertical: AppSize.s14, ), ), ); } ================================================ FILE: lib/features/data/data_source/auth/local/auth_local_data_source.dart ================================================ import 'package:shared_preferences/shared_preferences.dart'; abstract class BaseAuthLocalDataSource { Future setUserLoggedIn(String uid); Future removeUser(String uid); String? getUser(); } class AuthLocalDataSource extends BaseAuthLocalDataSource { final SharedPreferences sharedPreferences; AuthLocalDataSource({required this.sharedPreferences}); @override Future setUserLoggedIn(String uid) { return sharedPreferences.setString('uid', uid); } @override Future removeUser(String uid) { return sharedPreferences.remove(uid); } @override String? getUser() { return sharedPreferences.getString('uid'); } } ================================================ FILE: lib/features/data/data_source/auth/remote/auth_remote_data_source.dart ================================================ import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/foundation.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../../domain/usecases/auth/sign_in_with_phone_number_usecase.dart'; import '../../../../domain/usecases/auth/verify_otp_usecase.dart'; import '../../../../domain/usecases/auth/save_userdata_to_firebase_usecase.dart'; import '../../../models/user_model.dart'; abstract class BaseAuthRemoteDataSource { Future signInWithPhoneNumber( SignInWithPhoneNumberParameters parameters); Future verifyOtp(VerifyOtpParameters parameters); Future saveUserDataToFirebase(UserDataParameters parameters); Future getCurrentUid(); Future signOut(); Future getCurrentUser(); Stream getUserById(String uId); Future setUserState(bool isOnline); Future updateProfilePic(String path); } class AuthRemoteDataSource extends BaseAuthRemoteDataSource { final FirebaseAuth auth; final FirebaseFirestore firestore; final FirebaseStorage firebaseStorage; String _verificationId = ''; AuthRemoteDataSource({ required this.auth, required this.firestore, required this.firebaseStorage, }); @override Future getCurrentUid() async => auth.currentUser!.uid; @override Future signOut() async => await auth.signOut(); @override Future signInWithPhoneNumber( SignInWithPhoneNumberParameters parameters) async { await auth.verifyPhoneNumber( phoneNumber: parameters.phoneNumber, verificationCompleted: (AuthCredential credential) async { await auth.signInWithCredential(credential); if (kDebugMode) { print("phone verified : Token ${credential.token}"); } }, verificationFailed: (e) { throw Exception(e.message); }, codeSent: (String verificationId, int? resendToken) { _verificationId = verificationId; }, codeAutoRetrievalTimeout: (String verificationId) { //_verificationId = verificationId; if (kDebugMode) { print("time out :$verificationId"); } }, timeout: const Duration(minutes: 1), ); } @override Future verifyOtp(VerifyOtpParameters parameters) async { PhoneAuthCredential credential = PhoneAuthProvider.credential( verificationId: _verificationId, smsCode: parameters.smsOtpCode, ); await auth.signInWithCredential(credential); } @override Future saveUserDataToFirebase(UserDataParameters parameters) async { String uId = await getCurrentUid(); String photoUrl = ''; if (parameters.profilePic != null) { photoUrl = await _storeFileToFirebase( 'profilePic/$uId', parameters.profilePic!, ); } var user = UserModel( name: parameters.name, uId: uId, status: AppStrings.heyThere, profilePic: photoUrl, phoneNumber: auth.currentUser!.phoneNumber!, isOnline: true, groupId: const [], lastSeen: DateTime.now(), ); var userDoc = await firestore.collection('users').doc(uId).get(); if (userDoc.exists) { await firestore.collection('users').doc(uId).update(user.toMap()); } else { await firestore.collection('users').doc(uId).set(user.toMap()); } } Future _storeFileToFirebase(String path, File file) async { UploadTask uploadTask = firebaseStorage.ref().child(path).putFile(file); TaskSnapshot snap = await uploadTask; String downloadUrl = await snap.ref.getDownloadURL(); return downloadUrl; } Future _deleteFileFromFirebase(String path)async{ return await firebaseStorage.refFromURL(path).delete(); } @override Future getCurrentUser() async { var userData = await firestore.collection('users').doc(await getCurrentUid()).get(); UserModel user = UserModel.fromMap(userData.data()!); return user; } @override Stream getUserById(String uId) { print('doneee'); return firestore.collection('users').doc(uId).snapshots().map( (event){ return UserModel.fromMap(event.data()!); }, ); } @override Future setUserState(bool isOnline) async { await firestore.collection('users').doc(auth.currentUser!.uid).update({ 'isOnline': isOnline, 'lastSeen': DateTime.now().millisecondsSinceEpoch, }); } @override Future updateProfilePic(String path)async { String uId = auth.currentUser!.uid; //firstly delete previus image var userData = await firestore.collection('users').doc(uId).get(); UserModel user = UserModel.fromMap(userData.data()!); if(user.profilePic.isNotEmpty){ await _deleteFileFromFirebase(user.profilePic); } //then upload new image String photoUrl = await _storeFileToFirebase( 'profilePic/$uId', File(path), ); await firestore.collection('users').doc(auth.currentUser!.uid).update({ 'profilePic': photoUrl, }); } } ================================================ FILE: lib/features/data/data_source/call/call_data_source.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:uuid/uuid.dart'; import '../../../../core/functions/navigator.dart'; import '../../../../core/utils/routes/routes_manager.dart'; import '/features/data/models/call_model.dart'; import '../../../domain/usecases/call/end_call_usecase.dart'; import '../../../domain/usecases/call/make_call_usecase.dart'; import '../../models/user_model.dart'; abstract class BaseCallDataSource { Future makeCall(MakeCallParameters parameters); Future endCall(EndCallParameters parameters); Stream callStream(); } class CallDataSource extends BaseCallDataSource { final FirebaseFirestore _firestore; final FirebaseAuth _auth; CallDataSource(this._firestore, this._auth); Future _currentUser() async { var userDataMap = await _firestore.collection('users').doc(_auth.currentUser!.uid).get(); UserModel user = UserModel.fromMap(userDataMap.data()!); return user; } @override Stream callStream() => _firestore.collection('call').doc(_auth.currentUser!.uid).snapshots(); @override Future endCall(EndCallParameters parameters) async { await _firestore.collection('call').doc(parameters.callerId).delete(); await _firestore.collection('call').doc(parameters.receiverId).delete(); } @override Future makeCall(MakeCallParameters parameters)async { String callId = const Uuid().v1(); UserModel currentUser = await _currentUser(); CallModel senderCallData = CallModel( callerId: currentUser.uId, callerName: currentUser.name, callerPic: currentUser.profilePic, receiverId: parameters.receiverId, receiverName: parameters.receiverName, receiverPic: parameters.receiverPic, callId: callId, hasDialled: true, ); CallModel receiverCallData = CallModel( callerId: currentUser.uId, callerName: currentUser.name, callerPic: currentUser.profilePic, receiverId: parameters.receiverId, receiverName: parameters.receiverName, receiverPic: parameters.receiverPic, callId: callId, hasDialled: false, ); await _firestore .collection('call') .doc(senderCallData.callerId) .set(senderCallData.toMap()); await _firestore .collection('call') .doc(senderCallData.receiverId) .set(receiverCallData.toMap()); return senderCallData; } } ================================================ FILE: lib/features/data/data_source/chat/local/chat_local_data_source.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:giphy_get/giphy_get.dart'; abstract class BaseChatLocalDataSource{ Future pickGif(BuildContext context); } class ChatLocalDataSource extends BaseChatLocalDataSource{ @override Future pickGif(BuildContext context)async { GiphyGif? gif; gif = await GiphyGet.getGif( context: context, apiKey: 'GmpyPrPF9RVZ1LJY1iAe6O8MTWrBpNob', ); return gif; } } ================================================ FILE: lib/features/data/data_source/chat/remote/chat_remote_data_source.dart ================================================ import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:uuid/uuid.dart'; import '../../../../../core/enums/messge_type.dart'; import '../../../../../core/shared/message_replay.dart'; import '../../../../domain/usecases/chat/get_chat_messages_usecase.dart'; import '../../../../domain/usecases/chat/send_file_message_usecase.dart'; import '../../../../domain/usecases/chat/send_gif_message_usecase.dart'; import '../../../../domain/usecases/chat/send_text_message_usecase.dart'; import '../../../../domain/usecases/chat/set_chat_message_seen_usecase.dart'; import '../../../models/contact_chat_model.dart'; import '../../../models/message_model.dart'; import '../../../models/user_model.dart'; abstract class BaseChatRemoteDataSource { Future sendTextMessage(TextMessageParameters parameters); Future sendFileMessage(FileMessageParameters parameters); Future sendGifMessage(GifMessageParameters parameters); Stream> getChatMessages( GetChatMessagesParameters parameters); Stream> getContactsChat(Map map); Future setChatMessageSeen(SetChatMessageSeenParameters parameters); Stream getNumOfMessageNotSeen(String senderId); } class ChatRemoteDataSource extends BaseChatRemoteDataSource { final FirebaseFirestore _firestore; final FirebaseAuth _auth; final FirebaseStorage firebaseStorage; ChatRemoteDataSource(this._firestore, this._auth, this.firebaseStorage); Future _currentUser() async { var userDataMap = await _firestore.collection('users').doc(_auth.currentUser!.uid).get(); UserModel user = UserModel.fromMap(userDataMap.data()!); return user; } @override Future sendTextMessage(TextMessageParameters parameters) async { UserModel receiverUserData; var timeSent = DateTime.now(); var messageId = const Uuid().v1(); var userDataMap = await _firestore.collection('users').doc(parameters.receiverId).get(); receiverUserData = UserModel.fromMap(userDataMap.data()!); UserModel senderUser = await _currentUser(); /// this is bad to call sender user here i will refactor it. _saveDataToContactsSubCollection( senderUser, receiverUserData, parameters.text, timeSent, ); _saveMessageToMessageSubCollection( senderId: senderUser.uId, receiverId: parameters.receiverId, text: parameters.text, timeSent: timeSent, messageId: messageId, messageType: MessageType.text, messageReplay: parameters.messageReplay, senderUserName: senderUser.name, ); } void _saveDataToContactsSubCollection( UserModel senderUserData, UserModel receiverUserData, String text, DateTime timeSent, ) async { // users -> receiver user id => chats -> current user id -> set data ContactChatModel receiverChatContact = ContactChatModel( name: senderUserData.name, profilePic: senderUserData.profilePic, contactId: senderUserData.uId, lastMessage: text, timeSent: timeSent, phoneNumber: senderUserData.phoneNumber, ); await _firestore .collection('users') .doc(receiverUserData.uId) .collection('chats') .doc(senderUserData.uId) .set(receiverChatContact.toMAp()); // users -> current user id => chats -> receiver user id -> set data ContactChatModel senderChatContact = ContactChatModel( name: receiverUserData.name, profilePic: receiverUserData.profilePic, contactId: receiverUserData.uId, lastMessage: text, timeSent: timeSent, phoneNumber: receiverUserData.phoneNumber, ); await _firestore .collection('users') .doc(senderUserData.uId) .collection('chats') .doc(receiverUserData.uId) .set(senderChatContact.toMAp()); } void _saveMessageToMessageSubCollection({ required String senderId, required String receiverId, required String text, required DateTime timeSent, required String messageId, required MessageType messageType, required MessageReplay? messageReplay, required String senderUserName, }) async { MessageModel message = MessageModel( senderId: senderId, receiverId: receiverId, text: text, messageId: messageId, timeSent: timeSent, isSeen: false, messageType: messageType, repliedMessage: messageReplay == null ? '' : messageReplay.message, senderName: senderUserName, repliedTo: messageReplay == null ? '' : messageReplay.isMe ? senderUserName : messageReplay.repliedTo, repliedMessageType: messageReplay == null ? MessageType.text : messageReplay.messageType, ); // users -> sender id -> chats -> receiver id -> messages ->message id ->store message await _firestore .collection('users') .doc(senderId) .collection('chats') .doc(receiverId) .collection('messages') .doc(messageId) .set(message.toMap()); // users -> receiver id -> chats -> sender id -> messages ->message id ->store message await _firestore .collection('users') .doc(receiverId) .collection('chats') .doc(senderId) .collection('messages') .doc(messageId) .set(message.toMap()); } @override Future sendFileMessage(FileMessageParameters parameters) async { DateTime timeSent = DateTime.now(); String messageId = const Uuid().v1(); UserModel senderUser = await _currentUser(); var fileUrl = await _storeFileToFirebase( 'chat/${parameters.messageType.type}/${senderUser.uId}/${parameters.receiverId}/$messageId}', parameters.file, ); UserModel receiverUserData; var userDataMap = await _firestore.collection('users').doc(parameters.receiverId).get(); receiverUserData = UserModel.fromMap(userDataMap.data()!); String contactMessage; switch (parameters.messageType) { case MessageType.image: contactMessage = '📷 Photo'; break; case MessageType.video: contactMessage = '🎥 Video'; break; case MessageType.audio: contactMessage = '🎙️ Audio'; break; case MessageType.gif: contactMessage = 'Gif'; break; default: contactMessage = 'Other'; } _saveDataToContactsSubCollection( senderUser, receiverUserData, contactMessage, timeSent, ); _saveMessageToMessageSubCollection( senderId: senderUser.uId, receiverId: parameters.receiverId, text: fileUrl, timeSent: timeSent, messageId: messageId, messageType: parameters.messageType, messageReplay: parameters.messageReplay, senderUserName: senderUser.name ); } Future _storeFileToFirebase(String path, File file) async { UploadTask uploadTask = firebaseStorage.ref().child(path).putFile(file); TaskSnapshot snap = await uploadTask; String downloadUrl = await snap.ref.getDownloadURL(); return downloadUrl; } @override Future sendGifMessage(GifMessageParameters parameters) async { UserModel receiverUserData; var timeSent = DateTime.now(); var messageId = const Uuid().v1(); var userDataMap = await _firestore.collection('users').doc(parameters.receiverId).get(); receiverUserData = UserModel.fromMap(userDataMap.data()!); UserModel senderUser = await _currentUser(); _saveDataToContactsSubCollection( senderUser, receiverUserData, 'Gif', timeSent, ); _saveMessageToMessageSubCollection( senderId: senderUser.uId, receiverId: parameters.receiverId, text: parameters.gifUrl, timeSent: timeSent, messageId: messageId, messageType: MessageType.gif, senderUserName: senderUser.name, messageReplay: parameters.messageReplay ); } @override Stream> getChatMessages( GetChatMessagesParameters parameters) { return _firestore .collection('users') .doc(_auth.currentUser!.uid) .collection('chats') .doc(parameters.receiverId) .collection('messages') .orderBy('timeSent') .snapshots() .map((event) { List messages = []; for (var document in event.docs) { messages.add(MessageModel.fromMap(document.data())); } return messages; }); } @override Stream> getContactsChat(Map map) { return _firestore .collection('users') .doc(_auth.currentUser!.uid) .collection('chats') .orderBy('timeSent', descending: true) .snapshots() .asyncMap((event) async{ List contacts = []; for (var document in event.docs) { ContactChatModel contactChat = ContactChatModel.fromMap(document.data()); var userData = await _firestore .collection('users') .doc(contactChat.contactId) .get(); var user = UserModel.fromMap(userData.data()!); contacts.add( ContactChatModel( name: map.containsKey(contactChat.contactId) ? map[contactChat.contactId]['name'] : user.name, profilePic: user.profilePic, contactId: user.uId, lastMessage: contactChat.lastMessage, timeSent: contactChat.timeSent, phoneNumber: contactChat.phoneNumber, ), ); } return contacts; }); } @override Stream getNumOfMessageNotSeen(String senderId){ return _firestore .collection('users') .doc(_auth.currentUser!.uid) .collection('chats') .doc(senderId) .collection('messages') .orderBy('timeSent') .snapshots() .map((event) { int num= 0; for (var document in event.docs) { MessageModel message = MessageModel.fromMap(document.data()); if(message.senderId == senderId){ if(!message.isSeen){ num++; } } } return num; }); } @override Future setChatMessageSeen( SetChatMessageSeenParameters parameters) async { await _firestore .collection('users') .doc(_auth.currentUser!.uid) .collection('chats') .doc(parameters.receiverId) .collection('messages') .doc(parameters.messageId) .update({ 'isSeen': true, }); // users -> receiver id -> chats -> sender id -> messages ->message id ->store message await _firestore .collection('users') .doc(parameters.receiverId) .collection('chats') .doc(_auth.currentUser!.uid) .collection('messages') .doc(parameters.messageId) .update({ 'isSeen': true, }); } } ================================================ FILE: lib/features/data/data_source/firebase_storage_remote_data_source.dart ================================================ import 'dart:io'; import 'package:firebase_storage/firebase_storage.dart'; abstract class BaseFirebaseStorageRemoteDataSource{ Future storeFileToFirebase(String path, File file); } class FirebaseStorageRemoteDataSource extends BaseFirebaseStorageRemoteDataSource{ final FirebaseStorage firebaseStorage; FirebaseStorageRemoteDataSource(this.firebaseStorage); @override Future storeFileToFirebase(String path, File file) async{ UploadTask uploadTask =firebaseStorage.ref().child(path).putFile(file); TaskSnapshot snap = await uploadTask; String downloadUrl = await snap.ref.getDownloadURL(); return downloadUrl; } } ================================================ FILE: lib/features/data/data_source/select_contact/local/get_contacts_local_data_source.dart ================================================ import 'package:flutter_contacts/flutter_contacts.dart'; import '/core/error/exceptions.dart'; abstract class BaseSelectContactsLocalDataSource{ Future> getContacts(); } class SelectContactsLocalDataSource extends BaseSelectContactsLocalDataSource{ @override Future> getContacts()async { if(await FlutterContacts.requestPermission()){ return await FlutterContacts.getContacts(withProperties: true,); }else{ throw(const CachedException(message: 'Not Allowed to get contacts')); } } } ================================================ FILE: lib/features/data/data_source/select_contact/remote/select_contact_remote_data_source.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_contacts/contact.dart'; import '/features/data/models/user_model.dart'; abstract class BaseSelectContactsRemoteDataSource { Future getAllContacts(List contacts); Future> contactsOnWhatsApp(); Future> contactsNotOnWhatsApp(); // Future getContactName(ContactNameParameters parameters); } class SelectContactsRemoteDataSource extends BaseSelectContactsRemoteDataSource { final FirebaseFirestore firestore; final FirebaseAuth _auth; SelectContactsRemoteDataSource(this.firestore, this._auth); List _contactsNotOnWhatsApp = []; Map contactsOnWhatsAppMap = {}; @override Future> contactsNotOnWhatsApp() async => _contactsNotOnWhatsApp; @override Future> contactsOnWhatsApp() async => contactsOnWhatsAppMap; @override Future getAllContacts(List contacts) async { _contactsNotOnWhatsApp = []; //contactsOnWhatsAppMap = {}; Map allContacts; var userCollection = await firestore.collection('users').get(); String phoneNum; bool numFound = false; for (int i = 0; i < contacts.length; i++) { numFound = false; phoneNum = contacts[i].phones.isNotEmpty ? contacts[i].phones[0].number.replaceAll(' ', '') : ''; /* if (userCollection.docs .where((element) => element.data().containsValue(phoneNum)) .isNotEmpty) { print(phoneNum); } */ for (var document in userCollection.docs) { var userData = UserModel.fromMap(document.data()); if (phoneNum == userData.phoneNumber && userData.uId != _auth.currentUser!.uid) { contactsOnWhatsAppMap.addAll({ userData.uId : { 'uId': userData.uId, 'profilePic': userData.profilePic, 'status': userData.status, 'name': contacts[i].displayName, } }); numFound = true; break; } } if (!numFound) _contactsNotOnWhatsApp.add(contacts[i]); } } /* @override Future getContactName(ContactNameParameters parameters) async { if (contactsOnWhatsAppMap.containsKey(parameters.uId)) { return contactsOnWhatsAppMap[parameters.uId]['name']; } var user = await firestore.collection('users').doc(parameters.uId).get(); var userData = UserModel.fromMap(user.data()!); return userData.phoneNumber; } */ } ================================================ FILE: lib/features/data/models/call_model.dart ================================================ import '../../domain/entities/call.dart'; class CallModel extends Call { const CallModel({ required super.callerId, required super.callerName, required super.callerPic, required super.receiverId, required super.receiverName, required super.receiverPic, required super.callId, required super.hasDialled, }); Map toMap() { return { 'callerId': callerId, 'callerName': callerName, 'callerPic': callerPic, 'receiverId': receiverId, 'receiverName': receiverName, 'receiverPic': receiverPic, 'callId': callId, 'hasDialled': hasDialled, }; } factory CallModel.fromMap(Map map) { return CallModel( callerId: map['callerId'] ?? '', callerName: map['callerName'] ?? '', callerPic: map['callerPic'] ?? '', receiverId: map['receiverId'] ?? '', receiverName: map['receiverName'] ?? '', receiverPic: map['receiverPic'] ?? '', callId: map['callId'] ?? '', hasDialled: map['hasDialled'] ?? false, ); } } ================================================ FILE: lib/features/data/models/contact_chat_model.dart ================================================ import '../../domain/entities/contact_chat.dart'; class ContactChatModel extends ContactChat { const ContactChatModel({ required super.name, required super.profilePic, required super.contactId, required super.lastMessage, required super.timeSent, required super.phoneNumber, }); Map toMAp() => { 'name': name, 'profilePic': profilePic, 'contactId': contactId, 'lastMessage': lastMessage, 'timeSent': timeSent.millisecondsSinceEpoch, 'phoneNumber' : phoneNumber, }; factory ContactChatModel.fromMap(Map map) => ContactChatModel( name: map['name'], profilePic: map['profilePic'], contactId: map['contactId'], lastMessage: map['lastMessage'], timeSent: DateTime.fromMillisecondsSinceEpoch(map['timeSent']), phoneNumber: map['phoneNumber'], ); } ================================================ FILE: lib/features/data/models/message_model.dart ================================================ import 'package:whatsapp_flutter_clone/core/enums/messge_type.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/message.dart'; class MessageModel extends Message { const MessageModel({ required super.senderId, required super.receiverId, required super.text, required super.messageId, required super.timeSent, required super.isSeen, required super.messageType, required super.repliedMessage, required super.repliedTo, required super.repliedMessageType, required super.senderName, }); Map toMap() => { 'senderId': senderId, 'receiverId': receiverId, 'text': text, 'messageId': messageId, 'timeSent': timeSent.millisecondsSinceEpoch, 'isSeen': isSeen, 'messageType': messageType.type, 'repliedMessage': repliedMessage, 'repliedTo': repliedTo, 'repliedMessageType': repliedMessageType.type, 'senderName': senderName, }; factory MessageModel.fromMap(Map map) => MessageModel( senderId: map['senderId'], receiverId: map['receiverId'], text: map['text'], messageId: map['messageId'], timeSent: DateTime.fromMillisecondsSinceEpoch(map['timeSent']), isSeen: map['isSeen'], messageType: (map['messageType'] as String).toEnum(), repliedMessage: map['repliedMessage'], repliedTo: map['repliedTo'], repliedMessageType: (map['repliedMessageType'] as String).toEnum(), senderName: map['senderName'], ); } ================================================ FILE: lib/features/data/models/user_model.dart ================================================ import '../../domain/entities/user.dart'; class UserModel extends UserEntity { const UserModel({ required super.name, required super.uId, required super.status, required super.profilePic, required super.phoneNumber, required super.isOnline, required super.groupId, required super.lastSeen, }); Map toMap() { return { 'name': name, 'uId': uId, 'status': status, 'profilePic': profilePic, 'phoneNumber': phoneNumber, 'isOnline': isOnline, 'groupId': groupId, 'lastSeen': lastSeen.millisecondsSinceEpoch, }; } factory UserModel.fromMap(Map map) { return UserModel( name: map['name'] ?? '', uId: map['uId'] ?? '', status: map['status'] ?? '', profilePic: map['profilePic'] ?? '', phoneNumber: map['phoneNumber'], isOnline: map['isOnline'] ?? false, groupId: List.from(map['groupId']), lastSeen: DateTime.fromMillisecondsSinceEpoch(map['lastSeen']), ); } } ================================================ FILE: lib/features/data/repository/auth_repository.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:firebase_auth/firebase_auth.dart'; import '/features/data/models/user_model.dart'; import '/features/domain/entities/user.dart'; import '../../../core/error/exceptions.dart'; import '../../../core/error/failure.dart'; import '../../domain/repository/base_auth_repository.dart'; import '../../domain/usecases/auth/save_userdata_to_firebase_usecase.dart'; import '../../domain/usecases/auth/sign_in_with_phone_number_usecase.dart'; import '../../domain/usecases/auth/verify_otp_usecase.dart'; import '../data_source/auth/local/auth_local_data_source.dart'; import '../data_source/auth/remote/auth_remote_data_source.dart'; class AuthRepository extends BaseAuthRepository { final BaseAuthRemoteDataSource remoteDataSource; final BaseAuthLocalDataSource localDataSource; AuthRepository({ required this.remoteDataSource, required this.localDataSource, }); @override Future> signInWithPhoneNumber( SignInWithPhoneNumberParameters parameters, ) async { final result = await remoteDataSource.signInWithPhoneNumber(parameters); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } @override Future> verifyOtp( VerifyOtpParameters parameters) async { final result = await remoteDataSource.verifyOtp(parameters); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } @override Future> saveUserDataToFirebase( UserDataParameters parameters) async { final result = await remoteDataSource.saveUserDataToFirebase(parameters); localDataSource.setUserLoggedIn(await remoteDataSource.getCurrentUid()); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } @override Future> getCurrentUid() async { final result = await remoteDataSource.getCurrentUid(); //final result = await _getUid(); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } @override Future> signOut() async { final result = await remoteDataSource.signOut(); await localDataSource.removeUser(localDataSource.getUser()!); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } @override Future> getCachedLocalCurrentUid() async { final result = await localDataSource.getUser(); try { return Right(result!); } on CachedException catch (failure) { return Left(CachedFailure(failure.message)); } } @override Future> getCurrentUser() async { final result = await remoteDataSource.getCurrentUser(); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } @override Future> setUserState(bool isOnline) async { final result = await remoteDataSource.setUserState(isOnline); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } @override Stream getUserById(String uId) { return remoteDataSource.getUserById(uId); } @override Future> updateProfilePic(String path) async { final result = await remoteDataSource.updateProfilePic(path); try { return Right(result); } on FirebaseAuthException catch (failure) { return Left(ServerFailure(failure.message!)); } } } ================================================ FILE: lib/features/data/repository/call_repository.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:dartz/dartz.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/call.dart'; import '../../../core/error/failure.dart'; import '../../domain/repository/base_call_repository.dart'; import '../../domain/usecases/call/end_call_usecase.dart'; import '../../domain/usecases/call/make_call_usecase.dart'; import '../data_source/call/call_data_source.dart'; class CallRepository extends BaseCallRepository{ final CallDataSource _callDataSource; CallRepository(this._callDataSource); @override Stream> callStream() => _callDataSource.callStream(); @override Future> endCall(EndCallParameters parameters) async{ final result = await _callDataSource.endCall(parameters); try{ return Right(result); }on FirebaseException catch(failure){ return Left(ServerFailure(failure.message!)); } } @override Future> makeCall(MakeCallParameters parameters)async { final result = await _callDataSource.makeCall(parameters); try{ return Right(result); }on FirebaseException catch(failure){ return Left(ServerFailure(failure.message!)); } } } ================================================ FILE: lib/features/data/repository/chat_repository.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:dartz/dartz.dart'; import '../../../core/error/failure.dart'; import '../../domain/entities/contact_chat.dart'; import '../../domain/entities/message.dart'; import '../../domain/repository/base_chat_repository.dart'; import '../../domain/usecases/chat/get_chat_messages_usecase.dart'; import '../../domain/usecases/chat/send_file_message_usecase.dart'; import '../../domain/usecases/chat/send_gif_message_usecase.dart'; import '../../domain/usecases/chat/send_text_message_usecase.dart'; import '../../domain/usecases/chat/set_chat_message_seen_usecase.dart'; import '../data_source/chat/remote/chat_remote_data_source.dart'; class ChatRepository extends BaseChatRepository { final BaseChatRemoteDataSource _remoteDataSource; //final BaseSelectContactRepository _selectContactRepository; ChatRepository(this._remoteDataSource); @override Stream> getChatMessages(GetChatMessagesParameters parameters) { return _remoteDataSource.getChatMessages(parameters); } @override Stream> getContactsChat(Map map) { return _remoteDataSource.getContactsChat(map); } @override Future> sendFileMessage(FileMessageParameters parameters)async { final result = await _remoteDataSource.sendFileMessage(parameters); try{ return Right(result); }on FirebaseException catch(failure){ return Left(ServerFailure(failure.message!)); } } @override Future> sendTextMessage(TextMessageParameters parameters) async { final result = await _remoteDataSource.sendTextMessage(parameters); try{ return Right(result); }on FirebaseException catch(failure){ return Left(ServerFailure(failure.message!)); } } @override Future> setChatMessageSeen(SetChatMessageSeenParameters parameters)async { final result = await _remoteDataSource.setChatMessageSeen(parameters); try{ return Right(result); }on FirebaseException catch(failure){ return Left(ServerFailure(failure.message!)); } } @override Future> sendGifMessage(GifMessageParameters parameters)async { final result = await _remoteDataSource.sendGifMessage(parameters); try{ return Right(result); }on FirebaseException catch(failure){ return Left(ServerFailure(failure.message!)); } } @override Stream getNumOfMessageNotSeen(String senderId) { return _remoteDataSource.getNumOfMessageNotSeen(senderId); } } ================================================ FILE: lib/features/data/repository/select_contact_Repository.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_contacts/contact.dart'; import '../../../core/error/exceptions.dart'; import '../../../core/error/failure.dart'; import '../../domain/repository/base_select_contact_repository.dart'; import '../data_source/select_contact/local/get_contacts_local_data_source.dart'; import '../data_source/select_contact/remote/select_contact_remote_data_source.dart'; class SelectContactRepository extends BaseSelectContactRepository{ final BaseSelectContactsRemoteDataSource remoteDataSource; final BaseSelectContactsLocalDataSource localDataSource; SelectContactRepository(this.remoteDataSource, this.localDataSource); @override Future> getAllContacts() async{ final result = await remoteDataSource.getAllContacts(await localDataSource.getContacts()); try{ return Right(result); }on CachedException catch(failure){ return Left(CachedFailure(failure.message)); }on FirebaseAuthException catch(failure){ return Left(ServerFailure(failure.message!)); } } @override Future>> getContactsNotOnWhatsApp()async { final result = await remoteDataSource.contactsNotOnWhatsApp(); try{ return Right(result); }on FirebaseAuthException catch(failure){ return Left(ServerFailure(failure.message!)); } } @override Future>> getContactsOnWhatsApp()async { final result = await remoteDataSource.contactsOnWhatsApp(); try{ return Right(result); }on FirebaseAuthException catch(failure){ return Left(ServerFailure(failure.message!)); } } /* @override Future> getContactName(ContactNameParameters parameters)async { final result = await remoteDataSource.getContactName(parameters); try{ return Right(result); }on CachedException catch(failure){ return Left(CachedFailure(failure.message)); } } */ } ================================================ FILE: lib/features/domain/entities/call.dart ================================================ import 'package:equatable/equatable.dart'; class Call extends Equatable { final String callerId; final String callerName; final String callerPic; final String receiverId; final String receiverName; final String receiverPic; final String callId; final bool hasDialled; const Call({ required this.callerId, required this.callerName, required this.callerPic, required this.receiverId, required this.receiverName, required this.receiverPic, required this.callId, required this.hasDialled, }); @override List get props => [ callerId, callerName, callerPic, receiverId, receiverName, receiverPic, callId, hasDialled, ]; } ================================================ FILE: lib/features/domain/entities/contact_chat.dart ================================================ import 'package:equatable/equatable.dart'; class ContactChat extends Equatable { final String name; final String profilePic; final String contactId; final String lastMessage; final DateTime timeSent; final String phoneNumber; const ContactChat({ required this.name, required this.profilePic, required this.contactId, required this.lastMessage, required this.timeSent, required this.phoneNumber, }); @override List get props => [ name, profilePic, contactId, lastMessage, timeSent, phoneNumber, ]; } ================================================ FILE: lib/features/domain/entities/message.dart ================================================ import 'package:equatable/equatable.dart'; import '../../../core/enums/messge_type.dart'; class Message extends Equatable { final String senderId; final String receiverId; final String senderName; final String text; final String messageId; final DateTime timeSent; final bool isSeen; final MessageType messageType; //replay message final String repliedMessage; final String repliedTo; final MessageType repliedMessageType; const Message({ required this.senderId, required this.receiverId, required this.text, required this.messageId, required this.timeSent, required this.isSeen, required this.messageType, required this.repliedMessage, required this.repliedTo, required this.repliedMessageType, required this.senderName, }); @override List get props => [ senderId, receiverId, text, messageId, timeSent, isSeen, messageType, repliedMessage, repliedTo, repliedMessageType, senderName, ]; } ================================================ FILE: lib/features/domain/entities/user.dart ================================================ import 'package:equatable/equatable.dart'; class UserEntity extends Equatable { final String name; final String uId; final String status; final String profilePic; final String phoneNumber; final bool isOnline; final List groupId; final DateTime lastSeen; const UserEntity({ required this.name, required this.uId, required this.status, required this.profilePic, required this.phoneNumber, required this.isOnline, required this.groupId, required this.lastSeen, }); @override List get props => [ name, uId, status, profilePic, phoneNumber, isOnline, groupId, lastSeen, ]; } ================================================ FILE: lib/features/domain/repository/base_auth_repository.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../core/error/failure.dart'; import '../entities/user.dart'; import '../usecases/auth/save_userdata_to_firebase_usecase.dart'; import '../usecases/auth/sign_in_with_phone_number_usecase.dart'; import '../usecases/auth/verify_otp_usecase.dart'; abstract class BaseAuthRepository{ Future> signInWithPhoneNumber(SignInWithPhoneNumberParameters parameters); Future> verifyOtp(VerifyOtpParameters parameters); Future> saveUserDataToFirebase(UserDataParameters parameters); Future> getCurrentUid(); Future> getCachedLocalCurrentUid(); Future> signOut(); Future> getCurrentUser(); Stream getUserById(String uId); Future> setUserState(bool isOnline); Future> updateProfilePic(String path); } ================================================ FILE: lib/features/domain/repository/base_call_repository.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:dartz/dartz.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/call.dart'; import '../../../core/error/failure.dart'; import '../usecases/call/end_call_usecase.dart'; import '../usecases/call/make_call_usecase.dart'; abstract class BaseCallRepository{ Future> makeCall(MakeCallParameters parameters); Future> endCall(EndCallParameters parameters); Stream callStream(); } ================================================ FILE: lib/features/domain/repository/base_chat_repository.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../core/error/failure.dart'; import '../entities/contact_chat.dart'; import '../entities/message.dart'; import '../usecases/chat/get_chat_messages_usecase.dart'; import '../usecases/chat/send_file_message_usecase.dart'; import '../usecases/chat/send_gif_message_usecase.dart'; import '../usecases/chat/send_text_message_usecase.dart'; import '../usecases/chat/set_chat_message_seen_usecase.dart'; abstract class BaseChatRepository{ Future> sendTextMessage(TextMessageParameters parameters); Future> sendFileMessage(FileMessageParameters parameters); Future> sendGifMessage(GifMessageParameters parameters); Stream> getContactsChat(Map map); Stream> getChatMessages(GetChatMessagesParameters parameters); Future> setChatMessageSeen(SetChatMessageSeenParameters parameters); Stream getNumOfMessageNotSeen(String senderId); } ================================================ FILE: lib/features/domain/repository/base_select_contact_repository.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:flutter_contacts/contact.dart'; import '../../../core/error/failure.dart'; import '../usecases/select_contact/get_contact_name_usecase.dart'; abstract class BaseSelectContactRepository{ Future> getAllContacts(); Future>> getContactsNotOnWhatsApp(); Future>> getContactsOnWhatsApp(); //Future> getContactName(ContactNameParameters parameters); } ================================================ FILE: lib/features/domain/usecases/auth/get_cached_local_current_uid_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_auth_repository.dart'; class GetCachedLocalCurrentUidUseCase extends BaseUseCase{ final BaseAuthRepository baseFirebaseRepository; GetCachedLocalCurrentUidUseCase(this.baseFirebaseRepository); @override Future> call(NoParameters parameters)async { return await baseFirebaseRepository.getCachedLocalCurrentUid(); } } ================================================ FILE: lib/features/domain/usecases/auth/get_current_uid_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_auth_repository.dart'; class GetCurrentUidUseCase extends BaseUseCase{ final BaseAuthRepository baseFirebaseRepository; GetCurrentUidUseCase(this.baseFirebaseRepository); @override Future> call(NoParameters parameters) async{ return await baseFirebaseRepository.getCurrentUid(); } } ================================================ FILE: lib/features/domain/usecases/auth/get_current_user_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:whatsapp_flutter_clone/core/error/failure.dart'; import 'package:whatsapp_flutter_clone/core/usecase/base_use_case.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/user.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_auth_repository.dart'; class GetCurrentUserUseCase extends BaseUseCase{ final BaseAuthRepository _authRepository; GetCurrentUserUseCase(this._authRepository); @override Future> call(NoParameters parameters) async{ return await _authRepository.getCurrentUser(); } } ================================================ FILE: lib/features/domain/usecases/auth/get_user_by_id_usecase.dart ================================================ import '../../../../core/usecase/base_use_case.dart'; import '../../entities/user.dart'; import '../../repository/base_auth_repository.dart'; class GetUserByIdUseCase extends StreamBaseUseCase { final BaseAuthRepository _baseAuthRepository; GetUserByIdUseCase(this._baseAuthRepository); @override Stream call(String parameters) { return _baseAuthRepository.getUserById(parameters); } } ================================================ FILE: lib/features/domain/usecases/auth/save_userdata_to_firebase_usecase.dart ================================================ import 'dart:io'; import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_auth_repository.dart'; class SaveUserDataToFirebaseUseCase extends BaseUseCase{ final BaseAuthRepository baseFirebaseRepository; SaveUserDataToFirebaseUseCase(this.baseFirebaseRepository); @override Future> call(UserDataParameters parameters) async{ return await baseFirebaseRepository.saveUserDataToFirebase(parameters); } } class UserDataParameters extends Equatable { final String name; final File? profilePic; const UserDataParameters({required this.name, this.profilePic}); @override List get props => [ name, profilePic, ]; } ================================================ FILE: lib/features/domain/usecases/auth/set_user_state_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_auth_repository.dart'; class SetUserStateUseCase extends BaseUseCase{ final BaseAuthRepository _baseAuthRepository; SetUserStateUseCase(this._baseAuthRepository); @override Future> call(bool parameters) async{ return await _baseAuthRepository.setUserState(parameters); } } ================================================ FILE: lib/features/domain/usecases/auth/sign_in_with_phone_number_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_auth_repository.dart'; class SignInWithPhoneNumberUseCase extends BaseUseCase { final BaseAuthRepository baseFirebaseRepository; SignInWithPhoneNumberUseCase(this.baseFirebaseRepository); @override Future> call(SignInWithPhoneNumberParameters parameters) async{ return await baseFirebaseRepository.signInWithPhoneNumber(parameters); } } class SignInWithPhoneNumberParameters extends Equatable { final String phoneNumber; const SignInWithPhoneNumberParameters({required this.phoneNumber}); @override List get props => [phoneNumber]; } ================================================ FILE: lib/features/domain/usecases/auth/signout_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_auth_repository.dart'; class SignOutUseCase extends BaseUseCase{ final BaseAuthRepository baseFirebaseRepository; SignOutUseCase(this.baseFirebaseRepository); @override Future> call(NoParameters parameters)async { return await baseFirebaseRepository.signOut(); } } ================================================ FILE: lib/features/domain/usecases/auth/update_profile_pic_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:whatsapp_flutter_clone/core/error/failure.dart'; import 'package:whatsapp_flutter_clone/core/usecase/base_use_case.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_auth_repository.dart'; class UpdateProfilePicUseCase extends BaseUseCase{ final BaseAuthRepository _baseAuthRepository; UpdateProfilePicUseCase(this._baseAuthRepository); @override Future> call(String parameters)async { return await _baseAuthRepository.updateProfilePic(parameters); } } ================================================ FILE: lib/features/domain/usecases/auth/verify_otp_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_auth_repository.dart'; class VerifyOtpUseCase extends BaseUseCase { final BaseAuthRepository baseFirebaseRepository; VerifyOtpUseCase(this.baseFirebaseRepository); @override Future> call(VerifyOtpParameters parameters)async { return await baseFirebaseRepository.verifyOtp(parameters); } } class VerifyOtpParameters extends Equatable { final String smsOtpCode; const VerifyOtpParameters({required this.smsOtpCode}); @override List get props => [smsOtpCode]; } ================================================ FILE: lib/features/domain/usecases/call/call_stream_usecase.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_call_repository.dart'; class CallStreamUseCase extends StreamBaseUseCase{ final BaseCallRepository _baseCallRepository; CallStreamUseCase(this._baseCallRepository); @override Stream call(NoParameters parameters) { return _baseCallRepository.callStream(); } } ================================================ FILE: lib/features/domain/usecases/call/end_call_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_call_repository.dart'; class EndCallUseCase extends BaseUseCase { final BaseCallRepository _baseCallRepository; EndCallUseCase(this._baseCallRepository); @override Future> call(EndCallParameters parameters) async { return await _baseCallRepository.endCall(parameters); } } class EndCallParameters extends Equatable { final String callerId; final String receiverId; const EndCallParameters({ required this.callerId, required this.receiverId, }); @override List get props => [ callerId, receiverId, ]; } ================================================ FILE: lib/features/domain/usecases/call/make_call_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/call.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_call_repository.dart'; class MakeCallUseCase extends BaseUseCase { final BaseCallRepository _baseCallRepository; MakeCallUseCase(this._baseCallRepository); @override Future> call(MakeCallParameters parameters) async { return await _baseCallRepository.makeCall(parameters); } } class MakeCallParameters extends Equatable { final String receiverId; final String receiverName; final String receiverPic; const MakeCallParameters({ required this.receiverId, required this.receiverName, required this.receiverPic, }); @override List get props => [ receiverId, receiverName, receiverPic, ]; } ================================================ FILE: lib/features/domain/usecases/chat/get_chat_messages_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../entities/message.dart'; import '../../repository/base_chat_repository.dart'; /* class GetChatMessagesUseCase extends StreamBaseUseCase, NoParameters>{ final BaseChatRepository _baseChatRepository; GetChatMessagesUseCase(this._baseChatRepository); @override Stream>> call(NoParameters parameters) { return _baseChatRepository.getChatMessages(); } } */ class GetChatMessagesUseCase extends StreamBaseUseCase, GetChatMessagesParameters>{ final BaseChatRepository _baseChatRepository; GetChatMessagesUseCase(this._baseChatRepository); @override Stream> call(GetChatMessagesParameters parameters) { return _baseChatRepository.getChatMessages(parameters); } } class GetChatMessagesParameters extends Equatable { final String receiverId; const GetChatMessagesParameters( {required this.receiverId,}); @override List get props => [ receiverId, ]; } ================================================ FILE: lib/features/domain/usecases/chat/get_contacts_chat_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../entities/contact_chat.dart'; import '../../repository/base_chat_repository.dart'; class GetContactsChatUseCase extends StreamBaseUseCase,Map >{ final BaseChatRepository _baseChatRepository; GetContactsChatUseCase(this._baseChatRepository); @override Stream> call(Map parameters) { return _baseChatRepository.getContactsChat(parameters); } } ================================================ FILE: lib/features/domain/usecases/chat/get_num_of_message_not_seen_usecase.dart ================================================ import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_chat_repository.dart'; class GetNumberOfMessageNotSeenUseCase extends StreamBaseUseCase{ final BaseChatRepository _baseChatRepository; GetNumberOfMessageNotSeenUseCase(this._baseChatRepository); @override Stream call(String parameters) { return _baseChatRepository.getNumOfMessageNotSeen(parameters); } } ================================================ FILE: lib/features/domain/usecases/chat/send_file_message_usecase.dart ================================================ import 'dart:io'; import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import 'package:whatsapp_flutter_clone/core/enums/messge_type.dart'; import 'package:whatsapp_flutter_clone/core/error/failure.dart'; import 'package:whatsapp_flutter_clone/core/shared/message_replay.dart'; import 'package:whatsapp_flutter_clone/core/usecase/base_use_case.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_chat_repository.dart'; class SendFileMessageUseCase extends BaseUseCase { final BaseChatRepository _baseChatRepository; SendFileMessageUseCase(this._baseChatRepository); @override Future> call(FileMessageParameters parameters) async { return await _baseChatRepository.sendFileMessage(parameters); } } class FileMessageParameters extends Equatable { final String receiverId; final MessageType messageType; final File file; final MessageReplay? messageReplay; const FileMessageParameters({ required this.receiverId, required this.messageType, required this.file, this.messageReplay, }); @override List get props => [ receiverId, messageType, file, messageReplay, ]; } ================================================ FILE: lib/features/domain/usecases/chat/send_gif_message_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import 'package:whatsapp_flutter_clone/core/error/failure.dart'; import 'package:whatsapp_flutter_clone/core/shared/message_replay.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_chat_repository.dart'; import '../../../../core/usecase/base_use_case.dart'; class SendGifMessageUseCase extends BaseUseCase { final BaseChatRepository _baseChatRepository; SendGifMessageUseCase(this._baseChatRepository); @override Future> call(GifMessageParameters parameters) async { return await _baseChatRepository.sendGifMessage(parameters); } } class GifMessageParameters extends Equatable { final String receiverId; final String gifUrl; final MessageReplay? messageReplay; const GifMessageParameters({ required this.receiverId, required this.gifUrl, this.messageReplay, }); @override List get props => [ receiverId, gifUrl, messageReplay, ]; } ================================================ FILE: lib/features/domain/usecases/chat/send_text_message_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import 'package:whatsapp_flutter_clone/core/shared/message_replay.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_chat_repository.dart'; class SendTextMessageUseCase extends BaseUseCase { final BaseChatRepository _baseChatRepository; SendTextMessageUseCase(this._baseChatRepository); @override Future> call(TextMessageParameters parameters) async { return await _baseChatRepository.sendTextMessage(parameters); } } class TextMessageParameters extends Equatable { final String text; final String receiverId; final MessageReplay? messageReplay; //final UserModel senderUser; const TextMessageParameters({ required this.receiverId, required this.text, this.messageReplay, }); @override List get props => [ text, receiverId, messageReplay, //senderUser, ]; } ================================================ FILE: lib/features/domain/usecases/chat/set_chat_message_seen_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import 'package:whatsapp_flutter_clone/core/error/failure.dart'; import 'package:whatsapp_flutter_clone/core/usecase/base_use_case.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_chat_repository.dart'; class SetChatMessageSeenUseCase extends BaseUseCase{ final BaseChatRepository _baseChatRepository; SetChatMessageSeenUseCase(this._baseChatRepository); @override Future> call(SetChatMessageSeenParameters parameters)async { return await _baseChatRepository.setChatMessageSeen(parameters); } } class SetChatMessageSeenParameters extends Equatable{ final String receiverId; final String messageId; const SetChatMessageSeenParameters({required this.receiverId, required this.messageId}); @override List get props => [receiverId,messageId,]; } ================================================ FILE: lib/features/domain/usecases/select_contact/get_all_contacts_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_select_contact_repository.dart'; class GetAllContactsUseCase extends BaseUseCase{ final BaseSelectContactRepository _baseSelectContactRepository; GetAllContactsUseCase(this._baseSelectContactRepository); @override Future> call(NoParameters parameters)async { return await _baseSelectContactRepository.getAllContacts(); } } ================================================ FILE: lib/features/domain/usecases/select_contact/get_contact_name_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import 'package:whatsapp_flutter_clone/core/error/failure.dart'; import 'package:whatsapp_flutter_clone/core/usecase/base_use_case.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_select_contact_repository.dart'; /* class GetContactNameUseCase extends BaseUseCase{ final BaseSelectContactRepository _selectContactRepository; GetContactNameUseCase(this._selectContactRepository); @override Future> call(ContactNameParameters parameters)async { return await _selectContactRepository.getContactName(parameters); } } class ContactNameParameters extends Equatable{ final String uId; const ContactNameParameters(this.uId); @override List get props => [uId]; } */ ================================================ FILE: lib/features/domain/usecases/select_contact/get_contacts_not_on_whats_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import 'package:flutter_contacts/contact.dart'; import 'package:whatsapp_flutter_clone/core/error/failure.dart'; import 'package:whatsapp_flutter_clone/core/usecase/base_use_case.dart'; import 'package:whatsapp_flutter_clone/features/domain/repository/base_select_contact_repository.dart'; class GetContactsNotOnWhatsUseCase extends BaseUseCase, NoParameters>{ final BaseSelectContactRepository _baseSelectContactRepository; GetContactsNotOnWhatsUseCase(this._baseSelectContactRepository); @override Future>> call(NoParameters parameters) async{ return await _baseSelectContactRepository.getContactsNotOnWhatsApp(); } } ================================================ FILE: lib/features/domain/usecases/select_contact/get_contacts_on_whats_usecase.dart ================================================ import 'package:dartz/dartz.dart'; import '../../../../core/error/failure.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../repository/base_select_contact_repository.dart'; class GetContactsOnWhatsUseCase extends BaseUseCase, NoParameters>{ final BaseSelectContactRepository _baseSelectContactRepository; GetContactsOnWhatsUseCase(this._baseSelectContactRepository); @override Future>> call(NoParameters parameters)async { return await _baseSelectContactRepository.getContactsOnWhatsApp(); } } ================================================ FILE: lib/features/presentation/components/contact_profile_pic_dialog.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../core/utils/constants/assets_manager.dart'; import '../../domain/entities/contact_chat.dart'; Future showContactProfilePicDialog( BuildContext context, { required ContactChat contact, }) { return showDialog( context: context, builder: (context) { return AlertDialog( scrollable: true, contentPadding: EdgeInsets.zero, alignment: Alignment.topCenter, content: Stack( children: [ Hero( tag: contact.contactId, child: CachedNetworkImage( width: 400, height: 300, imageUrl: contact.profilePic, placeholder: (context, url) => Stack( children: [ Image.asset(AppImage.genericProfileImage), const Align( alignment: Alignment.center, child: CircularProgressIndicator(), ) ], ), errorWidget: (context, url, error) => Image.asset( AppImage.genericProfileImage, ), //height: 180.0, fit: BoxFit.cover, ), ), Positioned( top: 0, right: 0, left: 0, child: Container( padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 8, ), color: Colors.black26, child: Text( contact.name, style: const TextStyle( color: Colors.white, fontSize: 20, ), ), ), ) ], ), actionsAlignment: MainAxisAlignment.spaceBetween, actionsPadding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), actions: [ IconButton( onPressed: () {}, icon: Icon( Icons.message, color: context.colorScheme.secondary, size: 25, ), ), IconButton( onPressed: () {}, icon: Icon( Icons.call, color: context.colorScheme.primary, size: 25, ), ), IconButton( onPressed: () {}, icon: Icon( Icons.videocam, color: context.colorScheme.primary, size: 25, ), ), IconButton( onPressed: () {}, icon: Icon( Icons.info_outline_rounded, color: context.colorScheme.primary, size: 25, ), ), ], ); }, ); } ================================================ FILE: lib/features/presentation/components/custom_list_tile.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../core/utils/constants/assets_manager.dart'; class CustomListTile extends StatelessWidget { final Widget? leading; final String title; final String? subTitle; final String? time; final Widget? titleButton; final int numOfMessageNotSeen; final VoidCallback onTap; final VoidCallback? onLeadingTap; const CustomListTile({ super.key, this.leading, required this.title, this.subTitle, this.time, this.numOfMessageNotSeen = 0, this.titleButton, required this.onTap, this.onLeadingTap, }); @override Widget build(BuildContext context) { return ListTile( onTap: onTap, leading: leading ?? InkWell( onTap: onLeadingTap, child: CircleAvatar( radius: 20, backgroundColor: Colors.white, child: Image.asset(AppImage.genericProfileImage), ), ), title: Row( children: [ Expanded( child: Text( title, //style: context.headlineSmall, style: context.headlineMedium, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), if (time != null) Text( time!, style: numOfMessageNotSeen>0 ? context.labelLarge!.copyWith(color: context.colorScheme.onPrimaryContainer) : context.bodyMedium, ), if (titleButton != null) SizedBox( height: 40, child: titleButton!, ), ], ), subtitle: subTitle != null ? Padding( padding: const EdgeInsets.only(top: 3), child: Row( children: [ //Icon(Icons.done_all,size: 20,), Expanded( child: Text( subTitle!, style: context.bodyMedium, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), if ( numOfMessageNotSeen >0 ) CircleAvatar( minRadius: 12, backgroundColor: context.colorScheme.onPrimaryContainer, child: Padding( padding: const EdgeInsets.all(4.0), child: Text( numOfMessageNotSeen.toString(), style: context.labelLarge, ), ), ), ], ), ) : null, ); } } ================================================ FILE: lib/features/presentation/components/custom_network_image.dart ================================================ import 'package:flutter/material.dart'; import '../../../core/utils/constants/assets_manager.dart'; class CustomNetworkImage extends StatelessWidget { final String imageUrl; const CustomNetworkImage({super.key, required this.imageUrl}); @override Widget build(BuildContext context) { return ClipOval( child: CircleAvatar( backgroundColor: Colors.white, backgroundImage: const AssetImage(AppImage.genericProfileImage), child: Image.network( imageUrl ?? '', errorBuilder: ((context, error, stackTrace) => const SizedBox()), ), ), ); } } ================================================ FILE: lib/features/presentation/components/custom_pop_up_menu_button.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/shared/pop_up_menu_item_model.dart'; class CustomPopUpMenuButton extends StatelessWidget { final List buttons; const CustomPopUpMenuButton({ super.key, required this.buttons, }); @override Widget build(BuildContext context) { return PopupMenuButton( icon: const Icon(Icons.more_vert), onSelected: (value){ buttons[value].onTap(); }, itemBuilder: (context) { return buttons.map((e) { int index = buttons.indexOf(e); return PopupMenuItem( value: index, child: FittedBox( fit: BoxFit.fill, child: Text(e.name), ), ); }).toList(); }, ); } } ================================================ FILE: lib/features/presentation/components/custom_text.dart ================================================ import 'package:flutter/material.dart'; class CustomText extends StatelessWidget { final String text; final TextTheme textStyle; const CustomText({super.key, required this.text, required this.textStyle}); @override Widget build(BuildContext context) { ThemeData theme =Theme.of(context); return Text( text, style: theme.textTheme.bodyMedium ); } } ================================================ FILE: lib/features/presentation/components/default_button.dart ================================================ import 'package:flutter/material.dart'; class DefaultButton extends StatelessWidget { final String text; final VoidCallback onPress; final double? width; const DefaultButton({ super.key, required this.text, required this.onPress, this.width, }); @override Widget build(BuildContext context) { return SizedBox( width: width, child: ElevatedButton( onPressed: onPress, child: Text(text), ), ); } } ================================================ FILE: lib/features/presentation/components/loader.dart ================================================ import 'package:flutter/material.dart'; class Loader extends StatelessWidget { const Loader({super.key}); @override Widget build(BuildContext context) { return const Center( child: CircularProgressIndicator(), ); } } ================================================ FILE: lib/features/presentation/components/my_cached_net_image.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import '../../../core/utils/constants/assets_manager.dart'; class MyCachedNetImage extends StatelessWidget { final String imageUrl; final double radius; const MyCachedNetImage({ super.key, required this.imageUrl, required this.radius, }); @override Widget build(BuildContext context) { return ClipOval( child: CachedNetworkImage( imageUrl: imageUrl, width: radius * 2, height: radius * 2, placeholder: (context, url) => Stack( children: [ Image.asset(AppImage.genericProfileImage), const Align( alignment: Alignment.center, child: CircularProgressIndicator(), ) ], ), errorWidget: (context, url, error) => Image.asset(AppImage.genericProfileImage), //height: 180.0, fit: BoxFit.cover, ), ); } } ================================================ FILE: lib/features/presentation/components/update_profile_pic_model_bottom_sheet.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import '../controllers/auth_cubit/auth_cubit.dart'; import '/core/extensions/extensions.dart'; import '../../../core/functions/navigator.dart'; import '../../../core/shared/commen.dart'; import '../../../core/utils/constants/font_manager.dart'; import '../../../core/utils/constants/strings_manager.dart'; Future showUpdateProfilePicModelBottomSheet(BuildContext context) { return showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(10), topRight: Radius.circular(10), ), ), builder: (context) { return Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( AppStrings.profilePhoto, style: context.titleLarge, ), Row( children: [ PickProfileImage( onTap: () {}, icon: Icons.photo_camera, name: AppStrings.camera, ), const SizedBox(width: 40), PickProfileImage( onTap: () { selectImageFromGallery(context); }, icon: Icons.photo, name: AppStrings.gallery, ), ], ) ], ), ); }, ); } void selectImageFromGallery(BuildContext context) async { File? image = await pickImageFromGallery(context); if(image != null){ cropImage(image.path).then((value) { if(value != null) { AuthCubit.get(context).updateProfilePic(value.path); } navigatePop(context); }); } } class PickProfileImage extends StatelessWidget { final VoidCallback onTap; final String name; final IconData icon; const PickProfileImage({ super.key, required this.onTap, required this.name, required this.icon, }); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only( bottom: 10, top: 30, ), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.grey.shade200, ), ), child: Icon( icon, color: context.colorScheme.secondary, ), ), ), Text( name, style: context.titleMedium!.copyWith( fontWeight: FontWeightManager.regular, ), ), ], ); } } ================================================ FILE: lib/features/presentation/controllers/auth_cubit/auth_cubit.dart ================================================ import 'dart:io'; import 'package:country_picker/country_picker.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../../domain/entities/user.dart'; import '../../../domain/usecases/auth/get_cached_local_current_uid_usecase.dart'; import '../../../domain/usecases/auth/get_current_user_usecase.dart'; import '../../../domain/usecases/auth/get_user_by_id_usecase.dart'; import '../../../domain/usecases/auth/save_userdata_to_firebase_usecase.dart'; import '../../../domain/usecases/auth/set_user_state_usecase.dart'; import '../../../domain/usecases/auth/signout_usecase.dart'; import '../../../domain/usecases/auth/sign_in_with_phone_number_usecase.dart'; import '../../../domain/usecases/auth/update_profile_pic_usecase.dart'; import '../../../domain/usecases/auth/verify_otp_usecase.dart'; part 'auth_state.dart'; class AuthCubit extends Cubit { final SignInWithPhoneNumberUseCase _signInWithPhoneNumberUseCase; final VerifyOtpUseCase _verifyOtpUseCase; final SaveUserDataToFirebaseUseCase _saveUserDataToFirebaseUseCase; final SignOutUseCase _signOutUseCase; final GetCachedLocalCurrentUidUseCase _cachedLocalCurrentUid; final GetUserByIdUseCase _getUserByIdUseCase; final SetUserStateUseCase _setUserStateUseCase; final GetCurrentUserUseCase _getCurrentUserUseCase; final UpdateProfilePicUseCase _updateProfilePicUseCase; AuthCubit( this._signInWithPhoneNumberUseCase, this._verifyOtpUseCase, this._saveUserDataToFirebaseUseCase, this._signOutUseCase, this._cachedLocalCurrentUid, this._getUserByIdUseCase, this._setUserStateUseCase, this._getCurrentUserUseCase, this._updateProfilePicUseCase, ) : super(AuthInitial()); static AuthCubit get(context) => BlocProvider.of(context); Country? country; void setCountry(Country myCountry) { country = myCountry; emit(SetCountrySuccessState()); } Future signInWithPhoneNumber({required String phoneNumber}) async { emit(SignInLoadingState()); final result = await _signInWithPhoneNumberUseCase( SignInWithPhoneNumberParameters( phoneNumber: phoneNumber, ), ); result.fold( (l) { emit(SignInErrorState()); }, (r) => emit(SignInSuccessState()), ); } Future verifyOtp({required String smsOtpCode}) async { emit(VerifyOtpLoadingState()); final result = await _verifyOtpUseCase( VerifyOtpParameters( smsOtpCode: smsOtpCode, ), ); result.fold( (l) => emit(VerifyOtpErrorState()), (r) => emit(VerifyOtpSuccessState()), ); } Future saveUserDataToFirebase({ required String name, File? profilePic, }) async { emit(SaveUserDataToFirebaseLoadingState()); final result = await _saveUserDataToFirebaseUseCase(UserDataParameters( name: name, profilePic: profilePic, )); result.fold( (l) => emit(SaveUserDataToFirebaseErrorState()), (r) => emit(SaveUserDataToFirebaseSuccessState()), ); } Future signOut() async { emit(SignOutLoadingState()); final result = await _signOutUseCase(const NoParameters()); result.fold( (l) => emit(SignOutErrorState()), (r) => emit(SignOutSuccessState()), ); } UserEntity? userEntity; Future getCurrentUser() async { emit(GetCurrentUserLoadingState()); final result = await _getCurrentUserUseCase(const NoParameters()); result.fold( (l) => emit(GetCurrentUserErrorState()), (r) { userEntity = r; emit(GetCurrentUserSuccessState()); }, ); } Future getCachedCurrentUid() async { final result = await _cachedLocalCurrentUid(const NoParameters()); result.fold( (l) => emit(GetCurrentLocalErrorState()), (r) => emit(GetCurrentLocalSuccessState(r)), ); } Stream getUserById(String uId) { return _getUserByIdUseCase(uId); } Future setUserState(bool isOnline) async { final result = await _setUserStateUseCase(isOnline); result.fold( (l) => emit(SetUserStateErrorState()), (r) => emit(SetUserStateSuccessState()), ); } Future updateProfilePic(String path) async { emit(UpdateProfilePicLoadingState()); final result = await _updateProfilePicUseCase(path); result.fold( (l) => emit(UpdateProfilePicErrorState()), (r) => emit(UpdateProfilePicSuccessState()), ); } } ================================================ FILE: lib/features/presentation/controllers/auth_cubit/auth_state.dart ================================================ part of 'auth_cubit.dart'; abstract class AuthState extends Equatable { const AuthState(); } class AuthInitial extends AuthState { @override List get props => []; } class SetCountrySuccessState extends AuthState{ @override List get props => []; } class SignInLoadingState extends AuthState{ @override List get props => []; } class SignInSuccessState extends AuthState{ @override List get props => []; } class SignInErrorState extends AuthState{ @override List get props => []; } class VerifyOtpLoadingState extends AuthState{ @override List get props => []; } class VerifyOtpSuccessState extends AuthState{ @override List get props => []; } class VerifyOtpErrorState extends AuthState{ @override List get props => []; } class SaveUserDataToFirebaseLoadingState extends AuthState{ @override List get props => []; } class SaveUserDataToFirebaseSuccessState extends AuthState{ @override List get props => []; } class SaveUserDataToFirebaseErrorState extends AuthState{ @override List get props => []; } class SignOutLoadingState extends AuthState{ @override List get props => []; } class SignOutSuccessState extends AuthState{ @override List get props => []; } class SignOutErrorState extends AuthState{ @override List get props => []; } class GetCurrentLocalSuccessState extends AuthState{ final String uId; const GetCurrentLocalSuccessState(this.uId); @override List get props => [uId]; } class GetCurrentLocalErrorState extends AuthState{ @override List get props => []; } class SetUserStateErrorState extends AuthState{ @override List get props => []; } class SetUserStateSuccessState extends AuthState{ @override List get props => []; } /////get current user class GetCurrentUserLoadingState extends AuthState{ @override List get props => []; } class GetCurrentUserErrorState extends AuthState{ @override List get props => []; } class GetCurrentUserSuccessState extends AuthState{ @override List get props => []; } //update profile pic class UpdateProfilePicLoadingState extends AuthState{ @override List get props => []; } class UpdateProfilePicErrorState extends AuthState{ @override List get props => []; } class UpdateProfilePicSuccessState extends AuthState{ @override List get props => []; } ================================================ FILE: lib/features/presentation/controllers/bottom_chat_cubit/bottom_chat_cubit.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; part 'bottom_chat_state.dart'; class BottomChatCubit extends Cubit { BottomChatCubit() : super(BottomChatInitial()); static BottomChatCubit get(context) => BlocProvider.of(context); bool isShownSendButton = false; bool isShowEmoji = false; bool isRecording = false; void showEmojiContainer() { isShowEmoji = true; emit(ShowEmojiContainerState()); } void hideEmojiContainer() { isShowEmoji = false; emit(HideEmojiContainerState()); } void toggleEmojiKeyboard(FocusNode focusNode) { if (isShowEmoji) { hideEmojiContainer(); focusNode.requestFocus(); } else { focusNode.unfocus(); showEmojiContainer(); } } void onTextFieldValChanged(String val) { if (val.trim().isNotEmpty) { isShownSendButton = true; emit(IsShowSendButtonTrueState()); } else { isShownSendButton = false; emit(IsShowSendButtonFalseState()); } } } ================================================ FILE: lib/features/presentation/controllers/bottom_chat_cubit/bottom_chat_state.dart ================================================ part of 'bottom_chat_cubit.dart'; abstract class BottomChatState extends Equatable { const BottomChatState(); } class BottomChatInitial extends BottomChatState { @override List get props => []; } class ShowEmojiContainerState extends BottomChatState { @override List get props => []; } class HideEmojiContainerState extends BottomChatState { @override List get props => []; } class ToggleEmojiKeyboardState extends BottomChatState { @override List get props => []; } class IsShowSendButtonFalseState extends BottomChatState { @override List get props => []; } class IsShowSendButtonTrueState extends BottomChatState { @override List get props => []; } ================================================ FILE: lib/features/presentation/controllers/call_cubit/call_cubit.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/call.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../../domain/usecases/call/call_stream_usecase.dart'; import '../../../domain/usecases/call/end_call_usecase.dart'; import '../../../domain/usecases/call/make_call_usecase.dart'; part 'call_state.dart'; class CallCubit extends Cubit { final CallStreamUseCase _callStreamUseCase; final EndCallUseCase _endCallUseCase; final MakeCallUseCase _makeCallUseCase; CallCubit( this._callStreamUseCase, this._endCallUseCase, this._makeCallUseCase, ) : super(CallInitial()); static CallCubit get(context) => BlocProvider.of(context); Stream callStream() => _callStreamUseCase(const NoParameters()); Future makeCall({ required String receiverId, required String receiverName, required String receiverPic, }) async { emit(MakeCallLoadingState()); final result = await _makeCallUseCase( MakeCallParameters( receiverId: receiverId, receiverName: receiverName, receiverPic: receiverPic, ), ); result.fold( (l) => emit(MakeCallErrorState()), (r) => emit(MakeCallSuccessState(call: r)), ); } Future endCall({ required String receiverId, required String callerId, }) async { emit(EndCallLoadingState()); final result = await _endCallUseCase( EndCallParameters( receiverId: receiverId, callerId: callerId, ), ); result.fold( (l) => emit(EndCallErrorState()), (r) => emit(EndCallSuccessState()), ); } } ================================================ FILE: lib/features/presentation/controllers/call_cubit/call_state.dart ================================================ part of 'call_cubit.dart'; abstract class CallState extends Equatable { const CallState(); } class CallInitial extends CallState { @override List get props => []; } class MakeCallLoadingState extends CallState { @override List get props => []; } class MakeCallSuccessState extends CallState { final Call call; const MakeCallSuccessState({required this.call}); @override List get props => []; } class MakeCallErrorState extends CallState { @override List get props => []; } class EndCallLoadingState extends CallState { @override List get props => []; } class EndCallSuccessState extends CallState { @override List get props => []; } class EndCallErrorState extends CallState { @override List get props => []; } ================================================ FILE: lib/features/presentation/controllers/chat_background_cubit/chat_background_cubit.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/core/utils/constants/assets_manager.dart'; part 'chat_background_state.dart'; class ChatBackgroundCubit extends Cubit { ChatBackgroundCubit() : super(ChatBackgroundInitial()); static ChatBackgroundCubit get(context) => BlocProvider.of(context); String backgroundImage =AppImage.chatBackground; void changeBackground(String image){ emit(ChangeBackgroundLoadingState()); backgroundImage= image; emit(ChangeBackgroundSuccessState()); } } ================================================ FILE: lib/features/presentation/controllers/chat_background_cubit/chat_background_state.dart ================================================ part of 'chat_background_cubit.dart'; abstract class ChatBackgroundState extends Equatable { const ChatBackgroundState(); } class ChatBackgroundInitial extends ChatBackgroundState { @override List get props => []; } class ChangeBackgroundLoadingState extends ChatBackgroundState { @override List get props => []; } class ChangeBackgroundSuccessState extends ChatBackgroundState { @override List get props => []; } ================================================ FILE: lib/features/presentation/controllers/chat_cubit/chat_cubit.dart ================================================ import 'dart:io'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/enums/messge_type.dart'; import '../../../../core/shared/message_replay.dart'; import '../../../domain/entities/contact_chat.dart'; import '../../../domain/entities/message.dart'; import '../../../domain/usecases/chat/get_chat_messages_usecase.dart'; import '../../../domain/usecases/chat/get_contacts_chat_usecase.dart'; import '../../../domain/usecases/chat/get_num_of_message_not_seen_usecase.dart'; import '../../../domain/usecases/chat/send_file_message_usecase.dart'; import '../../../domain/usecases/chat/send_gif_message_usecase.dart'; import '../../../domain/usecases/chat/send_text_message_usecase.dart'; import '../../../domain/usecases/chat/set_chat_message_seen_usecase.dart'; part 'chat_state.dart'; class ChatCubit extends Cubit { final SendTextMessageUseCase _sendTextMessageUseCase; final GetContactsChatUseCase _getContactsChatUseCase; final GetChatMessagesUseCase _getChatMessagesUseCase; final SetChatMessageSeenUseCase _setChatMessageSeenUseCase; final SendGifMessageUseCase _sendGifMessageUseCase; final SendFileMessageUseCase _sendFileMessageUseCase; final GetNumberOfMessageNotSeenUseCase _getNumberOfMessageNotSeenUseCase; // final GetContactNameUseCase _getContactNameUseCase; ChatCubit( this._sendTextMessageUseCase, this._getContactsChatUseCase, this._getChatMessagesUseCase, this._setChatMessageSeenUseCase, this._sendGifMessageUseCase, this._sendFileMessageUseCase, this._getNumberOfMessageNotSeenUseCase, ) : super(ChatInitial()); static ChatCubit get(context) => BlocProvider.of(context); MessageReplay? messageReplay; void onMessageSwipe({ required String message, required bool isMe, required MessageType messageType, required String repliedTo, }) { emit(MessageSwipeLoadingState()); messageReplay = MessageReplay( message: message, isMe: isMe, messageType: messageType, repliedTo: repliedTo, ); emit(MessageSwipeState()); } void cancelReplay() { messageReplay = null; emit(CancelReplayState()); } ////////////////////////////////////////////////////////////// ///sending messages Future sendTextMessage({ required String text, required String receiverId, }) async { emit(SendMessageLoadingState()); final result = await _sendTextMessageUseCase( TextMessageParameters( text: text, receiverId: receiverId, messageReplay: messageReplay, ), ); messageReplay = null; result.fold( (l) => emit(SendMessageErrorState()), (r) => emit(SendMessageSuccessState()), ); } Future sendGifMessage({ required String receiverId, required String gifUrl, }) async { final result = await _sendGifMessageUseCase( GifMessageParameters( receiverId: receiverId, gifUrl: gifUrl, messageReplay: messageReplay, ), ); messageReplay = null; result.fold( (l) => emit(SendMessageErrorState()), (r) => emit(SendMessageSuccessState()), ); } Future sendFileMessage({ required String receiverId, required MessageType messageType, required File file, }) async { final result = await _sendFileMessageUseCase( FileMessageParameters( receiverId: receiverId, messageType: messageType, file: file, messageReplay: messageReplay, ), ); messageReplay = null; result.fold( (l) => emit(SendMessageErrorState()), (r) => emit(SendMessageSuccessState()), ); } ////////////////////////////////////////////////////////////// ///get messages Stream> getContactsChat(Map map) { return _getContactsChatUseCase(map); } Stream> getChatMessages(String receiverId) { return _getChatMessagesUseCase( GetChatMessagesParameters( receiverId: receiverId, ), ); } ////////////////////////////////////////////////////////////// ///set message seen Future setChatMessageSeen({ required String receiverId, required String messageId, }) async { final result = await _setChatMessageSeenUseCase( SetChatMessageSeenParameters( receiverId: receiverId, messageId: messageId, ), ); result.fold( (l) => null, (r) => null, ); } ///get num of messages not seen Stream numOfMessageNotSeen(String senderId) => _getNumberOfMessageNotSeenUseCase(senderId); } ================================================ FILE: lib/features/presentation/controllers/chat_cubit/chat_state.dart ================================================ part of 'chat_cubit.dart'; abstract class ChatState extends Equatable { const ChatState(); } class ChatInitial extends ChatState { @override List get props => []; } class SendMessageLoadingState extends ChatState{ @override List get props => []; } class SendMessageSuccessState extends ChatState{ @override List get props => []; } class SendMessageErrorState extends ChatState{ @override List get props => []; } class GetContactNameErrorState extends ChatState{ @override List get props => []; } class GetContactNameSuccessState extends ChatState{ @override List get props => []; } //message swipe class MessageSwipeLoadingState extends ChatState{ @override List get props => []; } class MessageSwipeState extends ChatState{ @override List get props => []; } class CancelReplayState extends ChatState{ @override List get props => []; } ================================================ FILE: lib/features/presentation/controllers/select_contact_cubit/select_contact_cubit.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_contacts/contact.dart'; import '../../../../core/usecase/base_use_case.dart'; import '../../../domain/usecases/select_contact/get_all_contacts_usecase.dart'; import '../../../domain/usecases/select_contact/get_contacts_not_on_whats_usecase.dart'; import '../../../domain/usecases/select_contact/get_contacts_on_whats_usecase.dart'; part 'select_contact_state.dart'; class SelectContactCubit extends Cubit { final GetAllContactsUseCase _getAllContactsUseCase; final GetContactsOnWhatsUseCase _getContactsOnWhatsUseCase; final GetContactsNotOnWhatsUseCase _getContactsNotOnWhatsUseCase; SelectContactCubit( this._getAllContactsUseCase, this._getContactsOnWhatsUseCase, this._getContactsNotOnWhatsUseCase, ) : super(SelectContactInitial()); static SelectContactCubit get(context) => BlocProvider.of(context); Future getAllContacts() async { emit(GetAllContactsLoadingState()); final result = await _getAllContactsUseCase(const NoParameters()); result.fold( (l) => emit(GetAllContactsErrorState()), (r) => emit(GetAllContactsSuccessState()), ); } /////////// List contactNotOnWhats=[]; Future getContactsNotOnWhatsApp() async { emit(GetContactsNotOnWhatsLoadingState()); contactNotOnWhats = []; final result = await _getContactsNotOnWhatsUseCase(const NoParameters()); result.fold( (l) => emit(GetContactsNotOnWhatsErrorState()), (r) { for (var element in r) { contactNotOnWhats.add(element); } emit(GetContactsNotOnWhatsSuccessState()); } , ); } //////////// Map contactOnWhats={}; Future getContactsOnWhatsApp() async { emit(GetContactsOnWhatsLoadingState()); //contactOnWhats = {}; final result = await _getContactsOnWhatsUseCase(const NoParameters()); result.fold( (l) => emit(GetContactsOnWhatsErrorState()), (r) { contactOnWhats.addAll(r); emit(GetContactsOnWhatsSuccessState()); }, ); } } ================================================ FILE: lib/features/presentation/controllers/select_contact_cubit/select_contact_state.dart ================================================ part of 'select_contact_cubit.dart'; abstract class SelectContactState extends Equatable { const SelectContactState(); } class SelectContactInitial extends SelectContactState { @override List get props => []; } ///get all contacts class GetAllContactsLoadingState extends SelectContactState { @override List get props => []; } class GetAllContactsSuccessState extends SelectContactState { @override List get props => []; } class GetAllContactsErrorState extends SelectContactState { @override List get props => []; } ///get contacts not on whats class GetContactsNotOnWhatsLoadingState extends SelectContactState { @override List get props => []; } class GetContactsNotOnWhatsSuccessState extends SelectContactState { @override List get props => []; } class GetContactsNotOnWhatsErrorState extends SelectContactState { @override List get props => []; } /// get contacts on whatsApp class GetContactsOnWhatsLoadingState extends SelectContactState { @override List get props => []; } class GetContactsOnWhatsSuccessState extends SelectContactState { @override List get props => []; } class GetContactsOnWhatsErrorState extends SelectContactState { @override List get props => []; } ================================================ FILE: lib/features/presentation/views/calls/call_pickup_screen.dart ================================================ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:whatsapp_flutter_clone/core/functions/navigator.dart'; import 'package:whatsapp_flutter_clone/core/utils/routes/routes_manager.dart'; import 'package:whatsapp_flutter_clone/features/data/models/call_model.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/call.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/call_cubit/call_cubit.dart'; class CallPickupScreen extends StatelessWidget { final Widget scaffold; const CallPickupScreen({super.key, required this.scaffold}); @override Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.white, statusBarIconBrightness: Brightness.dark, ), ); return StreamBuilder( stream: CallCubit.get(context).callStream(), builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.data() != null) { Call call = CallModel.fromMap(snapshot.data!.data()! as Map); if (!call.hasDialled) { return Scaffold( body: Container( alignment: Alignment.center, padding: EdgeInsets.symmetric(vertical: 20), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Incoming call...', style: TextStyle( fontSize: 20, color: Colors.black, ), ), const SizedBox( height: 10, ), CircleAvatar( radius: 60, backgroundImage: NetworkImage(call.callerPic), ), const SizedBox( height: 50, ), Text( call.callerName, style: const TextStyle( fontSize: 20, color: Colors.black, fontWeight: FontWeight.bold, ), ), const SizedBox( height: 75, ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: () {}, icon: const Icon( Icons.call_end, color: Colors.red, size: 40, ), ), const SizedBox( width: 50, ), IconButton( onPressed: () { navigateTo( context, Routes.callRoute, arguments: { 'call': call, 'channelId': call.callId, }, ); }, icon: const Icon( Icons.call, color: Colors.green, size: 40, ), ), ], ) ], ), ), ); } } return scaffold; }, ); } } ================================================ FILE: lib/features/presentation/views/calls/call_screen.dart ================================================ import 'package:agora_uikit/agora_uikit.dart'; import 'package:flutter/material.dart'; import 'package:whatsapp_flutter_clone/config/agora_config.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/call.dart'; import 'package:whatsapp_flutter_clone/features/presentation/components/loader.dart'; class CallScreen extends StatefulWidget { final String channelId; final Call call; const CallScreen({super.key, required this.channelId, required this.call}); @override State createState() => _CallScreenState(); } class _CallScreenState extends State { AgoraClient? client; String baseUrl = ''; @override void initState() { super.initState(); client = AgoraClient( agoraConnectionData: AgoraConnectionData( appId: AgoraConfig.appId, channelName: widget.channelId, //tokenUrl: baseUrl, ), ); initAgora(); } void initAgora()async{ await client!.initialize(); } @override Widget build(BuildContext context) { return Scaffold( body: client ==null ? Loader(): SafeArea(child: Stack( children: [ AgoraVideoViewer(client: client!), AgoraVideoButtons(client: client!), ], )), ); } } ================================================ FILE: lib/features/presentation/views/calls/calls_page.dart ================================================ import 'package:flutter/material.dart'; class CallsPage extends StatelessWidget { const CallsPage({super.key}); @override Widget build(BuildContext context) { return const Center( child: Text('calls'), ); } } ================================================ FILE: lib/features/presentation/views/camera/camera_screen.dart ================================================ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import '../../../../core/functions/navigator.dart'; import '../../../../core/utils/routes/routes_manager.dart'; import '../../components/loader.dart'; import 'components/camera_appbar.dart'; import 'components/select_image_from_gallery_button.dart'; late List cameras; class CameraScreen extends StatefulWidget { final String receiverId; const CameraScreen({super.key, required this.receiverId,}); @override State createState() => _CameraScreenState(); } class _CameraScreenState extends State { late CameraController _cameraController; late Future _cameraValue; bool isFlashOn = false; bool isCameraFront = true; bool isRecording = false; @override void initState() { super.initState(); _cameraController = CameraController(cameras[0], ResolutionPreset.high); _cameraValue = _cameraController.initialize(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, appBar: CameraAppBar( isFlashOn: isFlashOn, onFlashPressed: toggleFlash, ), body: Column( children: [ Expanded( child: Stack( children: [ FutureBuilder( future: _cameraValue, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return SizedBox( width: double.infinity, child: CameraPreview(_cameraController), ); } else { return const Loader(); } }, ), Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SelectImageFromGalleryButton(receiverId: widget.receiverId), GestureDetector( onTap: () { if (!isRecording) takePhoto(context); }, onLongPress: ()async { await _cameraController.startVideoRecording(); setState(() { isRecording =true; }); }, onLongPressUp: () async{ XFile videoPath = await _cameraController.stopVideoRecording(); setState(() { isRecording = false; }); if(!mounted) return; navigateTo(context, Routes.sendingVideoViewRoute, arguments: { 'uId' : widget.receiverId, 'path' : videoPath.path, },); }, child: cameraIcon(), ), GestureDetector( onTap: toggleCameraFront, child: const CircleAvatar( radius: 30, backgroundColor: Colors.black38, child: Icon( Icons.flip_camera_ios, size: 30, ), ), ), ], ), ), ) ], ), ), const Padding( padding: EdgeInsets.only(top: 8, bottom: 8), child: Text( 'Hold for video, tap for photo', style: TextStyle(fontSize: 16), ), ), ], ), ); } Icon cameraIcon() { return isRecording ? const Icon( Icons.radio_button_on, color: Colors.red, size: 80, ) : const Icon( Icons.panorama_fish_eye, size: 80, color: Colors.white, ); } void toggleFlash() { setState(() { isFlashOn = !isFlashOn; }); isFlashOn ? _cameraController.setFlashMode(FlashMode.torch) : _cameraController.setFlashMode(FlashMode.off); } void toggleCameraFront() { setState(() { isCameraFront = !isCameraFront; }); int cameraPos = isCameraFront ? 0 : 1; _cameraController = CameraController(cameras[cameraPos], ResolutionPreset.high); _cameraValue = _cameraController.initialize(); } void takePhoto(BuildContext context) async { XFile file = await _cameraController.takePicture(); if(!mounted) return; navigateTo(context, Routes.sendingImageViewRoute, arguments: { 'path': file.path, 'uId' : widget.receiverId, }); } @override void dispose() { super.dispose(); _cameraController.dispose(); } } ================================================ FILE: lib/features/presentation/views/camera/components/camera_appbar.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../../../../core/functions/navigator.dart'; class CameraAppBar extends StatelessWidget implements PreferredSizeWidget { final VoidCallback onFlashPressed; final bool isFlashOn; const CameraAppBar({ super.key, required this.onFlashPressed, required this.isFlashOn, }); @override Widget build(BuildContext context) { return AppBar( backgroundColor: Colors.black, systemOverlayStyle: const SystemUiOverlayStyle( statusBarColor: Colors.black, ), leading: IconButton( // iconSize: 30, onPressed: () { navigatePop(context); }, icon: const Icon(Icons.clear), ), actions: [ IconButton( onPressed: onFlashPressed, icon: Icon( isFlashOn ? Icons.flash_on : Icons.flash_off, ), ), ], ); } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } ================================================ FILE: lib/features/presentation/views/camera/components/image_view_top_row_icons.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/functions/navigator.dart'; class ImageViewTopRowIcons extends StatelessWidget { final VoidCallback onCropButtonTaped; const ImageViewTopRowIcons({ super.key, required this.onCropButtonTaped, }); @override Widget build(BuildContext context) { return Row( children: [ IconButton( splashRadius: 20, iconSize: 30, color: Colors.white, onPressed: () { navigatePop(context); }, icon: const Icon(Icons.clear), ), const Spacer(), IconButton( splashRadius: 20, color: Colors.white, icon: const Icon( Icons.crop_rotate, size: 27, ), onPressed: onCropButtonTaped, ), IconButton( splashRadius: 20, color: Colors.white, icon: const Icon( Icons.emoji_emotions_outlined, size: 27, ), onPressed: () {}, ), IconButton( splashRadius: 20, color: Colors.white, icon: const Icon( Icons.title, size: 27, ), onPressed: (){}, ), IconButton( splashRadius: 20, color: Colors.white, splashColor: Colors.black38, icon: const Icon( Icons.edit, size: 27, ), onPressed: () {}, ), ], ); } } ================================================ FILE: lib/features/presentation/views/camera/components/select_image_from_gallery_button.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../../core/shared/commen.dart'; import '../../../../../core/utils/routes/routes_manager.dart'; class SelectImageFromGalleryButton extends StatelessWidget { final String receiverId; const SelectImageFromGalleryButton({ super.key, required this.receiverId, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { selectImageFromGallery(context); }, child: const CircleAvatar( radius: 30, backgroundColor: Colors.black38, child: Icon( Icons.photo, size: 30, ), ), ); } void selectImageFromGallery(BuildContext context) async { File? image = await pickImageFromGallery(context); //if (!mounted) return; if (image != null) { // ignore: use_build_context_synchronously navigateTo( context, Routes.sendingImageViewRoute, arguments: { 'path': image.path, 'uId': receiverId, }, ); } } } ================================================ FILE: lib/features/presentation/views/camera/components/sending_image_video_bottom_roww_widget.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/utils/constants/font_manager.dart'; class SendingImageVideoBottomRowWidget extends StatelessWidget { const SendingImageVideoBottomRowWidget({ super.key, required this.onSendButtonTaped, }); final VoidCallback onSendButtonTaped; @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: Container( margin: const EdgeInsets.all(8), padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( color: const Color(0XFF1F2C34), borderRadius: BorderRadius.circular(26), ), child: TextField( // controller: _messageController, cursorColor: context.colorScheme.secondary, // focusNode: focusNode, cursorHeight: 30, cursorWidth: 3, maxLines: null, textInputAction: TextInputAction.done, style: const TextStyle( fontSize: 20, color: Colors.white, fontWeight: FontWeight.w500, ), textAlignVertical: TextAlignVertical.bottom, decoration: const InputDecoration( filled: true, //fillColor: Colors, hintText: 'Add caption...', hintStyle: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeightManager.regular, ), border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, ), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: CircleAvatar( radius: 24, backgroundColor: context.colorScheme.secondary, child: GestureDetector( onTap: onSendButtonTaped, child: const Icon( Icons.send, color: Colors.white, size: 24, ), ), ), ), ], ); } } ================================================ FILE: lib/features/presentation/views/camera/components/video_view_top_row_widget.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/functions/navigator.dart'; class VideoViewTopRowWidget extends StatelessWidget { const VideoViewTopRowWidget({ super.key, }); @override Widget build(BuildContext context) { return Row( children: [ IconButton( splashRadius: 20, iconSize: 30, color: Colors.white, onPressed: () { navigatePop(context); }, icon: const Icon(Icons.clear), ), const Spacer(), IconButton( splashRadius: 20, color: Colors.white, icon: const Icon( Icons.emoji_emotions_outlined, size: 27, ), onPressed: () {}, ), IconButton( splashRadius: 20, color: Colors.white, icon: const Icon( Icons.title, size: 27, ), onPressed: () {}, ), IconButton( splashRadius: 20, color: Colors.white, splashColor: Colors.black38, icon: const Icon( Icons.edit, size: 27, ), onPressed: () {}, ), ], ); } } ================================================ FILE: lib/features/presentation/views/camera/sending_image_view_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import '../../../../core/enums/messge_type.dart'; import '../../../../core/shared/commen.dart'; import '../../controllers/chat_cubit/chat_cubit.dart'; import 'components/image_view_top_row_icons.dart'; import 'components/sending_image_video_bottom_roww_widget.dart'; class SendingImageViewPage extends StatefulWidget { String path; final String receiverId; SendingImageViewPage({ super.key, required this.path, required this.receiverId, }); @override State createState() => _SendingImageViewPageState(); } class _SendingImageViewPageState extends State { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: SafeArea( child: Stack( children: [ Image.file( File(widget.path), fit: BoxFit.contain, width: double.infinity, height: double.infinity, ), ImageViewTopRowIcons( onCropButtonTaped: () { cropImage(widget.path).then((value) { widget.path = value!.path; setState(() { }); }); }, ), Positioned( bottom: 5, right: 0, left: 0, child: SendingImageVideoBottomRowWidget( onSendButtonTaped: () { ChatCubit.get(context).sendFileMessage( receiverId: widget.receiverId, messageType: MessageType.image, file: File(widget.path), ); //to back to chat screen int count = 0; Navigator.of(context).popUntil((route) => count++ >= 2); }, ), ), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/camera/sending_video_view_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import '../../../../core/enums/messge_type.dart'; import '../../controllers/chat_cubit/chat_cubit.dart'; import 'components/sending_image_video_bottom_roww_widget.dart'; import 'components/video_view_top_row_widget.dart'; class SendingVideoViewPage extends StatefulWidget { final String path; final String receiverId; const SendingVideoViewPage({ super.key, required this.path, required this.receiverId, }); @override State createState() => _SendingVideoViewPageState(); } class _SendingVideoViewPageState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.file(File(widget.path)) ..initialize().then((value) { // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: SafeArea( child: Stack( children: [ Visibility( visible: _controller.value.isInitialized, child: SizedBox( width: double.infinity, child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ), ), ), playPauseButton(), const VideoViewTopRowWidget(), Positioned( bottom: 5, right: 0, left: 0, child: SendingImageVideoBottomRowWidget( onSendButtonTaped: () { ChatCubit.get(context).sendFileMessage( receiverId: widget.receiverId, messageType: MessageType.video, file: File(widget.path), ); //to back to chat screen int count = 0; Navigator.of(context).popUntil((route) => count++ >= 2); }, ), ), ], ), ), ); } Align playPauseButton() { return Align( alignment: Alignment.center, child: InkWell( onTap: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, child: CircleAvatar( radius: 30, backgroundColor: Colors.black38, child: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white, ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } } ================================================ FILE: lib/features/presentation/views/chat/chat_screen.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/features/presentation/views/calls/call_pickup_screen.dart'; import '../../controllers/chat_background_cubit/chat_background_cubit.dart'; import 'components/bottom_field/bottom_chat_with_icon.dart'; import 'components/chat_appbar.dart'; import 'components/message/messages_list.dart'; import 'components/bottom_field/recording_mic.dart'; class ChatScreen extends StatelessWidget { final String name; final String uId; const ChatScreen({super.key, required this.name, required this.uId}); @override Widget build(BuildContext context) { return CallPickupScreen( scaffold: Scaffold( appBar: ChatAppBar(name: name, receiverId: uId), body: Stack( children: [ BlocBuilder( builder: ((context, state) { return Image.asset( ChatBackgroundCubit.get(context).backgroundImage, fit: BoxFit.fill, width: double.infinity, height: double.infinity, ); }), ), Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ MessagesList(receiverId: uId), BottomChatWithIcon(receiverId: uId), ], ), RecordingMic(receiverId: uId), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/bottom_field/attchment_pop_up.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; class AttchementPopUp extends StatelessWidget { const AttchementPopUp({ super.key, }); @override Widget build(BuildContext context) { return PopupMenuButton( //RotationTransition to rotate icon with degree icon: const RotationTransition( turns: AlwaysStoppedAnimation(-45 / 360), child: Icon( Icons.attach_file, color: Colors.grey, size: 26, ), ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16),), // offset: const Offset(0, -340), offset: const Offset(0, -350), onSelected: (v){ if (FocusScope.of(context).hasFocus) { Future.delayed(const Duration(milliseconds: 50)).whenComplete( () { FocusScope.of(context).unfocus(); FocusScope.of(context).dispose(); }, ); } }, onCanceled: () { if (!FocusScope.of(context).hasFocus) { Future.delayed(const Duration(milliseconds: 50)) .whenComplete(() => FocusScope.of(context).unfocus()); FocusScope.of(context).dispose(); } }, constraints: const BoxConstraints.tightFor(width: double.infinity), itemBuilder: (context) { return [ PopupMenuItem( padding: const EdgeInsets.only(left: 40, right: 40, bottom: 15), enabled: false, onTap: () {}, child: Wrap( spacing: 8, runAlignment: WrapAlignment.spaceBetween, children: [ AttchmentCard( name: 'Document', color: Colors.deepPurpleAccent, icon: Icons.insert_drive_file, onPress: () { }, ), AttchmentCard( name: 'Camera', color: Colors.redAccent, icon: Icons.camera_alt, onPress: () {}, ), AttchmentCard( name: 'Gallery', color: Colors.purpleAccent, icon: Icons.photo, onPress: () {}, ), AttchmentCard( name: 'Audio', color: Colors.deepOrange, icon: Icons.headset_mic_rounded, onPress: () {}, ), AttchmentCard( name: 'Location', color: Colors.green, icon: Icons.location_on, onPress: () {}, ), AttchmentCard( name: 'Contact', color: Colors.cyan, icon: Icons.person, onPress: () { FlutterContacts.openExternalPick(); }, ), AttchmentCard( name: 'Poll', color: Colors.teal, icon: Icons.poll, onPress: () {}, ), ], ), ), ]; }, ); } } class AttchmentCard extends StatelessWidget { final String name; final IconData icon; final Color color; final VoidCallback onPress; const AttchmentCard({ super.key, required this.name, required this.icon, required this.color, required this.onPress, }); @override Widget build(BuildContext context) { return Container( width: 90, padding: const EdgeInsets.only(top: 15), child: Column( children: [ CircleAvatar( radius: 28, backgroundColor: color, child: IconButton( onPressed: onPress, splashRadius: 28, color: Colors.white, icon: Icon(icon), ), ), const SizedBox( height: 10, ), Text(name), ], ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/bottom_field/bottom_chat_field.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/core/extensions/extensions.dart'; import '../../../../../../core/functions/navigator.dart'; import '../../../../../../core/shared/message_replay.dart'; import '../../../../../../core/utils/constants/font_manager.dart'; import '../../../../../../core/utils/routes/routes_manager.dart'; import '../../../../controllers/chat_cubit/chat_cubit.dart'; import 'attchment_pop_up.dart'; import '../message/message_replay_preview.dart'; class BottomChatField extends StatelessWidget { final TextEditingController messageController; final FocusNode focusNode; final Function(String) onTextFieldValueChanged; final bool isShowEmoji; final VoidCallback toggleEmojiKeyboard; final String receiverId; const BottomChatField({ super.key, required this.messageController, required this.focusNode, required this.onTextFieldValueChanged, required this.isShowEmoji, required this.toggleEmojiKeyboard, required this.receiverId, }); @override Widget build(BuildContext context) { return Container( width: MediaQuery.of(context).size.width - 65, margin: const EdgeInsets.only( right: 5, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(26), ), child: Column( children: [ BlocBuilder( builder: (context, state) { MessageReplay? messageReplay = ChatCubit.get(context).messageReplay; if (messageReplay == null) { return const SizedBox(); } return MessageReplayPreview( messageReplay: messageReplay, ); }, ), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ SizedBox( width: 35, child: IconButton( onPressed: toggleEmojiKeyboard, color: Colors.grey, iconSize: 25, icon: Icon( isShowEmoji ? Icons.keyboard : Icons.emoji_emotions_outlined, ), ), ), Flexible( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: double.infinity, maxWidth: double.infinity, minHeight: 25, maxHeight: 135, ), child: Scrollbar( child: TextField( onChanged: onTextFieldValueChanged, /* onChanged: (val) { if (val.isNotEmpty) { setState(() { isShownSendButton = true; }); } else { setState(() { isShownSendButton = false; }); } }, */ controller: messageController, cursorColor: context.colorScheme.secondary, focusNode: focusNode, cursorHeight: 30, cursorWidth: 3, maxLines: null, textInputAction: TextInputAction.newline, style: TextStyle( fontSize: 20, //color: AppColors.blackLight, color: context.colorScheme.onTertiaryContainer, fontWeight: FontWeight.w500, ), textAlignVertical: TextAlignVertical.bottom, decoration: const InputDecoration( filled: true, fillColor: Colors.white, hintText: 'Message', hintStyle: TextStyle( color: Colors.grey, fontSize: 20, fontWeight: FontWeightManager.regular, ), border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, ), ), ), ), ), const AttchementPopUp(), if (messageController.text.isEmpty) IconButton( onPressed: () { navigateTo(context, Routes.cameraRoute, arguments: { 'uId': receiverId, }); }, color: Colors.grey, iconSize: 26, icon: const Icon(Icons.camera_alt_rounded), ), ], ), ], ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/bottom_field/bottom_chat_with_icon.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:giphy_get/giphy_get.dart'; import '../../../../../../core/shared/commen.dart'; import '../../../../controllers/bottom_chat_cubit/bottom_chat_cubit.dart'; import '../../../../controllers/chat_cubit/chat_cubit.dart'; import 'bottom_chat_field.dart'; import 'emoji_picker_widget.dart'; class BottomChatWithIcon extends StatefulWidget { final String receiverId; const BottomChatWithIcon({ super.key, required this.receiverId, }); @override State createState() => _BottomChatWithIconState(); } class _BottomChatWithIconState extends State { final TextEditingController messageController = TextEditingController(); FocusNode focusNode = FocusNode(); @override void initState() { super.initState(); focusNode.addListener(() { if (focusNode.hasFocus) { //hideEmojiContainer(); } }); } @override Widget build(BuildContext context) { return BlocConsumer< BottomChatCubit, BottomChatState>( listener: (context, state) {}, builder: (context, state) { BottomChatCubit cubit = BottomChatCubit.get(context); return WillPopScope( child: Column( // mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(4.0), child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, // mainAxisAlignment: MainAxisAlignment.start, children: [ BottomChatField( receiverId: widget.receiverId, focusNode: focusNode, isShowEmoji: cubit.isShowEmoji, messageController: messageController, onTextFieldValueChanged: (val) => cubit.onTextFieldValChanged(val), toggleEmojiKeyboard: () => cubit.toggleEmojiKeyboard(focusNode), ), if (cubit.isShownSendButton) BlocBuilder( builder: (context, state) { return GestureDetector( onTap: () { ChatCubit.get(context).sendTextMessage( text: messageController.text.trim(), receiverId: widget.receiverId, ); messageController.clear(); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(50.00), color: Colors.teal, ), width: 50, height: 50, child: Icon( Icons.send, color: Colors.teal[50], ), ), ); }, ), ], ), ), Offstage( offstage: !cubit.isShowEmoji, child: BlocBuilder( builder: (context, state) { return EmojiPickerWidget( messageController: messageController, onGifButtonTap: () { selectGif(ChatCubit.get(context)); }, ); }, ), ), ], ), onWillPop: () { if (cubit.isShowEmoji) { cubit.hideEmojiContainer(); } else { Navigator.pop(context); } return Future.value(false); }, ); }, ); } void selectGif(ChatCubit cubit) async { final gif = await pickGif(context); sendGifMessage(gif, cubit); } void sendGifMessage(GiphyGif? gif, ChatCubit cubit) { if (gif != null) { cubit.sendGifMessage( receiverId: widget.receiverId, gifUrl: gif.url!, ); } } @override void dispose() { super.dispose(); messageController.dispose(); } } ================================================ FILE: lib/features/presentation/views/chat/components/bottom_field/emoji_picker_widget.dart ================================================ import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; class EmojiPickerWidget extends StatelessWidget { final TextEditingController messageController; final VoidCallback onGifButtonTap; const EmojiPickerWidget({ super.key, required this.onGifButtonTap, required this.messageController, }); @override Widget build(BuildContext context) { return SizedBox( height: 250, child: Stack( alignment: Alignment.bottomCenter, children: [ EmojiPicker( onEmojiSelected: (category, emoji) { //messageController.text = messageController.text + emoji.emoji; }, onBackspacePressed: () { messageController.text.trimRight(); }, textEditingController: messageController, config: Config( columns: 8, iconColorSelected: context.colorScheme.secondary, indicatorColor: context.colorScheme.secondary, backspaceColor: Colors.black26, ), ), Container( height: 40, width: double.infinity, color: Colors.grey[200], padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 5, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ InkWell( onTap: onGifButtonTap, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( width: 2, color: context.colorScheme.onSurface, ), ), child: const Icon( Icons.gif, size: 30, color: Colors.black26, ), ), ), ], ), ), ], ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/bottom_field/recording_mic.dart ================================================ import 'dart:io'; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/core/enums/messge_type.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/chat_cubit/chat_cubit.dart'; import 'recording_mic_widget.dart'; import '../../../../controllers/bottom_chat_cubit/bottom_chat_cubit.dart'; class RecordingMic extends StatefulWidget { final String receiverId; const RecordingMic({super.key, required this.receiverId,}); @override State createState() => _RecordingMicState(); } class _RecordingMicState extends State { late final RecorderController recorderController; @override void initState() { super.initState(); recorderController = RecorderController(); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { BottomChatCubit cubit = BottomChatCubit.get(context); return Visibility( visible: !cubit.isShownSendButton, child: RecordingMicWidget( onVerticalScrollComplete: () {}, onHorizontalScrollComplete: () { cancelRecord(); }, onLongPress: () { startRecording(); }, onLongPressCancel: () { stopRecording(); }, onSend: () { stopRecording(); }, onTapCancel: () { cancelRecord(); }, ), ); }, ); } void startRecording() async { if (await recorderController.checkPermission()) { await recorderController.record(); } } void cancelRecord() async { await recorderController.stop(); } void stopRecording() async { final path = await recorderController.stop(); ChatCubit.get(context).sendFileMessage( receiverId: widget.receiverId, messageType: MessageType.audio, file: File(path!), ); } @override void dispose() { super.dispose(); recorderController.dispose(); } } ================================================ FILE: lib/features/presentation/views/chat/components/bottom_field/recording_mic_widget.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; import '/core/extensions/extensions.dart'; import '/core/utils/constants/assets_manager.dart'; import 'timer.dart'; class RecordingMicWidget extends StatefulWidget { const RecordingMicWidget({ Key? key, required this.onVerticalScrollComplete, required this.onHorizontalScrollComplete, required this.onLongPress, required this.onLongPressCancel, required this.onSend, required this.onTapCancel, }) : super(key: key); final VoidCallback onVerticalScrollComplete; final VoidCallback onHorizontalScrollComplete; final VoidCallback onLongPress; final VoidCallback onLongPressCancel; final VoidCallback onSend; final VoidCallback onTapCancel; @override _RecordingMicWidgetState createState() => _RecordingMicWidgetState(); } class _RecordingMicWidgetState extends State with SingleTickerProviderStateMixin { double micDx = 4; double micDy = 4; double micWidth = 50; double micHeight = 50; bool isVerticalScroll = true; bool showSwipeOptions = false; bool isVerticalActionComplete = false; bool isHorizontalActionComplete = false; bool isShowMic = false; bool isShowTime = false; late AnimationController _animationController; //Mic late Animation _micTranslateTop; late Animation _micRotationFirst; late Animation _micTranslateRight; late Animation _micTranslateLeft; late Animation _micRotationSecond; late Animation _micTranslateDown; late Animation _micInsideTrashTranslateDown; //Trash Can late Animation _trashWithCoverTranslateTop; late Animation _trashCoverRotationFirst; late Animation _trashCoverTranslateLeft; late Animation _trashCoverRotationSecond; late Animation _trashCoverTranslateRight; late Animation _trashWithCoverTranslateDown; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 2500), ); //Mic _micTranslateTop = Tween(begin: 0.0, end: -150.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.0, 0.45, curve: Curves.easeOut), ), ); _micRotationFirst = Tween(begin: 0.0, end: pi).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.0, 0.2), ), ); _micTranslateRight = Tween(begin: 0.0, end: 13.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.0, 0.1), ), ); _micTranslateLeft = Tween(begin: 0.0, end: -13.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.1, 0.2), ), ); _micRotationSecond = Tween(begin: 0.0, end: pi).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.2, 0.45), ), ); _micTranslateDown = Tween(begin: 0.0, end: 150.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.45, 0.79, curve: Curves.easeInOut), ), ); _micInsideTrashTranslateDown = Tween(begin: 0.0, end: 55.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.95, 1.0, curve: Curves.easeInOut), ), ); //Trash Can _trashWithCoverTranslateTop = Tween(begin: 30.0, end: -25.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.45, 0.6), ), ); _trashCoverRotationFirst = Tween(begin: 0.0, end: -pi / 3).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.6, 0.7), ), ); _trashCoverTranslateLeft = Tween(begin: 0.0, end: -18.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.6, 0.7), ), ); _trashCoverRotationSecond = Tween(begin: 0.0, end: pi / 3).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.8, 0.9), ), ); _trashCoverTranslateRight = Tween(begin: 0.0, end: 18.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.8, 0.9), ), ); _trashWithCoverTranslateDown = Tween(begin: 0.0, end: 55.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.95, 1.0, curve: Curves.easeInOut), ), ); } @override Widget build(BuildContext context) { //Size screenSize = context.size; return Stack( children: [ Positioned( bottom: 4, right: 50, child: Visibility( visible: showSwipeOptions || isVerticalActionComplete, child: Padding( padding: const EdgeInsets.only(right: 4.0), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(26.00), color: Colors.white, ), width: context.width(1) - 65, height: 50, ), ), ), ), Positioned( bottom: 15, right: 70, child: Visibility( visible: showSwipeOptions, child: Shimmer.fromColors( direction: ShimmerDirection.rtl, baseColor: Colors.grey.shade500, highlightColor: Colors.grey.shade300, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: const [ Icon( Icons.chevron_left, ), Text( "swipe to cancel", style: TextStyle(fontSize: 16), ), ], ), ), ), ), Positioned( bottom: 15, right: 70, child: Visibility( visible: isVerticalActionComplete, child: GestureDetector( onTap: () { setState(() { isVerticalActionComplete = false; isShowMic =false; isShowTime = false; }); widget.onTapCancel(); }, child: const Text( "Cancel", style: TextStyle( fontSize: 16, color: Colors.red, ), ), ), ), ), Positioned( bottom: 120, right: 0, child: Visibility( visible: showSwipeOptions, child: Container( width: 50, height: 50, decoration: BoxDecoration( borderRadius: BorderRadius.circular(50.00), color: Colors.white, ), child: Shimmer.fromColors( baseColor: Colors.grey.shade500, highlightColor: Colors.grey.shade300, child: const Icon( Icons.lock_rounded, size: 30, ), ), ), ), ), Positioned( bottom: micDy, right: micDx, child: GestureDetector( // this is used when the recording is locked and you want to save the audio onTap: () { if (isVerticalActionComplete) { setState(() { isVerticalActionComplete = false; isShowMic =false; isShowTime=false; }); widget.onSend(); } }, onLongPress: () { widget.onLongPress(); isVerticalActionComplete = false; isHorizontalActionComplete = false; setState(() { micWidth = 80; micHeight = 80; showSwipeOptions = true; isShowMic =true; isShowTime=true; }); }, onLongPressEnd: (LongPressEndDetails lg) { setState(() { micWidth = 50; micHeight = 50; micDy = 4; micDx = 4; showSwipeOptions = false; }); if (!isVerticalActionComplete && !isHorizontalActionComplete) { widget.onLongPressCancel(); setState(() { isShowMic =false; isShowTime=false; }); } }, onLongPressMoveUpdate: (LongPressMoveUpdateDetails longPressData) { longPressUpdate(longPressData); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(50.00), color: Colors.teal, ), width: micWidth, height: micHeight, child: Icon( isVerticalActionComplete ? Icons.send : Icons.mic, color: Colors.teal[50], ), ), ), ), Positioned( left: 10, bottom: -10, child: Visibility( visible: isShowMic, child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ AnimatedBuilder( animation: _animationController, builder: (context, child) { return Transform( transform: Matrix4.identity() ..translate(0.0, 10)..translate( _micTranslateRight.value)..translate( _micTranslateLeft.value)..translate( 0.0, _micTranslateTop.value)..translate( 0.0, _micTranslateDown.value)..translate( 0.0, _micInsideTrashTranslateDown.value), child: Transform.rotate( angle: _micRotationFirst.value, child: Transform.rotate( angle: _micRotationSecond.value, child: child, ), ), ); }, child: const Icon( Icons.mic, color: Color(0xFFef5552), size: 30, ), ), AnimatedBuilder( animation: _trashWithCoverTranslateTop, builder: (context, child) { return Transform( transform: Matrix4.identity() ..translate( 0.0, _trashWithCoverTranslateTop.value)..translate( 0.0, _trashWithCoverTranslateDown.value), child: child, ); }, child: Column( children: [ AnimatedBuilder( animation: _trashCoverRotationFirst, builder: (context, child) { return Transform( transform: Matrix4.identity() ..translate( _trashCoverTranslateLeft.value)..translate( _trashCoverTranslateRight.value), child: Transform.rotate( angle: _trashCoverRotationSecond.value, child: Transform.rotate( angle: _trashCoverRotationFirst.value, child: child, ), ), ); }, child: const Image( //image: AssetImage('assets/images/trash_cover.png'), image: AssetImage(AppImage.trashCoverImg), width: 30, ), ), const Padding( padding: EdgeInsets.only(top: 1.5), child: Image( image: //AssetImage('assets/images/trash_container.png'), AssetImage(AppImage.trashContainerImg), width: 30, ), ), ], ), ), ], ), ), ), Positioned( bottom: 15, left: 45, child: Visibility( visible: isShowTime, child: const TimerWidget(), ), ), ], ); } void longPressUpdate(LongPressMoveUpdateDetails longPressData) { //determine the direction of the swipe if (longPressData.localPosition.direction > 1) { isVerticalScroll = false; } else { isVerticalScroll = true; } // handle the swipe data and move the mic in vertical direction if (isVerticalScroll) { if (longPressData.localPosition.dy < 0) { if (longPressData.localPosition.dy > -100) { setState(() { micDy = -longPressData.localPosition.dy; }); } else { // reset only once if (showSwipeOptions) { isVerticalActionComplete = true; widget.onVerticalScrollComplete(); showSwipeOptions = false; resetMicPosition(); } } } else { resetMicPosition(); } } // handle the swipe data and move the mic in horizontal direction if (!isVerticalScroll) { if (longPressData.localPosition.dx < 0) { if (longPressData.localPosition.dx > -150) { setState(() { micDx = -longPressData.localPosition.dx; }); } else { // reset only once if (showSwipeOptions) { isHorizontalActionComplete = true; isShowTime=false; _animationController.forward().then((_) { _animationController.reset(); setState(() { isShowMic =false; }); }); widget.onHorizontalScrollComplete(); showSwipeOptions = false; resetMicPosition(); } } } else { resetMicPosition(); } } // reset mic size when the swipe reaches the vertical bounds if (longPressData.localPosition.dy < -100 && micHeight != 50) { setState(() { micWidth = 50; micHeight = 50; }); } // reset mic size when the swipe reaches the horizontal bounds if (longPressData.localPosition.dy < -150 && micWidth != 50) { setState(() { micWidth = 50; micHeight = 50; }); } } void resetMicPosition() { setState(() { micDx = 4; micDy = 4; }); } @override void dispose() { _animationController.dispose(); super.dispose(); } } ================================================ FILE: lib/features/presentation/views/chat/components/bottom_field/timer.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; /// This widget shows the count-up timer class TimerWidget extends StatefulWidget { const TimerWidget({Key? key}) : super(key: key); @override _TimerWidgetState createState() => _TimerWidgetState(); } class _TimerWidgetState extends State { Duration duration = const Duration(); Timer? timer; @override void initState() { super.initState(); startTimer(); } void startTimer() { timer = Timer.periodic(const Duration(seconds: 1), (_) => addTimer()); } void addTimer() { const addSeconds = 1; setState(() { final seconds = duration.inSeconds + addSeconds; duration = Duration(seconds: seconds); }); } @override Widget build(BuildContext context) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); return Text( "$minutes:$seconds", style: TextStyle( fontSize: 20, color: Colors.teal.shade700, ), ); } @override void dispose() { timer?.cancel(); super.dispose(); } } ================================================ FILE: lib/features/presentation/views/chat/components/chat_appbar.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/call_cubit/call_cubit.dart'; import '../../../components/custom_network_image.dart'; import '/core/extensions/time_extension.dart'; import '/core/functions/navigator.dart'; import '/core/shared/pop_up_menu_item_model.dart'; import '/core/utils/constants/strings_manager.dart'; import '/core/utils/routes/routes_manager.dart'; import '../../../../domain/entities/user.dart'; import '../../../components/custom_pop_up_menu_button.dart'; import '../../../components/loader.dart'; import '../../../controllers/auth_cubit/auth_cubit.dart'; class ChatAppBar extends StatelessWidget implements PreferredSizeWidget { final String name; final String receiverId; const ChatAppBar({ super.key, required this.name, required this.receiverId, }); @override Widget build(BuildContext context) { return StreamBuilder( stream: AuthCubit.get(context).getUserById(receiverId), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Loader(); } UserEntity userdata = snapshot.data!; return AppBar( leadingWidth: 70, titleSpacing: 0, leading: Container( margin: const EdgeInsets.only(bottom: 5), decoration: BoxDecoration( borderRadius: BorderRadius.circular(50), ), child: InkWell( borderRadius: BorderRadius.circular(50), onTap: () { navigateAndRemove(context, Routes.mainLayoutRoute); }, child: Row( children: [ const Icon(Icons.arrow_back_rounded), Hero( tag: userdata.uId, child: CustomNetworkImage(imageUrl: userdata.profilePic), ), ], ), ), ), title: SizedBox( width: double.infinity, height: kToolbarHeight, child: InkWell( onTap: () { navigateTo( context, Routes.senderUserProfileRoute, arguments: userdata, ); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(name), Text( userdata.isOnline ? 'Online' : userdata.lastSeen.lastSeen, style: const TextStyle( fontSize: 10, fontWeight: FontWeight.normal, ), ), ], ), ), ), ), actions: [ BlocConsumer( listener: (context,state){ if(state is MakeCallSuccessState){ navigateTo( context, Routes.callRoute, arguments: { 'call': state.call, 'channelId': state.call.callId, }, ); } }, builder: (context,state){ return IconButton( onPressed: () { CallCubit.get(context).makeCall( receiverId: receiverId, receiverName: name, receiverPic: userdata.profilePic, ); }, splashRadius: 20, icon: const Icon(Icons.videocam), ); }, ), IconButton( onPressed: () {}, splashRadius: 20, icon: const Icon(Icons.call), ), CustomPopUpMenuButton( buttons: _buttons(context), ), ], ); }); } List _buttons(context) => [ PopUpMenuItemModel( name: AppStrings.viewContact, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.mediaLinksAndDocs, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.search, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.muteNotifications, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.disappearingMessages, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.wallpaper, onTap: () { navigateTo(context, Routes.wallpaperRoute); }, ), PopUpMenuItemModel( name: AppStrings.more, onTap: () {}, ), ]; @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { const CustomAppBar({super.key}); @override Widget build(BuildContext context) { return AppBar( ); } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } ================================================ FILE: lib/features/presentation/views/chat/components/message/first_message_small_curved_bubble.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; class FirstMessageSmallCurvedBubble extends StatelessWidget { final bool isMe; const FirstMessageSmallCurvedBubble({ super.key, required this.isMe, }); @override Widget build(BuildContext context) { return ClipPath( clipper: MyCustomClipper(isMe), child: Container( width: 10, height: 15, decoration: BoxDecoration( color: isMe ? context.colorScheme.surface : Colors.white, ), ), ); } } class MyCustomClipper extends CustomClipper { final bool isMe; MyCustomClipper(this.isMe); @override Path getClip(Size size) { Path path = Path(); return isMe ? myCustomRightPath(path, size) : senderCustomLeftPath(path, size); } Path myCustomRightPath(Path path, Size size) { double w = size.width; double h = size.height; path.lineTo(w - 2.5, 0); path.quadraticBezierTo(w, 2.5, w - 2.5, 5); path.lineTo(0, h); path.close(); return path; } Path senderCustomLeftPath(Path path, Size size) { double w = size.width; double h = size.height; path.lineTo(2.5, 0); path.quadraticBezierTo(0, 2.5, 2.5, 5); path.lineTo(w, h); path.lineTo(w, 0); path.close(); return path; } @override bool shouldReclip(covariant CustomClipper oldClipper) { return true; } } ================================================ FILE: lib/features/presentation/views/chat/components/message/message_replay_card.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../../core/enums/messge_type.dart'; import '../../../../controllers/chat_cubit/chat_cubit.dart'; class ReplayMessageCard extends StatelessWidget { final bool showCloseButton; final MessageType repliedMessageType; final String text; final bool isMe; final String repliedTo; //final MessageReplay messageReplay; const ReplayMessageCard({ super.key, this.showCloseButton = false, required this.repliedMessageType, required this.text, required this.isMe, required this.repliedTo, }); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(5), child: Container( padding: const EdgeInsets.only( left: 10, right: 5, top: 5, bottom: 8, ), decoration: BoxDecoration( //color: Color(0xffF5F7F6), color: Colors.black.withOpacity(0.03), border: Border( left: BorderSide( color: isMe ? Colors.teal : Colors.deepPurple, width: 5, ), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( isMe ? 'You' : repliedTo, style: TextStyle( fontWeight: FontWeight.bold, color: isMe ? Colors.teal : Colors.deepPurple, ), ), ), if (showCloseButton) GestureDetector( onTap: () { ChatCubit.get(context).cancelReplay(); }, child: const Icon( Icons.close, size: 16, ), ) ], ), const SizedBox(height: 8), ReplayMessageContent( repliedMessageType: repliedMessageType, text: text, ), ], ), ), ); } } class ReplayMessageContent extends StatelessWidget { //final MessageReplay messageReplay; final MessageType repliedMessageType; final String text; const ReplayMessageContent({ super.key, required this.repliedMessageType, required this.text, }); @override Widget build(BuildContext context) { switch (repliedMessageType) { case MessageType.text: return Text( text, style: const TextStyle(color: Colors.black38, fontSize: 14), maxLines: 3, overflow: TextOverflow.ellipsis, ); case MessageType.image: return Row( children: const [ Icon(Icons.image,color: Colors.black38,), SizedBox(width: 4,), Text( 'Photo', style: TextStyle(color: Colors.black38, fontSize: 14), ), ], ); case MessageType.gif: return Row( children: const [ Icon(Icons.gif), Text( 'GIF', style: TextStyle(color: Colors.black38, fontSize: 14), ), ], ); case MessageType.video: return Row( children: const [ Icon(Icons.videocam), Text( 'Video', style: TextStyle(color: Colors.black38, fontSize: 14), ), ], ); case MessageType.audio: return Row( children: const [ Icon( Icons.mic, size: 18, color: Colors.black38, ), Text( 'Voice message', style: TextStyle(color: Colors.black38, fontSize: 14), ), ], ); default: return Text( text, maxLines: 3, overflow: TextOverflow.ellipsis, ); } } } ================================================ FILE: lib/features/presentation/views/chat/components/message/message_replay_preview.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../../core/shared/message_replay.dart'; import 'message_replay_card.dart'; class MessageReplayPreview extends StatelessWidget { final MessageReplay messageReplay; const MessageReplayPreview({ super.key, required this.messageReplay, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( //color: Colors.white12, color: Colors.white, borderRadius: BorderRadius.only( topRight: Radius.circular(12), topLeft: Radius.circular(12), ), ), child: ReplayMessageCard( //messageReplay: messageReplay, showCloseButton: true, isMe: messageReplay.isMe, text: messageReplay.message, repliedMessageType: messageReplay.messageType, repliedTo: messageReplay.repliedTo, ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message/messages_list.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import '/core/extensions/extensions.dart'; import '/core/extensions/time_extension.dart'; import '../../../../../../core/functions/date_converter.dart'; import '../../../../../domain/entities/message.dart'; import '../../../../controllers/chat_cubit/chat_cubit.dart'; import 'sender_message_card.dart'; import 'my_message_card.dart'; class MessagesList extends StatefulWidget { final String receiverId; const MessagesList({ super.key, required this.receiverId, }); @override State createState() => _MessagesListState(); } class _MessagesListState extends State { late ScrollController messageController = ScrollController(); void scrollToBottom() { final bottomOffset = messageController.position.maxScrollExtent; messageController.animateTo( bottomOffset, duration: const Duration(milliseconds: 1000), curve: Curves.easeInOut, ); } @override void dispose() { messageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { bool isFirst = false; return Expanded( child: StreamBuilder>( stream: ChatCubit.get(context).getChatMessages(widget.receiverId), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const SizedBox(); } //to scroll to bottom SchedulerBinding.instance.addPostFrameCallback((_) { messageController .jumpTo(messageController.position.maxScrollExtent); }); return ListView.builder( controller: messageController, itemCount: snapshot.data?.length, shrinkWrap: true, padding: EdgeInsets.only(bottom: 5), physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { var message = snapshot.data![index]; //////////////////////////////////////////// isFirst = false; var priviesMessage = (index > 0) ? snapshot.data![index - 1] : null; //check to make message small bubble for first message if (index == 0 || message.senderId != priviesMessage!.senderId || !message.timeSent.isSameDay(priviesMessage.timeSent)) { isFirst = true; } ///////////////////////////////////////////// //set chat message seen if (!message.isSeen && message.receiverId != widget.receiverId) { ChatCubit.get(context).setChatMessageSeen( receiverId: widget.receiverId, messageId: message.messageId, ); } return Column( children: [ if (index == 0 || !DateConverter.isSameDay( message.timeSent, snapshot.data![index - 1].timeSent, )) ChatTimeCard(dateTime: message.timeSent), if (message.receiverId == widget.receiverId) MyMessageCard( message: message, isFirst: isFirst, ), if (message.receiverId != widget.receiverId) SenderMessageCard( message: message, isFirst: isFirst, ), ], ); }, ); }, ), ); } } class ChatTimeCard extends StatelessWidget { final DateTime dateTime; const ChatTimeCard({ super.key, required this.dateTime, }); @override Widget build(BuildContext context) { return FittedBox( fit: BoxFit.scaleDown, child: Container( margin: const EdgeInsets.all(5), alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 8,), decoration: BoxDecoration( color: context.colorScheme.surfaceVariant, borderRadius: BorderRadius.circular(10), ), child: Text( dateTime.chatDayTime, style: context.displaySmall, ), ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message/my_message_card.dart ================================================ import 'package:flutter/material.dart'; import 'package:swipe_to/swipe_to.dart'; import '/core/extensions/extensions.dart'; import '../../../../../domain/entities/message.dart'; import '../../../../controllers/chat_cubit/chat_cubit.dart'; import 'first_message_small_curved_bubble.dart'; import '../message_content/message_content.dart'; import 'message_replay_card.dart'; class MyMessageCard extends StatelessWidget { final Message message; final bool isFirst; const MyMessageCard({ super.key, required this.message, required this.isFirst, }); @override Widget build(BuildContext context) { final isReplying = message.repliedMessage.isNotEmpty; return SwipeTo( onRightSwipe: () { ChatCubit.get(context).onMessageSwipe( message: message.text, isMe: true, messageType: message.messageType, repliedTo: message.senderName, ); }, child: Align( alignment: Alignment.centerRight, child: Padding( padding: EdgeInsets.only(top: 5, right: isFirst ? 5 : 15), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end, children: [ ConstrainedBox( constraints: BoxConstraints( maxWidth: context.width(0.8), maxHeight: 400, ), child: Card( elevation: 2, margin: const EdgeInsets.all(0), color: context.colorScheme.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: const Radius.circular(10), bottomLeft: const Radius.circular(10), bottomRight: const Radius.circular(10), topRight: isFirst ? Radius.zero : const Radius.circular(10), ), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (isReplying) Padding( padding: const EdgeInsets.all(5.0), child: ReplayMessageCard( text: message.repliedMessage, repliedMessageType: message.repliedMessageType, isMe: message.repliedTo == message.senderName, repliedTo: message.repliedTo, ), ), MessageContent( message: message, isMe: true, ), ], ), ), ), if (isFirst) const FirstMessageSmallCurvedBubble( isMe: true, ), ], ), ), ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message/sender_message_card.dart ================================================ import 'package:flutter/material.dart'; import 'package:swipe_to/swipe_to.dart'; import '../../../../../../core/extensions/extensions.dart'; import '../../../../../domain/entities/message.dart'; import '../../../../controllers/chat_cubit/chat_cubit.dart'; import '../message_content/message_content.dart'; import 'first_message_small_curved_bubble.dart'; import 'message_replay_card.dart'; class SenderMessageCard extends StatelessWidget { final Message message; final bool isFirst; const SenderMessageCard({ super.key, required this.message, required this.isFirst, }); @override Widget build(BuildContext context) { final isReplying = message.repliedMessage.isNotEmpty; return SwipeTo( onLeftSwipe: () { ChatCubit.get(context).onMessageSwipe( message: message.text, isMe: false, messageType: message.messageType, repliedTo: message.senderName, ); }, child: Align( alignment: Alignment.centerLeft, child: Padding( padding: EdgeInsets.only( top: 2.5, left: isFirst ? 5 : 15, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ if (isFirst) const FirstMessageSmallCurvedBubble(isMe: false), ConstrainedBox( constraints: BoxConstraints( maxWidth: context.width(0.8), maxHeight: 400, ), child: Card( elevation: 0, margin: const EdgeInsets.all(0), color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: const Radius.circular(10), bottomLeft: const Radius.circular(10), bottomRight: const Radius.circular(10), topLeft: isFirst ? Radius.zero : const Radius.circular(10), ), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (isReplying) Padding( padding: const EdgeInsets.all(5.0), child: ReplayMessageCard( text: message.repliedMessage, repliedMessageType: message.repliedMessageType, isMe: message.repliedTo != message.senderName, repliedTo: message.repliedTo, ), ), MessageContent(message: message, isMe: false), ], ), ), ), ], ), ), ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message_content/audio_player_widget.dart ================================================ import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; import 'package:whatsapp_flutter_clone/core/utils/constants/assets_manager.dart'; import 'package:whatsapp_flutter_clone/features/domain/entities/message.dart'; import 'package:whatsapp_flutter_clone/features/presentation/views/chat/components/message_content/time_sent_widget.dart'; class AudioPlayerWidget extends StatefulWidget { final Message message; final bool isMe; const AudioPlayerWidget({ super.key, required this.message, required this.isMe, }); @override State createState() => _AudioPlayerWidgetState(); } class _AudioPlayerWidgetState extends State { AudioPlayer audioPlayer = AudioPlayer(); Duration? totalDuration; Duration? newTiming; bool isPlaying = false; void initAudio() { debugPrint("Audio Initialized"); audioPlayer.play(UrlSource(widget.message.text)); audioPlayer.getDuration().then((value) { debugPrint(value.toString()); }); audioPlayer.onPositionChanged.listen((event) { setState(() { newTiming = event; }); }); audioPlayer.onDurationChanged.listen((updatedDuration) { totalDuration = updatedDuration; }); } void pauseAudio() { audioPlayer.pause(); } void stopAudio() { audioPlayer.stop(); } void seekAudio(Duration durationToSeek) { audioPlayer.seek(durationToSeek); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(5.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 55, child: Stack( children: [ CircleAvatar( radius: 25, backgroundColor: Colors.white, child: Image.asset(AppImage.genericProfileImage,), ), const Positioned( bottom: 0, right: 0, child: Icon( Icons.mic, color: Colors.black54, ), ), ], ), ), GestureDetector( onTap: () { if (isPlaying) { pauseAudio(); setState(() { isPlaying = !isPlaying; }); } else { if (newTiming.toString() == "null") { initAudio(); } else { audioPlayer.resume(); } setState(() { isPlaying = !isPlaying; }); } }, child: Icon( isPlaying ? Icons.pause : Icons.play_arrow_rounded, size: 40, color: Colors.black54, ), ), Expanded( child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( height: 35, child: Slider( value: newTiming == null ? 0 : newTiming!.inMilliseconds.toDouble(), min: 0, max: totalDuration == null ? 20 : totalDuration!.inMilliseconds.toDouble(), activeColor: Colors.grey, inactiveColor: Colors.black38, onChanged: (value) { setState(() { seekAudio(Duration(milliseconds: value.toInt())); }); }, ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( (newTiming.toString() == "null") ? "0:00" : newTiming.toString().split('.').first, style: const TextStyle(color: Colors.grey, fontSize: 14), ), TimeSentWidget( message: widget.message, isMe: widget.isMe, textColor: Colors.grey) ], ) ], ), ), ], ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message_content/image_widget.dart ================================================ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:whatsapp_flutter_clone/features/presentation/views/chat/components/message_content/time_sent_widget.dart'; import '../../../../../domain/entities/message.dart'; class ImageWidget extends StatelessWidget { const ImageWidget({ super.key, required this.message, required this.isMe, }); final Message message; final bool isMe; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(5), child: ClipRRect( borderRadius: BorderRadius.circular(10), child: Stack( children: [ CachedNetworkImage( imageUrl: message.text, fit: BoxFit.cover, maxHeightDiskCache: 390, placeholder: (context, url) => const SizedBox(), ), Positioned( bottom: 4, right: 8, child: TimeSentWidget( message: message, isMe: isMe, textColor: Colors.white, ), ), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message_content/message_content.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../../core/enums/messge_type.dart'; import '../../../../../domain/entities/message.dart'; import 'audio_player_widget.dart'; import 'image_widget.dart'; import 'text_widget.dart'; import 'video_palyer_widget.dart'; class MessageContent extends StatelessWidget { final Message message; final bool isMe; const MessageContent({ super.key, required this.message, required this.isMe, }); @override Widget build(BuildContext context) { switch (message.messageType) { case MessageType.text: return TextWidget(message: message, isMe: isMe); case MessageType.image: return ImageWidget(message: message, isMe: isMe); case MessageType.video: return VideoPlayerItem(message: message, isMe: isMe); case MessageType.audio: return AudioPlayerWidget(message: message, isMe: isMe); default: return TextWidget(message: message, isMe: isMe); } } } ================================================ FILE: lib/features/presentation/views/chat/components/message_content/text_widget.dart ================================================ import 'package:flutter/material.dart'; import '/features/presentation/views/chat/components/message_content/time_sent_widget.dart'; import '/core/extensions/extensions.dart'; import '../../../../../domain/entities/message.dart'; class TextWidget extends StatelessWidget { const TextWidget({ super.key, required this.message, required this.isMe, }); final Message message; final bool isMe; @override Widget build(BuildContext context) { return Wrap( crossAxisAlignment: WrapCrossAlignment.end, alignment: WrapAlignment.end, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text( message.text, style: context.displaySmall, overflow: TextOverflow.visible, ), ), TimeSentWidget( message: message, isMe: isMe, textColor: Colors.grey, ), ], ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message_content/time_sent_widget.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '/core/extensions/time_extension.dart'; import '../../../../../domain/entities/message.dart'; class TimeSentWidget extends StatelessWidget { const TimeSentWidget({ super.key, required this.message, required this.isMe, required this.textColor, }); final Message message; final bool isMe; final Color textColor; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(right: 4, bottom: 4), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( message.timeSent.amPmMode, style: TextStyle( fontSize: 13, color: textColor, ), ), const SizedBox( width: 5, ), if (isMe) Icon( Icons.done_all, size: 20, color: message.isSeen ? context.colorScheme.onTertiary : textColor, ), ], ), ); } } ================================================ FILE: lib/features/presentation/views/chat/components/message_content/video_palyer_widget.dart ================================================ import 'package:cached_video_player/cached_video_player.dart'; import 'package:flutter/material.dart'; import 'package:whatsapp_flutter_clone/features/presentation/views/chat/components/message_content/time_sent_widget.dart'; import '../../../../../domain/entities/message.dart'; class VideoPlayerItem extends StatefulWidget { final Message message; final bool isMe; const VideoPlayerItem({ super.key, required this.message, required this.isMe, }); @override _VideoPlayerItemState createState() => _VideoPlayerItemState(); } class _VideoPlayerItemState extends State { late CachedVideoPlayerController videoPlayerController; bool isPlay = false; @override void initState() { super.initState(); videoPlayerController = CachedVideoPlayerController.network(widget.message.text) ..initialize().then((value) { videoPlayerController.setVolume(1); }); } @override void dispose() { super.dispose(); videoPlayerController.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(5.0), child: ClipRRect( borderRadius: BorderRadius.circular(10), child: AspectRatio( aspectRatio: 16 / 9, child: Stack( children: [ CachedVideoPlayer(videoPlayerController), Align( alignment: Alignment.center, child: IconButton( onPressed: () { if (isPlay) { videoPlayerController.pause(); } else { videoPlayerController.play(); } setState(() { isPlay = !isPlay; }); }, icon: Icon( isPlay ? Icons.pause_circle : Icons.play_circle, size: 40, ), ), ), Positioned( bottom: 0, right: 4, child: TimeSentWidget( message: widget.message, isMe: widget.isMe, textColor: Colors.white, ), ), ], ), ), ), ); } } ================================================ FILE: lib/features/presentation/views/contacts_chat/components/chat_contact_card.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/time_extension.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../../core/utils/routes/routes_manager.dart'; import '../../../../domain/entities/contact_chat.dart'; import '../../../components/contact_profile_pic_dialog.dart'; import '../../../components/custom_list_tile.dart'; import '../../../components/my_cached_net_image.dart'; import '../../../controllers/chat_cubit/chat_cubit.dart'; class ChatContactCard extends StatelessWidget { final ContactChat chatContact; //final String? name; const ChatContactCard({ super.key, required this.chatContact, }); @override Widget build(BuildContext context) { // this stream to get num of message not seen return StreamBuilder( stream: ChatCubit.get(context).numOfMessageNotSeen(chatContact.contactId), builder: (context, snapshot) { return CustomListTile( title: chatContact.name, onTap: () { navigateTo(context, Routes.chatRoute, arguments: { 'name': chatContact.name, 'uId': chatContact.contactId, }); }, subTitle: chatContact.lastMessage, time: chatContact.timeSent.chatContactTime, numOfMessageNotSeen: snapshot.data ?? 0, leading: Hero( tag: chatContact.contactId, child: InkWell( borderRadius: BorderRadius.circular(25), onTap: () { showContactProfilePicDialog(context, contact: chatContact,); }, child: MyCachedNetImage( imageUrl: chatContact.profilePic, radius: 30, ), ), ), ); }, ); } } ================================================ FILE: lib/features/presentation/views/contacts_chat/contacts_chat_page.dart ================================================ import 'package:flutter/material.dart'; import '../../../domain/entities/contact_chat.dart'; import '../../components/loader.dart'; import '../../controllers/chat_cubit/chat_cubit.dart'; import 'components/chat_contact_card.dart'; class ContactsChatPage extends StatelessWidget { const ContactsChatPage({super.key}); @override Widget build(BuildContext context) { return StreamBuilder>( stream: ChatCubit.get(context).getContactsChat({}), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Loader(); } return ListView.builder( padding: const EdgeInsets.only(top: 10), itemCount: snapshot.data!.length, shrinkWrap: true, physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { return ChatContactCard( chatContact: snapshot.data![index], ); }, ); }, ); } } ================================================ FILE: lib/features/presentation/views/login/components/landing_image.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/extensions/extensions.dart'; import '../../../../../core/utils/constants/assets_manager.dart'; class LandingImage extends StatelessWidget { const LandingImage({super.key}); @override Widget build(BuildContext context) { return Image.asset( AppImage.landingImg, width: context.width(0.65), color: context.colorScheme.onSecondaryContainer, ); } } ================================================ FILE: lib/features/presentation/views/login/components/login_appbar.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '/core/extensions/extensions.dart'; class LoginAppBar extends StatelessWidget implements PreferredSizeWidget { final List? actions; final Widget? title; const LoginAppBar({ super.key, this.actions, this.title, }); @override Widget build(BuildContext context) { return AppBar( centerTitle: true, systemOverlayStyle: SystemUiOverlayStyle( statusBarColor: context.theme.scaffoldBackgroundColor, statusBarIconBrightness: _brightness(context), statusBarBrightness: _brightness(context), ), backgroundColor: context.theme.scaffoldBackgroundColor, title: title, actions: actions, ); } Brightness _brightness(BuildContext context) { return context.theme.brightness == Brightness.light ? Brightness.dark : Brightness.light; } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } ================================================ FILE: lib/features/presentation/views/login/components/login_profile_pic.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../components/update_profile_pic_model_bottom_sheet.dart'; class LoginProfilePic extends StatelessWidget { const LoginProfilePic({ super.key, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { showUpdateProfilePicModelBottomSheet(context); }, child: CircleAvatar( radius: 60, backgroundColor: context.colorScheme.onSurface, child: const Icon( Icons.add_a_photo, color: Colors.grey, size: 45, ), ), ); } } ================================================ FILE: lib/features/presentation/views/login/components/privacy_policy_link_and_terms_of_service.dart ================================================ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import '../../../../../core/extensions/extensions.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../../../core/utils/constants/values_manager.dart'; class PrivacyPolicyLinkAndTermsOfService extends StatelessWidget { const PrivacyPolicyLinkAndTermsOfService({super.key}); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, padding: const EdgeInsets.all(AppPadding.p10), child: Center( child: Text.rich( //style: context.bodyMedium, style: context.titleMedium, textAlign: TextAlign.center, TextSpan( children: [ const TextSpan( text: AppStrings.readOur, ), buildTextSpanBlueButton( text: AppStrings.privacyPolicy, onTap: () { // code to open / launch terms of service link here }, context: context, ), const TextSpan( text: AppStrings.tapAgreeAnd, ), buildTextSpanBlueButton( text: AppStrings.termsOfService, onTap: () { // code to open / launch terms of service link here }, context: context, ), ], ), ), ), ); } TextSpan buildTextSpanBlueButton({ required String text, required VoidCallback onTap, required BuildContext context, }) { return TextSpan( text: text, /* style: context.bodyMedium!.copyWith( color: AppColors.checkMarkBlue, ), */ style: context.titleMedium!.copyWith( color: context.colorScheme.onTertiary, ), recognizer: TapGestureRecognizer()..onTap = onTap, ); } } ================================================ FILE: lib/features/presentation/views/login/components/sign_out_button.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../../core/utils/routes/routes_manager.dart'; import '../../../components/default_button.dart'; import '../../../controllers/auth_cubit/auth_cubit.dart'; class SignOutButton extends StatelessWidget { const SignOutButton ({super.key}); @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if(state is SignOutSuccessState){ navigateAndRemove(context, Routes.landingRoute); } }, builder: (context, state) { return DefaultButton( text: 'signout', onPress: () { AuthCubit.get(context).signOut(); }, ); }, ); } } ================================================ FILE: lib/features/presentation/views/login/login_landing_screen.dart ================================================ import 'package:flutter/material.dart'; import '../../../../core/extensions/extensions.dart'; import '../../../../core/functions/navigator.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../../../core/utils/constants/values_manager.dart'; import '../../../../core/utils/routes/routes_manager.dart'; import '../../components/default_button.dart'; import 'components/landing_image.dart'; import 'components/login_appbar.dart'; import 'components/privacy_policy_link_and_terms_of_service.dart'; class LandingScreen extends StatelessWidget { const LandingScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: const LoginAppBar(), body: SafeArea( child: Column( children: [ Text( AppStrings.welcomeToWhatsApp, style: context.displayLarge!.copyWith( fontSize: 28, ), ), const Spacer(), const LandingImage(), const Spacer(), const PrivacyPolicyLinkAndTermsOfService(), const SizedBox(height: AppSize.s14), DefaultButton( text: AppStrings.agreeAndContinue, width: context.width(0.7), onPress: () { navigateAndReplace(context, Routes.loginRoute); }, ), const SizedBox(height: AppSize.s80), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/login/login_loading_screen.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/extensions/extensions.dart'; import '../../../../core/functions/navigator.dart'; import '../../../../core/shared/commen.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../../../core/utils/routes/routes_manager.dart'; import '../../controllers/auth_cubit/auth_cubit.dart'; import 'components/landing_image.dart'; import 'components/login_appbar.dart'; class LoginLoadingScreen extends StatelessWidget { const LoginLoadingScreen({super.key}); @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if (state is SaveUserDataToFirebaseSuccessState) { navigateAndRemove(context, Routes.mainLayoutRoute); /* if (kDebugMode) { print('lllll'); } sl() .saveData( key: AppStrings.uId, value: sl().toString(), ) .then((value) { Navigator.of(context).pushNamedAndRemoveUntil( Routes.mainLayoutRoute, (route) => false, ); }); */ } if (state is SaveUserDataToFirebaseErrorState) { showSnackBar(context: context, content: 'content err'); } }, builder: (context, state) { return Scaffold( appBar: const LoginAppBar( title: Text( AppStrings.initializing, ), ), body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( AppStrings.pleaseWaitAMoment, style: context.displayLarge, ), const Spacer(), const LandingImage(), const Spacer(), const CircularProgressIndicator(), const SizedBox(height: 60), ], ), ), ); }, ); } } ================================================ FILE: lib/features/presentation/views/login/login_otp_screen.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sms_autofill/sms_autofill.dart'; import '../../../../core/extensions/extensions.dart'; import '../../../../core/functions/navigator.dart'; import '../../../../core/utils/constants/font_manager.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../../../core/utils/constants/values_manager.dart'; import '../../../../core/utils/routes/routes_manager.dart'; import '../../controllers/auth_cubit/auth_cubit.dart'; import 'components/login_appbar.dart'; class OtpScreen extends StatefulWidget { final String phoneNumber; const OtpScreen({super.key, required this.phoneNumber}); @override State createState() => _OtpScreenState(); } class _OtpScreenState extends State { @override void initState() { super.initState(); _listenSmsCode(); } _listenSmsCode() async { await SmsAutoFill().listenForCode(); } @override void dispose() { SmsAutoFill().unregisterListener(); super.dispose(); } @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if (state is VerifyOtpSuccessState) { navigateAndRemove(context, Routes.loginProfileInfoRoute); } }, builder: (context, state) { AuthCubit cubit = AuthCubit.get(context); return Scaffold( appBar: LoginAppBar( title: Text( AppStrings.verifyingYourNumber, style: context.displayLarge, ), ), body: Padding( padding: const EdgeInsets.symmetric(horizontal: AppPadding.p20), child: Column( children: [ Text( AppStrings.waitingToDetectSms, style: context.titleSmall, ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( widget.phoneNumber, style: const TextStyle( color: Colors.black, fontSize: 14, fontWeight: FontWeightManager.semiBold, ), ), TextButton( onPressed: () {}, child: const Text( AppStrings.wrongNumber, ), ), ], ), SizedBox( width: context.width(0.45), child: PinFieldAutoFill( codeLength: 6, autoFocus: true, cursor: Cursor( color: context.colorScheme.secondary, enabled: true, height: 30, ), onCodeChanged: (value) { if (value!.length == 6) { cubit.verifyOtp(smsOtpCode: value.trim()); } }, ), /* child: TextField( keyboardType: TextInputType.number, onChanged: (val) { if (val.length == 6) { cubit.verifyOtp(smsOtpCode: val.trim()); } }, decoration: const InputDecoration( hintText: ' __ __ __ __', hintStyle: TextStyle( fontSize: 20, ), ), ), */ ), Padding( padding: const EdgeInsets.symmetric(vertical: AppPadding.p18), child: Text( AppStrings.enter6DigitCode, style: context.bodySmall, ), ), Row( children: [ const Icon( Icons.message, color: Colors.grey, ), const SizedBox( width: 16, ), const Text( AppStrings.resendSms, style: TextStyle( color: Colors.grey, ), ), const Spacer(), TweenAnimationBuilder( tween: Tween(begin: 60.0, end: 0), duration: const Duration(seconds: 60), builder: (context, value, child) => Text( '00:${value.toInt()}', style: const TextStyle( color: Colors.grey, ), ), onEnd: () {}, ), ], ), ], ), ), ); }, ); } } ================================================ FILE: lib/features/presentation/views/login/login_profile_info_screen.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '/core/extensions/extensions.dart'; import '../../../../core/functions/navigator.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../../../core/utils/constants/values_manager.dart'; import '../../../../core/utils/routes/routes_manager.dart'; import '../../controllers/auth_cubit/auth_cubit.dart'; import '../../controllers/select_contact_cubit/select_contact_cubit.dart'; import 'components/login_appbar.dart'; import 'components/login_profile_pic.dart'; class LoginProfileInfoScreen extends StatefulWidget { const LoginProfileInfoScreen({super.key}); @override State createState() => _LoginProfileInfoScreenState(); } class _LoginProfileInfoScreenState extends State { TextEditingController nameController = TextEditingController(); @override Widget build(BuildContext context) { return Builder(builder: (context) { SelectContactCubit.get(context).getAllContacts().then((value) { SelectContactCubit.get(context).getContactsOnWhatsApp(); SelectContactCubit.get(context).getContactsNotOnWhatsApp(); }); return BlocConsumer( listener: (context, state) { if (state is SaveUserDataToFirebaseLoadingState) { navigateAndRemove(context, Routes.loginLoadingRoute); } }, builder: (context, state) { AuthCubit cubit = AuthCubit.get(context); return Scaffold( appBar: LoginAppBar( title: Text( AppStrings.profileInfo, style: context.displayLarge, ), ), body: Padding( padding: const EdgeInsets.all(AppPadding.p18), child: Column( children: [ Text( AppStrings.pleaseProvideYourName, textAlign: TextAlign.center, style: context.titleMedium, ), const SizedBox(height: 20), const LoginProfilePic(), const SizedBox(height: 20), Row( children: [ Expanded( child: TextField( cursorHeight: 30, controller: nameController, cursorColor: context.colorScheme.secondary, decoration: const InputDecoration( hintText: AppStrings.typeYourNameHere, ), ), ), IconButton( onPressed: () {}, icon: const Icon( Icons.emoji_emotions_outlined, color: Colors.grey, ), ), ], ), const Spacer(), ElevatedButton( onPressed: () { if (nameController.text.length > 4) { cubit.saveUserDataToFirebase( name: nameController.text, ); } }, child: const Text( AppStrings.next, ), ), ], ), ), ); }, ); },); } @override void dispose() { nameController.dispose(); super.dispose(); } } ================================================ FILE: lib/features/presentation/views/login/login_screen.dart ================================================ import 'package:country_picker/country_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/extensions/extensions.dart'; import '../../../../core/functions/navigator.dart'; import '../../../../core/functions/app_dialogs.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../../../core/utils/constants/values_manager.dart'; import '../../../../core/utils/routes/routes_manager.dart'; import '../../components/default_button.dart'; import '../../controllers/auth_cubit/auth_cubit.dart'; import 'components/login_appbar.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { TextEditingController phoneController = TextEditingController(); TextEditingController countryCodeController = TextEditingController(); late String phoneNumber; @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if (state is SignInSuccessState) { navigateAndRemove(context, Routes.otpRoute, arguments: phoneNumber); } }, builder: (context, state) { AuthCubit cubit = AuthCubit.get(context); return Scaffold( appBar: LoginAppBar( title: Text( AppStrings.enterYourPhoneNumber, style: context.displayLarge, ), actions: [ IconButton( onPressed: () {}, icon: Icon( Icons.more_vert, color: context.colorScheme.onSurfaceVariant, ), ), ], ), body: Padding( padding: const EdgeInsets.symmetric(horizontal: AppPadding.p14), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( AppStrings.whatsAppWillNeed, textAlign: TextAlign.center, //style: context.bodyLarge, style: context.titleSmall, ), TextButton( onPressed: () { AppDialogs.permissionDialog( context, onContinuePressed: () {}, contentText: AppStrings.toRetrieveYourPhone, ); }, child: const Text( AppStrings.whatIsMyNumber, ), ), SizedBox( width: context.width(0.7), child: Column( children: [ Row( children: [ Expanded( child: Center( child: InkWell( onTap: () { showCountryPicker( context: context, showPhoneCode: true, onSelect: (myCountry) { cubit.setCountry(myCountry); countryCodeController.text = cubit.country!.phoneCode; }, ); }, child: Text( cubit.country == null ? AppStrings.pickCountry : cubit.country!.name, textAlign: TextAlign.center, style: context.titleLarge, ), ), ), ), Icon( Icons.arrow_drop_down, color: context.colorScheme.primary, size: AppSize.s28, ), ], ), const Divider( height: 0, thickness: 0.7, ), Row( children: [ SizedBox( width: AppSize.s60, child: TextField( //enabled: false, readOnly: true, controller: countryCodeController, style: TextStyle( color: context .colorScheme.onTertiaryContainer), decoration: InputDecoration( prefix: Container( margin: const EdgeInsets.only(right: 10), width: 10, alignment: Alignment.bottomLeft, child: Text( '+', style: context.titleLarge!.copyWith( color: context .colorScheme.onTertiaryContainer), ), ), ), ), ), const SizedBox( width: AppSize.s14, ), Expanded( child: TextField( controller: phoneController, keyboardType: TextInputType.phone, cursorColor: context.colorScheme.secondary, cursorHeight: 30, style: TextStyle( color: context .colorScheme.onTertiaryContainer), decoration: const InputDecoration( hintText: AppStrings.phoneNumber, ), ), ), ], ), const SizedBox(height: AppSize.s12), Text( AppStrings.carrierChargesMayApply, style: context.titleMedium, ), ], ), ), const Spacer(), DefaultButton( text: AppStrings.next, onPress: () { if (phoneController.text.isNotEmpty && countryCodeController.text.isNotEmpty) { String number = phoneController.text.trim(); phoneNumber = '+${countryCodeController.text}$number'; AppDialogs.submitPhoneDialog( context: context, phoneNumber: '+${countryCodeController.text} ${phoneController.text}', okPressed: () { navigatePop(context); cubit.signInWithPhoneNumber( phoneNumber: phoneNumber,); }, ); } }, ), const SizedBox( height: AppSize.s28, ), ], ), ), ), ); }, ); } @override void dispose() { super.dispose(); phoneController.dispose(); countryCodeController.dispose(); } } ================================================ FILE: lib/features/presentation/views/main_layout/components/fab.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../../core/utils/routes/routes_manager.dart'; class FAB extends StatelessWidget { final int index; const FAB({ super.key, required this.index, }); @override Widget build(BuildContext context) { switch(index){ case 0: return const ChatsFAB(); case 1: return const StatusTwoFAB(); case 2: return const CallsFAB(); default: return const ChatsFAB(); } } } class CallsFAB extends StatelessWidget { const CallsFAB({ super.key, }); @override Widget build(BuildContext context) { return FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add_ic_call_rounded), ); } } class ChatsFAB extends StatelessWidget { const ChatsFAB({ super.key, }); @override Widget build(BuildContext context) { return FloatingActionButton( onPressed: () { navigateTo(context, Routes.selectContactRoute); }, child: const Icon(Icons.message), ); } } class StatusTwoFAB extends StatelessWidget { const StatusTwoFAB({ super.key, }); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ SizedBox( width: 45, child: FloatingActionButton( onPressed: () {}, backgroundColor: const Color(0XFFE9EDEF), child: const Icon(Icons.edit,color: Colors.black54,), ), ), const SizedBox(height: 10), FloatingActionButton( onPressed: () {}, child: const Icon(Icons.camera_alt_rounded), ), ], ); } } ================================================ FILE: lib/features/presentation/views/main_layout/components/sliver_appbar_actions.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../../core/shared/pop_up_menu_item_model.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../../../core/utils/routes/routes_manager.dart'; import '../../../components/custom_pop_up_menu_button.dart'; List buildMainLayoutSliverAppBarActions( BuildContext context, { required int index, }) { //this to change pop up for chats calls status switch (index) { case 0: return actionButtons( context, onSearchPressed: () {}, popUpMenuItems: _chatPopUpMenuItem(context), ); case 1: return actionButtons( context, onSearchPressed: () {}, popUpMenuItems: _statusPopUpMenuItem(context), ); case 2: return actionButtons( context, onSearchPressed: () {}, popUpMenuItems: _callPopUpMenuItem(context), ); default: return actionButtons( context, onSearchPressed: () {}, popUpMenuItems: _callPopUpMenuItem(context), ); } } List actionButtons( BuildContext context, { required VoidCallback onSearchPressed, required List popUpMenuItems, }) { return [ IconButton( onPressed: () {}, icon: const Icon(Icons.camera_alt_outlined), ), IconButton( onPressed: () {}, icon: const Icon(Icons.search), ), CustomPopUpMenuButton(buttons: popUpMenuItems), ]; } List _chatPopUpMenuItem(BuildContext context) => [ PopUpMenuItemModel( name: AppStrings.newGroup, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.newBroadcast, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.linkedDevices, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.starredMessage, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.settings, onTap: () => navigateTo(context, Routes.settingsRoute), ), ]; List _statusPopUpMenuItem(BuildContext context) => [ PopUpMenuItemModel( name: AppStrings.statusPrivacy, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.settings, onTap: () {}, ), ]; List _callPopUpMenuItem(BuildContext context) => [ PopUpMenuItemModel( name: AppStrings.clearCallLog, onTap: () { }, ), PopUpMenuItemModel( name: AppStrings.settings, onTap: () {}, ), ]; ================================================ FILE: lib/features/presentation/views/main_layout/main_layout_screen.dart ================================================ import 'package:flutter/material.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../controllers/auth_cubit/auth_cubit.dart'; import '../calls/calls_page.dart'; import '../contacts_chat/contacts_chat_page.dart'; import '../status/status_page.dart'; import 'components/fab.dart'; import 'components/sliver_appbar_actions.dart'; class MainLayoutScreen extends StatefulWidget { const MainLayoutScreen({super.key}); @override State createState() => _MainLayoutScreenState(); } class _MainLayoutScreenState extends State with TickerProviderStateMixin, WidgetsBindingObserver { //with WidgetsBindingObserver to check online and offline mode // TickerProviderStateMixin to make tabBar late final TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _tabController.addListener(changeActions); WidgetsBinding.instance .addObserver(this); // to check online and offline mode } void changeActions() { buildMainLayoutSliverAppBarActions( context, index: _tabController.index, ); setState(() {}); } // to check online and offline mode @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.resumed: AuthCubit.get(context).setUserState(true); break; case AppLifecycleState.detached: case AppLifecycleState.inactive: case AppLifecycleState.paused: AuthCubit.get(context).setUserState(false); break; } } @override Widget build(BuildContext context) { return Builder(builder: (context) { return Scaffold( body: NestedScrollView( body: TabBarView( controller: _tabController, children: const [ ContactsChatPage(), StatusPage(), CallsPage(), ], ), headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverAppBar( title: const Text(AppStrings.appName), pinned: true, //automaticallyImplyLeading to remove back button automaticallyImplyLeading: false, floating: true, forceElevated: innerBoxIsScrolled, actions: buildMainLayoutSliverAppBarActions( context, index: _tabController.index, ), bottom: TabBar( indicatorColor: Colors.white, indicatorWeight: 4, tabs: [ Tab(text: AppStrings.chats.toUpperCase()), Tab(text: AppStrings.status.toUpperCase()), Tab(text: AppStrings.calls.toUpperCase()), ], controller: _tabController, ), ), ]; }, ), floatingActionButton: FAB(index: _tabController.index), ); },); } @override void dispose() { super.dispose(); WidgetsBinding.instance.removeObserver(this); } } ================================================ FILE: lib/features/presentation/views/select_contact/components/contacts_not_on_whatsapp_list.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_contacts/contact.dart'; import '../../../../../core/extensions/extensions.dart'; import '../../../../../core/utils/constants/font_manager.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../components/custom_list_tile.dart'; class ContactsNotOnWhatsAppList extends StatelessWidget { final List contactNotOnWhats; const ContactsNotOnWhatsAppList({ super.key, required this.contactNotOnWhats, }); @override Widget build(BuildContext context) { return ListView.builder( itemCount: contactNotOnWhats.length, physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { return CustomListTile( title: contactNotOnWhats[index].displayName, onTap: () {}, titleButton: TextButton( onPressed: () {}, child: Text( AppStrings.invite, style: TextStyle( color: context.colorScheme.secondary, fontWeight: FontWeightManager.medium, ), ), ), onLeadingTap: () {}, ); }, ); } } ================================================ FILE: lib/features/presentation/views/select_contact/components/contacts_on_whatsapp_list.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../../core/utils/routes/routes_manager.dart'; import '../../../components/custom_list_tile.dart'; import '../../../components/my_cached_net_image.dart'; class ContactsOnWhatsAppList extends StatelessWidget { final Map contactOnWhats; const ContactsOnWhatsAppList({ super.key, required this.contactOnWhats, }); @override Widget build(BuildContext context) { return ListView.builder( itemCount: contactOnWhats.length, physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { var contact = contactOnWhats.values.toList()[index]; return CustomListTile( title: contact['name'], onTap: () { navigateAndRemove( context, Routes.chatRoute, arguments: { 'uId': contact['uId'], 'name': contact['name'], }, ); }, onLeadingTap: () {}, subTitle: contact['status'], leading: MyCachedNetImage( imageUrl: contact['profilePic'], radius: 22, ), ); }, ); } } ================================================ FILE: lib/features/presentation/views/select_contact/components/new_group_contact_community_buttons_List.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/utils/constants/assets_manager.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../components/custom_list_tile.dart'; class NewGroupContactCommunityButtonsList extends StatelessWidget { const NewGroupContactCommunityButtonsList({super.key}); @override Widget build(BuildContext context) { return Column( children: [ CustomListTile( onTap: () {}, leading: CircleAvatar( backgroundColor: context.colorScheme.secondary, child: const Icon( Icons.group, color: Colors.white70, ), ), title: AppStrings.newGroup, ), CustomListTile( onTap: () { FlutterContacts.openExternalInsert(); }, leading: CircleAvatar( backgroundColor: context.colorScheme.secondary, child: const Icon( Icons.person_add, color: Colors.white70, ), ), title: AppStrings.newContact, titleButton: GestureDetector( onTap: () { }, child: Padding( padding: const EdgeInsets.all(4.0), child: Image.asset( AppImage.qrCode, ), ), ), ), CustomListTile( onTap: () {}, leading: CircleAvatar( backgroundColor: context.colorScheme.secondary, child: const Icon( Icons.groups, color: Colors.white70, ), ), title: AppStrings.newCommunity, ), ], ); } } ================================================ FILE: lib/features/presentation/views/select_contact/components/select_contact_appbar.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/shared/pop_up_menu_item_model.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../components/custom_pop_up_menu_button.dart'; import '../../../controllers/select_contact_cubit/select_contact_cubit.dart'; class SelectContactAppBar extends StatelessWidget implements PreferredSizeWidget { final int numOfContacts; final SelectContactState state; const SelectContactAppBar({ super.key, required this.numOfContacts, required this.state, }); @override Widget build(BuildContext context) { return AppBar( title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( AppStrings.selectContact, style: context.headlineLarge, ), const SizedBox(height: 3), Text( '$numOfContacts ${AppStrings.contacts}', style: context.bodySmall, ), ], ), actions: [ if (state is GetAllContactsLoadingState || state is GetContactsOnWhatsLoadingState) FittedBox( fit: BoxFit.scaleDown, child: SizedBox( width: 18, height: 18, child: CircularProgressIndicator( color: context.colorScheme.onPrimary, strokeWidth: 2, ), ), ), IconButton( onPressed: () {}, icon: const Icon(Icons.search), ), CustomPopUpMenuButton(buttons: _buttons(context)), ], ); } List _buttons(context) => [ PopUpMenuItemModel( name: AppStrings.inviteAFriend, onTap: () {}, ), PopUpMenuItemModel( name: AppStrings.contacts, onTap: () { FlutterContacts.openExternalPick().then((value) { if (kDebugMode) { print(value!.displayName); } }); }, ), PopUpMenuItemModel( name: AppStrings.refresh, onTap: () { SelectContactCubit.get(context).getAllContacts().then((value) { SelectContactCubit.get(context).getContactsOnWhatsApp(); SelectContactCubit.get(context).getContactsNotOnWhatsApp(); }); }, ), PopUpMenuItemModel( name: AppStrings.help, onTap: () {}, ), ]; @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } ================================================ FILE: lib/features/presentation/views/select_contact/select_contact_screen.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../controllers/select_contact_cubit/select_contact_cubit.dart'; import 'components/contacts_not_on_whatsapp_list.dart'; import 'components/contacts_on_whatsapp_list.dart'; import 'components/select_contact_appbar.dart'; import 'components/new_group_contact_community_buttons_List.dart'; class SelectContactScreen extends StatelessWidget { const SelectContactScreen({super.key}); @override Widget build(BuildContext context) { return Builder(builder: (context) { SelectContactCubit.get(context).getAllContacts().then((value) { SelectContactCubit.get(context).getContactsOnWhatsApp(); SelectContactCubit.get(context).getContactsNotOnWhatsApp(); }); return BlocConsumer( listener: (context, state) {}, builder: (context, state) { SelectContactCubit cubit = SelectContactCubit.get(context); return Scaffold( appBar: SelectContactAppBar( numOfContacts: cubit.contactOnWhats.length + cubit.contactNotOnWhats.length, state: state, ), body: SingleChildScrollView( physics: const ScrollPhysics(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const NewGroupContactCommunityButtonsList(), const SmallText(AppStrings.contactsOnWhatsApp), ContactsOnWhatsAppList( contactOnWhats: cubit.contactOnWhats, ), const SmallText(AppStrings.inviteToWhatsApp), ContactsNotOnWhatsAppList( contactNotOnWhats: cubit.contactNotOnWhats, ), ], ), ), ); }, ); }); } } class SmallText extends StatelessWidget { final String text; const SmallText( this.text, { super.key, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Text(text), ); } } ================================================ FILE: lib/features/presentation/views/sender_profile/components/body.dart ================================================ import 'package:flutter/material.dart'; import 'status_container.dart'; import 'encryption_container.dart'; import 'notification_container.dart'; class Body extends StatelessWidget { final String status; const Body({super.key, required this.status}); @override Widget build(BuildContext context) { return SliverList( delegate: SliverChildListDelegate( [ const SizedBox(height: 20), StatusContainer(status: status), const NotificationContainer(), const EncryptionContainer(), // to fill up the rest of the space to enable scrolling const SizedBox(height: 550), ], ), ); } } ================================================ FILE: lib/features/presentation/views/sender_profile/components/encryption_container.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; class EncryptionContainer extends StatelessWidget { const EncryptionContainer({ super.key, }); @override Widget build(BuildContext context) { return Container( color: Colors.grey.withOpacity(0.1), child: Container( color: Colors.white, margin: const EdgeInsets.only(top: 10), padding: const EdgeInsets.all(16), child: Column( children: [ ListTile( title: Text( AppStrings.encryption, style: context.headlineMedium, ), leading: const Icon(Icons.lock), subtitle: Text( AppStrings.messagesAndCallsAre, style: context.titleMedium, ), ), ListTile( title: Text( AppStrings.disappearingMessage, style: context.headlineMedium, ), leading: const Icon(Icons.timelapse), subtitle: Text( AppStrings.off, style: context.titleMedium, ), ), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/sender_profile/components/notification_container.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; class NotificationContainer extends StatelessWidget { const NotificationContainer({ super.key, }); @override Widget build(BuildContext context) { return Container( color: Colors.grey.withOpacity(0.1), child: Container( color: Colors.white, margin: const EdgeInsets.only(top: 10), padding: const EdgeInsets.all(16), child: Column( children: [ ListTile( title: Text( AppStrings.muteNotification, style: context.headlineMedium, ), leading: const Icon(Icons.notifications), trailing: Switch( onChanged: (v) {}, value: false, ), ), ListTile( title: Text( AppStrings.customNotification, style: context.headlineMedium, ), leading: const Icon(Icons.music_note), ), ListTile( title: Text( AppStrings.mediaVisibility, style: context.headlineMedium, ), leading: const Icon(Icons.image), ), ], )), ); } } ================================================ FILE: lib/features/presentation/views/sender_profile/components/phone_and_name.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; class PhoneAndName extends StatelessWidget { final String name; final String phoneNum; const PhoneAndName({ super.key, required this.name, required this.phoneNum, }); @override Widget build(BuildContext context) { return Column( children: [ const SizedBox(height: 35), Text( name, style: context.headlineMedium!.copyWith(fontSize: 22), ), const SizedBox(height: 10), Text( phoneNum, style: context.bodyMedium, ), const SizedBox(height: 30), ], ); } } ================================================ FILE: lib/features/presentation/views/sender_profile/components/sender_profile_icons.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '/core/extensions/extensions.dart'; class SenderProfileIcons extends StatelessWidget { const SenderProfileIcons({ Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ ButtonWidget(text: AppStrings.call,icon: Icons.call), SizedBox(width: 20), ButtonWidget(text: AppStrings.video,icon: Icons.videocam), SizedBox(width: 20), ButtonWidget(text: AppStrings.search,icon: Icons.search), ], ); } } class ButtonWidget extends StatelessWidget { final IconData icon; final String text; const ButtonWidget({ super.key, required this.icon, required this.text, }); @override Widget build(BuildContext context) { return Column( children: [ Icon( icon, size: 30, color: context.colorScheme.secondary, ), const SizedBox(height: 5), Text( text, style: const TextStyle( fontSize: 18, color: Color.fromARGB(255, 8, 141, 125), ), ), ], ); } } ================================================ FILE: lib/features/presentation/views/sender_profile/components/sender_user_profile_appbar.dart ================================================ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:whatsapp_flutter_clone/core/utils/constants/assets_manager.dart'; import 'package:whatsapp_flutter_clone/features/presentation/components/custom_network_image.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../domain/entities/user.dart'; class SenderUserProfilePageAppBar extends SliverPersistentHeaderDelegate { BuildContext context; final UserEntity user; Tween? profilePicTranslateTween; SenderUserProfilePageAppBar({required this.context, required this.user}) { profilePicTranslateTween = Tween( begin: context.width(1) / 2 - 45 - 40 + 15, end: 40.0, ); } static final appBarColorTween = ColorTween( begin: Colors.white, end: const Color(0XFF008069), ); static final statusBarBrightnessTween = Tween( begin: Brightness.dark, end: Brightness.light, ); static final appbarIconColorTween = ColorTween( begin: Colors.grey[800], end: Colors.white, ); static final nameTranslateTween = Tween(begin: 20.0, end: 0.0); static final nameFontSizeTween = Tween(begin: 20.0, end: 16.0); static final profileImageRadiusTween = Tween(begin: 3.5, end: 1.0); @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { final relativeScroll = min(shrinkOffset, 45) / 45; final relativeScroll70px = min(shrinkOffset, 70) / 70; SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle( statusBarColor: appBarColorTween.transform(relativeScroll), statusBarIconBrightness: statusBarBrightnessTween.transform(relativeScroll), ), ); return Container( color: appBarColorTween.transform(relativeScroll), child: Stack( children: [ Stack( children: [ Positioned( left: 0, child: IconButton( onPressed: () { navigatePop(context); }, icon: const Icon(Icons.arrow_back, size: 25), color: appbarIconColorTween.transform(relativeScroll), ), ), Positioned( right: 0, child: IconButton( onPressed: () {}, icon: const Icon(Icons.more_vert, size: 25), color: appbarIconColorTween.transform(relativeScroll), ), ), Positioned( top: 15, left: 90, child: displayName(relativeScroll70px), ), Positioned( top: 5, left: profilePicTranslateTween!.transform(relativeScroll70px), child: displayProfilePicture(relativeScroll70px, user), ), ], ), ], ), ); } Widget displayProfilePicture( double relativeFullScrollOffset, UserEntity user) { return Transform( transform: Matrix4.identity() ..scale( profileImageRadiusTween.transform(relativeFullScrollOffset), ), child: Hero( tag: user.uId, child: CustomNetworkImage(imageUrl: user.profilePic), ), ); } Widget displayName(double relativeFullScrollOffset) { if (relativeFullScrollOffset >= 0.8) { return Transform( transform: Matrix4.identity() ..translate( 0.0, nameTranslateTween.transform((relativeFullScrollOffset - 0.8) * 5), ), child: Text( user.name, style: TextStyle( fontSize: nameFontSizeTween .transform((relativeFullScrollOffset - 0.8) * 5), color: Colors.white, fontWeight: FontWeight.w500, ), ), ); } return const SizedBox.shrink(); } @override double get maxExtent => 120; @override double get minExtent => 50; @override bool shouldRebuild(SenderUserProfilePageAppBar oldDelegate) { return true; } } ================================================ FILE: lib/features/presentation/views/sender_profile/components/status_container.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; class StatusContainer extends StatelessWidget { const StatusContainer({ super.key, required this.status, }); final String status; @override Widget build(BuildContext context) { return Container( color: Colors.grey.withOpacity(0.1), child: Container( margin: const EdgeInsets.only(top: 10), padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.white10, offset: Offset(0, 10), blurRadius: 20, ), ], ), child: Text( status, style: context.headlineMedium, ), ), ); } } ================================================ FILE: lib/features/presentation/views/sender_profile/sender_profile_page.dart ================================================ import 'package:flutter/material.dart'; import '../../../domain/entities/user.dart'; import 'components/body.dart'; import 'components/phone_and_name.dart'; import 'components/sender_profile_icons.dart'; import 'components/sender_user_profile_appbar.dart'; class SenderUserProfilePage extends StatelessWidget { final UserEntity user; const SenderUserProfilePage({ super.key, required this.user, }); @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( backgroundColor: Colors.white, body: CustomScrollView( slivers: [ SliverPersistentHeader( delegate: SenderUserProfilePageAppBar( context: context, user: user, ), pinned: true, ), SliverToBoxAdapter( child: Column( children: [ PhoneAndName( name: user.name, phoneNum: user.phoneNumber, ), const SenderProfileIcons(), ], ), ), Body( status: user.status, ), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/settings/components/about_card.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../../domain/entities/user.dart'; class AboutCard extends StatelessWidget { const AboutCard({ super.key, required this.user, }); final UserEntity user; @override Widget build(BuildContext context) { return InkWell( onTap: () {}, child: ListTile( leading: const Icon(Icons.info_outline_rounded), title: const Text(AppStrings.about), subtitle: Text( user.status, maxLines: 1, overflow: TextOverflow.ellipsis, style: context.headlineMedium, ), trailing: Icon( Icons.edit, color: context.colorScheme.secondary, ), ), ); } } ================================================ FILE: lib/features/presentation/views/settings/components/name_card.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '/core/extensions/extensions.dart'; import '../../../../domain/entities/user.dart'; class NameCard extends StatelessWidget { const NameCard({ super.key, required this.user, }); final UserEntity user; @override Widget build(BuildContext context) { return InkWell( onTap: () {}, child: Column( children: [ ListTile( leading: const Icon(Icons.person), title: const Text(AppStrings.name), subtitle: Text( user.name, style: context.headlineMedium, ), trailing: Icon( Icons.edit, color: context.colorScheme.secondary, ), ), Padding( padding: const EdgeInsets.only(left: 70, right: 20, bottom: 10), child: Text( AppStrings.thisIsNotYourUser, style: context.titleMedium, ), ), Padding( padding: const EdgeInsets.only(left: 70), child: Divider( color: context.colorScheme.onSurfaceVariant.withOpacity(0.2), ), ), ], ), ); } } ================================================ FILE: lib/features/presentation/views/settings/components/phone_card.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/utils/constants/strings_manager.dart'; import '../../../../domain/entities/user.dart'; class PhoneCard extends StatelessWidget { const PhoneCard({ super.key, required this.user, }); final UserEntity user; @override Widget build(BuildContext context) { return InkWell( onTap: () {}, child: ListTile( leading: const Icon(Icons.phone), title: const Text(AppStrings.phone), subtitle: Text( user.phoneNumber, style: context.headlineMedium, ), ), ); } } ================================================ FILE: lib/features/presentation/views/settings/components/profile_card.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; import '../../../../../core/functions/navigator.dart'; import '../../../../../core/utils/constants/assets_manager.dart'; import '../../../../../core/utils/routes/routes_manager.dart'; import '../../../../domain/entities/user.dart'; import '../../../components/my_cached_net_image.dart'; import '../../../controllers/auth_cubit/auth_cubit.dart'; class ProfileCard extends StatelessWidget { const ProfileCard({super.key}); @override Widget build(BuildContext context) { return FutureBuilder( future: AuthCubit.get(context).getCurrentUser(), builder: (context,snapshot){ if (snapshot.connectionState == ConnectionState.done) { UserEntity user = AuthCubit.get(context).userEntity!; return InkWell( onTap: () { navigateTo(context, Routes.profileRoute, arguments: user); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), child: Row( children: [ Hero( tag: user.uId, child: MyCachedNetImage( radius: 30, imageUrl: user.profilePic, ), ), const SizedBox( width: 16, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user.name ?? '', style: context.headlineMedium, ), const SizedBox( height: 5, ), Text( user.status ?? '', style: context.bodyMedium, overflow: TextOverflow.ellipsis, ) ], ), ), //const Spacer(), Image.asset( AppImage.qrCode, width: 30, color: context.colorScheme.secondary, ), ], ), ), ); } return const SizedBox(); }, ); } } ================================================ FILE: lib/features/presentation/views/settings/components/profile_pic_circle_card.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/auth_cubit/auth_cubit.dart'; import '../../../../domain/entities/user.dart'; import '../../../components/my_cached_net_image.dart'; import '../../../components/update_profile_pic_model_bottom_sheet.dart'; class ProfilePicCircleCard extends StatelessWidget { const ProfilePicCircleCard({ super.key, required this.user, }); final UserEntity user; @override Widget build(BuildContext context) { UserEntity? newUser; return BlocConsumer( listener: (context, state) { if (state is UpdateProfilePicSuccessState) { AuthCubit.get(context).getCurrentUser(); } if(state is GetCurrentUserSuccessState){ newUser = AuthCubit.get(context).userEntity; } }, builder: (context, state) { return Stack( children: [ GestureDetector( onTap: () { showUpdateProfilePicModelBottomSheet(context); }, child: Hero( tag: user.uId, child: MyCachedNetImage( imageUrl: newUser?.profilePic ?? user.profilePic, radius: 85, ), ), ), const Positioned( bottom: 0, right: 0, child: CircleAvatar( radius: 20, backgroundColor: Colors.teal, child: Icon( Icons.camera_alt_rounded, color: Colors.white, ), ), ) ], ); }, ); } } ================================================ FILE: lib/features/presentation/views/settings/components/setting_bottom_text.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; class SettingBottomText extends StatelessWidget { const SettingBottomText({ super.key, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 30, top: 10), child: RichText( textAlign: TextAlign.center, text: TextSpan( children: [ TextSpan( text: 'from\n', style: context.titleMedium, ), TextSpan( text: 'Mohamed Elgohary', style: context.headlineMedium, ), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/settings/components/settings_item_card.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; class SettingsItemCard extends StatelessWidget { const SettingsItemCard({ super.key, required this.item, }); final Map item; @override Widget build(BuildContext context) { return InkWell( onTap: item['onTab'], child: Padding( padding: const EdgeInsets.all(25.0), child: Row( children: [ Icon( item['icon'], color: context.colorScheme.onSurface.withOpacity(0.6), size: 26, ), const SizedBox( width: 30, ), RichText( text: TextSpan( children: [ TextSpan( text: '${item['title']}\n', style: context.headlineMedium, ), TextSpan( text: item['subtitle'], style: context.titleMedium, ), ], ), ), ], ), ), ); } } ================================================ FILE: lib/features/presentation/views/settings/profile_screen.dart ================================================ import 'package:flutter/material.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import '../../../domain/entities/user.dart'; import '../../controllers/auth_cubit/auth_cubit.dart'; import 'components/about_card.dart'; import 'components/name_card.dart'; import 'components/phone_card.dart'; import 'components/profile_pic_circle_card.dart'; class ProfileScreen extends StatelessWidget { final UserEntity user; const ProfileScreen({super.key, required this.user}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(AppStrings.profile), ), body: Column( children: [ const SizedBox(height: 30), ProfilePicCircleCard(user: user), const SizedBox(height: 20), NameCard(user: user), AboutCard(user: user), PhoneCard(user: user) ], ), ); } } ================================================ FILE: lib/features/presentation/views/settings/settings_screen.dart ================================================ import 'package:flutter/material.dart'; import '../../../../core/utils/constants/strings_manager.dart'; import 'components/profile_card.dart'; import 'components/setting_bottom_text.dart'; import 'components/settings_item_card.dart'; class SettingsScreen extends StatelessWidget { SettingsScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(AppStrings.settings), ), body: SingleChildScrollView( child: Column( children: [ const ProfileCard(), ...List.generate( _settingItems.length, (index) => SettingsItemCard( item: _settingItems[index], ), ), const SettingBottomText(), ], ), ), ); } final List> _settingItems = [ { 'icon': Icons.key, 'onTab': () {}, 'title': 'Account', 'subtitle': 'Security notifications, change number', }, { 'icon': Icons.privacy_tip, 'onTab': () {}, 'title': 'Privacy', 'subtitle': 'Bloc contacts, disappearing messages', }, { 'icon': Icons.chat_sharp, 'onTab': () {}, 'title': 'Chats', 'subtitle': 'Theme, wallpapers, chat history', }, { 'icon': Icons.notifications, 'onTab': () {}, 'title': 'Notifications', 'subtitle': 'Message, group & call tones', }, { 'icon': Icons.data_usage, 'onTab': () {}, 'title': 'Storage and data', 'subtitle': 'Network usage, auto download', }, { 'icon': Icons.language, 'onTab': () {}, 'title': 'App language', 'subtitle': 'English', }, { 'icon': Icons.help_outline, 'onTab': () {}, 'title': 'Help', 'subtitle': 'Help centre, contact us, privacy policy', }, { 'icon': Icons.group, 'onTab': () {}, 'title': 'Invite a friend', 'subtitle': '', }, ]; } ================================================ FILE: lib/features/presentation/views/splash/components/bottom_text.dart ================================================ import 'package:flutter/material.dart'; import '/core/extensions/extensions.dart'; class BottomText extends StatelessWidget { const BottomText({ super.key, }); @override Widget build(BuildContext context) { return RichText( textAlign: TextAlign.center, text: TextSpan( children: [ TextSpan( text: 'from\n', style: context.headlineMedium, ), TextSpan( text: 'Mohamed El-Gohary', style: context.headlineMedium!.copyWith( color: context.colorScheme.onSecondaryContainer, fontSize: 20, ), ), ], ), ); } } ================================================ FILE: lib/features/presentation/views/splash/components/splash_icon.dart ================================================ import 'package:flutter/material.dart'; import '../../../../../core/utils/constants/assets_manager.dart'; class SplashIcon extends StatelessWidget { const SplashIcon({ super.key, }); @override Widget build(BuildContext context) { return Expanded( child: Center( child: Image.asset( AppImage.splashLightImg, width: 300, ), ), ); } } ================================================ FILE: lib/features/presentation/views/splash/splash_screen.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '/core/functions/navigator.dart'; import '/core/services/services_locator.dart' as di; import '../../../../core/utils/routes/routes_manager.dart'; import '../../../data/data_source/auth/local/auth_local_data_source.dart'; import 'components/bottom_text.dart'; import 'components/splash_icon.dart'; class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @override State createState() => _SplashScreenState(); } class _SplashScreenState extends State { Timer? _timer; _startDelay() { _timer = Timer(const Duration(seconds: 1), _goNext); } _goNext() { di.sl().getUser() != null ? navigateAndRemove(context, Routes.mainLayoutRoute) : navigateAndRemove(context, Routes.landingRoute); } @override void initState() { super.initState(); _startDelay(); } @override Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.white, statusBarIconBrightness:Brightness.dark, ), ); return Scaffold( body: Column( children: const [ SplashIcon(), BottomText(), SizedBox(height: 50), ], ), ); } @override void dispose() { _timer?.cancel(); super.dispose(); } } ================================================ FILE: lib/features/presentation/views/status/status_page.dart ================================================ import 'package:flutter/material.dart'; class StatusPage extends StatelessWidget { const StatusPage({super.key}); @override Widget build(BuildContext context) { return const Center( child: Text('status'), ); } } ================================================ FILE: lib/features/presentation/views/wallpaper/wallpaper_screen.dart ================================================ import 'package:flutter/material.dart'; import 'package:whatsapp_flutter_clone/core/functions/navigator.dart'; import 'package:whatsapp_flutter_clone/core/utils/constants/assets_manager.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/chat_background_cubit/chat_background_cubit.dart'; class WallpaperScreen extends StatelessWidget { const WallpaperScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text( 'Custom Wallpaper', ), ), body: GridView( physics: const BouncingScrollPhysics(), // if you want IOS bouncing effect, otherwise remove this line gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), children: wallpapers.map((name) { return InkWell( onTap: (){ ChatBackgroundCubit.get(context).changeBackground(name); navigatePop(context); }, child: Card( child: Image.asset( name, fit: BoxFit.cover, ), ), ); }).toList(), ), ); } } List wallpapers = [ AppImage.chatBackground, AppImage.chatBackground2, AppImage.chatBackground3, AppImage.chatBackgroundDark, ]; ================================================ FILE: lib/info.dart ================================================ import 'features/domain/entities/contact_chat.dart'; /* List info2 = [ ContactChat( name: 'Rivaan Ranawat', profilePic: 'https://upload.wikimedia.org/wikipedia/commons/8/85/Elon_Musk_Royal_Society_%28crop1%29.jpg', contactId: '1', lastMessage: 'Hey, how are you doing?', timeSent: DateTime.utc(2022), ), ContactChat( name: 'Mohamed Elgohary', profilePic: 'https://upload.wikimedia.org/wikipedia/commons/8/85/Elon_Musk_Royal_Society_%28crop1%29.jpg', contactId: '1', lastMessage: 'Hey, how are you doing?', timeSent: DateTime.utc(2022), ), ]; */ const info = [ { 'name': 'Rivaan Ranawat', 'message': 'Hey, how are you doing?', 'time': '3:53 pm', 'numOfMessageNotSeen': 50, 'profilePic': 'https://upload.wikimedia.org/wikipedia/commons/8/85/Elon_Musk_Royal_Society_%28crop1%29.jpg', }, { 'name': 'John Doe', 'message': 'Hello, whats up', 'time': '2:25 pm', 'numOfMessageNotSeen': 1, 'profilePic': 'https://www.socialketchup.in/wp-content/uploads/2020/05/fi-vill-JOHN-DOE.jpg', }, { 'name': 'Naman Ranawat', 'message': 'Hello, I want to sleep.', 'time': '1:03 pm', 'numOfMessageNotSeen': 3, 'profilePic': 'https://media.cntraveler.com/photos/60596b398f4452dac88c59f8/16:9/w_3999,h_2249,c_limit/MtFuji-GettyImages-959111140.jpg', }, { 'name': 'Dad', 'message': 'Call me, have some work', 'time': '12:06 pm', 'numOfMessageNotSeen': 0, 'profilePic': 'https://pbs.twimg.com/profile_images/1419974913260232732/Cy_CUavB.jpg', }, { 'name': 'Mom', 'message': 'You ate food?', 'time': '10:00 am', 'numOfMessageNotSeen': 20, 'profilePic': 'https://uploads.dailydot.com/2018/10/olli-the-polite-cat.jpg?auto=compress%2Cformat&ixlib=php-3.3.0', }, { 'name': 'Jurica', 'message': 'Yo!!!!! Long time, no see!?', 'time': '9:53 am', 'numOfMessageNotSeen': 100, 'profilePic': 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8cmFuZG9tJTIwcGVvcGxlfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=900&q=60', }, { 'name': 'Albert Dera', 'message': 'Am I fat?', 'time': '7:25 am', 'numOfMessageNotSeen': 3, 'profilePic': 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8cmFuZG9tJTIwcGVvcGxlfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=900&q=60', }, { 'name': 'Joseph', 'message': 'I am from International Olym...', 'time': '6:02 am', 'numOfMessageNotSeen': 3, 'profilePic': 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cmFuZG9tJTIwcGVvcGxlfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=900&q=60', }, { 'name': 'Sikandar', 'message': 'Lets Code!', 'time': '4:56 am', 'numOfMessageNotSeen': 3, 'profilePic': 'https://images.unsplash.com/photo-1619194617062-5a61b9c6a049?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTB8fHJhbmRvbSUyMHBlb3BsZXxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=900&q=60', }, { 'name': 'Ian Dooley', 'message': 'Images by Unsplash', 'time': '1:00 am', 'numOfMessageNotSeen': 3, 'profilePic': 'https://images.unsplash.com/photo-1539571696357-5a69c17a67c6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8cmFuZG9tJTIwcGVvcGxlfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=900&q=60', }, ]; const messages = [ {"isMe": false, "text": "Hey What is up with you!!", "time": "10:00 am"}, {"isMe": true, "text": "im fine,wbu?", "time": "11:00 am"}, {"isMe": false, "text": "I am great man!", "time": "11:01 am"}, { "isMe": false, "text": "Just messaged cuz I had some work.", "time": "11:01 am" }, {"isMe": true, "text": "Obviously, say", "time": "11:03 am"}, { "isMe": false, "text": "haha I wanted you to check out my new channel ^^", "time": "11:04 am" }, { "isMe": true, "text": " Sure, what is the channel name?", "time": "11:05 am" }, { "isMe": false, "text": "Rivaan Ranawat", "time": "11:06 am", }, { "isMe": true, "text": "Looks great to me!", "time": "11:15 am", }, {"isMe": false, "text": "Thanks bro!", "time": "11:17 am"}, {"isMe": false, "text": "Did you subscribe?", "time": "11:16 am"}, {"isMe": true, "text": "Yes, surely bro!", "time": "11:17 am"}, { "isMe": false, "text": "Cool, did you like the content?", "time": "11:18 am", }, { "isMe": true, "text": "I loved it?", "time": "11:19 am", }, { "isMe": false, "text": "OMG! Woah! Thanks!", "time": "11:20 am", }, ]; ================================================ FILE: lib/main.dart ================================================ import 'package:camera/camera.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:whatsapp_flutter_clone/features/presentation/controllers/call_cubit/call_cubit.dart'; import 'core/utils/thems/my_colors.dart'; import 'core/shared/bloc_observer.dart'; import 'core/utils/constants/strings_manager.dart'; import 'core/utils/routes/routes_manager.dart'; import 'core/services/services_locator.dart' as di; import 'core/utils/thems/theme_manager.dart'; import 'features/presentation/controllers/auth_cubit/auth_cubit.dart'; import 'features/presentation/controllers/bottom_chat_cubit/bottom_chat_cubit.dart'; import 'features/presentation/controllers/chat_background_cubit/chat_background_cubit.dart'; import 'features/presentation/controllers/chat_cubit/chat_cubit.dart'; import 'features/presentation/controllers/select_contact_cubit/select_contact_cubit.dart'; import 'features/presentation/views/camera/camera_screen.dart'; //csccccccc Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); Bloc.observer = MyBlocObserver(); await di.init(); cameras = await availableCameras(); runApp(const MyApp()); } //kjjkjkjkj class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider(create: (context) => di.sl()..getCurrentUser()), BlocProvider( create: (context) => di.sl() ..getAllContacts() ..getContactsOnWhatsApp(), ), BlocProvider(create: (context) => di.sl()), BlocProvider(create: (context) => di.sl()), BlocProvider(create: (context) => di.sl()), BlocProvider(create: (context)=> di.sl()), ], child: MaterialApp( title: AppStrings.appName, debugShowCheckedModeBanner: false, theme: createTheme(LightColors()), onGenerateRoute: AppRoutes.onGenerateRoute, initialRoute: Routes.splashRoute, ), ); } } ================================================ FILE: pubspec.yaml ================================================ name: whatsapp_flutter_clone description: A new Flutter application. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: '>=2.18.6 <3.0.0' dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 country_picker: ^2.0.16 equatable: ^2.0.5 dartz: ^0.10.1 flutter_bloc: ^8.1.1 get_it: ^7.2.0 shared_preferences: ^2.0.15 flutter_svg: ^1.1.6 flutter_contacts: ^1.1.5+1 uuid: ^3.0.7 intl: ^0.17.0 cached_network_image: ^3.2.3 giphy_get: ^3.1.1 emoji_picker_flutter: ^1.5.1 camera: ^0.10.1 path_provider: ^2.0.11 image_picker: ^0.8.6 video_player: ^2.4.9 shimmer: ^2.0.0 agora_uikit: ^1.1.0 audio_waveforms: ^0.1.5+1 cached_video_player: ^2.0.3 audioplayers: ^1.1.1 swipe_to: ^1.0.2 sms_autofill: ^2.2.0 image_cropper: ^3.0.1 firebase_auth: ^4.2.5 firebase_core: ^2.1.1 cloud_firestore: ^4.0.3 firebase_storage: ^11.0.3 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 flutter: uses-material-design: true assets: - assets/images/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages fonts: - family: Montserrat fonts: - asset: assets/fonts/Montserrat-Bold.ttf weight: 700 - asset: assets/fonts/Montserrat-SemiBold.ttf weight: 600 - asset: assets/fonts/Montserrat-Medium.ttf weight: 500 - asset: assets/fonts/Montserrat-Regular.ttf weight: 400 - asset: assets/fonts/Montserrat-Light.ttf weight: 300 ================================================ FILE: test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:whatsapp_flutter_clone/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget( const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }