├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore └── Podfile ├── assets ├── background.jpg ├── profile_default.png └── background_wallpaper.png ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_group_chat │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── lib ├── features │ ├── global │ │ ├── const │ │ │ ├── app_const.dart │ │ │ └── page_const.dart │ │ ├── common │ │ │ └── common.dart │ │ ├── theme │ │ │ └── style.dart │ │ ├── widgets │ │ │ └── container_button.dart │ │ ├── custom_tab_bar │ │ │ ├── custom_tab_bar_item.dart │ │ │ └── custom_tab_bar.dart │ │ └── custom_text_field │ │ │ └── textfield_container.dart │ ├── storage │ │ ├── domain │ │ │ ├── repository │ │ │ │ └── cloud_storage_repository.dart │ │ │ └── usecases │ │ │ │ ├── upload_group_image_usecase.dart │ │ │ │ └── upload_profile_image_usecase.dart │ │ ├── data │ │ │ ├── remote_data_source │ │ │ │ ├── cloud_storage_remote_data_source.dart │ │ │ │ └── cloud_storage_remote_data_source_impl.dart │ │ │ └── repository │ │ │ │ └── cloud_storage_repository_impl.dart │ │ └── storage_injection_container.dart │ ├── group │ │ ├── domain │ │ │ ├── entities │ │ │ │ ├── single_chat_entity.dart │ │ │ │ ├── group_entity.dart │ │ │ │ └── text_message_entity.dart │ │ │ ├── usecases │ │ │ │ ├── get_groups_usecase.dart │ │ │ │ ├── get_create_group_usecase.dart │ │ │ │ ├── update_group_usecase.dart │ │ │ │ ├── get_messages_usecase.dart │ │ │ │ └── send_text_message_usecase.dart │ │ │ └── repositories │ │ │ │ └── group_repository.dart │ │ ├── data │ │ │ ├── remote_data_source │ │ │ │ ├── group_remote_data_source.dart │ │ │ │ └── group_remote_data_source_impl.dart │ │ │ ├── repositories │ │ │ │ └── group_repository_impl.dart │ │ │ └── models │ │ │ │ ├── group_model.dart │ │ │ │ └── text_message_model.dart │ │ ├── presentation │ │ │ ├── cubits │ │ │ │ ├── chat │ │ │ │ │ ├── chat_state.dart │ │ │ │ │ └── chat_cubit.dart │ │ │ │ └── group │ │ │ │ │ ├── group_state.dart │ │ │ │ │ └── group_cubit.dart │ │ │ ├── pages │ │ │ │ ├── group_page.dart │ │ │ │ └── chat │ │ │ │ │ └── single_chat_page.dart │ │ │ └── create_group │ │ │ │ └── create_group_page.dart │ │ └── group_injection_container.dart │ ├── user │ │ ├── domain │ │ │ ├── usercases │ │ │ │ ├── sign_out_usecase.dart │ │ │ │ ├── is_sign_in_usecase.dart │ │ │ │ ├── google_auth_usecase.dart │ │ │ │ ├── get_current_uid_usecase.dart │ │ │ │ ├── forgot_password_usecase.dart │ │ │ │ ├── sign_in_usecase.dart │ │ │ │ ├── sign_up_usecase.dart │ │ │ │ ├── get_all_users_usecase.dart │ │ │ │ ├── get_update_user_usecase.dart │ │ │ │ ├── get_single_user_usecase.dart │ │ │ │ └── get_create_current_user_usecase.dart │ │ │ ├── repository │ │ │ │ └── user_repository.dart │ │ │ └── entities │ │ │ │ └── user_entity.dart │ │ ├── presentation │ │ │ ├── cubit │ │ │ │ ├── auth │ │ │ │ │ ├── auth_state.dart │ │ │ │ │ └── auth_cubit.dart │ │ │ │ ├── credential │ │ │ │ │ ├── credential_state.dart │ │ │ │ │ └── credential_cubit.dart │ │ │ │ ├── user │ │ │ │ │ ├── user_state.dart │ │ │ │ │ └── user_cubit.dart │ │ │ │ └── single_user │ │ │ │ │ ├── single_user_state.dart │ │ │ │ │ └── single_user_cubit.dart │ │ │ └── pages │ │ │ │ ├── all_users │ │ │ │ └── all_users_page.dart │ │ │ │ ├── credential │ │ │ │ ├── forgot_password_page.dart │ │ │ │ ├── login_page.dart │ │ │ │ └── sign_up_page.dart │ │ │ │ └── profile │ │ │ │ └── profile_page.dart │ │ ├── data │ │ │ ├── remote_data_source │ │ │ │ ├── user_remote_data_source.dart │ │ │ │ └── user_remote_data_source_impl.dart │ │ │ ├── model │ │ │ │ └── user_model.dart │ │ │ └── repository │ │ │ │ └── user_repository_impl.dart │ │ └── user_injection_container.dart │ ├── injection_container.dart │ └── app │ │ └── home │ │ └── home_page.dart ├── on_generate_route.dart └── main.dart ├── .gitignore ├── .metadata ├── README.md ├── pubspec.yaml └── pubspec.lock /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/assets/background.jpg -------------------------------------------------------------------------------- /assets/profile_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/assets/profile_default.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /assets/background_wallpaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/assets/background_wallpaper.png -------------------------------------------------------------------------------- /lib/features/global/const/app_const.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | class AppConst { 5 | static String appName = "Flutter Group Chat"; 6 | } -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirk3321/flutter-group-chat/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutter_group_chat/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_group_chat 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /lib/features/storage/domain/repository/cloud_storage_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'dart:io'; 5 | 6 | abstract class CloudStorageRepository{ 7 | Future uploadProfileImage({required File file}); 8 | Future uploadGroupImage({required File file}); 9 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/storage/data/remote_data_source/cloud_storage_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import 'dart:io'; 7 | 8 | abstract class CloudStorageRemoteDataSource{ 9 | Future uploadProfileImage({required File file}); 10 | Future uploadGroupImage({required File file}); 11 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/global/const/page_const.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class PageConst { 4 | static const String registrationPage="registrationPage"; 5 | static const String loginPage="loginPage"; 6 | static const String forgotPage="forgotPage"; 7 | static const String createGroupPage="createGroupPage"; 8 | static const String singleChatPage="singleChatPage"; 9 | } -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/group/domain/entities/single_chat_entity.dart: -------------------------------------------------------------------------------- 1 | class SingleChatEntity { 2 | final String groupId; 3 | final String groupName; 4 | final String uid; 5 | final String username; 6 | 7 | SingleChatEntity( 8 | {required this.groupId, 9 | required this.groupName, 10 | required this.uid, 11 | required this.username}); 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/global/common/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertoast/fluttertoast.dart'; 3 | 4 | void toast(String msg) { 5 | Fluttertoast.showToast( 6 | msg: msg, 7 | toastLength: Toast.LENGTH_SHORT, 8 | gravity: ToastGravity.BOTTOM, 9 | fontSize: 16, 10 | backgroundColor: Colors.red); 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/sign_out_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 7 | 8 | class SignOutUseCase { 9 | final UserRepository repository; 10 | 11 | SignOutUseCase({required this.repository}); 12 | 13 | Future call()async{ 14 | return repository.signOut(); 15 | } 16 | } -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/is_sign_in_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 6 | 7 | class IsSignInUseCase { 8 | 9 | final UserRepository repository; 10 | 11 | IsSignInUseCase({required this.repository}); 12 | 13 | Future call()async{ 14 | return repository.isSignIn(); 15 | } 16 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/google_auth_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 5 | 6 | 7 | 8 | class GoogleAuthUseCase { 9 | final UserRepository repository; 10 | 11 | GoogleAuthUseCase({required this.repository}); 12 | 13 | 14 | Future call() { 15 | return repository.googleAuth(); 16 | } 17 | 18 | 19 | } -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/get_current_uid_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 8 | 9 | class GetCurrentUIDUseCase{ 10 | final UserRepository repository; 11 | 12 | GetCurrentUIDUseCase({required this.repository}); 13 | Future call()async{ 14 | return await repository.getCurrentUId(); 15 | } 16 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/forgot_password_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 10 | 11 | class ForgotPasswordUseCase{ 12 | final UserRepository repository; 13 | 14 | ForgotPasswordUseCase({required this.repository}); 15 | 16 | Future call(String email){ 17 | return repository.forgotPassword(email); 18 | } 19 | } -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/features/group/domain/usecases/get_groups_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 6 | import 'package:flutter_group_chat/features/group/domain/repositories/group_repository.dart'; 7 | 8 | class GetGroupsUseCase{ 9 | final GroupRepository repository; 10 | 11 | GetGroupsUseCase({required this.repository}); 12 | 13 | Stream> call(){ 14 | return repository.getGroups(); 15 | } 16 | } -------------------------------------------------------------------------------- /lib/features/storage/domain/usecases/upload_group_image_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:flutter_group_chat/features/storage/domain/repository/cloud_storage_repository.dart'; 8 | 9 | class UploadGroupImageUseCase{ 10 | final CloudStorageRepository repository; 11 | 12 | UploadGroupImageUseCase({required this.repository}); 13 | 14 | 15 | Future call({required File file}) async { 16 | return repository.uploadGroupImage(file: file); 17 | } 18 | } -------------------------------------------------------------------------------- /lib/features/storage/domain/usecases/upload_profile_image_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:flutter_group_chat/features/storage/domain/repository/cloud_storage_repository.dart'; 7 | 8 | class UploadProfileImageUseCase{ 9 | final CloudStorageRepository repository; 10 | 11 | UploadProfileImageUseCase({required this.repository}); 12 | 13 | 14 | Future call({required File file}) async { 15 | return repository.uploadProfileImage(file: file); 16 | } 17 | } -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/sign_in_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 12 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 13 | 14 | class SignInUseCase{ 15 | final UserRepository repository; 16 | 17 | SignInUseCase({required this.repository}); 18 | 19 | Future call(UserEntity user){ 20 | return repository.signIn(user); 21 | } 22 | } -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/sign_up_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 12 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 13 | 14 | class SignUpUseCase{ 15 | final UserRepository repository; 16 | 17 | SignUpUseCase({required this.repository}); 18 | 19 | Future call(UserEntity user){ 20 | return repository.signUp(user); 21 | } 22 | } -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/get_all_users_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 7 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 8 | 9 | class GetAllUsersUseCase{ 10 | final UserRepository repository; 11 | 12 | GetAllUsersUseCase({required this.repository}); 13 | 14 | Stream> call(UserEntity user){ 15 | return repository.getAllUsers(user); 16 | } 17 | } -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/get_update_user_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 9 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 10 | 11 | class GetUpdateUserUseCase{ 12 | final UserRepository repository; 13 | 14 | GetUpdateUserUseCase({required this.repository}); 15 | Future call(UserEntity user){ 16 | return repository.getUpdateUser(user); 17 | } 18 | } -------------------------------------------------------------------------------- /lib/features/group/domain/usecases/get_create_group_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/repositories/group_repository.dart'; 6 | 7 | class GetCreateGroupUseCase { 8 | final GroupRepository repository; 9 | 10 | GetCreateGroupUseCase({required this.repository}); 11 | 12 | 13 | Future call(GroupEntity group){ 14 | return repository.getCreateGroup(group); 15 | } 16 | } -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /lib/features/group/domain/usecases/update_group_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 8 | import 'package:flutter_group_chat/features/group/domain/repositories/group_repository.dart'; 9 | 10 | class UpdateGroupUseCase{ 11 | final GroupRepository repository; 12 | 13 | UpdateGroupUseCase({required this.repository}); 14 | Future call(GroupEntity groupEntity){ 15 | return repository.updateGroup(groupEntity); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/get_single_user_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 8 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 9 | 10 | class GetSingleUserUseCase{ 11 | final UserRepository repository; 12 | 13 | GetSingleUserUseCase({required this.repository}); 14 | 15 | Stream> call(UserEntity user){ 16 | return repository.getSingleUser(user); 17 | } 18 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/features/user/domain/usercases/get_create_current_user_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 4 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 5 | 6 | class GetCreateCurrentUserUseCase { 7 | final UserRepository repository; 8 | 9 | GetCreateCurrentUserUseCase({required this.repository}); 10 | 11 | 12 | Future call(UserEntity user) { 13 | return repository.getCreateCurrentUser(user); 14 | } 15 | 16 | 17 | } -------------------------------------------------------------------------------- /lib/features/group/domain/usecases/get_messages_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 9 | import 'package:flutter_group_chat/features/group/domain/repositories/group_repository.dart'; 10 | 11 | class GetMessageUseCase{ 12 | final GroupRepository repository; 13 | 14 | GetMessageUseCase({required this.repository}); 15 | 16 | Stream> call(String channelId){ 17 | return repository.getMessages(channelId); 18 | } 19 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/auth/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_cubit.dart'; 2 | 3 | abstract class AuthState extends Equatable { 4 | const AuthState(); 5 | } 6 | 7 | class AuthInitial extends AuthState { 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class Authenticated extends AuthState { 13 | final String uid; 14 | 15 | Authenticated({required this.uid}); 16 | 17 | @override 18 | List get props => [uid]; 19 | } 20 | 21 | class UnAuthenticated extends AuthState { 22 | @override 23 | List get props => []; 24 | } 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/features/group/domain/usecases/send_text_message_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 7 | import 'package:flutter_group_chat/features/group/domain/repositories/group_repository.dart'; 8 | 9 | class SendTextMessageUseCase{ 10 | final GroupRepository repository; 11 | 12 | SendTextMessageUseCase({required this.repository}); 13 | 14 | Future call(TextMessageEntity textMessageEntity,String channelId)async{ 15 | return await repository.sendTextMessage(textMessageEntity,channelId); 16 | } 17 | } -------------------------------------------------------------------------------- /lib/features/group/domain/repositories/group_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 6 | 7 | abstract class GroupRepository{ 8 | Future getCreateGroup(GroupEntity groupEntity); 9 | Future updateGroup(GroupEntity groupEntity); 10 | Stream> getGroups(); 11 | Future sendTextMessage(TextMessageEntity textMessageEntity,String channelId); 12 | Stream> getMessages(String channelId); 13 | } -------------------------------------------------------------------------------- /lib/features/group/data/remote_data_source/group_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 6 | 7 | abstract class GroupRemoteDataSource{ 8 | Future getCreateGroup(GroupEntity groupEntity); 9 | Future updateGroup(GroupEntity groupEntity); 10 | Stream> getGroups(); 11 | Future sendTextMessage(TextMessageEntity textMessageEntity,String channelId); 12 | Stream> getMessages(String channelId); 13 | } 14 | -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/credential/credential_state.dart: -------------------------------------------------------------------------------- 1 | part of 'credential_cubit.dart'; 2 | 3 | abstract class CredentialState extends Equatable { 4 | const CredentialState(); 5 | } 6 | 7 | class CredentialInitial extends CredentialState { 8 | @override 9 | List get props => []; 10 | } 11 | 12 | 13 | class CredentialLoading extends CredentialState { 14 | @override 15 | List get props => []; 16 | } 17 | 18 | class CredentialSuccess extends CredentialState { 19 | @override 20 | List get props => []; 21 | } 22 | 23 | class CredentialFailure extends CredentialState { 24 | @override 25 | List get props => []; 26 | } -------------------------------------------------------------------------------- /lib/features/user/domain/repository/user_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 4 | 5 | abstract class UserRepository{ 6 | Future googleAuth(); 7 | Future getCreateCurrentUser(UserEntity user); 8 | Future forgotPassword(String email); 9 | 10 | Future isSignIn(); 11 | Future signIn(UserEntity user); 12 | Future signUp(UserEntity user); 13 | Future signOut(); 14 | Future getUpdateUser(UserEntity user); 15 | Future getCurrentUId(); 16 | Stream> getAllUsers(UserEntity user); 17 | Stream> getSingleUser(UserEntity user); 18 | 19 | } -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/user/user_state.dart: -------------------------------------------------------------------------------- 1 | part of 'user_cubit.dart'; 2 | 3 | abstract class UserState extends Equatable { 4 | const UserState(); 5 | } 6 | 7 | class UserInitial extends UserState { 8 | @override 9 | List get props => []; 10 | } 11 | 12 | 13 | 14 | class UserLoading extends UserState { 15 | @override 16 | List get props => []; 17 | } 18 | 19 | class UserLoaded extends UserState { 20 | 21 | final List users; 22 | 23 | UserLoaded({required this.users}); 24 | 25 | @override 26 | List get props => [users]; 27 | } 28 | 29 | class UserFailure extends UserState { 30 | @override 31 | List get props => []; 32 | } -------------------------------------------------------------------------------- /lib/features/group/presentation/cubits/chat/chat_state.dart: -------------------------------------------------------------------------------- 1 | part of 'chat_cubit.dart'; 2 | 3 | abstract class ChatState extends Equatable { 4 | const ChatState(); 5 | } 6 | 7 | class ChatInitial extends ChatState { 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class ChatLoading extends ChatState { 13 | @override 14 | List get props => []; 15 | } 16 | class ChatLoaded extends ChatState { 17 | 18 | final List messages; 19 | 20 | ChatLoaded({required this.messages}); 21 | 22 | @override 23 | List get props => [messages]; 24 | } 25 | 26 | class ChatFailure extends ChatState { 27 | @override 28 | List get props => []; 29 | } 30 | -------------------------------------------------------------------------------- /lib/features/user/domain/entities/user_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class UserEntity extends Equatable { 4 | final String? name; 5 | final String? email; 6 | final String? uid; 7 | final String? status; 8 | final String? profileUrl; 9 | final String? password; 10 | 11 | UserEntity( 12 | {this.name, 13 | this.email, 14 | this.uid, 15 | this.status, 16 | this.profileUrl, 17 | this.password}); 18 | 19 | @override 20 | // TODO: implement props 21 | List get props => [ 22 | name, 23 | email, 24 | uid, 25 | status, 26 | profileUrl, 27 | password, 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/features/group/presentation/cubits/group/group_state.dart: -------------------------------------------------------------------------------- 1 | part of 'group_cubit.dart'; 2 | 3 | abstract class GroupState extends Equatable { 4 | const GroupState(); 5 | } 6 | 7 | class GroupInitial extends GroupState { 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class GroupLoaded extends GroupState { 13 | 14 | final List groups; 15 | 16 | GroupLoaded({required this.groups}); 17 | 18 | @override 19 | List get props => [groups]; 20 | } 21 | 22 | class GroupLoading extends GroupState { 23 | @override 24 | List get props => []; 25 | } 26 | 27 | class GroupFailure extends GroupState { 28 | @override 29 | List get props => []; 30 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /lib/features/user/data/remote_data_source/user_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 5 | 6 | abstract class UserRemoteDataSource{ 7 | 8 | 9 | Future googleAuth(); 10 | Future getCreateCurrentUser(UserEntity user); 11 | Future forgotPassword(String email); 12 | 13 | Future isSignIn(); 14 | Future signIn(UserEntity user); 15 | Future signUp(UserEntity user); 16 | Future signOut(); 17 | Future getUpdateUser(UserEntity user); 18 | Future getCurrentUId(); 19 | Stream> getAllUsers(UserEntity user); 20 | Stream> getSingleUser(UserEntity user); 21 | 22 | } -------------------------------------------------------------------------------- /lib/features/global/theme/style.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | final Color darkPrimaryColor = Color.fromRGBO(56,142,60, 1); 8 | final Color lightPrimaryColor = Color.fromRGBO(200,230,201, 1); 9 | final Color primaryColor = Color.fromRGBO(76,175,80,1); 10 | final colorD96F43=Color.fromRGBO(217 , 111, 67, 1); 11 | final Color textIconColor = Color.fromRGBO(255,255,255,1); 12 | final Color textPrimaryColor = Color.fromRGBO(33,33,33,1); 13 | final Color dividerColor = Color.fromRGBO(189,189,189,1); 14 | final Color textIconColorGray = Colors.black; 15 | final Color greenColor = Color.fromRGBO(76,175,80,1); 16 | final color747480= Color.fromRGBO(116 , 116 , 128, 1); 17 | final colorC1C1C1=Color.fromRGBO(193 , 193 , 193, 1); -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/single_user/single_user_state.dart: -------------------------------------------------------------------------------- 1 | part of 'single_user_cubit.dart'; 2 | 3 | abstract class SingleUserState extends Equatable { 4 | const SingleUserState(); 5 | } 6 | 7 | class SingleUserInitial extends SingleUserState { 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class SingleUserLoading extends SingleUserState { 13 | @override 14 | List get props => []; 15 | } 16 | 17 | class SingleUserLoaded extends SingleUserState { 18 | 19 | final UserEntity currentUser; 20 | 21 | SingleUserLoaded({required this.currentUser}); 22 | 23 | @override 24 | List get props => [currentUser]; 25 | } 26 | 27 | 28 | class SingleUserFailure extends SingleUserState { 29 | @override 30 | List get props => []; 31 | } -------------------------------------------------------------------------------- /lib/features/group/domain/entities/group_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class GroupEntity extends Equatable { 5 | final String? groupName; 6 | final String? groupProfileImage; 7 | final Timestamp? createAt; 8 | final String? groupId; 9 | final String? uid; 10 | final String? lastMessage; 11 | 12 | GroupEntity({ 13 | this.groupName, 14 | this.groupProfileImage, 15 | this.createAt, 16 | this.groupId, 17 | this.uid, 18 | this.lastMessage, 19 | }); 20 | 21 | @override 22 | // TODO: implement props 23 | List get props => [ 24 | groupName, 25 | groupProfileImage, 26 | createAt, 27 | groupId, 28 | uid, 29 | lastMessage, 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath 'com.google.gms:google-services:4.3.15' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | tasks.register("clean", Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /lib/features/storage/data/repository/cloud_storage_repository_impl.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:flutter_group_chat/features/storage/data/remote_data_source/cloud_storage_remote_data_source.dart'; 8 | import 'package:flutter_group_chat/features/storage/domain/repository/cloud_storage_repository.dart'; 9 | 10 | class CloudStorageRepositoryImpl implements CloudStorageRepository{ 11 | 12 | 13 | final CloudStorageRemoteDataSource remoteDataSource; 14 | 15 | CloudStorageRepositoryImpl({required this.remoteDataSource}); 16 | 17 | @override 18 | Future uploadGroupImage({required File file}) async => 19 | remoteDataSource.uploadGroupImage(file: file); 20 | 21 | @override 22 | Future uploadProfileImage({required File file}) async => 23 | remoteDataSource.uploadProfileImage(file: file); 24 | 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/features/group/domain/entities/text_message_entity.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:equatable/equatable.dart'; 6 | 7 | class TextMessageEntity extends Equatable{ 8 | 9 | final String? recipientId; 10 | final String? senderId; 11 | final String? senderName; 12 | final String? type; 13 | final Timestamp? time; 14 | final String? content; 15 | final String? receiverName; 16 | final String? messageId; 17 | 18 | TextMessageEntity( 19 | {this.recipientId, 20 | this.senderId, 21 | this.senderName, 22 | this.type, 23 | this.time, 24 | this.content, 25 | this.receiverName, 26 | this.messageId}); 27 | 28 | @override 29 | // TODO: implement props 30 | List get props => [ 31 | recipientId, 32 | senderId, 33 | senderName, 34 | type, 35 | time, 36 | content, 37 | receiverName, 38 | messageId 39 | ]; 40 | 41 | } -------------------------------------------------------------------------------- /lib/features/global/widgets/container_button.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 6 | 7 | class ContainerButton extends StatelessWidget { 8 | final VoidCallback? onTap; 9 | final String? title; 10 | const ContainerButton({Key? key,this.title,this.onTap}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return InkWell( 15 | onTap: onTap, 16 | child: Container( 17 | alignment: Alignment.center, 18 | height: 44, 19 | width: MediaQuery.of(context).size.width, 20 | decoration: BoxDecoration( 21 | color: greenColor, 22 | borderRadius: BorderRadius.all(Radius.circular(10)), 23 | ), 24 | child: Text( 25 | title!, 26 | style: TextStyle( 27 | fontSize: 17, 28 | fontWeight: FontWeight.w700, 29 | color: Colors.white), 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/single_user/single_user_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 6 | import 'package:flutter_group_chat/features/user/domain/usercases/get_single_user_usecase.dart'; 7 | 8 | part 'single_user_state.dart'; 9 | 10 | class SingleUserCubit extends Cubit { 11 | final GetSingleUserUseCase getSingleUserUseCase; 12 | SingleUserCubit({required this.getSingleUserUseCase}) : super(SingleUserInitial()); 13 | 14 | Future getSingleUserProfile({required UserEntity user})async{ 15 | emit(SingleUserLoading()); 16 | try{ 17 | final streamResponse= getSingleUserUseCase.call(user); 18 | streamResponse.listen((user) { 19 | emit(SingleUserLoaded(currentUser: user.first)); 20 | }); 21 | }on SocketException catch(_){ 22 | emit(SingleUserFailure()); 23 | }catch(_){ 24 | emit(SingleUserFailure()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/features/user/data/model/user_model.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 5 | 6 | class UserModel extends UserEntity{ 7 | 8 | UserModel({ 9 | String? name, 10 | String? email, 11 | String? uid, 12 | String? status, 13 | String? profileUrl, 14 | }) : super ( 15 | name: name, 16 | email: email, 17 | uid: uid, 18 | status: status, 19 | profileUrl: profileUrl, 20 | ); 21 | 22 | 23 | factory UserModel.fromSnapshot(DocumentSnapshot snapshot){ 24 | var snapshotMap = snapshot.data() as Map; 25 | 26 | return UserModel( 27 | name: snapshotMap['name'], 28 | profileUrl: snapshotMap['profileUrl'], 29 | status: snapshotMap['status'], 30 | uid: snapshotMap['uid'], 31 | email: snapshotMap['email'] 32 | ); 33 | } 34 | 35 | Map toDocument(){ 36 | return { 37 | "name": name, 38 | "email": email, 39 | "uid": uid, 40 | "status": status, 41 | "profileUrl": profileUrl, 42 | }; 43 | } 44 | 45 | 46 | } -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 17 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 18 | - platform: android 19 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 20 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 21 | - platform: ios 22 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 23 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /lib/features/injection_container.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:firebase_auth/firebase_auth.dart'; 7 | import 'package:firebase_storage/firebase_storage.dart'; 8 | import 'package:flutter_group_chat/features/group/group_injection_container.dart'; 9 | import 'package:flutter_group_chat/features/storage/storage_injection_container.dart'; 10 | import 'package:flutter_group_chat/features/user/user_injection_container.dart'; 11 | import 'package:get_it/get_it.dart'; 12 | import 'package:google_sign_in/google_sign_in.dart'; 13 | 14 | 15 | final sl = GetIt.instance; 16 | 17 | Future init() async { 18 | 19 | 20 | 21 | /// External 22 | final auth = FirebaseAuth.instance; 23 | final fireStore = FirebaseFirestore.instance; 24 | final GoogleSignIn googleSignIn = GoogleSignIn(); 25 | final FirebaseStorage storage = FirebaseStorage.instance; 26 | 27 | 28 | 29 | 30 | sl.registerLazySingleton(() => auth); 31 | sl.registerLazySingleton(() => fireStore); 32 | sl.registerLazySingleton(() => googleSignIn); 33 | sl.registerLazySingleton(() => storage); 34 | 35 | 36 | await userInjectionContainer(); 37 | await storageInjectionContainer(); 38 | await groupInjectionContainer(); 39 | 40 | } -------------------------------------------------------------------------------- /lib/features/global/custom_tab_bar/custom_tab_bar_item.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | class CustomTabBarItem extends StatelessWidget { 8 | final String text; 9 | final double width; 10 | final double height; 11 | final Color borderColor; 12 | final double borderWidth; 13 | final Color textColor; 14 | final VoidCallback onTap; 15 | 16 | const CustomTabBarItem({ 17 | Key? key, 18 | this.text="", 19 | this.width = 50.0, 20 | this.height = 50.0, 21 | this.borderColor = Colors.white, 22 | this.borderWidth = 3.0, 23 | this.textColor = Colors.white, 24 | required this.onTap, 25 | }) : super(key: key); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return InkWell( 30 | onTap: onTap, 31 | child: Container( 32 | width: width, 33 | height: height, 34 | alignment: Alignment.center, 35 | decoration: BoxDecoration( 36 | border: Border( 37 | bottom: BorderSide(color: borderColor, width: borderWidth))), 38 | child: Text( 39 | text, 40 | textAlign: TextAlign.center, 41 | style: TextStyle( 42 | fontSize: 16, fontWeight: FontWeight.w500, color: textColor), 43 | ), 44 | ), 45 | ); 46 | } 47 | } -------------------------------------------------------------------------------- /lib/features/global/custom_text_field/textfield_container.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 6 | 7 | class TextFieldContainer extends StatelessWidget { 8 | final bool? isObscureText; 9 | final TextInputType? keyboardType; 10 | final TextEditingController? controller; 11 | final String? hintText; 12 | final IconData? prefixIcon; 13 | const TextFieldContainer({Key? key,this.hintText,this.prefixIcon,this.isObscureText,this.keyboardType,this.controller}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | height: 45, 19 | width: MediaQuery.of(context).size.width, 20 | decoration: BoxDecoration( 21 | color: color747480.withOpacity(.2), 22 | borderRadius: BorderRadius.circular(10) 23 | ), 24 | child: TextField( 25 | obscureText:isObscureText == true? true:false, 26 | keyboardType: keyboardType==null?TextInputType.text:keyboardType, 27 | controller: controller, 28 | decoration: InputDecoration( 29 | prefixIcon: Icon(prefixIcon==null?Icons.circle:prefixIcon), 30 | hintText: hintText, 31 | border: InputBorder.none 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/features/group/data/repositories/group_repository_impl.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/group/data/remote_data_source/group_remote_data_source.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 6 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 7 | import 'package:flutter_group_chat/features/group/domain/repositories/group_repository.dart'; 8 | 9 | class GroupRepositoryImpl implements GroupRepository { 10 | 11 | final GroupRemoteDataSource remoteDataSource; 12 | 13 | GroupRepositoryImpl({required this.remoteDataSource}); 14 | 15 | @override 16 | Future getCreateGroup(GroupEntity groupEntity) async => 17 | remoteDataSource.getCreateGroup(groupEntity); 18 | 19 | @override 20 | Stream> getGroups() => 21 | remoteDataSource.getGroups(); 22 | 23 | @override 24 | Stream> getMessages(String channelId) => 25 | remoteDataSource.getMessages(channelId); 26 | 27 | @override 28 | Future sendTextMessage(TextMessageEntity textMessageEntity, String channelId) => 29 | remoteDataSource.sendTextMessage(textMessageEntity, channelId); 30 | 31 | @override 32 | Future updateGroup(GroupEntity groupEntity) => 33 | remoteDataSource.updateGroup(groupEntity); 34 | 35 | } -------------------------------------------------------------------------------- /lib/features/group/data/models/group_model.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 5 | 6 | class GroupModel extends GroupEntity{ 7 | GroupModel({ 8 | final String? groupName, 9 | final String? groupProfileImage, 10 | final String? uid, 11 | final Timestamp? createAt, 12 | final String? groupId, 13 | final String? lastMessage, 14 | }):super( 15 | groupName: groupName, 16 | createAt: createAt, 17 | groupId: groupId, 18 | groupProfileImage: groupProfileImage, 19 | uid: uid, 20 | lastMessage: lastMessage, 21 | ); 22 | 23 | 24 | factory GroupModel.fromSnapshot(DocumentSnapshot snapshot) { 25 | return GroupModel( 26 | groupName: snapshot.get('groupName'), 27 | createAt: snapshot.get('createAt'), 28 | groupId: snapshot.get('groupId'), 29 | groupProfileImage: snapshot.get('groupProfileImage'), 30 | lastMessage: snapshot.get('lastMessage'), 31 | uid: snapshot.get('uid'), 32 | ); 33 | } 34 | 35 | Map toDocument() { 36 | return { 37 | "groupName": groupName, 38 | "createAt": createAt, 39 | "groupId": groupId, 40 | "groupProfileImage": groupProfileImage, 41 | "lastMessage": lastMessage, 42 | "uid": uid, 43 | }; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/user/user_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 6 | import 'package:flutter_group_chat/features/user/domain/usercases/get_all_users_usecase.dart'; 7 | import 'package:flutter_group_chat/features/user/domain/usercases/get_update_user_usecase.dart'; 8 | 9 | part 'user_state.dart'; 10 | 11 | class UserCubit extends Cubit { 12 | final GetAllUsersUseCase getAllUsersUseCase; 13 | final GetUpdateUserUseCase getUpdateUserUseCase; 14 | UserCubit({required this.getAllUsersUseCase,required this.getUpdateUserUseCase}) : super(UserInitial()); 15 | 16 | 17 | Future getUsers({required UserEntity user})async{ 18 | emit(UserLoading()); 19 | try{ 20 | final streamResponse= getAllUsersUseCase.call(user); 21 | streamResponse.listen((users) { 22 | emit(UserLoaded(users: users)); 23 | }); 24 | }on SocketException catch(_){ 25 | emit(UserFailure()); 26 | }catch(_){ 27 | emit(UserFailure()); 28 | } 29 | } 30 | 31 | Future getUpdateUser({required UserEntity user})async{ 32 | try{ 33 | await getUpdateUserUseCase.call(user); 34 | }on SocketException catch(_){ 35 | emit(UserFailure()); 36 | }catch(_){ 37 | emit(UserFailure()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/features/storage/data/remote_data_source/cloud_storage_remote_data_source_impl.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:firebase_storage/firebase_storage.dart'; 4 | import 'package:flutter_group_chat/features/storage/data/remote_data_source/cloud_storage_remote_data_source.dart'; 5 | 6 | class CloudStorageRemoteDataSourceImpl implements CloudStorageRemoteDataSource { 7 | final FirebaseStorage storage; 8 | 9 | CloudStorageRemoteDataSourceImpl({required this.storage}); 10 | 11 | @override 12 | Future uploadGroupImage({required File file}) async{ 13 | final ref = storage.ref().child( 14 | "group/${DateTime.now().millisecondsSinceEpoch}${getNameOnly(file.path)}", 15 | ); 16 | 17 | final uploadTask = ref.putFile(file); 18 | 19 | final imageUrl = (await uploadTask.whenComplete(() {})).ref.getDownloadURL(); 20 | 21 | 22 | return imageUrl; 23 | } 24 | 25 | @override 26 | Future uploadProfileImage({required File file}) async { 27 | final ref = storage.ref().child( 28 | "profile/${DateTime.now().millisecondsSinceEpoch}${getNameOnly(file.path)}", 29 | ); 30 | 31 | final uploadTask = ref.putFile(file); 32 | 33 | final imageUrl = (await uploadTask.whenComplete(() {})).ref.getDownloadURL(); 34 | 35 | 36 | return imageUrl; 37 | } 38 | 39 | static String getNameOnly(String path) { 40 | return path.split('/').last.split('%').last.split("?").first; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/features/storage/storage_injection_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_group_chat/features/injection_container.dart'; 2 | import 'package:flutter_group_chat/features/storage/data/remote_data_source/cloud_storage_remote_data_source.dart'; 3 | import 'package:flutter_group_chat/features/storage/data/remote_data_source/cloud_storage_remote_data_source_impl.dart'; 4 | import 'package:flutter_group_chat/features/storage/data/repository/cloud_storage_repository_impl.dart'; 5 | import 'package:flutter_group_chat/features/storage/domain/repository/cloud_storage_repository.dart'; 6 | import 'package:flutter_group_chat/features/storage/domain/usecases/upload_group_image_usecase.dart'; 7 | import 'package:flutter_group_chat/features/storage/domain/usecases/upload_group_image_usecase.dart'; 8 | import 'package:flutter_group_chat/features/storage/domain/usecases/upload_profile_image_usecase.dart'; 9 | 10 | Future storageInjectionContainer() async { 11 | 12 | 13 | ///UseCases 14 | sl.registerLazySingleton(() => 15 | UploadProfileImageUseCase(repository: sl.call())); 16 | sl.registerLazySingleton(() => 17 | UploadGroupImageUseCase(repository: sl.call())); 18 | 19 | /// Repository 20 | sl.registerLazySingleton( 21 | () => CloudStorageRepositoryImpl(remoteDataSource: sl.call())); 22 | 23 | /// Remote DataSource 24 | sl.registerLazySingleton( 25 | () => CloudStorageRemoteDataSourceImpl(storage: sl.call())); 26 | } 27 | -------------------------------------------------------------------------------- /lib/features/group/presentation/cubits/chat/chat_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 6 | import 'package:flutter_group_chat/features/group/domain/usecases/get_messages_usecase.dart'; 7 | import 'package:flutter_group_chat/features/group/domain/usecases/send_text_message_usecase.dart'; 8 | 9 | part 'chat_state.dart'; 10 | 11 | class ChatCubit extends Cubit { 12 | final SendTextMessageUseCase sendTextMessageUseCase; 13 | final GetMessageUseCase getMessageUseCase; 14 | 15 | ChatCubit({ 16 | required this.sendTextMessageUseCase, 17 | required this.getMessageUseCase, 18 | }) : super(ChatInitial()); 19 | 20 | Future getMessages({required String channelId})async{ 21 | emit(ChatLoading()); 22 | try{ 23 | final streamResponse= getMessageUseCase.call(channelId); 24 | streamResponse.listen((messages) { 25 | emit(ChatLoaded(messages: messages)); 26 | }); 27 | }on SocketException catch(_){ 28 | emit(ChatFailure()); 29 | }catch(_){ 30 | emit(ChatFailure()); 31 | } 32 | } 33 | 34 | Future sendTextMessage({required TextMessageEntity textMessageEntity,required String channelId})async{ 35 | try{ 36 | await sendTextMessageUseCase.call(textMessageEntity, channelId); 37 | }on SocketException catch(_){ 38 | emit(ChatFailure()); 39 | }catch(_){ 40 | emit(ChatFailure()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/features/group/data/models/text_message_model.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 6 | 7 | class TextMessageModel extends TextMessageEntity{ 8 | 9 | TextMessageModel({ 10 | String? recipientId, 11 | String? senderId, 12 | String? senderName, 13 | String? type, 14 | Timestamp? time, 15 | String? content, 16 | String? receiverName, 17 | String? messageId, 18 | }) : super( 19 | recipientId: recipientId, 20 | senderId: senderId, 21 | senderName: senderName, 22 | type: type, 23 | time: time, 24 | content: content, 25 | receiverName: receiverName, 26 | messageId: messageId, 27 | ); 28 | 29 | 30 | factory TextMessageModel.fromSnapshot(DocumentSnapshot snapshot) { 31 | return TextMessageModel( 32 | recipientId: snapshot.get('recipientId'), 33 | senderId: snapshot.get('senderId'), 34 | senderName: snapshot.get('senderName'), 35 | type: snapshot.get('type'), 36 | time: snapshot.get('time'), 37 | content: snapshot.get('content'), 38 | receiverName: snapshot.get('receiverName'), 39 | messageId: snapshot.get('messageId'), 40 | ); 41 | } 42 | 43 | Map toDocument() { 44 | return { 45 | "recipientId": recipientId, 46 | "senderId": senderId, 47 | "senderName": senderName, 48 | "type": type, 49 | "time": time, 50 | "content": content, 51 | "receiverName": receiverName, 52 | "messageId": messageId, 53 | }; 54 | } 55 | 56 | 57 | } -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/auth/auth_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:flutter_group_chat/features/user/domain/usercases/get_current_uid_usecase.dart'; 4 | import 'package:flutter_group_chat/features/user/domain/usercases/is_sign_in_usecase.dart'; 5 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_out_usecase.dart'; 6 | 7 | part 'auth_state.dart'; 8 | 9 | class AuthCubit extends Cubit { 10 | final IsSignInUseCase isSignInUseCase; 11 | final SignOutUseCase signOutUseCase; 12 | final GetCurrentUIDUseCase getCurrentUIDUseCase; 13 | AuthCubit( 14 | {required this.isSignInUseCase, required this.signOutUseCase, required this.getCurrentUIDUseCase}) : super(AuthInitial()); 15 | 16 | 17 | 18 | 19 | Future appStarted()async { 20 | try{ 21 | 22 | final isSignIn =await isSignInUseCase.call(); 23 | 24 | if (isSignIn == true){ 25 | final uid=await getCurrentUIDUseCase.call(); 26 | 27 | 28 | emit(Authenticated(uid: uid)); 29 | }else{ 30 | emit(UnAuthenticated()); 31 | } 32 | 33 | }catch(_){ 34 | emit(UnAuthenticated()); 35 | } 36 | } 37 | 38 | Future loggedIn()async{ 39 | try{ 40 | 41 | final uid = await getCurrentUIDUseCase.call(); 42 | 43 | emit(Authenticated(uid: uid)); 44 | 45 | }catch(_){ 46 | emit(UnAuthenticated()); 47 | } 48 | } 49 | 50 | Future loggedOut()async{ 51 | try{ 52 | 53 | await signOutUseCase.call(); 54 | emit(UnAuthenticated()); 55 | 56 | }catch(_){ 57 | emit(UnAuthenticated()); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | target.build_configurations.each do |config| 44 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "540262256184", 4 | "project_id": "flutter-group-chat-60cfb", 5 | "storage_bucket": "flutter-group-chat-60cfb.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:540262256184:android:022f5533c6504db310a0cd", 11 | "android_client_info": { 12 | "package_name": "com.example.flutter_group_chat" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "540262256184-2jdprbj3qc8li0ummb3f1hhkpme5l64v.apps.googleusercontent.com", 18 | "client_type": 1, 19 | "android_info": { 20 | "package_name": "com.example.flutter_group_chat", 21 | "certificate_hash": "769e4fc31d1ad22eb04cbf984b9789a767e161c6" 22 | } 23 | }, 24 | { 25 | "client_id": "540262256184-lqt94bj4nbd9hcre6cj1mlea3sm6juqj.apps.googleusercontent.com", 26 | "client_type": 3 27 | } 28 | ], 29 | "api_key": [ 30 | { 31 | "current_key": "AIzaSyBf4T2n84xCa-XLnYWqImgFy3Fl60vh7kw" 32 | } 33 | ], 34 | "services": { 35 | "appinvite_service": { 36 | "other_platform_oauth_client": [ 37 | { 38 | "client_id": "540262256184-8iji4jve0dt9nvqsb9lv7aobl7cpu50v.apps.googleusercontent.com", 39 | "client_type": 3 40 | }, 41 | { 42 | "client_id": "540262256184-jcleb651gv0spmckk1ttn8qlrc033267.apps.googleusercontent.com", 43 | "client_type": 2, 44 | "ios_info": { 45 | "bundle_id": "com.example.flutterGroupChat" 46 | } 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | ], 53 | "configuration_version": "1" 54 | } -------------------------------------------------------------------------------- /lib/features/user/data/repository/user_repository_impl.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/user/data/remote_data_source/user_remote_data_source.dart'; 5 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 6 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 7 | 8 | class UserRepositoryImpl implements UserRepository { 9 | 10 | final UserRemoteDataSource remoteDataSource; 11 | 12 | UserRepositoryImpl({required this.remoteDataSource}); 13 | 14 | 15 | 16 | 17 | 18 | @override 19 | Future forgotPassword(String email) async => remoteDataSource.forgotPassword(email); 20 | 21 | @override 22 | Stream> getAllUsers(UserEntity user) => 23 | remoteDataSource.getAllUsers(user); 24 | 25 | @override 26 | Future getCreateCurrentUser(UserEntity user)async => 27 | remoteDataSource.getCreateCurrentUser(user); 28 | 29 | @override 30 | Future getCurrentUId() async => 31 | remoteDataSource.getCurrentUId(); 32 | 33 | @override 34 | Stream> getSingleUser(UserEntity user) => 35 | remoteDataSource.getSingleUser(user); 36 | 37 | @override 38 | Future getUpdateUser(UserEntity user) async => 39 | remoteDataSource.getUpdateUser(user); 40 | 41 | @override 42 | Future googleAuth() async => 43 | remoteDataSource.googleAuth(); 44 | 45 | @override 46 | Future isSignIn() async => 47 | remoteDataSource.isSignIn(); 48 | 49 | @override 50 | Future signIn(UserEntity user) async => 51 | remoteDataSource.signIn(user); 52 | 53 | @override 54 | Future signOut() async => 55 | remoteDataSource.signOut(); 56 | 57 | @override 58 | Future signUp(UserEntity user) async => 59 | remoteDataSource.signUp(user); 60 | 61 | } -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 17 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /lib/features/group/presentation/cubits/group/group_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 6 | import 'package:flutter_group_chat/features/group/domain/usecases/get_create_group_usecase.dart'; 7 | import 'package:flutter_group_chat/features/group/domain/usecases/get_groups_usecase.dart'; 8 | import 'package:flutter_group_chat/features/group/domain/usecases/update_group_usecase.dart'; 9 | 10 | part 'group_state.dart'; 11 | 12 | class GroupCubit extends Cubit { 13 | final GetCreateGroupUseCase getCreateGroupUseCase; 14 | final GetGroupsUseCase getGroupsUseCase; 15 | final UpdateGroupUseCase updateGroupUseCase; 16 | 17 | GroupCubit({ 18 | required this.getCreateGroupUseCase, 19 | required this.getGroupsUseCase, 20 | required this.updateGroupUseCase, 21 | }) : super(GroupInitial()); 22 | 23 | 24 | 25 | Future getGroups()async{ 26 | emit(GroupLoading()); 27 | try{ 28 | final streamResponse= getGroupsUseCase.call(); 29 | streamResponse.listen((groups) { 30 | emit(GroupLoaded(groups: groups)); 31 | }); 32 | }on SocketException catch(_){ 33 | emit(GroupFailure()); 34 | }catch(_){ 35 | emit(GroupFailure()); 36 | } 37 | 38 | 39 | } 40 | 41 | Future getCreateGroup({required GroupEntity groupEntity})async{ 42 | try{ 43 | await getCreateGroupUseCase.call(groupEntity); 44 | }on SocketException catch(_){ 45 | emit(GroupFailure()); 46 | }catch(_){ 47 | emit(GroupFailure()); 48 | } 49 | } 50 | 51 | Future updateGroup({required GroupEntity groupEntity})async{ 52 | try{ 53 | await updateGroupUseCase.call(groupEntity); 54 | }on SocketException catch(_){ 55 | emit(GroupFailure()); 56 | }catch(_){ 57 | emit(GroupFailure()); 58 | } 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lib/features/user/presentation/pages/all_users/all_users_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_group_chat/features/user/presentation/cubit/user/user_cubit.dart'; 4 | import 'package:network_image/network_image.dart'; 5 | 6 | class AllUsersPage extends StatelessWidget { 7 | const AllUsersPage({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | body: BlocBuilder( 13 | builder: (context, userState) { 14 | if (userState is UserLoaded) { 15 | final users = userState.users; 16 | 17 | 18 | return ListView.builder( 19 | itemCount: users.length, itemBuilder: (context, index) { 20 | 21 | final user = users[index]; 22 | 23 | return ListTile( 24 | title:Text("${user.name}"), 25 | subtitle: Text("${user.status}"), 26 | leading: Container( 27 | height: 50, 28 | width: 50, 29 | child: ClipRRect( 30 | borderRadius: BorderRadius.circular(50), 31 | child: NetworkImageWidget( 32 | borderRadiusImageFile: 50, 33 | imageFileBoxFit: BoxFit.cover, 34 | placeHolderBoxFit: BoxFit.cover, 35 | networkImageBoxFit: BoxFit.cover, 36 | imageUrl: user.profileUrl, 37 | progressIndicatorBuilder: Center( 38 | child: CircularProgressIndicator(), 39 | ), 40 | placeHolder: "assets/profile_default.png", 41 | ), 42 | ), 43 | ), 44 | trailing: Icon(Icons.favorite), 45 | ); 46 | }); 47 | } 48 | 49 | 50 | return Center( 51 | child: CircularProgressIndicator(), 52 | ); 53 | }, 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Group Chat (firebase + clean architecture) 2 | 3 | ### Show some and star the repo to support the project 4 | 5 |
6 | 7 | 8 |
9 | 10 | Flutter Firebase Clean Architecture Complete Course in 5 plus hours 11 |
12 |

13 |
14 | 15 | 16 | 17 | 18 | ### Screenshots 19 | 20 |

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |

29 | 30 | 31 | 32 | 33 | 34 | ### # The Clean Architecture [proposed by our friendly Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) 35 | 36 |

37 | 38 |

39 | 40 | ### Created & Maintained By 41 | 42 | [@MA](https://github.com/amirk3321) , Youtube : [@eTechViral](https://www.youtube.com/channel/UCO6gMNHYhRqyzbskNh4gG_A) , Twitter : [@MA](https://www.instagram.com/m.amir.k.official/) LinkedIn [@MA](https://www.linkedin.com/in/muhammad-aamir-119542b3/) 43 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/features/group/group_injection_container.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter_group_chat/features/group/data/remote_data_source/group_remote_data_source.dart'; 5 | import 'package:flutter_group_chat/features/group/data/remote_data_source/group_remote_data_source_impl.dart'; 6 | import 'package:flutter_group_chat/features/group/data/repositories/group_repository_impl.dart'; 7 | import 'package:flutter_group_chat/features/group/domain/repositories/group_repository.dart'; 8 | import 'package:flutter_group_chat/features/group/domain/usecases/get_create_group_usecase.dart'; 9 | import 'package:flutter_group_chat/features/group/domain/usecases/get_groups_usecase.dart'; 10 | import 'package:flutter_group_chat/features/group/domain/usecases/get_messages_usecase.dart'; 11 | import 'package:flutter_group_chat/features/group/domain/usecases/send_text_message_usecase.dart'; 12 | import 'package:flutter_group_chat/features/group/domain/usecases/update_group_usecase.dart'; 13 | import 'package:flutter_group_chat/features/group/presentation/cubits/chat/chat_cubit.dart'; 14 | import 'package:flutter_group_chat/features/group/presentation/cubits/group/group_cubit.dart'; 15 | import 'package:flutter_group_chat/features/injection_container.dart'; 16 | 17 | Future groupInjectionContainer() async { 18 | 19 | //Future Cubit/Bloc 20 | sl.registerFactory(() => GroupCubit( 21 | getGroupsUseCase: sl.call(), 22 | getCreateGroupUseCase: sl.call(), 23 | updateGroupUseCase: sl.call(), 24 | )); 25 | sl.registerFactory(() => ChatCubit( 26 | getMessageUseCase: sl.call(), 27 | sendTextMessageUseCase: sl.call(), 28 | )); 29 | 30 | 31 | //UseCases 32 | 33 | sl.registerLazySingleton( 34 | () => GetCreateGroupUseCase(repository: sl.call())); 35 | sl.registerLazySingleton( 36 | () => GetGroupsUseCase(repository: sl.call())); 37 | sl.registerLazySingleton( 38 | () => UpdateGroupUseCase(repository: sl.call())); 39 | sl.registerLazySingleton( 40 | () => GetMessageUseCase(repository: sl.call())); 41 | sl.registerLazySingleton( 42 | () => SendTextMessageUseCase(repository: sl.call())); 43 | 44 | 45 | 46 | //Repository 47 | sl.registerLazySingleton( 48 | () => GroupRepositoryImpl(remoteDataSource: sl.call())); 49 | 50 | //Remote DataSource 51 | sl.registerLazySingleton( 52 | () => GroupRemoteDataSourceImpl(firestore: sl.call(),)); 53 | } -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply plugin: 'com.google.gms.google-services' 27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 28 | 29 | android { 30 | namespace "com.example.flutter_group_chat" 31 | compileSdkVersion flutter.compileSdkVersion 32 | ndkVersion flutter.ndkVersion 33 | 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | 39 | kotlinOptions { 40 | jvmTarget = '1.8' 41 | } 42 | 43 | sourceSets { 44 | main.java.srcDirs += 'src/main/kotlin' 45 | } 46 | 47 | defaultConfig { 48 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 49 | applicationId "com.example.flutter_group_chat" 50 | // You can update the following values to match your application needs. 51 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 52 | minSdkVersion 21 53 | targetSdkVersion flutter.targetSdkVersion 54 | versionCode flutterVersionCode.toInteger() 55 | versionName flutterVersionName 56 | } 57 | 58 | buildTypes { 59 | release { 60 | // TODO: Add your own signing config for the release build. 61 | // Signing with the debug keys for now, so `flutter run --release` works. 62 | signingConfig signingConfigs.debug 63 | } 64 | } 65 | } 66 | 67 | flutter { 68 | source '../..' 69 | } 70 | 71 | dependencies { 72 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 73 | } 74 | -------------------------------------------------------------------------------- /lib/features/global/custom_tab_bar/custom_tab_bar.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_group_chat/features/global/custom_tab_bar/custom_tab_bar_item.dart'; 6 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 7 | 8 | typedef TabClickListener=Function(int index); 9 | 10 | class CustomTabBar extends StatefulWidget { 11 | final TabClickListener tabClickListener; 12 | final int index; 13 | 14 | const CustomTabBar({Key? key, this.index = 0,required this.tabClickListener}) : super(key: key); 15 | 16 | @override 17 | _CustomTabBarState createState() => _CustomTabBarState(); 18 | } 19 | 20 | class _CustomTabBarState extends State { 21 | int _indexHolder=0; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Container( 26 | height: 50, 27 | decoration: BoxDecoration( 28 | color: primaryColor, 29 | ), 30 | child: Row( 31 | children: [ 32 | Expanded( 33 | child: CustomTabBarItem( 34 | width: 50, 35 | text: "Groups", 36 | textColor: widget.index == 0 ? textIconColor : textIconColorGray, 37 | borderColor: widget.index == 0 ? textIconColor : Colors.transparent, 38 | onTap: (){ 39 | setState(() { 40 | _indexHolder=0; 41 | }); 42 | widget.tabClickListener(_indexHolder); 43 | }, 44 | ), 45 | ), 46 | Expanded( 47 | child: CustomTabBarItem( 48 | text: "Users", 49 | textColor: widget.index == 1 ? textIconColor : textIconColorGray, 50 | borderColor: widget.index == 1 ? textIconColor : Colors.transparent, 51 | onTap: (){ 52 | setState(() { 53 | _indexHolder=1; 54 | }); 55 | widget.tabClickListener(_indexHolder); 56 | }, 57 | ), 58 | ), 59 | Expanded( 60 | child: CustomTabBarItem( 61 | text: "Profile", 62 | textColor: widget.index == 2 ? textIconColor : textIconColorGray, 63 | borderColor: widget.index == 2 ? textIconColor : Colors.transparent, 64 | onTap: (){ 65 | setState(() { 66 | _indexHolder=2; 67 | }); 68 | widget.tabClickListener(_indexHolder); 69 | }, 70 | ), 71 | ) 72 | ], 73 | ), 74 | ); 75 | } 76 | } -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | CFBundleURLTypes 9 | 10 | 11 | CFBundleTypeRole 12 | Editor 13 | CFBundleURLSchemes 14 | 15 | 16 | 17 | com.googleusercontent.apps.540262256184-jcleb651gv0spmckk1ttn8qlrc033267 18 | 19 | 20 | 21 | NSPhotoLibraryUsageDescription 22 | Allow Group Chat to your photo or video library to update your account’s profile image. 23 | 24 | CFBundleDevelopmentRegion 25 | $(DEVELOPMENT_LANGUAGE) 26 | CFBundleDisplayName 27 | Flutter Group Chat 28 | CFBundleExecutable 29 | $(EXECUTABLE_NAME) 30 | CFBundleIdentifier 31 | $(PRODUCT_BUNDLE_IDENTIFIER) 32 | CFBundleInfoDictionaryVersion 33 | 6.0 34 | CFBundleName 35 | flutter_group_chat 36 | CFBundlePackageType 37 | APPL 38 | CFBundleShortVersionString 39 | $(FLUTTER_BUILD_NAME) 40 | CFBundleSignature 41 | ???? 42 | CFBundleVersion 43 | $(FLUTTER_BUILD_NUMBER) 44 | LSRequiresIPhoneOS 45 | 46 | UILaunchStoryboardName 47 | LaunchScreen 48 | UIMainStoryboardFile 49 | Main 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | UIViewControllerBasedStatusBarAppearance 64 | 65 | CADisableMinimumFrameDurationOnPhone 66 | 67 | UIApplicationSupportsIndirectInputEvents 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /lib/on_generate_route.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_group_chat/features/global/const/page_const.dart'; 6 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 7 | import 'package:flutter_group_chat/features/group/domain/entities/single_chat_entity.dart'; 8 | import 'package:flutter_group_chat/features/group/presentation/create_group/create_group_page.dart'; 9 | import 'package:flutter_group_chat/features/group/presentation/pages/chat/single_chat_page.dart'; 10 | import 'package:flutter_group_chat/features/user/presentation/pages/credential/forgot_password_page.dart'; 11 | import 'package:flutter_group_chat/features/user/presentation/pages/credential/login_page.dart'; 12 | import 'package:flutter_group_chat/features/user/presentation/pages/credential/sign_up_page.dart'; 13 | 14 | class OnGenerateRoute{ 15 | 16 | 17 | static Route route(RouteSettings settings){ 18 | 19 | final args = settings.arguments; 20 | 21 | 22 | switch(settings.name){ 23 | case PageConst.loginPage :{ 24 | return materialPageBuilder(widget: LoginPage()); 25 | } 26 | case PageConst.forgotPage :{ 27 | return materialPageBuilder(widget: ForgotPasswordPage()); 28 | } 29 | case PageConst.registrationPage :{ 30 | return materialPageBuilder(widget: SignUpPage()); 31 | } 32 | 33 | case PageConst.createGroupPage :{ 34 | 35 | if (args is String){ 36 | return materialPageBuilder(widget: CreateGroupPage(uid: args,)); 37 | }else{ 38 | return materialPageBuilder(widget: ErrorPage()); 39 | } 40 | 41 | 42 | } 43 | case PageConst.singleChatPage :{ 44 | 45 | if (args is SingleChatEntity){ 46 | return materialPageBuilder(widget: SingleChatPage(singleChatEntity: args,)); 47 | }else{ 48 | return materialPageBuilder(widget: ErrorPage()); 49 | } 50 | 51 | 52 | } 53 | default: 54 | return materialPageBuilder(widget: ErrorPage()); 55 | } 56 | 57 | } 58 | 59 | 60 | 61 | 62 | } 63 | 64 | class ErrorPage extends StatelessWidget { 65 | const ErrorPage({Key? key}) : super(key: key); 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return Scaffold( 70 | appBar: AppBar( 71 | title: Text("error"), 72 | ), 73 | body: Center( 74 | child: Text("error"), 75 | ), 76 | ); 77 | } 78 | } 79 | 80 | 81 | 82 | MaterialPageRoute materialPageBuilder({required Widget widget}){ 83 | return MaterialPageRoute(builder: (_) =>widget); 84 | } -------------------------------------------------------------------------------- /lib/features/user/presentation/cubit/credential/credential_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 6 | import 'package:flutter_group_chat/features/user/domain/usercases/forgot_password_usecase.dart'; 7 | import 'package:flutter_group_chat/features/user/domain/usercases/google_auth_usecase.dart'; 8 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_in_usecase.dart'; 9 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_up_usecase.dart'; 10 | 11 | part 'credential_state.dart'; 12 | 13 | class CredentialCubit extends Cubit { 14 | final SignUpUseCase signUpUseCase; 15 | final SignInUseCase signInUseCase; 16 | final ForgotPasswordUseCase forgotPasswordUseCase; 17 | final GoogleAuthUseCase googleAuthUseCase; 18 | 19 | CredentialCubit( 20 | {required this.signUpUseCase, 21 | required this.signInUseCase, 22 | required this.forgotPasswordUseCase, 23 | required this.googleAuthUseCase}) 24 | : super(CredentialInitial()); 25 | 26 | Future forgotPassword({required String email}) async { 27 | try { 28 | await forgotPasswordUseCase.call(email); 29 | } on SocketException catch (_) { 30 | emit(CredentialFailure()); 31 | } catch (_) { 32 | emit(CredentialFailure()); 33 | } 34 | } 35 | 36 | Future signInSubmit({ 37 | required String email, 38 | required String password, 39 | }) async { 40 | emit(CredentialLoading()); 41 | try { 42 | await signInUseCase.call(UserEntity( 43 | email: email, 44 | password: password, 45 | )); 46 | emit(CredentialSuccess()); 47 | } on SocketException catch (_) { 48 | emit(CredentialFailure()); 49 | } catch (_) { 50 | emit(CredentialFailure()); 51 | } 52 | } 53 | 54 | Future signUpSubmit({required UserEntity user}) async { 55 | emit(CredentialLoading()); 56 | try { 57 | await signUpUseCase 58 | .call(user); 59 | 60 | emit(CredentialSuccess()); 61 | } on SocketException catch (_) { 62 | emit(CredentialFailure()); 63 | } catch (_) { 64 | emit(CredentialFailure()); 65 | } 66 | } 67 | 68 | Future googleAuthSubmit() async { 69 | emit(CredentialLoading()); 70 | try { 71 | await googleAuthUseCase.call(); 72 | emit(CredentialSuccess()); 73 | } on SocketException catch (_) { 74 | emit(CredentialFailure()); 75 | } catch (_) { 76 | emit(CredentialFailure()); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_core/firebase_core.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:flutter_group_chat/features/app/home/home_page.dart'; 5 | import 'package:flutter_group_chat/features/group/presentation/cubits/chat/chat_cubit.dart'; 6 | import 'package:flutter_group_chat/features/group/presentation/cubits/chat/chat_cubit.dart'; 7 | import 'package:flutter_group_chat/features/group/presentation/cubits/group/group_cubit.dart'; 8 | import 'package:flutter_group_chat/features/user/presentation/cubit/auth/auth_cubit.dart'; 9 | import 'package:flutter_group_chat/features/user/presentation/cubit/credential/credential_cubit.dart'; 10 | import 'package:flutter_group_chat/features/user/presentation/cubit/single_user/single_user_cubit.dart'; 11 | import 'package:flutter_group_chat/features/user/presentation/cubit/single_user/single_user_cubit.dart'; 12 | import 'package:flutter_group_chat/features/user/presentation/cubit/user/user_cubit.dart'; 13 | import 'package:flutter_group_chat/features/user/presentation/cubit/user/user_cubit.dart'; 14 | import 'package:flutter_group_chat/features/user/presentation/pages/credential/login_page.dart'; 15 | import 'package:flutter_group_chat/features/user/presentation/pages/credential/sign_up_page.dart'; 16 | import 'package:flutter_group_chat/on_generate_route.dart'; 17 | import 'features/injection_container.dart' as di; 18 | 19 | 20 | void main() async{ 21 | WidgetsFlutterBinding.ensureInitialized(); 22 | await Firebase.initializeApp(); 23 | await di.init(); 24 | runApp(MyApp()); 25 | } 26 | 27 | class MyApp extends StatelessWidget { 28 | @override 29 | Widget build(BuildContext context) { 30 | return MultiBlocProvider( 31 | providers: [ 32 | BlocProvider(create: (_) => di.sl()..appStarted()), 33 | BlocProvider(create: (_) => di.sl()), 34 | BlocProvider(create: (_) => di.sl()), 35 | BlocProvider(create: (_) => di.sl()), 36 | BlocProvider(create: (_) => di.sl()), 37 | BlocProvider(create: (_) => di.sl()), 38 | 39 | ], 40 | child: MaterialApp( 41 | title: 'Group Chat', 42 | onGenerateRoute: OnGenerateRoute.route, 43 | debugShowCheckedModeBanner: false, 44 | initialRoute: "/", 45 | theme: ThemeData( 46 | primarySwatch: Colors.green 47 | ), 48 | routes: { 49 | "/" : (context){ 50 | return BlocBuilder( 51 | builder: (context,authState){ 52 | 53 | if (authState is Authenticated){ 54 | return HomePage(uid: authState.uid,); 55 | }else{ 56 | return LoginPage(); 57 | } 58 | 59 | 60 | 61 | }, 62 | ); 63 | } 64 | }, 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/features/app/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_group_chat/features/global/custom_tab_bar/custom_tab_bar.dart'; 4 | import 'package:flutter_group_chat/features/group/presentation/cubits/group/group_cubit.dart'; 5 | import 'package:flutter_group_chat/features/group/presentation/pages/group_page.dart'; 6 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 7 | import 'package:flutter_group_chat/features/user/presentation/cubit/auth/auth_cubit.dart'; 8 | import 'package:flutter_group_chat/features/user/presentation/cubit/single_user/single_user_cubit.dart'; 9 | import 'package:flutter_group_chat/features/user/presentation/cubit/user/user_cubit.dart'; 10 | import 'package:flutter_group_chat/features/user/presentation/pages/all_users/all_users_page.dart'; 11 | import 'package:flutter_group_chat/features/user/presentation/pages/profile/profile_page.dart'; 12 | 13 | class HomePage extends StatefulWidget { 14 | final String uid; 15 | 16 | const HomePage({Key? key, required this.uid}) : super(key: key); 17 | 18 | @override 19 | State createState() => _HomePageState(); 20 | } 21 | 22 | class _HomePageState extends State { 23 | int _tabIndex = 0; 24 | 25 | List get pages => [GroupPage(uid: widget.uid),AllUsersPage(),ProfilePage()]; 26 | 27 | PageController _pageController = PageController(); 28 | 29 | 30 | @override 31 | void initState() { 32 | BlocProvider.of(context).getSingleUserProfile(user: UserEntity(uid: widget.uid)); 33 | BlocProvider.of(context).getUsers(user: UserEntity(uid: widget.uid)); 34 | BlocProvider.of(context).getGroups(); 35 | super.initState(); 36 | } 37 | 38 | 39 | @override 40 | void dispose() { 41 | _pageController.dispose(); 42 | super.dispose(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Scaffold( 48 | appBar: AppBar( 49 | elevation: 0.0, 50 | title: Text("Group Chat"), 51 | actions: [ 52 | Icon(Icons.search), 53 | SizedBox( 54 | width: 10, 55 | ), 56 | PopupMenuButton( 57 | icon: Icon(Icons.more_vert), 58 | itemBuilder: (BuildContext context) { 59 | return [ 60 | PopupMenuItem( 61 | onTap: () { 62 | BlocProvider.of(context).loggedOut(); 63 | }, 64 | child: Text( 65 | "Logout", 66 | ), 67 | ) 68 | ]; 69 | }, 70 | ), 71 | SizedBox( 72 | width: 10, 73 | ), 74 | ], 75 | ), 76 | body: Column( 77 | children: [ 78 | CustomTabBar( 79 | index: _tabIndex, 80 | tabClickListener: (int index) { 81 | setState(() { 82 | _tabIndex = index; 83 | _pageController.jumpToPage(index); 84 | }); 85 | }, 86 | ), 87 | Expanded( 88 | child: PageView.builder( 89 | controller: _pageController, 90 | onPageChanged: (index){ 91 | setState(() { 92 | _tabIndex = index; 93 | }); 94 | }, 95 | itemCount: pages.length, 96 | itemBuilder: (BuildContext context, int index) { 97 | return pages[index]; 98 | }, 99 | ), 100 | ) 101 | ], 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /lib/features/group/presentation/pages/group_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_group_chat/features/global/const/page_const.dart'; 4 | import 'package:flutter_group_chat/features/group/domain/entities/single_chat_entity.dart'; 5 | import 'package:flutter_group_chat/features/group/presentation/cubits/group/group_cubit.dart'; 6 | import 'package:flutter_group_chat/features/user/presentation/cubit/single_user/single_user_cubit.dart'; 7 | import 'package:network_image/network_image.dart'; 8 | 9 | class GroupPage extends StatefulWidget { 10 | final String uid; 11 | 12 | const GroupPage({Key? key, required this.uid}) : super(key: key); 13 | 14 | @override 15 | State createState() => _GroupPageState(); 16 | } 17 | 18 | class _GroupPageState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | floatingActionButton: FloatingActionButton( 23 | onPressed: () { 24 | Navigator.pushNamed(context, PageConst.createGroupPage, 25 | arguments: widget.uid); 26 | }, 27 | child: Icon(Icons.groups), 28 | ), 29 | body: BlocBuilder( 30 | builder: (context, singleUserSate) { 31 | if (singleUserSate is SingleUserLoaded) { 32 | final currentUser = singleUserSate.currentUser; 33 | 34 | return BlocBuilder( 35 | builder: (context, groupState) { 36 | if (groupState is GroupLoaded) { 37 | final groups = groupState.groups; 38 | return groups.isEmpty 39 | ? Center(child: Text("No Group Created Yet")) 40 | : ListView.builder( 41 | itemCount: groups.length, 42 | itemBuilder: (BuildContext context, int index) { 43 | final groupInfo = groups[index]; 44 | return ListTile( 45 | onTap: () { 46 | Navigator.pushNamed( 47 | context, PageConst.singleChatPage, 48 | arguments: SingleChatEntity( 49 | groupId: groupInfo.groupId!, 50 | groupName: groupInfo.groupName!, 51 | uid: currentUser.uid!, 52 | username: currentUser.name!)); 53 | }, 54 | title: Text("${groupInfo.groupName}"), 55 | subtitle: Text("${groupInfo.lastMessage}"), 56 | leading: Container( 57 | height: 50, 58 | width: 50, 59 | child: ClipRRect( 60 | borderRadius: BorderRadius.circular(50), 61 | child: NetworkImageWidget( 62 | borderRadiusImageFile: 50, 63 | imageFileBoxFit: BoxFit.cover, 64 | placeHolderBoxFit: BoxFit.cover, 65 | networkImageBoxFit: BoxFit.cover, 66 | imageUrl: groupInfo.groupProfileImage, 67 | progressIndicatorBuilder: Center( 68 | child: CircularProgressIndicator(), 69 | ), 70 | placeHolder: "assets/profile_default.png", 71 | ), 72 | ), 73 | ), 74 | ); 75 | }); 76 | } 77 | 78 | return Center( 79 | child: CircularProgressIndicator(), 80 | ); 81 | }, 82 | ); 83 | } 84 | 85 | return Center( 86 | child: CircularProgressIndicator(), 87 | ); 88 | }, 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/features/group/data/remote_data_source/group_remote_data_source_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter_group_chat/features/group/data/models/group_model.dart'; 3 | import 'package:flutter_group_chat/features/group/data/models/text_message_model.dart'; 4 | import 'package:flutter_group_chat/features/group/data/remote_data_source/group_remote_data_source.dart'; 5 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 6 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 7 | 8 | class GroupRemoteDataSourceImpl implements GroupRemoteDataSource { 9 | final FirebaseFirestore firestore; 10 | 11 | GroupRemoteDataSourceImpl({required this.firestore}); 12 | 13 | @override 14 | Future getCreateGroup(GroupEntity groupEntity) async { 15 | final groupCollection = firestore.collection("groups"); 16 | 17 | final groupId = groupCollection.doc().id; 18 | 19 | groupCollection.doc(groupId).get().then((groupShopShot) async { 20 | final newGroup = GroupModel( 21 | uid: groupEntity.uid, 22 | createAt: groupEntity.createAt, 23 | groupId: groupId, 24 | groupName: groupEntity.groupName, 25 | groupProfileImage: groupEntity.groupProfileImage, 26 | lastMessage: groupEntity.lastMessage, 27 | ).toDocument(); 28 | 29 | if (!groupShopShot.exists) { 30 | await groupCollection.doc(groupId).set(newGroup); 31 | 32 | return; 33 | } 34 | return; 35 | }); 36 | } 37 | 38 | @override 39 | Stream> getGroups() { 40 | final groupCollection = firestore.collection("groups"); 41 | return groupCollection 42 | .orderBy("createAt", descending: true) 43 | .snapshots() 44 | .map((querySnapshot) => 45 | querySnapshot.docs.map((e) => GroupModel.fromSnapshot(e)).toList()); 46 | } 47 | 48 | @override 49 | Stream> getMessages(String channelId) { 50 | final messagesCollection = firestore 51 | .collection("groupChatChannel") 52 | .doc(channelId) 53 | .collection("messages"); 54 | 55 | return messagesCollection.orderBy('time').snapshots().map((querySnap) => querySnap 56 | .docs 57 | .map((queryDoc) => TextMessageModel.fromSnapshot(queryDoc)) 58 | .toList()); 59 | } 60 | 61 | @override 62 | Future sendTextMessage( 63 | TextMessageEntity textMessageEntity, String channelId) async { 64 | final messagesCollection = firestore 65 | .collection("groupChatChannel") 66 | .doc(channelId) 67 | .collection("messages"); 68 | 69 | final messageId = messagesCollection.doc().id; 70 | 71 | final newTextMessage = TextMessageModel( 72 | content: textMessageEntity.content, 73 | senderName: textMessageEntity.senderName, 74 | senderId: textMessageEntity.senderId, 75 | recipientId: textMessageEntity.recipientId, 76 | receiverName: textMessageEntity.receiverName, 77 | time: textMessageEntity.time, 78 | messageId: messageId, 79 | type: "TEXT") 80 | .toDocument(); 81 | 82 | await messagesCollection.doc(messageId).set(newTextMessage); 83 | 84 | } 85 | 86 | @override 87 | Future updateGroup(GroupEntity groupEntity) async { 88 | Map groupInformation = Map(); 89 | 90 | final groupCollection = firestore.collection("groups"); 91 | 92 | if (groupEntity.groupProfileImage != null && 93 | groupEntity.groupProfileImage != "") 94 | groupInformation['groupProfileImage'] = groupEntity.groupProfileImage; 95 | 96 | 97 | if (groupEntity.createAt != null) 98 | groupInformation["createAt"] = groupEntity.createAt; 99 | 100 | if (groupEntity.groupName != null && groupEntity.groupName != "") 101 | groupInformation["groupName"] = groupEntity.groupName; 102 | 103 | if (groupEntity.lastMessage != null && groupEntity.lastMessage != "") 104 | groupInformation["lastMessage"] = groupEntity.lastMessage; 105 | 106 | groupCollection.doc(groupEntity.groupId).update(groupInformation); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/features/user/presentation/pages/credential/forgot_password_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_group_chat/features/global/common/common.dart'; 4 | import 'package:flutter_group_chat/features/global/const/app_const.dart'; 5 | import 'package:flutter_group_chat/features/global/const/page_const.dart'; 6 | import 'package:flutter_group_chat/features/global/custom_text_field/textfield_container.dart'; 7 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 8 | import 'package:flutter_group_chat/features/global/widgets/container_button.dart'; 9 | import 'package:flutter_group_chat/features/user/presentation/cubit/credential/credential_cubit.dart'; 10 | 11 | class ForgotPasswordPage extends StatefulWidget { 12 | const ForgotPasswordPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _ForgotPasswordPageState(); 16 | } 17 | 18 | class _ForgotPasswordPageState extends State { 19 | 20 | 21 | TextEditingController _emailController = TextEditingController(); 22 | 23 | 24 | @override 25 | void dispose() { 26 | _emailController.dispose(); 27 | super.dispose(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | body: SingleChildScrollView( 34 | child: Container( 35 | padding: EdgeInsets.symmetric(horizontal: 22, vertical: 32), 36 | child: Column( 37 | children: [ 38 | SizedBox( 39 | height: 20, 40 | ), 41 | Container( 42 | alignment: Alignment.topLeft, 43 | child: Text( 44 | 'Forgot Password', 45 | style: TextStyle( 46 | fontSize: 30, 47 | fontWeight: FontWeight.w700, 48 | color: greenColor), 49 | ), 50 | ), 51 | SizedBox( 52 | height: 10, 53 | ), 54 | Divider( 55 | thickness: 1, 56 | ), 57 | SizedBox( 58 | height: 30, 59 | ), 60 | Text( 61 | "Don't worry! Just fill in your email and ${AppConst.appName} will send you a link to rest your password.", 62 | style: TextStyle( 63 | fontSize: 14, 64 | color: Colors.black.withOpacity(.6), 65 | fontStyle: FontStyle.italic), 66 | ), 67 | SizedBox(height: 30,), 68 | TextFieldContainer( 69 | prefixIcon: Icons.email, 70 | controller: _emailController, 71 | hintText: "Email", 72 | ), 73 | SizedBox(height: 30,), 74 | ContainerButton( 75 | title: "Send Password Rest Email", 76 | onTap: (){ 77 | _submitForgotPasswordEmail(); 78 | }, 79 | ), 80 | SizedBox(height: 30,), 81 | Row( 82 | children: [ 83 | Text( 84 | 'Remember the account information? ', 85 | style: 86 | TextStyle(fontSize: 14, fontWeight: FontWeight.w500), 87 | ), 88 | InkWell( 89 | onTap: (){ 90 | Navigator.pushNamedAndRemoveUntil(context, PageConst.loginPage, (route) => false); 91 | }, 92 | child: Text( 93 | 'Login', 94 | style: TextStyle( 95 | fontSize: 14, 96 | fontWeight: FontWeight.w700, 97 | color: greenColor), 98 | ), 99 | ), 100 | ], 101 | ) 102 | ], 103 | ), 104 | ), 105 | ), 106 | ); 107 | } 108 | 109 | void _submitForgotPasswordEmail() { 110 | 111 | if (_emailController.text.isEmpty){ 112 | toast("Enter your email"); 113 | return; 114 | } 115 | 116 | 117 | BlocProvider.of(context).forgotPassword(email: _emailController.text).then((value) { 118 | toast("Email has been sent please check your mail."); 119 | }); 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_group_chat 2 | description: A new Flutter project. 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 1.0.0+1 20 | 21 | environment: 22 | sdk: '>=3.0.0 <4.0.0' 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | #serive locator 34 | get_it: ^7.0.0 35 | #state management 36 | flutter_bloc: ^8.1.2 37 | equatable: 2.0.5 38 | font_awesome_flutter: 39 | 40 | network_image: ^0.1.1 41 | 42 | intl: ^0.18.1 43 | 44 | #firebase 45 | firebase_core : 46 | firebase_auth: ^4.6.1 47 | cloud_firestore: ^4.7.1 48 | firebase_storage: ^11.2.1 49 | google_sign_in: ^6.1.0 50 | image_picker: ^0.8.7+5 51 | fluttertoast: ^8.2.1 52 | #message shape bubble 53 | bubble: ^1.2.1 54 | 55 | # The following adds the Cupertino Icons font to your application. 56 | # Use with the CupertinoIcons class for iOS style icons. 57 | cupertino_icons: ^1.0.5 58 | 59 | dev_dependencies: 60 | flutter_test: 61 | sdk: flutter 62 | 63 | # The "flutter_lints" package below contains a set of recommended lints to 64 | # encourage good coding practices. The lint set provided by the package is 65 | # activated in the `analysis_options.yaml` file located at the root of your 66 | # package. See that file for information about deactivating specific lint 67 | # rules and activating additional ones. 68 | flutter_lints: ^2.0.0 69 | 70 | # For information on the generic Dart part of this file, see the 71 | # following page: https://dart.dev/tools/pub/pubspec 72 | 73 | # The following section is specific to Flutter packages. 74 | flutter: 75 | 76 | # The following line ensures that the Material Icons font is 77 | # included with your application, so that you can use the icons in 78 | # the material Icons class. 79 | uses-material-design: true 80 | 81 | # To add assets to your application, add an assets section, like this: 82 | assets: 83 | - assets/. 84 | # - images/a_dot_ham.jpeg 85 | 86 | # An image asset can refer to one or more resolution-specific "variants", see 87 | # https://flutter.dev/assets-and-images/#resolution-aware 88 | 89 | # For details regarding adding assets from package dependencies, see 90 | # https://flutter.dev/assets-and-images/#from-packages 91 | 92 | # To add custom fonts to your application, add a fonts section here, 93 | # in this "flutter" section. Each entry in this list should have a 94 | # "family" key with the font family name, and a "fonts" key with a 95 | # list giving the asset and other descriptors for the font. For 96 | # example: 97 | # fonts: 98 | # - family: Schyler 99 | # fonts: 100 | # - asset: fonts/Schyler-Regular.ttf 101 | # - asset: fonts/Schyler-Italic.ttf 102 | # style: italic 103 | # - family: Trajan Pro 104 | # fonts: 105 | # - asset: fonts/TrajanPro.ttf 106 | # - asset: fonts/TrajanPro_Bold.ttf 107 | # weight: 700 108 | # 109 | # For details regarding fonts from package dependencies, 110 | # see https://flutter.dev/custom-fonts/#from-packages 111 | -------------------------------------------------------------------------------- /lib/features/user/data/remote_data_source/user_remote_data_source_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter_group_chat/features/user/data/model/user_model.dart'; 4 | import 'package:flutter_group_chat/features/user/data/remote_data_source/user_remote_data_source.dart'; 5 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 6 | import 'package:google_sign_in/google_sign_in.dart'; 7 | 8 | class UserRemoteDataSourceImpl implements UserRemoteDataSource { 9 | final FirebaseFirestore fireStore; 10 | final FirebaseAuth auth; 11 | final GoogleSignIn googleSignIn; 12 | 13 | UserRemoteDataSourceImpl( 14 | {required this.fireStore, 15 | required this.auth, 16 | required this.googleSignIn}); 17 | 18 | @override 19 | Future forgotPassword(String email) async{ 20 | await auth.sendPasswordResetEmail(email: email); 21 | } 22 | 23 | @override 24 | Stream> getAllUsers(UserEntity user) { 25 | final userCollection = fireStore.collection("users"); 26 | 27 | return userCollection 28 | .where("uid", isNotEqualTo: user.uid) 29 | .snapshots() 30 | .map((querySnapshot) { 31 | return querySnapshot.docs.map((e) => UserModel.fromSnapshot(e)).toList(); 32 | }); 33 | } 34 | 35 | @override 36 | Future getCreateCurrentUser(UserEntity user) async { 37 | final userCollection = fireStore.collection("users"); 38 | 39 | final uid = await getCurrentUId(); 40 | 41 | userCollection.doc(uid).get().then((userDoc) { 42 | if (!userDoc.exists) { 43 | final newUser = UserModel( 44 | email: user.email, 45 | uid: uid, 46 | status: user.status, 47 | profileUrl: user.profileUrl, 48 | name: user.name, 49 | ).toDocument(); 50 | 51 | userCollection.doc(uid).set(newUser); 52 | } else { 53 | print("User already exists"); 54 | return; 55 | } 56 | }); 57 | } 58 | 59 | @override 60 | Future getCurrentUId() async => auth.currentUser!.uid; 61 | 62 | @override 63 | Stream> getSingleUser(UserEntity user) { 64 | final userCollection = fireStore.collection("users"); 65 | 66 | return userCollection 67 | .limit(1) 68 | .where("uid", isEqualTo: user.uid) 69 | .snapshots() 70 | .map((querySnapshot) { 71 | return querySnapshot.docs.map((e) => UserModel.fromSnapshot(e)).toList(); 72 | }); 73 | } 74 | 75 | @override 76 | Future getUpdateUser(UserEntity user) async { 77 | final userCollection = fireStore.collection("users"); 78 | 79 | Map userInformation = Map(); 80 | 81 | if (user.profileUrl != null && user.profileUrl != "") { 82 | userInformation['profileUrl'] = user.profileUrl; 83 | } 84 | 85 | if (user.status != null && user.status != "") { 86 | userInformation['status'] = user.status; 87 | } 88 | 89 | if (user.name != null && user.name != "") { 90 | userInformation['name'] = user.name; 91 | } 92 | 93 | await userCollection.doc(user.uid).update(userInformation); 94 | } 95 | 96 | @override 97 | Future googleAuth() async{ 98 | final userCollection = fireStore.collection("users"); 99 | 100 | try{ 101 | 102 | final GoogleSignInAccount? account = await googleSignIn.signIn(); 103 | 104 | final GoogleSignInAuthentication googleAuth = 105 | await account!.authentication; 106 | 107 | final AuthCredential credential = GoogleAuthProvider.credential( 108 | accessToken: googleAuth.accessToken, 109 | idToken: googleAuth.idToken, 110 | ); 111 | 112 | final information = (await auth.signInWithCredential(credential)).user; 113 | 114 | getCreateCurrentUser(UserEntity( 115 | name: information!.displayName, 116 | email: information.email, 117 | status: "", 118 | profileUrl: information.photoURL, 119 | )); 120 | 121 | 122 | }catch(_){} 123 | } 124 | 125 | @override 126 | Future isSignIn() async { 127 | return auth.currentUser?.uid != null; 128 | } 129 | 130 | @override 131 | Future signIn(UserEntity user) async { 132 | await auth.signInWithEmailAndPassword( 133 | email: user.email!, password: user.password!); 134 | } 135 | 136 | @override 137 | Future signOut() async { 138 | await auth.signOut(); 139 | } 140 | 141 | @override 142 | Future signUp(UserEntity user) async { 143 | await auth 144 | .createUserWithEmailAndPassword( 145 | email: user.email!, password: user.password!) 146 | .then((value) { 147 | getCreateCurrentUser(user); 148 | }); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /lib/features/user/user_injection_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_group_chat/features/user/data/remote_data_source/user_remote_data_source.dart'; 2 | import 'package:flutter_group_chat/features/user/data/remote_data_source/user_remote_data_source_impl.dart'; 3 | import 'package:flutter_group_chat/features/user/data/repository/user_repository_impl.dart'; 4 | import 'package:flutter_group_chat/features/user/domain/repository/user_repository.dart'; 5 | import 'package:flutter_group_chat/features/user/domain/usercases/forgot_password_usecase.dart'; 6 | import 'package:flutter_group_chat/features/user/domain/usercases/get_all_users_usecase.dart'; 7 | import 'package:flutter_group_chat/features/user/domain/usercases/get_all_users_usecase.dart'; 8 | import 'package:flutter_group_chat/features/user/domain/usercases/get_create_current_user_usecase.dart'; 9 | import 'package:flutter_group_chat/features/user/domain/usercases/get_create_current_user_usecase.dart'; 10 | import 'package:flutter_group_chat/features/user/domain/usercases/get_current_uid_usecase.dart'; 11 | import 'package:flutter_group_chat/features/user/domain/usercases/get_current_uid_usecase.dart'; 12 | import 'package:flutter_group_chat/features/user/domain/usercases/get_single_user_usecase.dart'; 13 | import 'package:flutter_group_chat/features/user/domain/usercases/get_single_user_usecase.dart'; 14 | import 'package:flutter_group_chat/features/user/domain/usercases/get_update_user_usecase.dart'; 15 | import 'package:flutter_group_chat/features/user/domain/usercases/get_update_user_usecase.dart'; 16 | import 'package:flutter_group_chat/features/user/domain/usercases/google_auth_usecase.dart'; 17 | import 'package:flutter_group_chat/features/user/domain/usercases/google_auth_usecase.dart'; 18 | import 'package:flutter_group_chat/features/user/domain/usercases/is_sign_in_usecase.dart'; 19 | import 'package:flutter_group_chat/features/user/domain/usercases/is_sign_in_usecase.dart'; 20 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_in_usecase.dart'; 21 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_in_usecase.dart'; 22 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_out_usecase.dart'; 23 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_out_usecase.dart'; 24 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_up_usecase.dart'; 25 | import 'package:flutter_group_chat/features/user/domain/usercases/sign_up_usecase.dart'; 26 | import 'package:flutter_group_chat/features/user/presentation/cubit/auth/auth_cubit.dart'; 27 | import 'package:flutter_group_chat/features/user/presentation/cubit/credential/credential_cubit.dart'; 28 | import 'package:flutter_group_chat/features/user/presentation/cubit/credential/credential_cubit.dart'; 29 | import 'package:flutter_group_chat/features/user/presentation/cubit/single_user/single_user_cubit.dart'; 30 | import 'package:flutter_group_chat/features/user/presentation/cubit/single_user/single_user_cubit.dart'; 31 | import 'package:flutter_group_chat/features/user/presentation/cubit/user/user_cubit.dart'; 32 | import 'package:flutter_group_chat/features/user/presentation/cubit/user/user_cubit.dart'; 33 | 34 | import '../injection_container.dart'; 35 | 36 | Future userInjectionContainer() async { 37 | //Cubit or Bloc 38 | sl.registerFactory(() => AuthCubit( 39 | isSignInUseCase: sl.call(), 40 | signOutUseCase: sl.call(), 41 | getCurrentUIDUseCase: sl.call())); 42 | 43 | sl.registerFactory( 44 | () => SingleUserCubit(getSingleUserUseCase: sl.call())); 45 | 46 | sl.registerFactory(() => UserCubit( 47 | getAllUsersUseCase: sl.call(), 48 | getUpdateUserUseCase: sl.call(), 49 | )); 50 | 51 | sl.registerFactory(() => CredentialCubit( 52 | forgotPasswordUseCase: sl.call(), 53 | googleAuthUseCase: sl.call(), 54 | signInUseCase: sl.call(), 55 | signUpUseCase: sl.call())); 56 | 57 | //UseCases 58 | sl.registerLazySingleton( 59 | () => ForgotPasswordUseCase(repository: sl.call())); 60 | sl.registerLazySingleton( 61 | () => GetAllUsersUseCase(repository: sl.call())); 62 | sl.registerLazySingleton( 63 | () => GetCreateCurrentUserUseCase(repository: sl.call())); 64 | sl.registerLazySingleton( 65 | () => GetCurrentUIDUseCase(repository: sl.call())); 66 | sl.registerLazySingleton( 67 | () => GetSingleUserUseCase(repository: sl.call())); 68 | sl.registerLazySingleton( 69 | () => GetUpdateUserUseCase(repository: sl.call())); 70 | sl.registerLazySingleton( 71 | () => GoogleAuthUseCase(repository: sl.call())); 72 | sl.registerLazySingleton( 73 | () => IsSignInUseCase(repository: sl.call())); 74 | sl.registerLazySingleton( 75 | () => SignInUseCase(repository: sl.call())); 76 | sl.registerLazySingleton( 77 | () => SignOutUseCase(repository: sl.call())); 78 | sl.registerLazySingleton( 79 | () => SignUpUseCase(repository: sl.call())); 80 | 81 | //Repository 82 | sl.registerLazySingleton( 83 | () => UserRepositoryImpl(remoteDataSource: sl.call())); 84 | 85 | // RemoteDataSource 86 | 87 | sl.registerLazySingleton(() => UserRemoteDataSourceImpl( 88 | fireStore: sl.call(), auth: sl.call(), googleSignIn: sl.call())); 89 | } 90 | -------------------------------------------------------------------------------- /lib/features/group/presentation/create_group/create_group_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_group_chat/features/global/common/common.dart'; 7 | import 'package:flutter_group_chat/features/global/custom_text_field/textfield_container.dart'; 8 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 9 | import 'package:flutter_group_chat/features/global/widgets/container_button.dart'; 10 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 11 | import 'package:flutter_group_chat/features/group/presentation/cubits/group/group_cubit.dart'; 12 | import 'package:flutter_group_chat/features/injection_container.dart' as di; 13 | import 'package:flutter_group_chat/features/storage/domain/usecases/upload_group_image_usecase.dart'; 14 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 15 | import 'package:image_picker/image_picker.dart'; 16 | import 'package:network_image/network_image.dart'; 17 | 18 | class CreateGroupPage extends StatefulWidget { 19 | final String uid; 20 | 21 | const CreateGroupPage({Key? key, required this.uid}) : super(key: key); 22 | 23 | @override 24 | State createState() => _CreateGroupPageState(); 25 | } 26 | 27 | class _CreateGroupPageState extends State { 28 | File? _groupImage; 29 | 30 | bool _isImageUploading=false; 31 | 32 | TextEditingController _groupNameController = TextEditingController(); 33 | 34 | @override 35 | void dispose() { 36 | _groupNameController.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | Future getImage() async { 41 | try { 42 | final pickedFile = await ImagePicker().pickImage( 43 | source: ImageSource.gallery, 44 | imageQuality: 40, 45 | ); 46 | 47 | setState(() { 48 | if (pickedFile != null) { 49 | _groupImage = File(pickedFile.path); 50 | } else { 51 | print('No image selected.'); 52 | } 53 | }); 54 | } catch (e) { 55 | toast("error $e"); 56 | } 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return Scaffold( 62 | appBar: AppBar( 63 | title: Text("Create Group"), 64 | ), 65 | body: Container( 66 | margin: EdgeInsets.all(25), 67 | child: Column( 68 | children: [ 69 | InkWell( 70 | onTap: () { 71 | getImage(); 72 | }, 73 | child: Container( 74 | height: 80, 75 | width: 80, 76 | child: NetworkImageWidget( 77 | imageFile: _groupImage, 78 | borderRadiusImageFile: 50, 79 | imageFileBoxFit: BoxFit.cover, 80 | placeHolderBoxFit: BoxFit.cover, 81 | networkImageBoxFit: BoxFit.cover, 82 | imageUrl: "", 83 | progressIndicatorBuilder: Center( 84 | child: CircularProgressIndicator(), 85 | ), 86 | placeHolder: "assets/profile_default.png", 87 | ), 88 | ), 89 | ), 90 | SizedBox( 91 | height: 14, 92 | ), 93 | Text( 94 | 'Add Group Image', 95 | style: TextStyle( 96 | color: greenColor, fontSize: 16, fontWeight: FontWeight.w400), 97 | ), 98 | SizedBox( 99 | height: 20, 100 | ), 101 | TextFieldContainer( 102 | controller: _groupNameController, 103 | keyboardType: TextInputType.text, 104 | hintText: 'group name', 105 | prefixIcon: FontAwesomeIcons.edit, 106 | ), 107 | SizedBox( 108 | height: 17, 109 | ), 110 | Divider( 111 | thickness: 2, 112 | indent: 120, 113 | endIndent: 120, 114 | ), 115 | SizedBox( 116 | height: 17, 117 | ), 118 | ContainerButton( 119 | onTap: () { 120 | _createNewGroup(); 121 | }, 122 | title: "Create new Group", 123 | ), 124 | SizedBox(height: 20,), 125 | _isImageUploading==true?Row( 126 | children: [ 127 | Text("Please wait for moment..."), 128 | SizedBox(width: 10,), 129 | CircularProgressIndicator(), 130 | ], 131 | ):Text(""), 132 | ], 133 | ), 134 | ), 135 | ); 136 | } 137 | 138 | void _createNewGroup() { 139 | if (_groupNameController.text.isEmpty) { 140 | toast("Enter Group name"); 141 | return; 142 | } 143 | if (_groupImage ==null){ 144 | toast("Please select group image"); 145 | return; 146 | } 147 | 148 | setState(() { 149 | _isImageUploading =true; 150 | 151 | }); 152 | 153 | 154 | 155 | 156 | if (_groupImage != null) { 157 | di 158 | .sl() 159 | .call(file: _groupImage!) 160 | .then((imageUrl) { 161 | BlocProvider.of(context) 162 | .getCreateGroup( 163 | groupEntity: GroupEntity( 164 | createAt: Timestamp.now(), 165 | lastMessage: "", 166 | groupName: _groupNameController.text, 167 | uid: widget.uid, 168 | groupProfileImage: imageUrl, 169 | )) 170 | .then((value) { 171 | toast("group created successfully"); 172 | _clear(); 173 | 174 | }); 175 | }); 176 | } 177 | } 178 | 179 | void _clear(){ 180 | setState(() { 181 | _groupNameController.clear(); 182 | _groupImage =null; 183 | _isImageUploading=false; 184 | }); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /lib/features/user/presentation/pages/profile/profile_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_group_chat/features/global/common/common.dart'; 6 | import 'package:flutter_group_chat/features/global/custom_text_field/textfield_container.dart'; 7 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 8 | import 'package:flutter_group_chat/features/global/widgets/container_button.dart'; 9 | import 'package:flutter_group_chat/features/storage/domain/usecases/upload_profile_image_usecase.dart'; 10 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 11 | import 'package:flutter_group_chat/features/user/presentation/cubit/single_user/single_user_cubit.dart'; 12 | import 'package:flutter_group_chat/features/user/presentation/cubit/user/user_cubit.dart'; 13 | import 'package:image_picker/image_picker.dart'; 14 | import 'package:network_image/network_image.dart'; 15 | import 'package:flutter_group_chat/features/injection_container.dart' as di; 16 | 17 | class ProfilePage extends StatefulWidget { 18 | const ProfilePage({Key? key}) : super(key: key); 19 | 20 | @override 21 | State createState() => _ProfilePageState(); 22 | } 23 | 24 | class _ProfilePageState extends State { 25 | 26 | File? _image; 27 | 28 | 29 | TextEditingController _nameController=TextEditingController(); 30 | TextEditingController _statusController=TextEditingController(); 31 | TextEditingController _emailController=TextEditingController(); 32 | 33 | 34 | @override 35 | void dispose() { 36 | _nameController.dispose(); 37 | _statusController.dispose(); 38 | _emailController.dispose(); 39 | super.dispose(); 40 | } 41 | 42 | 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | body: BlocBuilder( 48 | builder: (context, singleUserState) { 49 | 50 | 51 | if (singleUserState is SingleUserLoaded){ 52 | 53 | return _bodyWidget(singleUserState.currentUser); 54 | } 55 | 56 | 57 | return Center( 58 | child: CircularProgressIndicator(), 59 | ); 60 | }, 61 | ), 62 | ); 63 | } 64 | 65 | 66 | Future getImage() async { 67 | try { 68 | final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery,imageQuality: 40,); 69 | 70 | setState(() { 71 | if (pickedFile != null) { 72 | _image = File(pickedFile.path); 73 | } else { 74 | print('No image selected.'); 75 | } 76 | }); 77 | } catch (e) { 78 | toast("error $e"); 79 | } 80 | } 81 | 82 | Widget _bodyWidget(UserEntity currentUser) { 83 | _nameController.value = TextEditingValue(text: "${currentUser.name}"); 84 | _emailController.value = TextEditingValue(text: "${currentUser.email}"); 85 | _statusController.value = TextEditingValue(text: "${currentUser.status}"); 86 | return Container( 87 | margin: EdgeInsets.all(15), 88 | child: Column( 89 | children: [ 90 | InkWell( 91 | onTap: (){ 92 | getImage(); 93 | }, 94 | child: Container( 95 | height: 80, 96 | width: 80, 97 | child: NetworkImageWidget( 98 | imageFile: _image, 99 | borderRadiusImageFile: 50, 100 | imageFileBoxFit: BoxFit.cover, 101 | placeHolderBoxFit: BoxFit.cover, 102 | networkImageBoxFit: BoxFit.cover, 103 | imageUrl: currentUser.profileUrl, 104 | progressIndicatorBuilder: Center( 105 | child: CircularProgressIndicator(), 106 | ), 107 | placeHolder: "assets/profile_default.png", 108 | ), 109 | ), 110 | ), 111 | SizedBox( 112 | height: 14, 113 | ), 114 | Text( 115 | 'Remove profile photo', 116 | style: TextStyle( 117 | color: greenColor, 118 | fontSize: 16, 119 | fontWeight: FontWeight.w400), 120 | ), 121 | SizedBox( 122 | height: 14, 123 | ), 124 | TextFieldContainer( 125 | hintText: "username", 126 | prefixIcon: Icons.person, 127 | controller: _nameController, 128 | ), 129 | SizedBox( 130 | height: 14, 131 | ), 132 | AbsorbPointer( 133 | child: TextFieldContainer( 134 | hintText: "email", 135 | prefixIcon: Icons.alternate_email, 136 | controller: _emailController, 137 | ), 138 | ), 139 | SizedBox( 140 | height: 14, 141 | ), 142 | TextFieldContainer( 143 | hintText: "status", 144 | prefixIcon: Icons.data_array, 145 | controller: _statusController, 146 | ), 147 | SizedBox( 148 | height: 14, 149 | ), 150 | ContainerButton( 151 | title: "Update Profile", 152 | onTap: () { 153 | _updateProfile(currentUser.uid!); 154 | }, 155 | ) 156 | ], 157 | ), 158 | ); 159 | } 160 | 161 | void _updateProfile(String uid){ 162 | 163 | if (_image !=null){ 164 | di.sl().call(file: _image!).then((imageUrl) { 165 | 166 | BlocProvider.of(context).getUpdateUser(user: UserEntity( 167 | uid: uid, 168 | name: _nameController.text, 169 | status: _statusController.text, 170 | profileUrl: imageUrl 171 | )).then((value) { 172 | toast("Profile Updated Successfully"); 173 | }); 174 | }); 175 | }else{ 176 | BlocProvider.of(context).getUpdateUser(user: UserEntity( 177 | uid: uid, 178 | name: _nameController.text, 179 | status: _statusController.text, 180 | )).then((value){ 181 | toast("Profile Updated Successfully"); 182 | }); 183 | } 184 | 185 | 186 | 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /lib/features/user/presentation/pages/credential/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_group_chat/features/app/home/home_page.dart'; 4 | import 'package:flutter_group_chat/features/global/common/common.dart'; 5 | import 'package:flutter_group_chat/features/global/const/page_const.dart'; 6 | import 'package:flutter_group_chat/features/global/custom_text_field/textfield_container.dart'; 7 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 8 | import 'package:flutter_group_chat/features/global/widgets/container_button.dart'; 9 | import 'package:flutter_group_chat/features/user/presentation/cubit/auth/auth_cubit.dart'; 10 | import 'package:flutter_group_chat/features/user/presentation/cubit/credential/credential_cubit.dart'; 11 | import 'package:fluttertoast/fluttertoast.dart'; 12 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 13 | 14 | class LoginPage extends StatefulWidget { 15 | const LoginPage({Key? key}) : super(key: key); 16 | 17 | @override 18 | State createState() => _LoginPageState(); 19 | } 20 | 21 | class _LoginPageState extends State { 22 | 23 | TextEditingController _emailController = TextEditingController(); 24 | TextEditingController _passwordController = TextEditingController(); 25 | 26 | 27 | @override 28 | void dispose() { 29 | _emailController.dispose(); 30 | _passwordController.dispose(); 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Scaffold( 37 | body: BlocConsumer( 38 | builder: (context, credentialState) { 39 | if (credentialState is CredentialLoading) { 40 | return Center( 41 | child: CircularProgressIndicator(), 42 | ); 43 | } 44 | 45 | 46 | if (credentialState is CredentialSuccess) { 47 | return BlocBuilder( 48 | builder: (context, authState) { 49 | if (authState is Authenticated) { 50 | return HomePage(uid: authState.uid,); 51 | } else { 52 | return _bodyWidget(); 53 | } 54 | }, 55 | ); 56 | } 57 | 58 | 59 | return _bodyWidget(); 60 | }, 61 | listener: (context, credentialState) { 62 | if (credentialState is CredentialSuccess) { 63 | BlocProvider.of(context).loggedIn(); 64 | } 65 | 66 | if (credentialState is CredentialFailure) { 67 | toast("wrong email please check"); 68 | //toast 69 | //alertDialog 70 | ///SnackBar 71 | } 72 | }, 73 | ), 74 | ); 75 | } 76 | 77 | 78 | Widget _bodyWidget() { 79 | return SingleChildScrollView( 80 | child: Container( 81 | margin: EdgeInsets.symmetric(horizontal: 22, vertical: 32), 82 | child: Column( 83 | children: [ 84 | SizedBox(height: 40,), 85 | Align( 86 | alignment: Alignment.topLeft, 87 | child: Text( 88 | "Login", 89 | style: TextStyle( 90 | fontSize: 35, 91 | fontWeight: FontWeight.w700, 92 | color: greenColor, 93 | ), 94 | ), 95 | ), 96 | SizedBox( 97 | height: 10, 98 | ), 99 | Divider( 100 | thickness: 1, 101 | ), 102 | SizedBox(height: 10,), 103 | 104 | TextFieldContainer( 105 | prefixIcon: Icons.email, 106 | controller: _emailController, 107 | hintText: "Email", 108 | ), 109 | SizedBox(height: 20,), 110 | TextFieldContainer( 111 | prefixIcon: Icons.email, 112 | controller: _passwordController, 113 | hintText: "Password", 114 | isObscureText: true, 115 | ), 116 | SizedBox(height: 10,), 117 | Align( 118 | alignment: Alignment.topRight, 119 | child: InkWell( 120 | onTap: () { 121 | //Navigate 122 | Navigator.pushNamed(context, PageConst.forgotPage); 123 | }, 124 | child: Text("Forgot Password", style: TextStyle( 125 | fontSize: 16, 126 | fontWeight: FontWeight.w700, 127 | color: greenColor, 128 | ),), 129 | ), 130 | ), 131 | SizedBox(height: 20,), 132 | ContainerButton( 133 | onTap: () { 134 | _submitLogin(); 135 | }, 136 | title: "Login", 137 | ), 138 | SizedBox(height: 20,), 139 | Row( 140 | children: [ 141 | Text( 142 | "don't have an Account", 143 | style: 144 | TextStyle(fontSize: 14, fontWeight: FontWeight.w500), 145 | ), 146 | SizedBox( 147 | width: 5, 148 | ), 149 | InkWell( 150 | onTap: () { 151 | Navigator.pushNamedAndRemoveUntil( 152 | context, PageConst.registrationPage, (routes) => false); 153 | }, 154 | child: Text( 155 | 'Register', 156 | style: TextStyle( 157 | fontSize: 14, 158 | fontWeight: FontWeight.w700, 159 | color: greenColor), 160 | ), 161 | ), 162 | ], 163 | ), 164 | SizedBox(height: 20,), 165 | GestureDetector( 166 | onTap: () { 167 | //Submit Google Login 168 | BlocProvider.of(context).googleAuthSubmit(); 169 | }, 170 | child: Container( 171 | height: 50, 172 | width: 50, 173 | decoration: BoxDecoration( 174 | color: Colors.red, 175 | shape: BoxShape.circle, 176 | boxShadow: [ 177 | BoxShadow( 178 | color: Colors.black.withOpacity(.2), 179 | offset: Offset(1.0, 1.0), 180 | spreadRadius: 1, 181 | blurRadius: 1, 182 | ) 183 | ] 184 | ), 185 | child: Icon(FontAwesomeIcons.google, color: Colors.white,), 186 | ), 187 | ), 188 | ], 189 | ), 190 | ), 191 | ); 192 | } 193 | 194 | 195 | void _submitLogin() { 196 | if (_emailController.text.isEmpty) { 197 | toast("Enter Your Email"); 198 | return; 199 | } 200 | 201 | if (_passwordController.text.isEmpty){ 202 | toast("Enter Your Password"); 203 | return; 204 | } 205 | 206 | 207 | BlocProvider.of(context).signInSubmit( 208 | email: _emailController.text, password: _passwordController.text); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /lib/features/user/presentation/pages/credential/sign_up_page.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_group_chat/features/app/home/home_page.dart'; 7 | import 'package:flutter_group_chat/features/global/common/common.dart'; 8 | import 'package:flutter_group_chat/features/global/const/page_const.dart'; 9 | import 'package:flutter_group_chat/features/global/custom_text_field/textfield_container.dart'; 10 | import 'package:flutter_group_chat/features/global/theme/style.dart'; 11 | import 'package:flutter_group_chat/features/global/widgets/container_button.dart'; 12 | import 'package:flutter_group_chat/features/user/domain/entities/user_entity.dart'; 13 | import 'package:flutter_group_chat/features/user/presentation/cubit/auth/auth_cubit.dart'; 14 | import 'package:flutter_group_chat/features/user/presentation/cubit/credential/credential_cubit.dart'; 15 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 16 | 17 | class SignUpPage extends StatefulWidget { 18 | const SignUpPage({Key? key}) : super(key: key); 19 | 20 | @override 21 | State createState() => _SignUpPageState(); 22 | } 23 | 24 | class _SignUpPageState extends State { 25 | 26 | TextEditingController _usernameController = TextEditingController(); 27 | TextEditingController _emailController = TextEditingController(); 28 | TextEditingController _passwordController = TextEditingController(); 29 | TextEditingController _passwordAgainController = TextEditingController(); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | body: BlocConsumer( 35 | builder: (context, credentialState) { 36 | if (credentialState is CredentialLoading) { 37 | return Center( 38 | child: CircularProgressIndicator(), 39 | ); 40 | } 41 | 42 | 43 | if (credentialState is CredentialSuccess) { 44 | return BlocBuilder( 45 | builder: (context, authState) { 46 | if (authState is Authenticated) { 47 | return HomePage(uid: authState.uid,); 48 | } else { 49 | return _bodyWidget(); 50 | } 51 | }, 52 | ); 53 | } 54 | 55 | 56 | return _bodyWidget(); 57 | }, 58 | listener: (context, credentialState) { 59 | if (credentialState is CredentialSuccess) { 60 | BlocProvider.of(context).loggedIn(); 61 | } 62 | 63 | if (credentialState is CredentialFailure) { 64 | toast("wrong email please check"); 65 | //toast 66 | //alertDialog 67 | ///SnackBar 68 | } 69 | }, 70 | ), 71 | ); 72 | } 73 | Widget _bodyWidget(){ 74 | return SingleChildScrollView( 75 | child: Container( 76 | margin: EdgeInsets.symmetric(horizontal: 22,vertical: 32), 77 | child: Column( 78 | children: [ 79 | SizedBox(height: 40,), 80 | Align( 81 | alignment: Alignment.topLeft, 82 | child: Text( 83 | "Registration", 84 | style: TextStyle( 85 | fontSize: 35, 86 | fontWeight: FontWeight.w700, 87 | color: greenColor, 88 | ), 89 | ), 90 | ), 91 | SizedBox( 92 | height: 10, 93 | ), 94 | Divider( 95 | thickness: 1, 96 | ), 97 | SizedBox(height: 10,), 98 | 99 | TextFieldContainer( 100 | prefixIcon: Icons.person, 101 | controller: _usernameController, 102 | hintText: "username", 103 | ), 104 | SizedBox(height: 20,), 105 | TextFieldContainer( 106 | prefixIcon: Icons.email, 107 | controller: _emailController, 108 | hintText: "Email", 109 | ), 110 | SizedBox(height: 20,), 111 | Divider( 112 | thickness: 2, 113 | indent: 120, 114 | endIndent: 120, 115 | ), 116 | SizedBox(height: 20,), 117 | TextFieldContainer( 118 | prefixIcon: Icons.lock, 119 | controller: _passwordController, 120 | hintText: "Password", 121 | isObscureText: true, 122 | ), 123 | SizedBox(height: 20,), 124 | TextFieldContainer( 125 | prefixIcon: Icons.lock, 126 | controller: _passwordAgainController, 127 | hintText: "Password (Again)", 128 | isObscureText: true, 129 | ), 130 | 131 | SizedBox(height: 20,), 132 | ContainerButton( 133 | onTap: (){ 134 | _submitSignUp(); 135 | }, 136 | title: "Sign Up", 137 | ), 138 | SizedBox(height: 20,), 139 | Row( 140 | children: [ 141 | Text( 142 | "Do you have already an account?", 143 | style: 144 | TextStyle(fontSize: 14, fontWeight: FontWeight.w500), 145 | ), 146 | SizedBox( 147 | width: 5, 148 | ), 149 | InkWell( 150 | onTap: (){ 151 | //Navigator.pushNamedAndRemoveUntil(context, "/registration", (route) => false); 152 | Navigator.pushNamedAndRemoveUntil(context, PageConst.loginPage,(routes) => false); 153 | }, 154 | child: Text( 155 | 'Login', 156 | style: TextStyle( 157 | fontSize: 14, 158 | fontWeight: FontWeight.w700, 159 | color: greenColor), 160 | ), 161 | ), 162 | ], 163 | ), 164 | SizedBox(height: 20,), 165 | Row( 166 | mainAxisAlignment: MainAxisAlignment.center, 167 | children: [ 168 | Text( 169 | 'By clicking register, you agree to the ', 170 | style: TextStyle( 171 | fontSize: 12, 172 | fontWeight: FontWeight.w700, 173 | color: colorC1C1C1), 174 | ), 175 | Text( 176 | 'Privacy Policy', 177 | style: TextStyle( 178 | color: greenColor, 179 | fontSize: 12, 180 | fontWeight: FontWeight.w700), 181 | ), 182 | ], 183 | ), 184 | Row( 185 | mainAxisAlignment: MainAxisAlignment.center, 186 | children: [ 187 | Text( 188 | 'and ', 189 | style: TextStyle( 190 | fontSize: 12, 191 | fontWeight: FontWeight.w700, 192 | color: colorC1C1C1), 193 | ), 194 | Text( 195 | 'terms ', 196 | style: TextStyle( 197 | color: greenColor, 198 | fontSize: 12, 199 | fontWeight: FontWeight.w700), 200 | ), 201 | Text( 202 | 'of use', 203 | style: TextStyle( 204 | fontSize: 12, 205 | fontWeight: FontWeight.w700, 206 | color: colorC1C1C1), 207 | ), 208 | ], 209 | ) 210 | ], 211 | ), 212 | ), 213 | ); 214 | } 215 | 216 | 217 | void _submitSignUp(){ 218 | 219 | 220 | if (_usernameController.text.isEmpty){ 221 | toast("Enter username"); 222 | return; 223 | } 224 | 225 | if (_emailController.text.isEmpty){ 226 | toast("Enter email"); 227 | return; 228 | } 229 | 230 | if (_passwordController.text.isEmpty){ 231 | toast("Enter password"); 232 | return; 233 | } 234 | 235 | if (_passwordAgainController.text.isEmpty){ 236 | toast("Enter again password"); 237 | return; 238 | } 239 | 240 | if (_passwordController.text != _passwordAgainController.text){ 241 | toast("both password must be same"); 242 | return; 243 | } 244 | 245 | 246 | BlocProvider.of(context).signUpSubmit(user: UserEntity( 247 | name: _usernameController.text, 248 | profileUrl: "", 249 | status: "", 250 | email: _emailController.text, 251 | password: _passwordController.text, 252 | )); 253 | } 254 | } 255 | 256 | -------------------------------------------------------------------------------- /lib/features/group/presentation/pages/chat/single_chat_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bubble/bubble.dart'; 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | import 'package:flutter_group_chat/features/group/domain/entities/group_entity.dart'; 8 | import 'package:flutter_group_chat/features/group/domain/entities/single_chat_entity.dart'; 9 | import 'package:flutter_group_chat/features/group/domain/entities/text_message_entity.dart'; 10 | import 'package:flutter_group_chat/features/group/presentation/cubits/chat/chat_cubit.dart'; 11 | import 'package:flutter_group_chat/features/group/presentation/cubits/group/group_cubit.dart'; 12 | import 'package:intl/intl.dart'; 13 | 14 | class SingleChatPage extends StatefulWidget { 15 | final SingleChatEntity singleChatEntity; 16 | 17 | const SingleChatPage({Key? key, required this.singleChatEntity}) 18 | : super(key: key); 19 | 20 | @override 21 | State createState() => _SingleChatPageState(); 22 | } 23 | 24 | class _SingleChatPageState extends State { 25 | TextEditingController _messageController = TextEditingController(); 26 | 27 | ScrollController _scrollController = ScrollController(); 28 | 29 | @override 30 | void initState() { 31 | BlocProvider.of(context) 32 | .getMessages(channelId: widget.singleChatEntity.groupId); 33 | _messageController.addListener(() { 34 | setState(() {}); 35 | }); 36 | super.initState(); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | _messageController.dispose(); 42 | _scrollController.dispose(); 43 | super.dispose(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | 49 | return Scaffold( 50 | appBar: AppBar( 51 | title: Text("${widget.singleChatEntity.groupName}"), 52 | ), 53 | body: Stack( 54 | children: [ 55 | Container( 56 | height: double.infinity, 57 | width: double.infinity, 58 | child: Image.asset( 59 | "assets/background_wallpaper.png", 60 | fit: BoxFit.cover, 61 | )), 62 | BlocBuilder( 63 | builder: (context, chatState) { 64 | if (chatState is ChatLoaded) { 65 | final messages = chatState.messages; 66 | return Column( 67 | children: [ 68 | _messageListWidget(messages), 69 | _sendMessageTextField(), 70 | SizedBox( 71 | height: 20, 72 | ), 73 | ], 74 | ); 75 | } 76 | return Center(child: CircularProgressIndicator()); 77 | }, 78 | ), 79 | ], 80 | ), 81 | ); 82 | } 83 | 84 | Widget _messageLayout({ 85 | required String text, 86 | required String time, 87 | required Color color, 88 | required TextAlign align, 89 | required CrossAxisAlignment boxAlign, 90 | required CrossAxisAlignment crossAlign, 91 | required String name, 92 | required TextAlign alignName, 93 | required BubbleNip nip, 94 | }) { 95 | return Column( 96 | crossAxisAlignment: crossAlign, 97 | children: [ 98 | ConstrainedBox( 99 | constraints: BoxConstraints( 100 | maxWidth: MediaQuery.of(context).size.width * 0.90, 101 | ), 102 | child: Container( 103 | padding: EdgeInsets.all(8), 104 | margin: EdgeInsets.all(3), 105 | child: Bubble( 106 | color: color, 107 | nip: nip, 108 | child: Column( 109 | crossAxisAlignment: crossAlign, 110 | mainAxisSize: MainAxisSize.min, 111 | children: [ 112 | Text( 113 | "$name", 114 | textAlign: alignName, 115 | style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), 116 | ), 117 | Text( 118 | text, 119 | textAlign: align, 120 | style: TextStyle(fontSize: 16), 121 | ), 122 | Text( 123 | time, 124 | textAlign: align, 125 | style: TextStyle( 126 | fontSize: 12, 127 | color: Colors.black.withOpacity( 128 | .4, 129 | ), 130 | ), 131 | ) 132 | ], 133 | ), 134 | ), 135 | ), 136 | ) 137 | ], 138 | ); 139 | } 140 | 141 | _sendMessageTextField() { 142 | return Container( 143 | child: Row( 144 | children: [ 145 | Expanded( 146 | child: Container( 147 | decoration: BoxDecoration( 148 | color: Colors.white, 149 | borderRadius: BorderRadius.all(Radius.circular(80)), 150 | boxShadow: [ 151 | BoxShadow( 152 | color: Colors.black.withOpacity(.2), 153 | offset: Offset(0.0, 0.50), 154 | spreadRadius: 1, 155 | blurRadius: 1, 156 | ) 157 | ]), 158 | child: Row( 159 | children: [ 160 | SizedBox( 161 | width: 10, 162 | ), 163 | Icon( 164 | Icons.insert_emoticon, 165 | color: Colors.grey[500], 166 | ), 167 | SizedBox( 168 | width: 10, 169 | ), 170 | Expanded( 171 | child: Container( 172 | constraints: BoxConstraints(maxHeight: 60), 173 | child: Scrollbar( 174 | child: TextField( 175 | style: TextStyle(fontSize: 14), 176 | controller: _messageController, 177 | maxLines: null, 178 | decoration: InputDecoration( 179 | border: InputBorder.none, 180 | hintText: "Type a message"), 181 | ), 182 | ), 183 | ), 184 | ), 185 | Row( 186 | children: [ 187 | Icon( 188 | Icons.link, 189 | color: Colors.grey[500], 190 | ), 191 | SizedBox( 192 | width: 10, 193 | ), 194 | _messageController.text.isEmpty 195 | ? Icon( 196 | Icons.camera_alt, 197 | color: Colors.grey[500], 198 | ) 199 | : Text(""), 200 | ], 201 | ), 202 | SizedBox( 203 | width: 15, 204 | ), 205 | ], 206 | ), 207 | ), 208 | ), 209 | SizedBox( 210 | width: 5, 211 | ), 212 | InkWell( 213 | onTap: () { 214 | if (_messageController.text.isEmpty) { 215 | //TODO:send voice message 216 | } else { 217 | BlocProvider.of(context) 218 | .sendTextMessage( 219 | textMessageEntity: TextMessageEntity( 220 | time: Timestamp.now(), 221 | content: _messageController.text, 222 | senderName: widget.singleChatEntity.username, 223 | senderId: widget.singleChatEntity.uid, 224 | type: "TEXT"), 225 | channelId: widget.singleChatEntity.groupId) 226 | .then((value) { 227 | BlocProvider.of(context).updateGroup( 228 | groupEntity: GroupEntity( 229 | groupId: widget.singleChatEntity.groupId, 230 | lastMessage: _messageController.text, 231 | createAt: Timestamp.now(), 232 | )); 233 | _clear(); 234 | }); 235 | } 236 | }, 237 | child: Container( 238 | width: 45, 239 | height: 45, 240 | decoration: BoxDecoration( 241 | color: Colors.green, 242 | borderRadius: BorderRadius.all(Radius.circular(50))), 243 | child: Icon( 244 | _messageController.text.isEmpty ? Icons.mic : Icons.send, 245 | color: Colors.white, 246 | ), 247 | ), 248 | ) 249 | ], 250 | ), 251 | ); 252 | } 253 | 254 | void _clear() { 255 | setState(() { 256 | _messageController.clear(); 257 | }); 258 | } 259 | 260 | _messageListWidget(List messages) { 261 | if (_scrollController.hasClients) { 262 | Timer(Duration(milliseconds: 100), () { 263 | _scrollController.animateTo(_scrollController.position.maxScrollExtent, 264 | duration: Duration(milliseconds: 300), curve: Curves.easeIn); 265 | }); 266 | } 267 | 268 | return Expanded( 269 | child: ListView.builder( 270 | itemCount: messages.length, 271 | controller: _scrollController, 272 | itemBuilder: (BuildContext context, int index) { 273 | final singleMessage = messages[index]; 274 | 275 | if (singleMessage.senderId == widget.singleChatEntity.uid) 276 | return _messageLayout( 277 | name: "Me", 278 | alignName: TextAlign.end, 279 | color: Colors.lightGreen, 280 | time: DateFormat('hh:mm a').format(singleMessage.time!.toDate()), 281 | align: TextAlign.left, 282 | nip: BubbleNip.rightTop, 283 | boxAlign: CrossAxisAlignment.start, 284 | crossAlign: CrossAxisAlignment.end, 285 | text: singleMessage.content!, 286 | ); 287 | else 288 | return _messageLayout( 289 | color: Colors.white, 290 | nip: BubbleNip.leftTop, 291 | name: "${singleMessage.senderName}", 292 | alignName: TextAlign.end, 293 | time: DateFormat('hh:mm a').format(singleMessage.time!.toDate()), 294 | align: TextAlign.left, 295 | boxAlign: CrossAxisAlignment.start, 296 | crossAlign: CrossAxisAlignment.start, 297 | text: singleMessage.content!, 298 | ); 299 | }, 300 | )); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _flutterfire_internals: 5 | dependency: transitive 6 | description: 7 | name: _flutterfire_internals 8 | sha256: "8eb354cb8ebed8a9fdf63699d15deff533bc133128898afaf754926b57d611b6" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "1.3.1" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | bloc: 21 | dependency: transitive 22 | description: 23 | name: bloc 24 | sha256: "658a5ae59edcf1e58aac98b000a71c762ad8f46f1394c34a52050cafb3e11a80" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "8.1.1" 28 | boolean_selector: 29 | dependency: transitive 30 | description: 31 | name: boolean_selector 32 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.1.1" 36 | bubble: 37 | dependency: "direct main" 38 | description: 39 | name: bubble 40 | sha256: "65b992b8f8ba2e7e2871190cbdfaa0818b6de2f340bef37cb5ee1b61debe0226" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.2.1" 44 | cached_network_image: 45 | dependency: transitive 46 | description: 47 | name: cached_network_image 48 | sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "3.2.3" 52 | cached_network_image_platform_interface: 53 | dependency: transitive 54 | description: 55 | name: cached_network_image_platform_interface 56 | sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.0.0" 60 | cached_network_image_web: 61 | dependency: transitive 62 | description: 63 | name: cached_network_image_web 64 | sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.0.2" 68 | characters: 69 | dependency: transitive 70 | description: 71 | name: characters 72 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.3.0" 76 | clock: 77 | dependency: transitive 78 | description: 79 | name: clock 80 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.1.1" 84 | cloud_firestore: 85 | dependency: "direct main" 86 | description: 87 | name: cloud_firestore 88 | sha256: d597313deea7c06132c3e5d608b6c96b2804f39566f9074fd662753e64659895 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "4.7.1" 92 | cloud_firestore_platform_interface: 93 | dependency: transitive 94 | description: 95 | name: cloud_firestore_platform_interface 96 | sha256: f6900ac874e326eede700483cc33e034f64bfb98b0cbcdc6c1a5133146de52f9 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "5.14.1" 100 | cloud_firestore_web: 101 | dependency: transitive 102 | description: 103 | name: cloud_firestore_web 104 | sha256: "2f7d865102ff5209d279652d2954e161a1e74d410abe95ed2931818277df4677" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "3.5.1" 108 | collection: 109 | dependency: transitive 110 | description: 111 | name: collection 112 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "1.17.1" 116 | cross_file: 117 | dependency: transitive 118 | description: 119 | name: cross_file 120 | sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "0.3.3+4" 124 | crypto: 125 | dependency: transitive 126 | description: 127 | name: crypto 128 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "3.0.3" 132 | cupertino_icons: 133 | dependency: "direct main" 134 | description: 135 | name: cupertino_icons 136 | sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "1.0.5" 140 | equatable: 141 | dependency: "direct main" 142 | description: 143 | name: equatable 144 | sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "2.0.5" 148 | fake_async: 149 | dependency: transitive 150 | description: 151 | name: fake_async 152 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "1.3.1" 156 | ffi: 157 | dependency: transitive 158 | description: 159 | name: ffi 160 | sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "2.0.2" 164 | file: 165 | dependency: transitive 166 | description: 167 | name: file 168 | sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "6.1.4" 172 | firebase_auth: 173 | dependency: "direct main" 174 | description: 175 | name: firebase_auth 176 | sha256: "64ac4dc04b51aab9d17c23b496c90f948b9ce2065d7b83e0829c7a497d88f9ce" 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "4.6.1" 180 | firebase_auth_platform_interface: 181 | dependency: transitive 182 | description: 183 | name: firebase_auth_platform_interface 184 | sha256: "63fd67d125ae483722ff3742953e2e06bbc1e6cb3da68e5f7f4430d5f82f9373" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "6.15.1" 188 | firebase_auth_web: 189 | dependency: transitive 190 | description: 191 | name: firebase_auth_web 192 | sha256: "241a4ecce80da2014e3cd93d7b7e1a66e9b683e4241d466d73676ac90599b805" 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "5.5.1" 196 | firebase_core: 197 | dependency: "direct main" 198 | description: 199 | name: firebase_core 200 | sha256: "250678b816279b3240c3a33e1f76bf712c00718f1fbeffc85873a5da8c077379" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "2.13.0" 204 | firebase_core_platform_interface: 205 | dependency: transitive 206 | description: 207 | name: firebase_core_platform_interface 208 | sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "4.8.0" 212 | firebase_core_web: 213 | dependency: transitive 214 | description: 215 | name: firebase_core_web 216 | sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45" 217 | url: "https://pub.dev" 218 | source: hosted 219 | version: "2.5.0" 220 | firebase_storage: 221 | dependency: "direct main" 222 | description: 223 | name: firebase_storage 224 | sha256: "89ff2dfd353f953fb2bbbe04359ea1a46d643944f310cfed22dadf352fc16976" 225 | url: "https://pub.dev" 226 | source: hosted 227 | version: "11.2.1" 228 | firebase_storage_platform_interface: 229 | dependency: transitive 230 | description: 231 | name: firebase_storage_platform_interface 232 | sha256: c06ccd21c3ed20da6128629ab7d525f7b613caddfcd5466ba4a1ff58655261ac 233 | url: "https://pub.dev" 234 | source: hosted 235 | version: "4.4.1" 236 | firebase_storage_web: 237 | dependency: transitive 238 | description: 239 | name: firebase_storage_web 240 | sha256: "26a039f211b226fc216f9f06f13402bdf08661edb7c42cb1de3bd236afbbbf75" 241 | url: "https://pub.dev" 242 | source: hosted 243 | version: "3.6.1" 244 | flutter: 245 | dependency: "direct main" 246 | description: flutter 247 | source: sdk 248 | version: "0.0.0" 249 | flutter_bloc: 250 | dependency: "direct main" 251 | description: 252 | name: flutter_bloc 253 | sha256: "434951eea948dbe87f737b674281465f610b8259c16c097b8163ce138749a775" 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "8.1.2" 257 | flutter_blurhash: 258 | dependency: transitive 259 | description: 260 | name: flutter_blurhash 261 | sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "0.7.0" 265 | flutter_cache_manager: 266 | dependency: transitive 267 | description: 268 | name: flutter_cache_manager 269 | sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "3.3.0" 273 | flutter_lints: 274 | dependency: "direct dev" 275 | description: 276 | name: flutter_lints 277 | sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c 278 | url: "https://pub.dev" 279 | source: hosted 280 | version: "2.0.1" 281 | flutter_plugin_android_lifecycle: 282 | dependency: transitive 283 | description: 284 | name: flutter_plugin_android_lifecycle 285 | sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" 286 | url: "https://pub.dev" 287 | source: hosted 288 | version: "2.0.15" 289 | flutter_test: 290 | dependency: "direct dev" 291 | description: flutter 292 | source: sdk 293 | version: "0.0.0" 294 | flutter_web_plugins: 295 | dependency: transitive 296 | description: flutter 297 | source: sdk 298 | version: "0.0.0" 299 | fluttertoast: 300 | dependency: "direct main" 301 | description: 302 | name: fluttertoast 303 | sha256: "2f9c4d3f4836421f7067a28f8939814597b27614e021da9d63e5d3fb6e212d25" 304 | url: "https://pub.dev" 305 | source: hosted 306 | version: "8.2.1" 307 | font_awesome_flutter: 308 | dependency: "direct main" 309 | description: 310 | name: font_awesome_flutter 311 | sha256: "959ef4add147753f990b4a7c6cccb746d5792dbdc81b1cde99e62e7edb31b206" 312 | url: "https://pub.dev" 313 | source: hosted 314 | version: "10.4.0" 315 | get_it: 316 | dependency: "direct main" 317 | description: 318 | name: get_it 319 | sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468" 320 | url: "https://pub.dev" 321 | source: hosted 322 | version: "7.6.0" 323 | google_identity_services_web: 324 | dependency: transitive 325 | description: 326 | name: google_identity_services_web 327 | sha256: "7940fdc3b1035db4d65d387c1bdd6f9574deaa6777411569c05ecc25672efacd" 328 | url: "https://pub.dev" 329 | source: hosted 330 | version: "0.2.1" 331 | google_sign_in: 332 | dependency: "direct main" 333 | description: 334 | name: google_sign_in 335 | sha256: "776a4c988dc179c3b8e9201de0ad61bf350a4e75d378ff9d94c76880378c7bca" 336 | url: "https://pub.dev" 337 | source: hosted 338 | version: "6.1.0" 339 | google_sign_in_android: 340 | dependency: transitive 341 | description: 342 | name: google_sign_in_android 343 | sha256: "2a8b90b766ce00b03e7543f4ffeec97b6eb51fb6c3f31ce2a364bd1f1b9dd7fc" 344 | url: "https://pub.dev" 345 | source: hosted 346 | version: "6.1.14" 347 | google_sign_in_ios: 348 | dependency: transitive 349 | description: 350 | name: google_sign_in_ios 351 | sha256: "6ec0e13a4c5c646471b9f6a25ceb3ae76d339889d4c0f79b729bf0714215a63e" 352 | url: "https://pub.dev" 353 | source: hosted 354 | version: "5.6.2" 355 | google_sign_in_platform_interface: 356 | dependency: transitive 357 | description: 358 | name: google_sign_in_platform_interface 359 | sha256: "95a9e0a8701b5485f2ca330fd1fc6f918f5ce088042ce1019c5e389d8574ae4c" 360 | url: "https://pub.dev" 361 | source: hosted 362 | version: "2.4.0" 363 | google_sign_in_web: 364 | dependency: transitive 365 | description: 366 | name: google_sign_in_web 367 | sha256: "7e0ec507f4752383a6daa67d0cc775253cfc3b1d87907e7004e2c1b99c0a723f" 368 | url: "https://pub.dev" 369 | source: hosted 370 | version: "0.12.0" 371 | http: 372 | dependency: transitive 373 | description: 374 | name: http 375 | sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" 376 | url: "https://pub.dev" 377 | source: hosted 378 | version: "0.13.6" 379 | http_parser: 380 | dependency: transitive 381 | description: 382 | name: http_parser 383 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 384 | url: "https://pub.dev" 385 | source: hosted 386 | version: "4.0.2" 387 | image_picker: 388 | dependency: "direct main" 389 | description: 390 | name: image_picker 391 | sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270" 392 | url: "https://pub.dev" 393 | source: hosted 394 | version: "0.8.7+5" 395 | image_picker_android: 396 | dependency: transitive 397 | description: 398 | name: image_picker_android 399 | sha256: c2f3c66400649bd132f721c88218945d6406f693092b2f741b79ae9cdb046e59 400 | url: "https://pub.dev" 401 | source: hosted 402 | version: "0.8.6+16" 403 | image_picker_for_web: 404 | dependency: transitive 405 | description: 406 | name: image_picker_for_web 407 | sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" 408 | url: "https://pub.dev" 409 | source: hosted 410 | version: "2.1.12" 411 | image_picker_ios: 412 | dependency: transitive 413 | description: 414 | name: image_picker_ios 415 | sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0 416 | url: "https://pub.dev" 417 | source: hosted 418 | version: "0.8.7+4" 419 | image_picker_platform_interface: 420 | dependency: transitive 421 | description: 422 | name: image_picker_platform_interface 423 | sha256: "1991219d9dbc42a99aff77e663af8ca51ced592cd6685c9485e3458302d3d4f8" 424 | url: "https://pub.dev" 425 | source: hosted 426 | version: "2.6.3" 427 | intl: 428 | dependency: "direct main" 429 | description: 430 | name: intl 431 | sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" 432 | url: "https://pub.dev" 433 | source: hosted 434 | version: "0.18.1" 435 | js: 436 | dependency: transitive 437 | description: 438 | name: js 439 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 440 | url: "https://pub.dev" 441 | source: hosted 442 | version: "0.6.7" 443 | lints: 444 | dependency: transitive 445 | description: 446 | name: lints 447 | sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" 448 | url: "https://pub.dev" 449 | source: hosted 450 | version: "2.1.0" 451 | matcher: 452 | dependency: transitive 453 | description: 454 | name: matcher 455 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" 456 | url: "https://pub.dev" 457 | source: hosted 458 | version: "0.12.15" 459 | material_color_utilities: 460 | dependency: transitive 461 | description: 462 | name: material_color_utilities 463 | sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 464 | url: "https://pub.dev" 465 | source: hosted 466 | version: "0.2.0" 467 | meta: 468 | dependency: transitive 469 | description: 470 | name: meta 471 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 472 | url: "https://pub.dev" 473 | source: hosted 474 | version: "1.9.1" 475 | nested: 476 | dependency: transitive 477 | description: 478 | name: nested 479 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" 480 | url: "https://pub.dev" 481 | source: hosted 482 | version: "1.0.0" 483 | network_image: 484 | dependency: "direct main" 485 | description: 486 | name: network_image 487 | sha256: d257e7601f72089f18f1d05e62c9cd100f49bd256c30c98cba0b1cdaf3d517e3 488 | url: "https://pub.dev" 489 | source: hosted 490 | version: "0.1.1" 491 | octo_image: 492 | dependency: transitive 493 | description: 494 | name: octo_image 495 | sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" 496 | url: "https://pub.dev" 497 | source: hosted 498 | version: "1.0.2" 499 | path: 500 | dependency: transitive 501 | description: 502 | name: path 503 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 504 | url: "https://pub.dev" 505 | source: hosted 506 | version: "1.8.3" 507 | path_provider: 508 | dependency: transitive 509 | description: 510 | name: path_provider 511 | sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" 512 | url: "https://pub.dev" 513 | source: hosted 514 | version: "2.0.15" 515 | path_provider_android: 516 | dependency: transitive 517 | description: 518 | name: path_provider_android 519 | sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" 520 | url: "https://pub.dev" 521 | source: hosted 522 | version: "2.0.27" 523 | path_provider_foundation: 524 | dependency: transitive 525 | description: 526 | name: path_provider_foundation 527 | sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" 528 | url: "https://pub.dev" 529 | source: hosted 530 | version: "2.2.3" 531 | path_provider_linux: 532 | dependency: transitive 533 | description: 534 | name: path_provider_linux 535 | sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" 536 | url: "https://pub.dev" 537 | source: hosted 538 | version: "2.1.10" 539 | path_provider_platform_interface: 540 | dependency: transitive 541 | description: 542 | name: path_provider_platform_interface 543 | sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" 544 | url: "https://pub.dev" 545 | source: hosted 546 | version: "2.0.6" 547 | path_provider_windows: 548 | dependency: transitive 549 | description: 550 | name: path_provider_windows 551 | sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 552 | url: "https://pub.dev" 553 | source: hosted 554 | version: "2.1.6" 555 | pedantic: 556 | dependency: transitive 557 | description: 558 | name: pedantic 559 | sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" 560 | url: "https://pub.dev" 561 | source: hosted 562 | version: "1.11.1" 563 | platform: 564 | dependency: transitive 565 | description: 566 | name: platform 567 | sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" 568 | url: "https://pub.dev" 569 | source: hosted 570 | version: "3.1.0" 571 | plugin_platform_interface: 572 | dependency: transitive 573 | description: 574 | name: plugin_platform_interface 575 | sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" 576 | url: "https://pub.dev" 577 | source: hosted 578 | version: "2.1.4" 579 | process: 580 | dependency: transitive 581 | description: 582 | name: process 583 | sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" 584 | url: "https://pub.dev" 585 | source: hosted 586 | version: "4.2.4" 587 | provider: 588 | dependency: transitive 589 | description: 590 | name: provider 591 | sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f 592 | url: "https://pub.dev" 593 | source: hosted 594 | version: "6.0.5" 595 | quiver: 596 | dependency: transitive 597 | description: 598 | name: quiver 599 | sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 600 | url: "https://pub.dev" 601 | source: hosted 602 | version: "3.2.1" 603 | rxdart: 604 | dependency: transitive 605 | description: 606 | name: rxdart 607 | sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" 608 | url: "https://pub.dev" 609 | source: hosted 610 | version: "0.27.7" 611 | sky_engine: 612 | dependency: transitive 613 | description: flutter 614 | source: sdk 615 | version: "0.0.99" 616 | source_span: 617 | dependency: transitive 618 | description: 619 | name: source_span 620 | sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 621 | url: "https://pub.dev" 622 | source: hosted 623 | version: "1.9.1" 624 | sqflite: 625 | dependency: transitive 626 | description: 627 | name: sqflite 628 | sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 629 | url: "https://pub.dev" 630 | source: hosted 631 | version: "2.2.8+4" 632 | sqflite_common: 633 | dependency: transitive 634 | description: 635 | name: sqflite_common 636 | sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555 637 | url: "https://pub.dev" 638 | source: hosted 639 | version: "2.4.5" 640 | stack_trace: 641 | dependency: transitive 642 | description: 643 | name: stack_trace 644 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 645 | url: "https://pub.dev" 646 | source: hosted 647 | version: "1.11.0" 648 | stream_channel: 649 | dependency: transitive 650 | description: 651 | name: stream_channel 652 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 653 | url: "https://pub.dev" 654 | source: hosted 655 | version: "2.1.1" 656 | string_scanner: 657 | dependency: transitive 658 | description: 659 | name: string_scanner 660 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 661 | url: "https://pub.dev" 662 | source: hosted 663 | version: "1.2.0" 664 | synchronized: 665 | dependency: transitive 666 | description: 667 | name: synchronized 668 | sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" 669 | url: "https://pub.dev" 670 | source: hosted 671 | version: "3.1.0" 672 | term_glyph: 673 | dependency: transitive 674 | description: 675 | name: term_glyph 676 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 677 | url: "https://pub.dev" 678 | source: hosted 679 | version: "1.2.1" 680 | test_api: 681 | dependency: transitive 682 | description: 683 | name: test_api 684 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb 685 | url: "https://pub.dev" 686 | source: hosted 687 | version: "0.5.1" 688 | typed_data: 689 | dependency: transitive 690 | description: 691 | name: typed_data 692 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 693 | url: "https://pub.dev" 694 | source: hosted 695 | version: "1.3.2" 696 | uuid: 697 | dependency: transitive 698 | description: 699 | name: uuid 700 | sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" 701 | url: "https://pub.dev" 702 | source: hosted 703 | version: "3.0.7" 704 | vector_math: 705 | dependency: transitive 706 | description: 707 | name: vector_math 708 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 709 | url: "https://pub.dev" 710 | source: hosted 711 | version: "2.1.4" 712 | win32: 713 | dependency: transitive 714 | description: 715 | name: win32 716 | sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" 717 | url: "https://pub.dev" 718 | source: hosted 719 | version: "4.1.4" 720 | xdg_directories: 721 | dependency: transitive 722 | description: 723 | name: xdg_directories 724 | sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 725 | url: "https://pub.dev" 726 | source: hosted 727 | version: "1.0.0" 728 | sdks: 729 | dart: ">=3.0.0 <4.0.0" 730 | flutter: ">=3.3.0" 731 | --------------------------------------------------------------------------------