feat: Implement Firebase push notifications with support for flavor-specific configurations.
This commit is contained in:
@@ -3,6 +3,7 @@ plugins {
|
|||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
id("com.google.gms.google-services")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
48
android/app/src/demo/google-services.json
Normal file
48
android/app/src/demo/google-services.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "703616926518",
|
||||||
|
"project_id": "real-estate-2d71e",
|
||||||
|
"storage_bucket": "real-estate-2d71e.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:703616926518:android:270639d6887d2cb4fca997",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.requestn.real_estate_mobile.demo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyAwb_9fc7n9gbN_IQqWeYRkHSSykzVHCyc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:703616926518:android:4342b796fbabb690fca997",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.requestn.real_estate_mobile.dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyAwb_9fc7n9gbN_IQqWeYRkHSSykzVHCyc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
29
android/app/src/dev/google-services.json
Normal file
29
android/app/src/dev/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "703616926518",
|
||||||
|
"project_id": "real-estate-2d71e",
|
||||||
|
"storage_bucket": "real-estate-2d71e.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:703616926518:android:4342b796fbabb690fca997",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.requestn.real_estate_mobile.dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyAwb_9fc7n9gbN_IQqWeYRkHSSykzVHCyc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
67
android/app/src/local/google-services.json
Normal file
67
android/app/src/local/google-services.json
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "703616926518",
|
||||||
|
"project_id": "real-estate-2d71e",
|
||||||
|
"storage_bucket": "real-estate-2d71e.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:703616926518:android:270639d6887d2cb4fca997",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.requestn.real_estate_mobile.demo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyAwb_9fc7n9gbN_IQqWeYRkHSSykzVHCyc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:703616926518:android:4342b796fbabb690fca997",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.requestn.real_estate_mobile.dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyAwb_9fc7n9gbN_IQqWeYRkHSSykzVHCyc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:703616926518:android:2eb242f07b174c25fca997",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.requestn.real_estate_mobile.local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyAwb_9fc7n9gbN_IQqWeYRkHSSykzVHCyc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
@@ -33,6 +34,10 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
<!-- FCM default notification channel -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
|
android:value="high_importance_channel" />
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ plugins {
|
|||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.11.1" apply false
|
id("com.android.application") version "8.11.1" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||||
|
id("com.google.gms.google-services") version "4.4.2" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
platform :ios, '13.0'
|
||||||
# platform :ios, '13.0'
|
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
120
ios/Podfile.lock
120
ios/Podfile.lock
@@ -1,43 +1,161 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- emoji_picker_flutter (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- Firebase/CoreOnly (11.15.0):
|
||||||
|
- FirebaseCore (~> 11.15.0)
|
||||||
|
- Firebase/Messaging (11.15.0):
|
||||||
|
- Firebase/CoreOnly
|
||||||
|
- FirebaseMessaging (~> 11.15.0)
|
||||||
|
- firebase_core (3.15.2):
|
||||||
|
- Firebase/CoreOnly (= 11.15.0)
|
||||||
|
- Flutter
|
||||||
|
- firebase_messaging (15.2.10):
|
||||||
|
- Firebase/Messaging (= 11.15.0)
|
||||||
|
- firebase_core
|
||||||
|
- Flutter
|
||||||
|
- FirebaseCore (11.15.0):
|
||||||
|
- FirebaseCoreInternal (~> 11.15.0)
|
||||||
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
|
- FirebaseCoreInternal (11.15.0):
|
||||||
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
|
- FirebaseInstallations (11.15.0):
|
||||||
|
- FirebaseCore (~> 11.15.0)
|
||||||
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
|
- PromisesObjC (~> 2.4)
|
||||||
|
- FirebaseMessaging (11.15.0):
|
||||||
|
- FirebaseCore (~> 11.15.0)
|
||||||
|
- FirebaseInstallations (~> 11.0)
|
||||||
|
- GoogleDataTransport (~> 10.0)
|
||||||
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
|
- GoogleUtilities/Reachability (~> 8.1)
|
||||||
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
|
- nanopb (~> 3.30910.0)
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_local_notifications (0.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_secure_storage (6.0.0):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- GoogleDataTransport (10.1.0):
|
||||||
|
- nanopb (~> 3.30910.0)
|
||||||
|
- PromisesObjC (~> 2.4)
|
||||||
|
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||||
|
- GoogleUtilities/Environment
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- GoogleUtilities/Network
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Environment (8.1.0):
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Logger (8.1.0):
|
||||||
|
- GoogleUtilities/Environment
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Network (8.1.0):
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- "GoogleUtilities/NSData+zlib"
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Reachability
|
||||||
|
- "GoogleUtilities/NSData+zlib (8.1.0)":
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Privacy (8.1.0)
|
||||||
|
- GoogleUtilities/Reachability (8.1.0):
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/UserDefaults (8.1.0):
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- image_picker_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- nanopb (3.30910.0):
|
||||||
|
- nanopb/decode (= 3.30910.0)
|
||||||
|
- nanopb/encode (= 3.30910.0)
|
||||||
|
- nanopb/decode (3.30910.0)
|
||||||
|
- nanopb/encode (3.30910.0)
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- PromisesObjC (2.4.0)
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
||||||
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- Firebase
|
||||||
|
- FirebaseCore
|
||||||
|
- FirebaseCoreInternal
|
||||||
|
- FirebaseInstallations
|
||||||
|
- FirebaseMessaging
|
||||||
|
- GoogleDataTransport
|
||||||
|
- GoogleUtilities
|
||||||
|
- nanopb
|
||||||
|
- PromisesObjC
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
emoji_picker_flutter:
|
||||||
|
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
||||||
|
firebase_core:
|
||||||
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
|
firebase_messaging:
|
||||||
|
:path: ".symlinks/plugins/firebase_messaging/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_local_notifications:
|
||||||
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
|
image_picker_ios:
|
||||||
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
emoji_picker_flutter: 8e50ec5caac456a23a78637e02c6293ea0ac8771
|
||||||
|
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||||
|
firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c
|
||||||
|
firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7
|
||||||
|
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
||||||
|
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
||||||
|
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
||||||
|
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9
|
||||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||||
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
|
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||||
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
|
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||||
|
|
||||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
PODFILE CHECKSUM: f8c2dcdfb50bb67645580d28a6bf814fca30bdec
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -235,6 +235,7 @@
|
|||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
216CC2E0A0F2BEF06B288BBC /* [CP] Embed Pods Frameworks */,
|
216CC2E0A0F2BEF06B288BBC /* [CP] Embed Pods Frameworks */,
|
||||||
|
1433657A6E25DE0913AEB1D0 /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -306,6 +307,23 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
1433657A6E25DE0913AEB1D0 /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
216CC2E0A0F2BEF06B288BBC /* [CP] Embed Pods Frameworks */ = {
|
216CC2E0A0F2BEF06B288BBC /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import FirebaseCore
|
||||||
|
import FirebaseMessaging
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
@@ -7,10 +9,39 @@ import UIKit
|
|||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
FirebaseApp.configure()
|
||||||
|
|
||||||
|
// Request notification permissions
|
||||||
|
UNUserNotificationCenter.current().delegate = self
|
||||||
|
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
|
||||||
|
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in }
|
||||||
|
application.registerForRemoteNotifications()
|
||||||
|
|
||||||
|
Messaging.messaging().delegate = self
|
||||||
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
|
||||||
|
) {
|
||||||
|
Messaging.messaging().apnsToken = deviceToken
|
||||||
|
super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppDelegate: MessagingDelegate {
|
||||||
|
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
|
||||||
|
let dataDict: [String: String] = ["token": fcmToken ?? ""]
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.Name("FCMToken"),
|
||||||
|
object: nil,
|
||||||
|
userInfo: dataDict
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
ios/Runner/GoogleService-Info.plist
Normal file
30
ios/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>API_KEY</key>
|
||||||
|
<string>AIzaSyD67KPio-70XW_yGjrRUyq9ejdZoZA1rmE</string>
|
||||||
|
<key>GCM_SENDER_ID</key>
|
||||||
|
<string>703616926518</string>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>BUNDLE_ID</key>
|
||||||
|
<string>com.requestn.realEstateMobile.dev</string>
|
||||||
|
<key>PROJECT_ID</key>
|
||||||
|
<string>real-estate-2d71e</string>
|
||||||
|
<key>STORAGE_BUCKET</key>
|
||||||
|
<string>real-estate-2d71e.firebasestorage.app</string>
|
||||||
|
<key>IS_ADS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_ANALYTICS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_APPINVITE_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_GCM_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_SIGNIN_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>GOOGLE_APP_ID</key>
|
||||||
|
<string>1:703616926518:ios:301cac94994fbdc3fca997</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
30
ios/config/demo/GoogleService-Info.plist
Normal file
30
ios/config/demo/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>API_KEY</key>
|
||||||
|
<string>AIzaSyD67KPio-70XW_yGjrRUyq9ejdZoZA1rmE</string>
|
||||||
|
<key>GCM_SENDER_ID</key>
|
||||||
|
<string>703616926518</string>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>BUNDLE_ID</key>
|
||||||
|
<string>com.requestn.realEstateMobile.demo</string>
|
||||||
|
<key>PROJECT_ID</key>
|
||||||
|
<string>real-estate-2d71e</string>
|
||||||
|
<key>STORAGE_BUCKET</key>
|
||||||
|
<string>real-estate-2d71e.firebasestorage.app</string>
|
||||||
|
<key>IS_ADS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_ANALYTICS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_APPINVITE_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_GCM_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_SIGNIN_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>GOOGLE_APP_ID</key>
|
||||||
|
<string>1:703616926518:ios:2d2f7d163774722dfca997</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
30
ios/config/dev/GoogleService-Info.plist
Normal file
30
ios/config/dev/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>API_KEY</key>
|
||||||
|
<string>AIzaSyD67KPio-70XW_yGjrRUyq9ejdZoZA1rmE</string>
|
||||||
|
<key>GCM_SENDER_ID</key>
|
||||||
|
<string>703616926518</string>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>BUNDLE_ID</key>
|
||||||
|
<string>com.requestn.realEstateMobile.dev</string>
|
||||||
|
<key>PROJECT_ID</key>
|
||||||
|
<string>real-estate-2d71e</string>
|
||||||
|
<key>STORAGE_BUCKET</key>
|
||||||
|
<string>real-estate-2d71e.firebasestorage.app</string>
|
||||||
|
<key>IS_ADS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_ANALYTICS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_APPINVITE_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_GCM_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_SIGNIN_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>GOOGLE_APP_ID</key>
|
||||||
|
<string>1:703616926518:ios:301cac94994fbdc3fca997</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
30
ios/config/local/GoogleService-Info.plist
Normal file
30
ios/config/local/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>API_KEY</key>
|
||||||
|
<string>AIzaSyD67KPio-70XW_yGjrRUyq9ejdZoZA1rmE</string>
|
||||||
|
<key>GCM_SENDER_ID</key>
|
||||||
|
<string>703616926518</string>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>BUNDLE_ID</key>
|
||||||
|
<string>com.requestn.realEstateMobile.local</string>
|
||||||
|
<key>PROJECT_ID</key>
|
||||||
|
<string>real-estate-2d71e</string>
|
||||||
|
<key>STORAGE_BUCKET</key>
|
||||||
|
<string>real-estate-2d71e.firebasestorage.app</string>
|
||||||
|
<key>IS_ADS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_ANALYTICS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_APPINVITE_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_GCM_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_SIGNIN_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>GOOGLE_APP_ID</key>
|
||||||
|
<string>1:703616926518:ios:15652d299f9f2d7ffca997</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
27
ios/scripts/copy-firebase-config.sh
Executable file
27
ios/scripts/copy-firebase-config.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Copy the correct GoogleService-Info.plist based on build configuration
|
||||||
|
# This script is run as a build phase in Xcode
|
||||||
|
|
||||||
|
# Determine flavor from build configuration name
|
||||||
|
FLAVOR=""
|
||||||
|
if [[ "${CONFIGURATION}" == *"local"* ]]; then
|
||||||
|
FLAVOR="local"
|
||||||
|
elif [[ "${CONFIGURATION}" == *"demo"* ]]; then
|
||||||
|
FLAVOR="demo"
|
||||||
|
elif [[ "${CONFIGURATION}" == *"dev"* ]]; then
|
||||||
|
FLAVOR="dev"
|
||||||
|
else
|
||||||
|
# Default to dev
|
||||||
|
FLAVOR="dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLIST_SRC="${PROJECT_DIR}/config/${FLAVOR}/GoogleService-Info.plist"
|
||||||
|
PLIST_DEST="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
|
||||||
|
|
||||||
|
if [ -f "$PLIST_SRC" ]; then
|
||||||
|
echo "Copying GoogleService-Info.plist for flavor: ${FLAVOR}"
|
||||||
|
cp "$PLIST_SRC" "$PLIST_DEST"
|
||||||
|
else
|
||||||
|
echo "Warning: GoogleService-Info.plist not found for flavor: ${FLAVOR} at ${PLIST_SRC}"
|
||||||
|
echo "Using default from Runner/"
|
||||||
|
fi
|
||||||
195
lib/core/services/push_notification_service.dart
Normal file
195
lib/core/services/push_notification_service.dart
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:real_estate_mobile/features/notifications/data/notification_repository.dart';
|
||||||
|
|
||||||
|
/// Top-level handler for background messages (must be top-level function).
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
|
await Firebase.initializeApp();
|
||||||
|
debugPrint('[FCM] Background message: ${message.messageId}');
|
||||||
|
}
|
||||||
|
|
||||||
|
class PushNotificationService {
|
||||||
|
static final PushNotificationService _instance =
|
||||||
|
PushNotificationService._internal();
|
||||||
|
factory PushNotificationService() => _instance;
|
||||||
|
PushNotificationService._internal();
|
||||||
|
|
||||||
|
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
||||||
|
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
final NotificationRepository _repo = NotificationRepository();
|
||||||
|
|
||||||
|
String? _currentToken;
|
||||||
|
bool _initialized = false;
|
||||||
|
|
||||||
|
/// Callback when user taps a notification — set by the app to navigate.
|
||||||
|
void Function(String? actionUrl)? onNotificationTap;
|
||||||
|
|
||||||
|
/// Initialize Firebase Messaging and local notifications.
|
||||||
|
Future<void> initialize() async {
|
||||||
|
if (_initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
// Register background handler
|
||||||
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||||
|
|
||||||
|
// Request permission
|
||||||
|
final settings = await _messaging.requestPermission(
|
||||||
|
alert: true,
|
||||||
|
badge: true,
|
||||||
|
sound: true,
|
||||||
|
provisional: false,
|
||||||
|
);
|
||||||
|
debugPrint('[FCM] Permission: ${settings.authorizationStatus}');
|
||||||
|
|
||||||
|
if (settings.authorizationStatus == AuthorizationStatus.denied) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup local notifications for foreground display
|
||||||
|
await _setupLocalNotifications();
|
||||||
|
|
||||||
|
// Get token and register with backend
|
||||||
|
await _getAndRegisterToken();
|
||||||
|
|
||||||
|
// Listen for token refresh
|
||||||
|
_messaging.onTokenRefresh.listen(_onTokenRefresh);
|
||||||
|
|
||||||
|
// Handle foreground messages
|
||||||
|
FirebaseMessaging.onMessage.listen(_onForegroundMessage);
|
||||||
|
|
||||||
|
// Handle notification taps (app was in background)
|
||||||
|
FirebaseMessaging.onMessageOpenedApp.listen(_onMessageOpenedApp);
|
||||||
|
|
||||||
|
// Check if app was opened from a terminated state notification
|
||||||
|
final initialMessage = await _messaging.getInitialMessage();
|
||||||
|
if (initialMessage != null) {
|
||||||
|
_onMessageOpenedApp(initialMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS: set foreground notification presentation options
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
await _messaging.setForegroundNotificationPresentationOptions(
|
||||||
|
alert: true,
|
||||||
|
badge: true,
|
||||||
|
sound: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setupLocalNotifications() async {
|
||||||
|
const androidChannel = AndroidNotificationChannel(
|
||||||
|
'high_importance_channel',
|
||||||
|
'High Importance Notifications',
|
||||||
|
description: 'Used for important notifications',
|
||||||
|
importance: Importance.high,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _localNotifications
|
||||||
|
.resolvePlatformSpecificImplementation<
|
||||||
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
|
?.createNotificationChannel(androidChannel);
|
||||||
|
|
||||||
|
const androidSettings =
|
||||||
|
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
|
const iosSettings = DarwinInitializationSettings(
|
||||||
|
requestAlertPermission: false,
|
||||||
|
requestBadgePermission: false,
|
||||||
|
requestSoundPermission: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _localNotifications.initialize(
|
||||||
|
const InitializationSettings(
|
||||||
|
android: androidSettings,
|
||||||
|
iOS: iosSettings,
|
||||||
|
),
|
||||||
|
onDidReceiveNotificationResponse: (response) {
|
||||||
|
final payload = response.payload;
|
||||||
|
onNotificationTap?.call(payload);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getAndRegisterToken() async {
|
||||||
|
try {
|
||||||
|
final token = await _messaging.getToken();
|
||||||
|
if (token != null) {
|
||||||
|
_currentToken = token;
|
||||||
|
debugPrint('[FCM] Token: $token');
|
||||||
|
await _registerTokenWithBackend(token);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[FCM] Failed to get token: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onTokenRefresh(String token) async {
|
||||||
|
debugPrint('[FCM] Token refreshed: $token');
|
||||||
|
_currentToken = token;
|
||||||
|
await _registerTokenWithBackend(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _registerTokenWithBackend(String token) async {
|
||||||
|
try {
|
||||||
|
await _repo.registerFcmToken(token, Platform.isIOS ? 'ios' : 'android');
|
||||||
|
debugPrint('[FCM] Token registered with backend');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[FCM] Failed to register token: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onForegroundMessage(RemoteMessage message) {
|
||||||
|
debugPrint('[FCM] Foreground message: ${message.notification?.title}');
|
||||||
|
|
||||||
|
final notification = message.notification;
|
||||||
|
if (notification == null) return;
|
||||||
|
|
||||||
|
// Show local notification on Android (iOS handles it natively)
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
_localNotifications.show(
|
||||||
|
notification.hashCode,
|
||||||
|
notification.title,
|
||||||
|
notification.body,
|
||||||
|
const NotificationDetails(
|
||||||
|
android: AndroidNotificationDetails(
|
||||||
|
'high_importance_channel',
|
||||||
|
'High Importance Notifications',
|
||||||
|
channelDescription: 'Used for important notifications',
|
||||||
|
importance: Importance.high,
|
||||||
|
priority: Priority.high,
|
||||||
|
icon: '@mipmap/ic_launcher',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
payload: message.data['actionUrl'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onMessageOpenedApp(RemoteMessage message) {
|
||||||
|
debugPrint('[FCM] Notification tap: ${message.data}');
|
||||||
|
final actionUrl = message.data['actionUrl'] as String?;
|
||||||
|
onNotificationTap?.call(actionUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregister FCM token from backend (call on logout).
|
||||||
|
Future<void> unregister() async {
|
||||||
|
if (_currentToken == null) return;
|
||||||
|
try {
|
||||||
|
await _repo.removeFcmToken(_currentToken!);
|
||||||
|
debugPrint('[FCM] Token unregistered from backend');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[FCM] Failed to unregister token: $e');
|
||||||
|
}
|
||||||
|
_currentToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-register token after login.
|
||||||
|
Future<void> registerAfterLogin() async {
|
||||||
|
await _getAndRegisterToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,34 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:real_estate_mobile/core/services/push_notification_service.dart';
|
||||||
import 'package:real_estate_mobile/core/widgets/app_bottom_nav_bar.dart';
|
import 'package:real_estate_mobile/core/widgets/app_bottom_nav_bar.dart';
|
||||||
import 'package:real_estate_mobile/features/home/presentation/widgets/home_header.dart';
|
import 'package:real_estate_mobile/features/home/presentation/widgets/home_header.dart';
|
||||||
|
|
||||||
/// Root shell that provides the persistent header and bottom nav bar.
|
/// Root shell that provides the persistent header and bottom nav bar.
|
||||||
/// Only the content area (child) swaps when navigating between tabs.
|
/// Only the content area (child) swaps when navigating between tabs.
|
||||||
class AppShell extends StatelessWidget {
|
class AppShell extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const AppShell({super.key, required this.child});
|
const AppShell({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppShell> createState() => _AppShellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppShellState extends State<AppShell> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Wire push notification taps to GoRouter navigation
|
||||||
|
PushNotificationService().onNotificationTap = (actionUrl) {
|
||||||
|
if (mounted && actionUrl != null && actionUrl.isNotEmpty) {
|
||||||
|
context.go(actionUrl);
|
||||||
|
} else if (mounted) {
|
||||||
|
context.go('/notifications');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -31,8 +51,8 @@ class AppShell extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: KeyedSubtree(
|
child: KeyedSubtree(
|
||||||
key: ValueKey(child.runtimeType),
|
key: ValueKey(widget.child.runtimeType),
|
||||||
child: child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -515,37 +515,41 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
|
|||||||
// ── Name, verified badge, title, location, member since, bio ──
|
// ── Name, verified badge, title, location, member since, bio ──
|
||||||
Widget _buildProfileInfo(AgentProfile agent) {
|
Widget _buildProfileInfo(AgentProfile agent) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 38),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: Stack(
|
child: Column(
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// Name + Verified badge + (Verified Expert) on same line
|
// Name + Verified badge + (Verified Expert) + edit icon
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Flexible(
|
||||||
agent.firstName,
|
child: Text(
|
||||||
|
'${agent.firstName} ',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Fractul',
|
fontFamily: 'Fractul',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: AppColors.primaryDark,
|
color: AppColors.primaryDark,
|
||||||
),
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
),
|
||||||
if (agent.isVerified) ...[
|
if (agent.isVerified)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(bottom: 1),
|
padding: EdgeInsets.only(right: 4),
|
||||||
child: Icon(Icons.verified,
|
child: Icon(Icons.verified,
|
||||||
size: 16, color: Color(0xFF2196F3)),
|
size: 16, color: Color(0xFF2196F3)),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
Flexible(
|
||||||
],
|
child: Text(
|
||||||
Text(
|
|
||||||
agent.lastName,
|
agent.lastName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'Fractul',
|
fontFamily: 'Fractul',
|
||||||
@@ -553,6 +557,11 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
|
|||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: AppColors.primaryDark,
|
color: AppColors.primaryDark,
|
||||||
),
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (agent.isVerified) ...[
|
if (agent.isVerified) ...[
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
@@ -566,6 +575,19 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/icons/edit_icon.svg',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Title
|
// Title
|
||||||
@@ -587,10 +609,11 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (agent.location.isNotEmpty) ...[
|
if (agent.location.isNotEmpty) ...[
|
||||||
Icon(Icons.location_on,
|
SvgPicture.asset(
|
||||||
size: 16,
|
'assets/icons/location_filled_icon.svg',
|
||||||
color:
|
width: 16,
|
||||||
AppColors.primaryDark.withValues(alpha: 0.7)),
|
height: 16,
|
||||||
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
agent.location,
|
agent.location,
|
||||||
@@ -603,10 +626,15 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
],
|
],
|
||||||
Icon(Icons.calendar_today,
|
SvgPicture.asset(
|
||||||
size: 14,
|
'assets/icons/calendar_icon.svg',
|
||||||
color:
|
width: 16,
|
||||||
AppColors.primaryDark.withValues(alpha: 0.7)),
|
height: 16,
|
||||||
|
colorFilter: const ColorFilter.mode(
|
||||||
|
AppColors.primaryDark,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
agent.memberSinceText,
|
agent.memberSinceText,
|
||||||
@@ -638,21 +666,6 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Edit icon positioned at top-right
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {},
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
'assets/icons/edit_icon.svg',
|
|
||||||
width: 22,
|
|
||||||
height: 22,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:real_estate_mobile/core/network/api_client.dart';
|
import 'package:real_estate_mobile/core/network/api_client.dart';
|
||||||
import 'package:real_estate_mobile/core/network/api_exceptions.dart';
|
import 'package:real_estate_mobile/core/network/api_exceptions.dart';
|
||||||
|
import 'package:real_estate_mobile/core/services/push_notification_service.dart';
|
||||||
import 'package:real_estate_mobile/core/storage/secure_storage.dart';
|
import 'package:real_estate_mobile/core/storage/secure_storage.dart';
|
||||||
import 'package:real_estate_mobile/features/auth/data/auth_repository.dart';
|
import 'package:real_estate_mobile/features/auth/data/auth_repository.dart';
|
||||||
import 'package:real_estate_mobile/features/auth/data/models/register_request.dart';
|
import 'package:real_estate_mobile/features/auth/data/models/register_request.dart';
|
||||||
@@ -76,6 +77,8 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
status: AuthStatus.authenticated,
|
status: AuthStatus.authenticated,
|
||||||
user: user,
|
user: user,
|
||||||
);
|
);
|
||||||
|
// Register for push notifications after auth confirmed
|
||||||
|
_initPushNotifications();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Token is invalid/expired — clear and show login
|
// Token is invalid/expired — clear and show login
|
||||||
await SecureStorage.clearTokens();
|
await SecureStorage.clearTokens();
|
||||||
@@ -85,6 +88,8 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
state = state.copyWith(status: AuthStatus.loading);
|
state = state.copyWith(status: AuthStatus.loading);
|
||||||
|
// Unregister FCM token before clearing auth
|
||||||
|
await PushNotificationService().unregister();
|
||||||
await _repository.logout();
|
await _repository.logout();
|
||||||
state = const AuthState();
|
state = const AuthState();
|
||||||
}
|
}
|
||||||
@@ -117,6 +122,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
status: AuthStatus.authenticated,
|
status: AuthStatus.authenticated,
|
||||||
user: result['user'] as UserModel,
|
user: result['user'] as UserModel,
|
||||||
);
|
);
|
||||||
|
_initPushNotifications();
|
||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
status: AuthStatus.error,
|
status: AuthStatus.error,
|
||||||
@@ -166,6 +172,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
user: user,
|
user: user,
|
||||||
registeredEmail: email,
|
registeredEmail: email,
|
||||||
);
|
);
|
||||||
|
_initPushNotifications();
|
||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
status: AuthStatus.error,
|
status: AuthStatus.error,
|
||||||
@@ -182,6 +189,11 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initPushNotifications() {
|
||||||
|
final push = PushNotificationService();
|
||||||
|
push.initialize().then((_) => push.registerAfterLogin());
|
||||||
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
state = const AuthState();
|
state = const AuthState();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,39 @@ class NotificationRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register FCM token with backend.
|
||||||
|
/// POST /notifications/fcm-token
|
||||||
|
Future<void> registerFcmToken(String token, String device) async {
|
||||||
|
try {
|
||||||
|
await _dio.post('/notifications/fcm-token', data: {
|
||||||
|
'token': token,
|
||||||
|
'device': device,
|
||||||
|
});
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (e.error is ApiException) throw e.error as ApiException;
|
||||||
|
throw ApiException(
|
||||||
|
message: e.message ?? 'Failed to register FCM token',
|
||||||
|
statusCode: e.response?.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove FCM token from backend.
|
||||||
|
/// DELETE /notifications/fcm-token
|
||||||
|
Future<void> removeFcmToken(String token) async {
|
||||||
|
try {
|
||||||
|
await _dio.delete('/notifications/fcm-token', data: {
|
||||||
|
'token': token,
|
||||||
|
});
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (e.error is ApiException) throw e.error as ApiException;
|
||||||
|
throw ApiException(
|
||||||
|
message: e.message ?? 'Failed to remove FCM token',
|
||||||
|
statusCode: e.response?.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationsResponse {
|
class NotificationsResponse {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:real_estate_mobile/config/app_config.dart';
|
import 'package:real_estate_mobile/config/app_config.dart';
|
||||||
@@ -7,6 +8,7 @@ import 'package:real_estate_mobile/routing/app_router.dart';
|
|||||||
Future<void> mainCommon(Flavor flavor) async {
|
Future<void> mainCommon(Flavor flavor) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await AppConfig.init(flavor);
|
await AppConfig.init(flavor);
|
||||||
|
await Firebase.initializeApp();
|
||||||
runApp(const ProviderScope(child: MyApp()));
|
runApp(const ProviderScope(child: MyApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import Foundation
|
|||||||
|
|
||||||
import emoji_picker_flutter
|
import emoji_picker_flutter
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
|
import firebase_core
|
||||||
|
import firebase_messaging
|
||||||
|
import flutter_local_notifications
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
@@ -16,6 +19,9 @@ import url_launcher_macos
|
|||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
|
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
|||||||
96
pubspec.lock
96
pubspec.lock
@@ -9,6 +9,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "85.0.0"
|
version: "85.0.0"
|
||||||
|
_flutterfire_internals:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _flutterfire_internals
|
||||||
|
sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.59"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -241,6 +249,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
|
dbus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dbus
|
||||||
|
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.12"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -321,6 +337,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+5"
|
version: "0.9.3+5"
|
||||||
|
firebase_core:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_core
|
||||||
|
sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.15.2"
|
||||||
|
firebase_core_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_core_platform_interface
|
||||||
|
sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
|
firebase_core_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_core_web
|
||||||
|
sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.24.1"
|
||||||
|
firebase_messaging:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_messaging
|
||||||
|
sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.2.10"
|
||||||
|
firebase_messaging_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_platform_interface
|
||||||
|
sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.6.10"
|
||||||
|
firebase_messaging_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_web
|
||||||
|
sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.10.10"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -366,6 +430,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_local_notifications:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications
|
||||||
|
sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "18.0.1"
|
||||||
|
flutter_local_notifications_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_linux
|
||||||
|
sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.0"
|
||||||
|
flutter_local_notifications_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_platform_interface
|
||||||
|
sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.0"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1133,6 +1221,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
version: "0.7.9"
|
||||||
|
timezone:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timezone
|
||||||
|
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ dependencies:
|
|||||||
socket_io_client: ^3.0.2
|
socket_io_client: ^3.0.2
|
||||||
emoji_picker_flutter: ^4.3.0
|
emoji_picker_flutter: ^4.3.0
|
||||||
|
|
||||||
|
# Firebase
|
||||||
|
firebase_core: ^3.13.0
|
||||||
|
firebase_messaging: ^15.2.4
|
||||||
|
flutter_local_notifications: ^18.0.1
|
||||||
|
|
||||||
# Pin to avoid objective_c crash on iOS 26 simulator
|
# Pin to avoid objective_c crash on iOS 26 simulator
|
||||||
path_provider_foundation: 2.4.0
|
path_provider_foundation: 2.4.0
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
|
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi"));
|
registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
emoji_picker_flutter
|
emoji_picker_flutter
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
|
firebase_core
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user