From a4756bc01e4da729afec809f820e44ac7074f14b Mon Sep 17 00:00:00 2001 From: Shashwat-111 Date: Tue, 22 Apr 2025 15:07:20 +0530 Subject: [PATCH 1/4] Rebuild Android folder with declarative plugins {} block - Updated Kotlin version to 1.8.22 - Updated Android Gradle Plugin (AGP) version to 8.1.0 - Updated Gradle version to 8.3 - Updated Android files to align with the new declarative plugin structure --- android/.gitignore | 6 ++ android/app/build.gradle | 67 ++++++------------- android/app/src/debug/AndroidManifest.xml | 6 +- android/app/src/main/AndroidManifest.xml | 34 +++++----- .../com/example/chatapp/MainActivity.kt | 6 -- .../flutter_chat_app_tutorial/MainActivity.kt | 5 ++ .../res/drawable-v21/launch_background.xml | 12 ++++ .../app/src/main/res/values-night/styles.xml | 18 +++++ android/app/src/main/res/values/styles.xml | 12 ++-- android/app/src/profile/AndroidManifest.xml | 6 +- android/build.gradle | 22 ++---- android/gradle.properties | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- android/settings.gradle | 30 ++++++--- macos/Flutter/GeneratedPluginRegistrant.swift | 8 ++- 15 files changed, 123 insertions(+), 115 deletions(-) delete mode 100644 android/app/src/main/kotlin/com/example/chatapp/MainActivity.kt create mode 100644 android/app/src/main/kotlin/com/example/flutter_chat_app_tutorial/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/values-night/styles.xml diff --git a/android/.gitignore b/android/.gitignore index bc2100d..55afd91 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,3 +5,9 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle index 6d2adbd..60d1c5f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,67 +1,44 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'com.google.gms.google-services' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 28 + namespace = "com.example.flutter_chat_app_tutorial" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } - lintOptions { - disable 'InvalidPackage' + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.chatapp" - minSdkVersion 19 - multiDexEnabled true - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.flutter_chat_app_tutorial" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 23 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "com.android.support:multidex:1.0.3" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.google.firebase:firebase-analytics:17.2.2' + source = "../.." } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 05e2050..399f698 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9e348bd..446d4b6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,17 +1,13 @@ - - + - - @@ -44,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/chatapp/MainActivity.kt b/android/app/src/main/kotlin/com/example/chatapp/MainActivity.kt deleted file mode 100644 index 37930f0..0000000 --- a/android/app/src/main/kotlin/com/example/chatapp/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.chatapp - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/android/app/src/main/kotlin/com/example/flutter_chat_app_tutorial/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_chat_app_tutorial/MainActivity.kt new file mode 100644 index 0000000..6a30a8f --- /dev/null +++ b/android/app/src/main/kotlin/com/example/flutter_chat_app_tutorial/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.flutter_chat_app_tutorial + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 1f83a33..cb1ef88 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 05e2050..399f698 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/android/build.gradle b/android/build.gradle index b5e8417..d2ffbff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,32 +1,18 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath 'com.google.gms:google-services:4.3.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() - jcenter() + mavenCentral() } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/android/gradle.properties b/android/gradle.properties index 38c8d45..2597170 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 296b146..7bb2df6 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 5a2f14f..b9e43bd 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,15 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false } + +include ":app" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5dfcd5e..ee4892f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,11 +6,15 @@ import FlutterMacOS import Foundation import cloud_firestore +import firebase_auth import firebase_core -import shared_preferences_macos +import google_sign_in_ios +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FLTCloudFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTCloudFirestorePlugin")) + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } From ab74a9a769b2b30bf1008f5546d06109dc9f6559 Mon Sep 17 00:00:00 2001 From: Shashwat-111 Date: Tue, 22 Apr 2025 15:11:14 +0530 Subject: [PATCH 2/4] migrating to null safety --- .metadata | 24 +- analysis_options.yaml | 28 +++ lib/helper/helperfunctions.dart | 10 +- lib/main.dart | 36 +-- lib/models/user.dart | 4 +- lib/services/auth.dart | 63 +++-- lib/services/database.dart | 91 ++++---- lib/views/chat.dart | 149 ++++++------ lib/views/chatrooms.dart | 48 ++-- lib/views/search.dart | 22 +- lib/views/signin.dart | 33 +-- lib/views/signup.dart | 27 ++- lib/widget/widget.dart | 26 ++- pubspec.lock | 397 +++++++++++++++++++++----------- pubspec.yaml | 14 +- 15 files changed, 592 insertions(+), 380 deletions(-) create mode 100644 analysis_options.yaml diff --git a/.metadata b/.metadata index 3223172..ce66da9 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,27 @@ # This file should be version controlled and should not be manually edited. version: - revision: dadc3ead47e75319c8ed6b6f15899ad56725c68a - channel: master + revision: "4cf269e36de2573851eaef3c763994f8f9be494d" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: android + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/lib/helper/helperfunctions.dart b/lib/helper/helperfunctions.dart index f60a8f7..c1aac75 100644 --- a/lib/helper/helperfunctions.dart +++ b/lib/helper/helperfunctions.dart @@ -6,7 +6,7 @@ class HelperFunctions{ static String sharedPreferenceUserNameKey = "USERNAMEKEY"; static String sharedPreferenceUserEmailKey = "USEREMAILKEY"; - /// saving data to sharedpreference + /// saving data to shared preference static Future saveUserLoggedInSharedPreference(bool isUserLoggedIn) async{ SharedPreferences preferences = await SharedPreferences.getInstance(); @@ -23,19 +23,19 @@ class HelperFunctions{ return await preferences.setString(sharedPreferenceUserEmailKey, userEmail); } - /// fetching data from sharedpreference + /// fetching data from shared preference static Future getUserLoggedInSharedPreference() async{ SharedPreferences preferences = await SharedPreferences.getInstance(); - return await preferences.getBool(sharedPreferenceUserLoggedInKey); + return await preferences.getBool(sharedPreferenceUserLoggedInKey) ?? false; } static Future getUserNameSharedPreference() async{ SharedPreferences preferences = await SharedPreferences.getInstance(); - return await preferences.getString(sharedPreferenceUserNameKey); + return await preferences.getString(sharedPreferenceUserNameKey) ?? ""; } - static Future getUserEmailSharedPreference() async{ + static Future getUserEmailSharedPreference() async{ SharedPreferences preferences = await SharedPreferences.getInstance(); return await preferences.getString(sharedPreferenceUserEmailKey); } diff --git a/lib/main.dart b/lib/main.dart index 12b87cb..50ef00d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,26 +8,23 @@ void main() { } class MyApp extends StatefulWidget { - // This widget is the root of your application. @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { - - bool userIsLoggedIn; + bool? userIsLoggedIn; @override void initState() { - getLoggedInState(); super.initState(); + getLoggedInState(); } - getLoggedInState() async { - await HelperFunctions.getUserLoggedInSharedPreference().then((value){ - setState(() { - userIsLoggedIn = value; - }); + Future getLoggedInState() async { + bool? value = await HelperFunctions.getUserLoggedInSharedPreference(); + setState(() { + userIsLoggedIn = value; }); } @@ -37,18 +34,21 @@ class _MyAppState extends State { title: 'FlutterChat', debugShowCheckedModeBanner: false, theme: ThemeData( - primaryColor: Color(0xff145C9E), - scaffoldBackgroundColor: Color(0xff1F1F1F), - accentColor: Color(0xff007EF4), + primaryColor: const Color(0xff145C9E), + scaffoldBackgroundColor: const Color(0xff1F1F1F), + colorScheme: ColorScheme.light( + primary: const Color(0xff145C9E), + secondary: const Color(0xff007EF4), + ), fontFamily: "OverpassRegular", visualDensity: VisualDensity.adaptivePlatformDensity, ), - home: userIsLoggedIn != null ? userIsLoggedIn ? ChatRoom() : Authenticate() - : Container( - child: Center( - child: Authenticate(), - ), - ), + home: userIsLoggedIn == null + ? const Center(child: CircularProgressIndicator()) + : userIsLoggedIn! + ? ChatRoom() + : Authenticate(), ); } } + diff --git a/lib/models/user.dart b/lib/models/user.dart index c3344ad..3868288 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -1,4 +1,4 @@ -class User { +class MyUser { final String uid; - User({this.uid}); + MyUser({required this.uid}); } diff --git a/lib/services/auth.dart b/lib/services/auth.dart index dff62b8..d7f4698 100644 --- a/lib/services/auth.dart +++ b/lib/services/auth.dart @@ -7,15 +7,15 @@ import 'package:google_sign_in/google_sign_in.dart'; class AuthService { final FirebaseAuth _auth = FirebaseAuth.instance; - User _userFromFirebaseUser(FirebaseUser user) { - return user != null ? User(uid: user.uid) : null; + MyUser? _userFromFirebaseUser(User? user) { + return user != null ? MyUser(uid: user.uid) : null; } Future signInWithEmailAndPassword(String email, String password) async { try { - AuthResult result = await _auth.signInWithEmailAndPassword( + UserCredential result = await _auth.signInWithEmailAndPassword( email: email, password: password); - FirebaseUser user = result.user; + User? user = result.user; return _userFromFirebaseUser(user); } catch (e) { print(e.toString()); @@ -25,9 +25,9 @@ class AuthService { Future signUpWithEmailAndPassword(String email, String password) async { try { - AuthResult result = await _auth.createUserWithEmailAndPassword( + UserCredential result = await _auth.createUserWithEmailAndPassword( email: email, password: password); - FirebaseUser user = result.user; + User? user = result.user; return _userFromFirebaseUser(user); } catch (e) { print(e.toString()); @@ -44,26 +44,39 @@ class AuthService { } } - Future signInWithGoogle(BuildContext context) async { - final GoogleSignIn _googleSignIn = new GoogleSignIn(); + // + // Future signInWithGoogle(BuildContext context) async { + // try { + // final GoogleSignIn googleSignIn = GoogleSignIn(); + // final GoogleSignInAccount? googleUser = await googleSignIn.signIn(); + // + // if (googleUser == null) return null; // User cancelled sign-in + // + // final GoogleSignInAuthentication googleAuth = + // await googleUser.authentication; + // + // final AuthCredential credential = GoogleAuthProvider.credential( + // idToken: googleAuth.idToken, + // accessToken: googleAuth.accessToken, + // ); + // + // UserCredential result = await _auth.signInWithCredential(credential); + // User? userDetails = result.user; + // + // if (userDetails != null) { + // Navigator.push( + // context, + // MaterialPageRoute(builder: (context) => Chat()), + // ); + // } + // + // return userDetails; + // } catch (e) { + // print('Google Sign-In error: $e'); + // return null; + // } + // } - final GoogleSignInAccount googleSignInAccount = - await _googleSignIn.signIn(); - final GoogleSignInAuthentication googleSignInAuthentication = - await googleSignInAccount.authentication; - - final AuthCredential credential = GoogleAuthProvider.getCredential( - idToken: googleSignInAuthentication.idToken, - accessToken: googleSignInAuthentication.accessToken); - - AuthResult result = await _auth.signInWithCredential(credential); - FirebaseUser userDetails = result.user; - - if (result == null) { - } else { - Navigator.push(context, MaterialPageRoute(builder: (context) => Chat())); - } - } Future signOut() async { try { diff --git a/lib/services/database.dart b/lib/services/database.dart index a694a9a..33f1761 100644 --- a/lib/services/database.dart +++ b/lib/services/database.dart @@ -1,64 +1,73 @@ import 'package:cloud_firestore/cloud_firestore.dart'; class DatabaseMethods { - Future addUserInfo(userData) async { - Firestore.instance.collection("users").add(userData).catchError((e) { - print(e.toString()); - }); + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + + Future addUserInfo(Map userData) async { + try { + await _firestore.collection("users").add(userData); + } catch (e) { + print("Error adding user info: $e"); + } } - getUserInfo(String email) async { - return Firestore.instance - .collection("users") - .where("userEmail", isEqualTo: email) - .getDocuments() - .catchError((e) { - print(e.toString()); - }); + Future>> getUserInfo(String email) async { + try { + return await _firestore + .collection("users") + .where("userEmail", isEqualTo: email) + .get(); + } catch (e) { + print("Error fetching user info: $e"); + rethrow; + } } - searchByName(String searchField) { - return Firestore.instance - .collection("users") - .where('userName', isEqualTo: searchField) - .getDocuments(); + Future>> searchByName(String searchField) async { + try { + return await _firestore + .collection("users") + .where('userName', isEqualTo: searchField) + .get(); + } catch (e) { + print("Error searching user: $e"); + rethrow; + } } - Future addChatRoom(chatRoom, chatRoomId) { - Firestore.instance - .collection("chatRoom") - .document(chatRoomId) - .setData(chatRoom) - .catchError((e) { - print(e); - }); + Future addChatRoom(Map chatRoom, String chatRoomId) async { + try { + await _firestore.collection("chatRoom").doc(chatRoomId).set(chatRoom); + } catch (e) { + print("Error adding chatroom: $e"); + } } - getChats(String chatRoomId) async{ - return Firestore.instance + Stream>> getChats(String chatRoomId) { + return _firestore .collection("chatRoom") - .document(chatRoomId) + .doc(chatRoomId) .collection("chats") .orderBy('time') .snapshots(); } - - Future addMessage(String chatRoomId, chatMessageData){ - - Firestore.instance.collection("chatRoom") - .document(chatRoomId) - .collection("chats") - .add(chatMessageData).catchError((e){ - print(e.toString()); - }); + Future addMessage(String chatRoomId, Map chatMessageData) async { + try { + await _firestore + .collection("chatRoom") + .doc(chatRoomId) + .collection("chats") + .add(chatMessageData); + } catch (e) { + print("Error sending message: $e"); + } } - getUserChats(String itIsMyName) async { - return await Firestore.instance + Stream>> getUserChats(String userName) { + return _firestore .collection("chatRoom") - .where('users', arrayContains: itIsMyName) + .where('users', arrayContains: userName) .snapshots(); } - } diff --git a/lib/views/chat.dart b/lib/views/chat.dart index a86a99a..57c9847 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -8,29 +8,36 @@ import 'package:flutter/material.dart'; class Chat extends StatefulWidget { final String chatRoomId; - Chat({this.chatRoomId}); + Chat({required this.chatRoomId}); @override _ChatState createState() => _ChatState(); } class _ChatState extends State { - - Stream chats; + Stream? chats; TextEditingController messageEditingController = new TextEditingController(); - Widget chatMessages(){ - return StreamBuilder( + Widget chatMessages() { + return StreamBuilder( stream: chats, - builder: (context, snapshot){ - return snapshot.hasData ? ListView.builder( - itemCount: snapshot.data.documents.length, - itemBuilder: (context, index){ - return MessageTile( - message: snapshot.data.documents[index].data["message"], - sendByMe: Constants.myName == snapshot.data.documents[index].data["sendBy"], - ); - }) : Container(); + builder: (context, snapshot) { + if (!snapshot.hasData) + return const Center(child: CircularProgressIndicator()); + + final docs = snapshot.data!.docs; + + return ListView.builder( + itemCount: docs.length, + itemBuilder: (context, index) { + final data = docs[index].data() as Map; + + return MessageTile( + message: data['message'] ?? '', + sendByMe: Constants.myName == data['sendBy'], + ); + }, + ); }, ); } @@ -40,9 +47,7 @@ class _ChatState extends State { Map chatMessageMap = { "sendBy": Constants.myName, "message": messageEditingController.text, - 'time': DateTime - .now() - .millisecondsSinceEpoch, + 'time': DateTime.now().millisecondsSinceEpoch, }; DatabaseMethods().addMessage(widget.chatRoomId, chatMessageMap); @@ -55,27 +60,21 @@ class _ChatState extends State { @override void initState() { - DatabaseMethods().getChats(widget.chatRoomId).then((val) { - setState(() { - chats = val; - }); - }); super.initState(); + chats = DatabaseMethods().getChats(widget.chatRoomId); } @override Widget build(BuildContext context) { return Scaffold( - appBar: appBarMain(context), + appBar: AppBarMain(), body: Container( child: Stack( children: [ chatMessages(), - Container(alignment: Alignment.bottomCenter, - width: MediaQuery - .of(context) - .size - .width, + Container( + alignment: Alignment.bottomCenter, + width: MediaQuery.of(context).size.width, child: Container( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 24), color: Color(0x54FFFFFF), @@ -83,18 +82,19 @@ class _ChatState extends State { children: [ Expanded( child: TextField( - controller: messageEditingController, - style: simpleTextStyle(), - decoration: InputDecoration( - hintText: "Message ...", - hintStyle: TextStyle( - color: Colors.white, - fontSize: 16, - ), - border: InputBorder.none + controller: messageEditingController, + style: simpleTextStyle(), + decoration: InputDecoration( + hintText: "Message ...", + hintStyle: TextStyle( + color: Colors.white, + fontSize: 16, ), - )), - SizedBox(width: 16,), + border: InputBorder.none), + )), + SizedBox( + width: 16, + ), GestureDetector( onTap: () { addMessage(); @@ -109,13 +109,14 @@ class _ChatState extends State { const Color(0x0FFFFFFF) ], begin: FractionalOffset.topLeft, - end: FractionalOffset.bottomRight - ), - borderRadius: BorderRadius.circular(40) - ), + end: FractionalOffset.bottomRight), + borderRadius: BorderRadius.circular(40)), padding: EdgeInsets.all(12), - child: Image.asset("assets/images/send.png", - height: 25, width: 25,)), + child: Image.asset( + "assets/images/send.png", + height: 25, + width: 25, + )), ), ], ), @@ -126,61 +127,47 @@ class _ChatState extends State { ), ); } - } class MessageTile extends StatelessWidget { final String message; final bool sendByMe; - MessageTile({@required this.message, @required this.sendByMe}); - + MessageTile({required this.message, required this.sendByMe}); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only( - top: 8, - bottom: 8, - left: sendByMe ? 0 : 24, - right: sendByMe ? 24 : 0), + top: 8, bottom: 8, left: sendByMe ? 0 : 24, right: sendByMe ? 24 : 0), alignment: sendByMe ? Alignment.centerRight : Alignment.centerLeft, child: Container( - margin: sendByMe - ? EdgeInsets.only(left: 30) - : EdgeInsets.only(right: 30), - padding: EdgeInsets.only( - top: 17, bottom: 17, left: 20, right: 20), + margin: + sendByMe ? EdgeInsets.only(left: 30) : EdgeInsets.only(right: 30), + padding: EdgeInsets.only(top: 17, bottom: 17, left: 20, right: 20), decoration: BoxDecoration( - borderRadius: sendByMe ? BorderRadius.only( - topLeft: Radius.circular(23), - topRight: Radius.circular(23), - bottomLeft: Radius.circular(23) - ) : - BorderRadius.only( - topLeft: Radius.circular(23), - topRight: Radius.circular(23), - bottomRight: Radius.circular(23)), + borderRadius: sendByMe + ? BorderRadius.only( + topLeft: Radius.circular(23), + topRight: Radius.circular(23), + bottomLeft: Radius.circular(23)) + : BorderRadius.only( + topLeft: Radius.circular(23), + topRight: Radius.circular(23), + bottomRight: Radius.circular(23)), gradient: LinearGradient( - colors: sendByMe ? [ - const Color(0xff007EF4), - const Color(0xff2A75BC) - ] - : [ - const Color(0x1AFFFFFF), - const Color(0x1AFFFFFF) - ], - ) - ), + colors: sendByMe + ? [const Color(0xff007EF4), const Color(0xff2A75BC)] + : [const Color(0x1AFFFFFF), const Color(0x1AFFFFFF)], + )), child: Text(message, textAlign: TextAlign.start, style: TextStyle( - color: Colors.white, - fontSize: 16, - fontFamily: 'OverpassRegular', - fontWeight: FontWeight.w300)), + color: Colors.white, + fontSize: 16, + fontFamily: 'OverpassRegular', + fontWeight: FontWeight.w300)), ), ); } } - diff --git a/lib/views/chatrooms.dart b/lib/views/chatrooms.dart index d96f076..27a5cca 100644 --- a/lib/views/chatrooms.dart +++ b/lib/views/chatrooms.dart @@ -6,6 +6,7 @@ import 'package:chatapp/services/auth.dart'; import 'package:chatapp/services/database.dart'; import 'package:chatapp/views/chat.dart'; import 'package:chatapp/views/search.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; class ChatRoom extends StatefulWidget { @@ -14,26 +15,31 @@ class ChatRoom extends StatefulWidget { } class _ChatRoomState extends State { - Stream chatRooms; + late Stream chatRooms; Widget chatRoomsList() { - return StreamBuilder( + return StreamBuilder( stream: chatRooms, builder: (context, snapshot) { - return snapshot.hasData - ? ListView.builder( - itemCount: snapshot.data.documents.length, - shrinkWrap: true, - itemBuilder: (context, index) { - return ChatRoomsTile( - userName: snapshot.data.documents[index].data['chatRoomId'] - .toString() - .replaceAll("_", "") - .replaceAll(Constants.myName, ""), - chatRoomId: snapshot.data.documents[index].data["chatRoomId"], - ); - }) - : Container(); + if (!snapshot.hasData) return Container(); + + final docs = snapshot.data!.docs; + + return ListView.builder( + itemCount: docs.length, + shrinkWrap: true, + itemBuilder: (context, index) { + String chatRoomId = docs[index]['chatRoomId']; + String userName = chatRoomId + .replaceAll("_", "") + .replaceAll(Constants.myName, ""); + + return ChatRoomsTile( + userName: userName, + chatRoomId: chatRoomId, + ); + }, + ); }, ); } @@ -46,13 +52,7 @@ class _ChatRoomState extends State { getUserInfogetChats() async { Constants.myName = await HelperFunctions.getUserNameSharedPreference(); - DatabaseMethods().getUserChats(Constants.myName).then((snapshots) { - setState(() { - chatRooms = snapshots; - print( - "we got the data + ${chatRooms.toString()} this is name ${Constants.myName}"); - }); - }); + chatRooms = DatabaseMethods().getUserChats(Constants.myName); } @override @@ -96,7 +96,7 @@ class ChatRoomsTile extends StatelessWidget { final String userName; final String chatRoomId; - ChatRoomsTile({this.userName,@required this.chatRoomId}); + ChatRoomsTile({required this.userName,required this.chatRoomId}); @override Widget build(BuildContext context) { diff --git a/lib/views/search.dart b/lib/views/search.dart index bfcc6ee..4d3859d 100644 --- a/lib/views/search.dart +++ b/lib/views/search.dart @@ -15,7 +15,7 @@ class _SearchState extends State { DatabaseMethods databaseMethods = new DatabaseMethods(); TextEditingController searchEditingController = new TextEditingController(); - QuerySnapshot searchResultSnapshot; + QuerySnapshot>? searchResultSnapshot; bool isLoading = false; bool haveUserSearched = false; @@ -37,18 +37,22 @@ class _SearchState extends State { } } - Widget userList(){ - return haveUserSearched ? ListView.builder( + Widget userList() { + return haveUserSearched + ? ListView.builder( shrinkWrap: true, - itemCount: searchResultSnapshot.documents.length, - itemBuilder: (context, index){ + itemCount: searchResultSnapshot?.docs.length ?? 0, + itemBuilder: (context, index) { return userTile( - searchResultSnapshot.documents[index].data["userName"], - searchResultSnapshot.documents[index].data["userEmail"], + searchResultSnapshot!.docs[index]['userName'], + searchResultSnapshot!.docs[index]['userEmail'], ); - }) : Container(); + }, + ) + : Container(); } + /// 1.create a chatroom, send user to the chatroom, other userdetails sendMessage(String userName){ List users = [Constants.myName,userName]; @@ -136,7 +140,7 @@ class _SearchState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: appBarMain(context), + appBar: AppBarMain(), body: isLoading ? Container( child: Center( child: CircularProgressIndicator(), diff --git a/lib/views/signin.dart b/lib/views/signin.dart index 3ed58ee..a029e0d 100644 --- a/lib/views/signin.dart +++ b/lib/views/signin.dart @@ -28,7 +28,7 @@ class _SignInState extends State { bool isLoading = false; signIn() async { - if (formKey.currentState.validate()) { + if (formKey.currentState?.validate() ?? false) { setState(() { isLoading = true; }); @@ -38,14 +38,15 @@ class _SignInState extends State { emailEditingController.text, passwordEditingController.text) .then((result) async { if (result != null) { - QuerySnapshot userInfoSnapshot = - await DatabaseMethods().getUserInfo(emailEditingController.text); + QuerySnapshot> userInfoSnapshot = + await DatabaseMethods().getUserInfo(emailEditingController.text); HelperFunctions.saveUserLoggedInSharedPreference(true); HelperFunctions.saveUserNameSharedPreference( - userInfoSnapshot.documents[0].data["userName"]); + userInfoSnapshot.docs[0]['userName']); + HelperFunctions.saveUserEmailSharedPreference( - userInfoSnapshot.documents[0].data["userEmail"]); + userInfoSnapshot.docs[0]['userEmail']); Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => ChatRoom())); @@ -62,7 +63,7 @@ class _SignInState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: appBarMain(context), + appBar: AppBarMain(), body: isLoading ? Container( child: Center(child: CircularProgressIndicator()), @@ -78,11 +79,12 @@ class _SignInState extends State { children: [ TextFormField( validator: (val) { - return RegExp( - r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") - .hasMatch(val) + if (val == null || val.isEmpty) { + return "Email can't be empty"; + } + return RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(val) ? null - : "Please Enter Correct Email"; + : "Please enter a valid email"; }, controller: emailEditingController, style: simpleTextStyle(), @@ -90,11 +92,12 @@ class _SignInState extends State { ), TextFormField( obscureText: true, - validator: (val) { - return val.length > 6 - ? null - : "Enter Password 6+ characters"; - }, + validator: (val) { + if (val == null || val.isEmpty) { + return "Password can't be empty"; + } + return val.length > 6 ? null : "Enter password 6+ characters"; + }, style: simpleTextStyle(), controller: passwordEditingController, decoration: textFieldInputDecoration("password"), diff --git a/lib/views/signup.dart b/lib/views/signup.dart index 982b150..2bd9735 100644 --- a/lib/views/signup.dart +++ b/lib/views/signup.dart @@ -28,7 +28,7 @@ class _SignUpState extends State { singUp() async { - if(formKey.currentState.validate()){ + if(formKey.currentState?.validate() ?? false){ setState(() { isLoading = true; @@ -60,7 +60,7 @@ class _SignUpState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: appBarMain(context), + appBar: AppBarMain(), body: isLoading ? Container(child: Center(child: CircularProgressIndicator(),),) : Container( padding: EdgeInsets.symmetric(horizontal: 24), child: Column( @@ -73,17 +73,22 @@ class _SignUpState extends State { TextFormField( style: simpleTextStyle(), controller: usernameEditingController, - validator: (val){ - return val.isEmpty || val.length < 3 ? "Enter Username 3+ characters" : null; + validator: (val) { + return (val == null || val.isEmpty || val.length < 3) + ? "Enter Username 3+ characters" + : null; }, decoration: textFieldInputDecoration("username"), ), TextFormField( controller: emailEditingController, style: simpleTextStyle(), - validator: (val){ - return RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(val) ? - null : "Enter correct email"; + validator: (val) { + return (val == null || !RegExp( + r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") + .hasMatch(val)) + ? "Enter correct email" + : null; }, decoration: textFieldInputDecoration("email"), ), @@ -92,10 +97,11 @@ class _SignUpState extends State { style: simpleTextStyle(), decoration: textFieldInputDecoration("password"), controller: passwordEditingController, - validator: (val){ - return val.length < 6 ? "Enter Password 6+ characters" : null; + validator: (val) { + return (val == null || val.length < 6) + ? "Enter Password 6+ characters" + : null; }, - ), ], ), @@ -167,6 +173,5 @@ class _SignUpState extends State { ), ), ); - ; } } diff --git a/lib/widget/widget.dart b/lib/widget/widget.dart index 6a46000..f6340c2 100644 --- a/lib/widget/widget.dart +++ b/lib/widget/widget.dart @@ -1,14 +1,22 @@ import 'package:flutter/material.dart'; -Widget appBarMain(BuildContext context) { - return AppBar( - title: Image.asset( - "assets/images/logo.png", - height: 40, - ), - elevation: 0.0, - centerTitle: false, - ); +class AppBarMain extends StatelessWidget implements PreferredSizeWidget { + const AppBarMain({Key? key}) : super(key: key); + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + + @override + Widget build(BuildContext context) { + return AppBar( + title: Image.asset( + "assets/images/logo.png", + height: 40, + ), + elevation: 0.0, + centerTitle: false, + ); + } } InputDecoration textFieldInputDecoration(String hintText) { diff --git a/pubspec.lock b/pubspec.lock index 13e6d6e..1dfd400 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,139 +1,158 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: + _flutterfire_internals: dependency: transitive description: - name: archive - url: "https://pub.dartlang.org" + name: _flutterfire_internals + sha256: de9ecbb3ddafd446095f7e833c853aff2fa1682b017921fe63a833f9d6f0e422 + url: "https://pub.dev" source: hosted - version: "2.0.13" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.0" + version: "1.3.54" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.0.0" - charcode: + version: "2.1.1" + characters: dependency: transitive description: - name: charcode - url: "https://pub.dartlang.org" + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" cloud_firestore: dependency: "direct main" description: name: cloud_firestore - url: "https://pub.dartlang.org" + sha256: "89a5e32716794b6a8d0ec1b5dfda988194e92daedaa3f3bed66fa0d0a595252e" + url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "5.6.6" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - url: "https://pub.dartlang.org" + sha256: "9f012844eb59be6827ed97415875c5a29ccacd28bc79bf85b4680738251a33df" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "6.6.6" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - url: "https://pub.dartlang.org" + sha256: b8b754269be0e907acd9ff63ad60f66b84c78d330ca1d7e474f86c9527ddc803 + url: "https://pub.dev" source: hosted - version: "0.1.1+2" + version: "4.4.6" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" source: hosted - version: "1.14.12" - convert: - dependency: transitive + version: "1.18.0" + cupertino_icons: + dependency: "direct main" description: - name: convert - url: "https://pub.dartlang.org" + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" source: hosted - version: "2.1.1" - crypto: + version: "1.0.8" + fake_async: dependency: transitive description: - name: crypto - url: "https://pub.dartlang.org" + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "2.1.4" - cupertino_icons: - dependency: "direct main" + version: "1.3.1" + ffi: + dependency: transitive description: - name: cupertino_icons - url: "https://pub.dartlang.org" + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" source: hosted - version: "0.1.3" - firebase: + version: "2.1.3" + file: dependency: transitive description: - name: firebase - url: "https://pub.dartlang.org" + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.0.1" firebase_auth: dependency: "direct main" description: name: firebase_auth - url: "https://pub.dartlang.org" + sha256: "54c62b2d187709114dd09ce658a8803ee91f9119b0e0d3fc2245130ad9bff9ad" + url: "https://pub.dev" source: hosted - version: "0.15.4" + version: "5.5.2" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - url: "https://pub.dartlang.org" + sha256: "5402d13f4bb7f29f2fb819f3b6b5a5a56c9f714aef2276546d397e25ac1b6b8e" + url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "7.6.2" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - url: "https://pub.dartlang.org" + sha256: "2be496911f0807895d5fe8067b70b7d758142dd7fb26485cbe23e525e2547764" + url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "5.14.2" firebase_core: dependency: transitive description: name: firebase_core - url: "https://pub.dartlang.org" + sha256: "017d17d9915670e6117497e640b2859e0b868026ea36bf3a57feb28c3b97debe" + url: "https://pub.dev" source: hosted - version: "0.4.4+3" + version: "3.13.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - url: "https://pub.dartlang.org" + sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "5.4.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - url: "https://pub.dartlang.org" + sha256: "129a34d1e0fb62e2b488d988a1fc26cc15636357e50944ffee2862efe8929b23" + url: "https://pub.dev" source: hosted - version: "0.1.1+2" + version: "2.22.0" flutter: dependency: "direct main" description: flutter @@ -149,139 +168,230 @@ packages: description: flutter source: sdk version: "0.0.0" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9" + url: "https://pub.dev" + source: hosted + version: "0.3.3" google_sign_in: dependency: "direct main" description: name: google_sign_in - url: "https://pub.dartlang.org" + sha256: fad6ddc80c427b0bba705f2116204ce1173e09cf299f85e053d57a55e5b2dd56 + url: "https://pub.dev" + source: hosted + version: "6.2.2" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: "7af72e5502c313865c729223b60e8ae7bce0a1011b250c24edcf30d3d7032748" + url: "https://pub.dev" source: hosted - version: "4.4.3" + version: "6.1.35" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" + url: "https://pub.dev" + source: hosted + version: "5.9.0" google_sign_in_platform_interface: dependency: transitive description: name: google_sign_in_platform_interface - url: "https://pub.dartlang.org" + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "2.5.0" google_sign_in_web: dependency: transitive description: name: google_sign_in_web - url: "https://pub.dartlang.org" + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" + url: "https://pub.dev" source: hosted - version: "0.9.1" + version: "0.12.4+4" http: dependency: transitive description: name: http - url: "https://pub.dartlang.org" + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" source: hosted - version: "0.12.1" + version: "1.3.0" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted - version: "3.1.4" - image: + version: "4.0.2" + leak_tracker: dependency: transitive description: - name: image - url: "https://pub.dartlang.org" + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" source: hosted - version: "2.1.12" - js: + version: "10.0.5" + leak_tracker_flutter_testing: dependency: transitive description: - name: js - url: "https://pub.dartlang.org" + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" source: hosted - version: "0.6.1+1" + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" source: hosted - version: "0.12.6" + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" source: hosted - version: "1.1.8" + version: "1.15.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" source: hosted - version: "1.6.4" - pedantic: + version: "1.9.0" + path_provider_linux: dependency: transitive description: - name: pedantic - url: "https://pub.dartlang.org" + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" source: hosted - version: "1.9.0" - petitparser: + version: "2.2.1" + path_provider_platform_interface: dependency: transitive description: - name: petitparser - url: "https://pub.dartlang.org" + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" source: hosted - version: "3.0.2" - plugin_platform_interface: + version: "2.1.2" + path_provider_windows: dependency: transitive description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" source: hosted - version: "1.0.2" - quiver: + version: "2.3.0" + platform: dependency: transitive description: - name: quiver - url: "https://pub.dartlang.org" + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" random_string: dependency: "direct main" description: name: random_string - url: "https://pub.dartlang.org" + sha256: "03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.3.1" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" source: hosted - version: "0.5.7" - shared_preferences_macos: + version: "2.5.4" + shared_preferences_linux: dependency: transitive description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" source: hosted - version: "0.0.1+7" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" source: hosted - version: "0.1.2+4" + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -291,65 +401,90 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" source: hosted - version: "1.9.3" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" source: hosted - version: "0.2.15" + version: "0.7.2" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" source: hosted - version: "1.1.6" + version: "1.4.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.0.8" - xml: + version: "2.1.4" + vm_service: dependency: transitive description: - name: xml - url: "https://pub.dartlang.org" + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: - dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + dart: ">=3.5.1 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 38f6cf2..2ebc1a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ^3.5.1 dependencies: flutter: @@ -27,12 +27,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - google_sign_in: 4.4.3 - firebase_auth: 0.15.4 - shared_preferences: ^0.5.6+3 - random_string: ^2.0.1 - cloud_firestore: ^0.13.5 + cupertino_icons: ^1.0.8 + google_sign_in: ^6.2.2 + firebase_auth: ^5.5.2 + shared_preferences: ^2.5.3 + random_string: ^2.3.1 + cloud_firestore: ^5.6.6 dev_dependencies: flutter_test: From 8fbd43fe38744c84f57cd750b872855ae6d85c31 Mon Sep 17 00:00:00 2001 From: Shashwat-111 Date: Tue, 22 Apr 2025 19:12:07 +0530 Subject: [PATCH 3/4] added Firebase the updated way using FlutterFire CLI --- .gitignore | 5 +++ android/app/build.gradle | 3 ++ android/settings.gradle | 3 ++ lib/helper/helperfunctions.dart | 5 +++ lib/main.dart | 9 +++- lib/services/auth.dart | 3 -- lib/views/chatrooms.dart | 78 +++++++++++++++++++++------------ lib/views/search.dart | 10 +++-- lib/views/signin.dart | 1 + lib/views/signup.dart | 4 +- lib/widget/widget.dart | 1 + pubspec.lock | 2 +- pubspec.yaml | 1 + 13 files changed, 87 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 1ba9c33..9d0760c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,5 +39,10 @@ app.*.symbols # Obfuscation related app.*.map.json +# Firebase config files +android/app/google-services.json +firebase.json +lib/firebase_options.dart + # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/android/app/build.gradle b/android/app/build.gradle index 60d1c5f..7f2dffb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,8 @@ plugins { id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + // END: FlutterFire Configuration id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" diff --git a/android/settings.gradle b/android/settings.gradle index b9e43bd..9759a22 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,6 +19,9 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.1.0" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/lib/helper/helperfunctions.dart b/lib/helper/helperfunctions.dart index c1aac75..7b89048 100644 --- a/lib/helper/helperfunctions.dart +++ b/lib/helper/helperfunctions.dart @@ -40,4 +40,9 @@ class HelperFunctions{ return await preferences.getString(sharedPreferenceUserEmailKey); } + static Future signOut() async{ + SharedPreferences preferences = await SharedPreferences.getInstance(); + return await preferences.clear(); + } + } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 50ef00d..6fa3fb2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,16 @@ import 'package:chatapp/helper/authenticate.dart'; import 'package:chatapp/helper/helperfunctions.dart'; import 'package:chatapp/views/chatrooms.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'firebase_options.dart'; -void main() { + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); runApp(MyApp()); } diff --git a/lib/services/auth.dart b/lib/services/auth.dart index d7f4698..8a65d20 100644 --- a/lib/services/auth.dart +++ b/lib/services/auth.dart @@ -1,8 +1,5 @@ import 'package:chatapp/models/user.dart'; -import 'package:chatapp/views/chat.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:google_sign_in/google_sign_in.dart'; class AuthService { final FirebaseAuth _auth = FirebaseAuth.instance; diff --git a/lib/views/chatrooms.dart b/lib/views/chatrooms.dart index 27a5cca..4fe2e95 100644 --- a/lib/views/chatrooms.dart +++ b/lib/views/chatrooms.dart @@ -15,13 +15,41 @@ class ChatRoom extends StatefulWidget { } class _ChatRoomState extends State { - late Stream chatRooms; + Stream? chatRooms; + + @override + void initState() { + super.initState(); + getUserInfogetChats(); + } + + getUserInfogetChats() async { + Constants.myName = await HelperFunctions.getUserNameSharedPreference(); + debugPrint("The username is ${Constants.myName}"); + + Stream chats = + await DatabaseMethods().getUserChats(Constants.myName); + + setState(() { + chatRooms = chats; + }); + } Widget chatRoomsList() { + if (chatRooms == null) { + return Center(child: CircularProgressIndicator()); + } + return StreamBuilder( stream: chatRooms, builder: (context, snapshot) { - if (!snapshot.hasData) return Container(); + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { + return Center(child: Text("No chats yet.")); + } final docs = snapshot.data!.docs; @@ -32,8 +60,11 @@ class _ChatRoomState extends State { String chatRoomId = docs[index]['chatRoomId']; String userName = chatRoomId .replaceAll("_", "") - .replaceAll(Constants.myName, ""); - + .replaceAll(Constants.myName, "") + .trim(); + if (userName.isEmpty) { + return SizedBox.shrink(); + } return ChatRoomsTile( userName: userName, chatRoomId: chatRoomId, @@ -44,43 +75,35 @@ class _ChatRoomState extends State { ); } - @override - void initState() { - getUserInfogetChats(); - super.initState(); - } - - getUserInfogetChats() async { - Constants.myName = await HelperFunctions.getUserNameSharedPreference(); - chatRooms = DatabaseMethods().getUserChats(Constants.myName); - } - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Image.asset( - "assets/images/logo.png", - height: 40, - ), + backgroundColor: Theme.of(context).primaryColor, + title: Image.asset("assets/images/logo.png", height: 40), elevation: 0.0, centerTitle: false, actions: [ GestureDetector( onTap: () { + //clear stored shared preferences + HelperFunctions.signOut(); + + //sign out from firebase AuthService().signOut(); - Navigator.pushReplacement(context, - MaterialPageRoute(builder: (context) => Authenticate())); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => Authenticate()), + ); }, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Icon(Icons.exit_to_app)), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Icon(Icons.exit_to_app), + ), ) ], ), - body: Container( - child: chatRoomsList(), - ), + body: chatRoomsList(), floatingActionButton: FloatingActionButton( child: Icon(Icons.search), onPressed: () { @@ -92,6 +115,7 @@ class _ChatRoomState extends State { } } + class ChatRoomsTile extends StatelessWidget { final String userName; final String chatRoomId; diff --git a/lib/views/search.dart b/lib/views/search.dart index 4d3859d..01d3036 100644 --- a/lib/views/search.dart +++ b/lib/views/search.dart @@ -43,10 +43,12 @@ class _SearchState extends State { shrinkWrap: true, itemCount: searchResultSnapshot?.docs.length ?? 0, itemBuilder: (context, index) { - return userTile( - searchResultSnapshot!.docs[index]['userName'], - searchResultSnapshot!.docs[index]['userEmail'], - ); + final name = searchResultSnapshot!.docs[index]['userName']; + final email = searchResultSnapshot!.docs[index]['userEmail']; + + if (name == Constants.myName) return SizedBox.shrink(); // hide self + + return userTile(name, email); }, ) : Container(); diff --git a/lib/views/signin.dart b/lib/views/signin.dart index a029e0d..b0a6273 100644 --- a/lib/views/signin.dart +++ b/lib/views/signin.dart @@ -37,6 +37,7 @@ class _SignInState extends State { .signInWithEmailAndPassword( emailEditingController.text, passwordEditingController.text) .then((result) async { + print("sign in result: $result"); if (result != null) { QuerySnapshot> userInfoSnapshot = await DatabaseMethods().getUserInfo(emailEditingController.text); diff --git a/lib/views/signup.dart b/lib/views/signup.dart index 2bd9735..a618fe7 100644 --- a/lib/views/signup.dart +++ b/lib/views/signup.dart @@ -26,7 +26,7 @@ class _SignUpState extends State { final formKey = GlobalKey(); bool isLoading = false; - singUp() async { + signUp() async { if(formKey.currentState?.validate() ?? false){ setState(() { @@ -111,7 +111,7 @@ class _SignUpState extends State { ), GestureDetector( onTap: (){ - singUp(); + signUp(); }, child: Container( padding: EdgeInsets.symmetric(vertical: 16), diff --git a/lib/widget/widget.dart b/lib/widget/widget.dart index f6340c2..b73c742 100644 --- a/lib/widget/widget.dart +++ b/lib/widget/widget.dart @@ -9,6 +9,7 @@ class AppBarMain extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { return AppBar( + backgroundColor: Theme.of(context).primaryColor, title: Image.asset( "assets/images/logo.png", height: 40, diff --git a/pubspec.lock b/pubspec.lock index 1dfd400..888c210 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -130,7 +130,7 @@ packages: source: hosted version: "5.14.2" firebase_core: - dependency: transitive + dependency: "direct main" description: name: firebase_core sha256: "017d17d9915670e6117497e640b2859e0b868026ea36bf3a57feb28c3b97debe" diff --git a/pubspec.yaml b/pubspec.yaml index 2ebc1a1..ac31949 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 google_sign_in: ^6.2.2 + firebase_core: ^3.13.0 firebase_auth: ^5.5.2 shared_preferences: ^2.5.3 random_string: ^2.3.1 From 501c33e4b06f75bd6247f8c2561c4f6ae653950f Mon Sep 17 00:00:00 2001 From: Shashwat-111 Date: Tue, 22 Apr 2025 23:10:43 +0530 Subject: [PATCH 4/4] regenerated web files for compatibility --- web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes web/index.html | 32 ++++++++++++++++++-------------- web/manifest.json | 18 +++++++++++++++--- 4 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 web/icons/Icon-maskable-192.png create mode 100644 web/icons/Icon-maskable-512.png diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/web/index.html b/web/index.html index c7ec920..0658b60 100644 --- a/web/index.html +++ b/web/index.html @@ -1,6 +1,20 @@ + + @@ -8,26 +22,16 @@ - + - + - chatapp + flutter_chat_app_tutorial - - - + diff --git a/web/manifest.json b/web/manifest.json index 4a0c6ce..5dd8c4e 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -1,8 +1,8 @@ { - "name": "chatapp", - "short_name": "chatapp", + "name": "flutter_chat_app_tutorial", + "short_name": "flutter_chat_app_tutorial", "start_url": ".", - "display": "minimal-ui", + "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] }