TF-1202: Configure FCM
This commit is contained in:
@@ -22,6 +22,7 @@ if (flutterVersionName == null) {
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
@@ -32,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 32
|
||||
compileSdkVersion 33
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
@@ -40,8 +41,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.linagora.android.teammail"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 31
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
// The number of method references in a .dex file cannot exceed 64K
|
||||
@@ -51,6 +52,12 @@ android {
|
||||
]
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
// Use key information file
|
||||
signingConfigs {
|
||||
release {
|
||||
@@ -74,7 +81,9 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.7.0'
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
implementation 'androidx.window:window:1.0.0'
|
||||
}
|
||||
|
||||
@@ -32,12 +32,15 @@
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:showWhenLocked="true"
|
||||
android:turnScreenOn="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
@@ -56,10 +59,6 @@
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
@@ -6,8 +6,9 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:tmail_ui_user/features/caching/account_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/email_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/firebase_config_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/mailbox_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/recent_search_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/state_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart';
|
||||
|
||||
class CachingManager {
|
||||
|
||||
final MailboxCacheClient _mailboxCacheClient;
|
||||
final StateCacheClient _stateCacheClient;
|
||||
final EmailCacheClient _emailCacheClient;
|
||||
final RecentSearchCacheClient _recentSearchCacheClient;
|
||||
final AccountCacheClient _accountCacheClient;
|
||||
final FirebaseCacheClient _firebaseCacheClient;
|
||||
|
||||
CachingManager(
|
||||
this._mailboxCacheClient,
|
||||
@@ -21,6 +21,7 @@ class CachingManager {
|
||||
this._emailCacheClient,
|
||||
this._recentSearchCacheClient,
|
||||
this._accountCacheClient,
|
||||
this._firebaseCacheClient,
|
||||
);
|
||||
|
||||
Future<void> clearAll() async {
|
||||
@@ -39,6 +40,7 @@ class CachingManager {
|
||||
_emailCacheClient.deleteBox(),
|
||||
_recentSearchCacheClient.deleteBox(),
|
||||
_accountCacheClient.deleteBox(),
|
||||
_firebaseCacheClient.deleteBox(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:tmail_ui_user/features/caching/config/hive_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/model/firebase_cache.dart';
|
||||
|
||||
class FirebaseCacheClient extends HiveCacheClient<FirebaseCache> {
|
||||
|
||||
@override
|
||||
String get tableName => 'FirebaseCache';
|
||||
}
|
||||
@@ -13,4 +13,5 @@ class CachingConstants {
|
||||
static const int AUTHENTICATION_INFO_HIVE_CACHE_IDENTIFY = 11;
|
||||
static const int RECENT_LOGIN_URL_HIVE_CACHE_IDENTITY = 12;
|
||||
static const int RECENT_LOGIN_USERNAME_HIVE_CACHE_IDENTITY = 13;
|
||||
static const int FIREBASE_CONFIG_CACHE_IDENTITY = 14;
|
||||
}
|
||||
-1
@@ -124,7 +124,6 @@ class MailboxDashBoardController extends ReloadableController {
|
||||
final viewStateMarkAsReadMailbox = Rx<Either<Failure, Success>>(Right(UIState.idle));
|
||||
final vacationResponse = Rxn<VacationResponse>();
|
||||
final routerParameters = Rxn<Map<String, String?>>();
|
||||
|
||||
Session? sessionCurrent;
|
||||
Map<Role, MailboxId> mapDefaultMailboxIdByRole = {};
|
||||
Map<MailboxId, PresentationMailbox> mapMailboxById = {};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
|
||||
abstract class FirebaseDatasource {
|
||||
Future<FirebaseDto> getCurrentFirebase();
|
||||
|
||||
Future<void> setCurrentFirebase(FirebaseDto newCurrentFirebase);
|
||||
|
||||
Future<void> deleteCurrentFirebase(String token);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/datasource/firebase_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/local/firebase_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
|
||||
|
||||
class HiveFirebaseDatasourceImpl extends FirebaseDatasource {
|
||||
|
||||
final FirebaseCacheManager _firebaseCacheManager;
|
||||
final ExceptionThrower _exceptionThrower;
|
||||
|
||||
HiveFirebaseDatasourceImpl(this._firebaseCacheManager, this._exceptionThrower);
|
||||
|
||||
@override
|
||||
Future<FirebaseDto> getCurrentFirebase() {
|
||||
return Future.sync(() async {
|
||||
return await _firebaseCacheManager.getFirebase();
|
||||
}).catchError((error) {
|
||||
_exceptionThrower.throwException(error);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setCurrentFirebase(FirebaseDto newCurrentFirebase) {
|
||||
return Future.sync(() async {
|
||||
return await _firebaseCacheManager.setFirebase(newCurrentFirebase);
|
||||
}).catchError((error) {
|
||||
_exceptionThrower.throwException(error);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteCurrentFirebase(String token) {
|
||||
return Future.sync(() async {
|
||||
return await _firebaseCacheManager.deleteFirebase(token);
|
||||
}).catchError((error) {
|
||||
_exceptionThrower.throwException(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/model/firebase_cache.dart';
|
||||
|
||||
extension FirebaseCacheExtension on FirebaseCache {
|
||||
|
||||
FirebaseDto toFirebaseDto() {
|
||||
return FirebaseDto(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/model/firebase_cache.dart';
|
||||
|
||||
extension FirebaseExtensions on FirebaseDto {
|
||||
FirebaseCache toCache() {
|
||||
return FirebaseCache(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:tmail_ui_user/features/caching/firebase_config_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/extensions/firebase_extensions.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/model/firebase_cache.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/extensions/firebase_cache_extensions.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/exceptions/firebase_exception.dart';
|
||||
|
||||
class FirebaseCacheManager {
|
||||
final FirebaseCacheClient _firebaseCacheClient;
|
||||
|
||||
FirebaseCacheManager(this._firebaseCacheClient);
|
||||
|
||||
Future<FirebaseDto> getFirebase() async {
|
||||
try {
|
||||
final firebase = await _firebaseCacheClient.getItem(FirebaseCache.keyCacheValue);
|
||||
if(firebase != null ) {
|
||||
return firebase.toFirebaseDto();
|
||||
} else {
|
||||
throw NotFoundStoredFirebaseException();
|
||||
}
|
||||
} catch (e) {
|
||||
logError('FirebaseCacheManager::getFirebase(): $e');
|
||||
throw NotFoundStoredFirebaseException();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setFirebase(FirebaseDto firebaseDto) {
|
||||
log('FirebaseCacheManager::setFirebase(): $_firebaseCacheClient');
|
||||
return _firebaseCacheClient.insertItem(firebaseDto.token, firebaseDto.toCache());
|
||||
}
|
||||
|
||||
Future<void> deleteFirebase(String token) {
|
||||
log('FirebaseCacheManager::deleteSelectedFirebase(): $token');
|
||||
return _firebaseCacheClient.deleteItem(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart';
|
||||
|
||||
part 'firebase_cache.g.dart';
|
||||
|
||||
@HiveType(typeId: CachingConstants.FIREBASE_CONFIG_CACHE_IDENTITY)
|
||||
class FirebaseCache extends HiveObject with EquatableMixin {
|
||||
|
||||
static const String keyCacheValue = 'firebaseCache';
|
||||
|
||||
@HiveField(0)
|
||||
final String token;
|
||||
|
||||
FirebaseCache(this.token);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [token];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/datasource/firebase_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/repository/firebase_repository.dart';
|
||||
|
||||
class FirebaseRepositoryImpl extends FirebaseRepository {
|
||||
|
||||
final FirebaseDatasource _firebaseDatasource;
|
||||
|
||||
FirebaseRepositoryImpl(this._firebaseDatasource);
|
||||
|
||||
@override
|
||||
Future<FirebaseDto> getCurrentFirebase() {
|
||||
return _firebaseDatasource.getCurrentFirebase();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setCurrentFirebase(FirebaseDto newCurrentFirebase) {
|
||||
log('FirebaseRepositoryImpl::setCurrentFirebase(): $newCurrentFirebase');
|
||||
return _firebaseDatasource.setCurrentFirebase(newCurrentFirebase);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteCurrentFirebase(String token) {
|
||||
return _firebaseDatasource.deleteCurrentFirebase(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
class NotFoundStoredFirebaseException implements Exception {}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
|
||||
abstract class FirebaseRepository {
|
||||
Future<FirebaseDto> getCurrentFirebase();
|
||||
|
||||
Future<void> setCurrentFirebase(FirebaseDto newCurrentFirebase);
|
||||
|
||||
Future<void> deleteCurrentFirebase(String token);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:core/core.dart';
|
||||
|
||||
class DeleteFirebaseSuccess extends UIState {
|
||||
|
||||
|
||||
DeleteFirebaseSuccess();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class DeleteFirebaseFailure extends FeatureFailure {
|
||||
final dynamic exception;
|
||||
|
||||
DeleteFirebaseFailure(this.exception);
|
||||
|
||||
@override
|
||||
List<Object> get props => [exception];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:core/core.dart';
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
|
||||
class GetFirebaseSuccess extends UIState {
|
||||
|
||||
final FirebaseDto firebaseDto;
|
||||
|
||||
GetFirebaseSuccess(this.firebaseDto);
|
||||
|
||||
@override
|
||||
List<Object> get props => [firebaseDto];
|
||||
}
|
||||
|
||||
class GetFirebaseFailure extends FeatureFailure {
|
||||
final dynamic exception;
|
||||
|
||||
GetFirebaseFailure(this.exception);
|
||||
|
||||
@override
|
||||
List<Object> get props => [exception];
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:core/core.dart';
|
||||
|
||||
class SaveFirebaseSuccess extends UIState {
|
||||
|
||||
|
||||
SaveFirebaseSuccess();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class SaveFirebaseFailure extends FeatureFailure {
|
||||
final dynamic exception;
|
||||
|
||||
SaveFirebaseFailure(this.exception);
|
||||
|
||||
@override
|
||||
List<Object> get props => [exception];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:core/core.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/repository/firebase_repository.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/state/delete_firebase_state.dart';
|
||||
|
||||
class DeleteFirebaseCacheInteractor {
|
||||
final FirebaseRepository _firebaseRepository;
|
||||
|
||||
DeleteFirebaseCacheInteractor(this._firebaseRepository);
|
||||
|
||||
Future<Either<Failure, Success>> execute(String token) async {
|
||||
try {
|
||||
await _firebaseRepository.deleteCurrentFirebase(token);
|
||||
return Right<Failure, Success>(DeleteFirebaseSuccess());
|
||||
} catch (e) {
|
||||
logError('DeleteFirebaseCacheInteractor::execute(): $e');
|
||||
return Left<Failure, Success>(DeleteFirebaseFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/repository/firebase_repository.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/state/get_firebase_state.dart';
|
||||
|
||||
class GetFirebaseCacheInteractor {
|
||||
|
||||
final FirebaseRepository _firebaseRepository;
|
||||
|
||||
GetFirebaseCacheInteractor(this._firebaseRepository);
|
||||
|
||||
Future<Either<Failure, Success>> execute(
|
||||
OIDCConfiguration config,
|
||||
String refreshToken) async {
|
||||
try {
|
||||
final firebase = await _firebaseRepository.getCurrentFirebase();
|
||||
return Right<Failure, Success>(GetFirebaseSuccess(firebase));
|
||||
} catch (e) {
|
||||
logError('GetFirebaseCacheInteractor::execute(): $e');
|
||||
return Left<Failure, Success>(GetFirebaseFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:core/core.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/repository/firebase_repository.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/state/save_firebase_state.dart';
|
||||
|
||||
class SaveFirebaseCacheInteractor {
|
||||
final FirebaseRepository _firebaseRepository;
|
||||
|
||||
SaveFirebaseCacheInteractor(this._firebaseRepository);
|
||||
|
||||
Future<Either<Failure, Success>> execute(FirebaseDto firebaseDto) async {
|
||||
try {
|
||||
await _firebaseRepository.setCurrentFirebase(firebaseDto);
|
||||
return Right<Failure, Success>(SaveFirebaseSuccess());
|
||||
} catch (e) {
|
||||
logError('SaveFirebaseCacheInteractor::execute(): $e');
|
||||
return Left<Failure, Success>(SaveFirebaseFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tmail_ui_user/features/base/base_bindings.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/datasource/firebase_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/datasource_impl/hive_account_datasource_impl.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/local/firebase_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/repository/firebase_repository_impl.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/repository/firebase_repository.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/usecases/delete_firebase_cache_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_firebase_cache_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/usecases/save_firebase_cache_interactor.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
|
||||
|
||||
class FireBaseBindings extends BaseBindings {
|
||||
@override
|
||||
void bindingsController() {}
|
||||
|
||||
@override
|
||||
void bindingsDataSource() {
|
||||
Get.put<FirebaseDatasource>(Get.find<HiveFirebaseDatasourceImpl>());
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsDataSourceImpl() {
|
||||
Get.put(HiveFirebaseDatasourceImpl(
|
||||
Get.find<FirebaseCacheManager>(),
|
||||
Get.find<CacheExceptionThrower>(),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsInteractor() {
|
||||
Get.put(DeleteFirebaseCacheInteractor(Get.find<FirebaseRepositoryImpl>()));
|
||||
Get.put(SaveFirebaseCacheInteractor(Get.find<FirebaseRepositoryImpl>()));
|
||||
Get.put(GetFirebaseCacheInteractor(Get.find<FirebaseRepositoryImpl>()));
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsRepository() {
|
||||
Get.put<FirebaseRepository>(Get.find<FirebaseRepositoryImpl>());
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsRepositoryImpl() {
|
||||
Get.put(FirebaseRepositoryImpl(Get.find<FirebaseDatasource>()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import 'dart:async';
|
||||
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:get/get.dart';
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/usecases/save_firebase_cache_interactor.dart';
|
||||
import 'package:tmail_ui_user/firebase_options.dart';
|
||||
import 'notification_strings.dart';
|
||||
|
||||
final StreamController<NotificationResponse?> selectNotificationStream =
|
||||
StreamController<NotificationResponse?>.broadcast();
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
await NotificationService.displayPushNotification(message);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||
//handle action
|
||||
}
|
||||
|
||||
void onDidReceiveNotificationResponse(
|
||||
NotificationResponse details,
|
||||
) {
|
||||
selectNotificationStream.add(details);
|
||||
}
|
||||
|
||||
class NotificationService {
|
||||
factory NotificationService() => _instance;
|
||||
|
||||
NotificationService._internal();
|
||||
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
|
||||
static get _firebaseMessaging => FirebaseMessaging.instance;
|
||||
|
||||
static get _flutterLocalNotificationsPlugin =>
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
static final BehaviorSubject<NotificationResponse?>
|
||||
_notificationActionStream =
|
||||
BehaviorSubject<NotificationResponse?>.seeded(null);
|
||||
|
||||
static Stream<NotificationResponse?> get notificationActionStream =>
|
||||
_notificationActionStream.stream;
|
||||
|
||||
static Stream<String> get onTokenRefresh => _firebaseMessaging.onTokenRefresh;
|
||||
static bool _isFlutterLocalNotificationsInitialized = false;
|
||||
|
||||
static Future<void> initializeNotificationService(
|
||||
Function(NotificationResponse)? onDidReceiveNotificationResponse,
|
||||
) async {
|
||||
if (_isFlutterLocalNotificationsInitialized) return;
|
||||
final _saveFirebaseCacheInteractor = Get.find<SaveFirebaseCacheInteractor>();
|
||||
final token = await _firebaseMessaging.getToken();
|
||||
_saveFirebaseCacheInteractor.execute(FirebaseDto(token));
|
||||
await _initFirebaseMessaging();
|
||||
await _flutterLocalNotificationsPlugin.initialize(
|
||||
InitializationSettings(
|
||||
android: androidInitializationSettings,
|
||||
iOS: iosInitializationSettings,
|
||||
),
|
||||
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||
);
|
||||
await _requestPermissions();
|
||||
await _createNotificationChannels();
|
||||
_listenNotificationActionStream();
|
||||
_isFlutterLocalNotificationsInitialized = true;
|
||||
}
|
||||
|
||||
static Future<void> displayPushNotification(
|
||||
RemoteMessage notification,
|
||||
) async {
|
||||
try {
|
||||
await _flutterLocalNotificationsPlugin.show(
|
||||
createUniqueId,
|
||||
channelName,
|
||||
notification.data.toString(),
|
||||
pushNotificationDetails,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> cancelAllNotifications() async {
|
||||
await _flutterLocalNotificationsPlugin.cancelAll();
|
||||
}
|
||||
|
||||
static Future<void> _initFirebaseMessaging() async {
|
||||
FirebaseMessaging.onMessage.listen(_handleIncomingForegroundNotification);
|
||||
FirebaseMessaging.onMessageOpenedApp.listen(_handleOpenedAppNotification);
|
||||
}
|
||||
|
||||
static Future<void> _handleIncomingForegroundNotification(
|
||||
RemoteMessage remoteMessage,
|
||||
) async {
|
||||
await displayPushNotification(remoteMessage);
|
||||
}
|
||||
|
||||
static void _handleOpenedAppNotification(RemoteMessage remoteMessage) {}
|
||||
|
||||
static Future<void> _requestPermissions() async {
|
||||
if (Platform.isAndroid) {
|
||||
await _flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermission();
|
||||
} else {
|
||||
await _flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _listenNotificationActionStream() async {
|
||||
selectNotificationStream.stream
|
||||
.listen((event) => event)
|
||||
.onData((data) async {
|
||||
_notificationActionStream.add(data);
|
||||
switch (data?.notificationResponseType) {
|
||||
case NotificationResponseType.selectedNotification:
|
||||
break;
|
||||
case NotificationResponseType.selectedNotificationAction:
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _createNotificationChannels() async {
|
||||
await _flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(androidChannel);
|
||||
}
|
||||
|
||||
static Future<void> deleteToken() async => _firebaseMessaging.deleteToken();
|
||||
}
|
||||
|
||||
int get createUniqueId =>
|
||||
DateTime.now().millisecondsSinceEpoch.remainder(100000);
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
|
||||
const channelId = 'team_mail_channel_id';
|
||||
const channelKey = 'team_mail_channel_key';
|
||||
const channelName = 'Team Mail notifications';
|
||||
const channelDescription = 'Team Mail notifications';
|
||||
|
||||
final iosInitializationSettings = DarwinInitializationSettings(
|
||||
notificationCategories: [
|
||||
DarwinNotificationCategory(
|
||||
channelId,
|
||||
actions: <DarwinNotificationAction>[
|
||||
DarwinNotificationAction.plain(
|
||||
channelKey,
|
||||
channelName,
|
||||
options: {
|
||||
DarwinNotificationActionOption.foreground,
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
onDidReceiveLocalNotification:
|
||||
(int id, String? title, String? body, String? payload) {},
|
||||
);
|
||||
|
||||
const androidInitializationSettings =
|
||||
AndroidInitializationSettings('background');
|
||||
|
||||
const androidChannel = AndroidNotificationChannel(
|
||||
channelId,
|
||||
channelName,
|
||||
description: channelDescription,
|
||||
importance: Importance.max,
|
||||
enableLights: true,
|
||||
);
|
||||
|
||||
const pushNotificationDetails = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
channelId,
|
||||
channelName,
|
||||
channelDescription: channelDescription,
|
||||
visibility: NotificationVisibility.public,
|
||||
importance: Importance.max,
|
||||
ledOnMs: 100,
|
||||
ledOffMs: 1000,
|
||||
category: AndroidNotificationCategory.message,
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentSound: true,
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
),
|
||||
);
|
||||
@@ -3,6 +3,7 @@ import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/utils/app_toast.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:model/firebase/firebase_dto.dart';
|
||||
import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart';
|
||||
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/config/authorization_interceptors.dart';
|
||||
@@ -11,6 +12,8 @@ import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oi
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/usecases/save_firebase_cache_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/presentation/notification_service.dart';
|
||||
import 'package:tmail_ui_user/features/session/domain/state/get_session_state.dart';
|
||||
import 'package:tmail_ui_user/features/session/domain/usecases/get_session_interactor.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';
|
||||
@@ -26,6 +29,7 @@ class SessionController extends ReloadableController {
|
||||
final CachingManager _cachingManager;
|
||||
final DeleteAuthorityOidcInteractor _deleteAuthorityOidcInteractor;
|
||||
final AuthorizationInterceptors _authorizationInterceptors;
|
||||
final SaveFirebaseCacheInteractor _saveFirebaseCacheInteractor;
|
||||
final AppToast _appToast;
|
||||
final DynamicUrlInterceptors _dynamicUrlInterceptors;
|
||||
|
||||
@@ -40,13 +44,19 @@ class SessionController extends ReloadableController {
|
||||
this._authorizationInterceptors,
|
||||
this._appToast,
|
||||
this._dynamicUrlInterceptors,
|
||||
this._saveFirebaseCacheInteractor,
|
||||
) : super(logoutOidcInteractor,
|
||||
deleteAuthorityOidcInteractor,
|
||||
getAuthenticatedAccountInteractor);
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
Future<void> onReady() async {
|
||||
super.onReady();
|
||||
NotificationService.initializeNotificationService(
|
||||
onDidReceiveNotificationResponse);
|
||||
NotificationService.onTokenRefresh.listen((token) {
|
||||
_saveFirebaseCacheInteractor.execute(FirebaseDto(token));
|
||||
});
|
||||
final arguments = Get.arguments;
|
||||
if (arguments != null && arguments is String) {
|
||||
_getSession();
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_a
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/get_credential_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_token_oidc_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/domain/usecases/save_firebase_cache_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/session/domain/usecases/get_session_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/session/presentation/session_controller.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
|
||||
@@ -44,6 +45,7 @@ class SessionPageBindings extends BaseBindings {
|
||||
Get.find<AuthorizationInterceptors>(),
|
||||
Get.find<AppToast>(),
|
||||
Get.find<DynamicUrlInterceptors>(),
|
||||
Get.find<SaveFirebaseCacheInteractor>(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'package:core/core.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/presentation/notification_service.dart';
|
||||
import 'package:tmail_ui_user/firebase_options.dart';
|
||||
import 'package:tmail_ui_user/main/bindings/main_bindings.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations_delegate.dart';
|
||||
@@ -22,6 +26,13 @@ void main() async {
|
||||
statusBarColor: Colors.black,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
));
|
||||
|
||||
try {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
||||
} on FirebaseException catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
await MainBindings().dependencies();
|
||||
await HiveCacheConfig().setUp();
|
||||
await HiveCacheConfig.initializeEncryptionKey();
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:tmail_ui_user/features/caching/authentication_info_cache_client.
|
||||
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||
import 'package:tmail_ui_user/features/caching/email_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/encryption_key_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/firebase_config_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/mailbox_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/recent_login_url_cache_client.dart';
|
||||
import 'package:tmail_ui_user/features/caching/recent_login_username_cache_client.dart';
|
||||
@@ -22,6 +23,7 @@ import 'package:tmail_ui_user/features/login/data/local/oidc_configuration_cache
|
||||
import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/data/local/firebase_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
|
||||
|
||||
@@ -55,12 +57,15 @@ class LocalBindings extends Bindings {
|
||||
Get.put(RecentLoginUrlCacheManager((Get.find<RecentLoginUrlCacheClient>())));
|
||||
Get.put(RecentLoginUsernameCacheClient());
|
||||
Get.put(RecentLoginUsernameCacheManager(Get.find<RecentLoginUsernameCacheClient>()));
|
||||
Get.put(FirebaseCacheClient());
|
||||
Get.put(FirebaseCacheManager(Get.find<FirebaseCacheClient>()));
|
||||
Get.put(CachingManager(
|
||||
Get.find<MailboxCacheClient>(),
|
||||
Get.find<StateCacheClient>(),
|
||||
Get.find<EmailCacheClient>(),
|
||||
Get.find<RecentSearchCacheClient>(),
|
||||
Get.find<AccountCacheClient>(),
|
||||
Get.find<FirebaseCacheClient>(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tmail_ui_user/features/push_notification/presentation/firebase_bindings.dart';
|
||||
import 'package:tmail_ui_user/main/bindings/core/core_bindings.dart';
|
||||
import 'package:tmail_ui_user/main/bindings/credential/credential_bindings.dart';
|
||||
import 'package:tmail_ui_user/main/bindings/local/local_bindings.dart';
|
||||
@@ -17,5 +18,6 @@ class MainBindings extends Bindings {
|
||||
CredentialBindings().dependencies();
|
||||
NetWorkConnectionBindings().dependencies();
|
||||
SessionBindings().dependencies();
|
||||
FireBaseBindings().dependencies();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class FirebaseDto with EquatableMixin {
|
||||
final String token;
|
||||
|
||||
FirebaseDto(this.token);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [token];
|
||||
}
|
||||
+99
-8
@@ -8,6 +8,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "47.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -141,6 +148,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
cloud_firestore_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cloud_firestore_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.8.5"
|
||||
cloud_firestore_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cloud_firestore_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -364,9 +385,9 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: email_supported
|
||||
resolved-ref: "241d7ba620137d6534cc9bbb5fd10e182b93b94a"
|
||||
url: "https://github.com/linagora/enough_html_editor.git"
|
||||
ref: "feature/update_inapp_webview"
|
||||
resolved-ref: a805acd03aae48997b4e9625412a02aff975747e
|
||||
url: "https://github.com/ManhNTX/enough_html_editor.git"
|
||||
source: git
|
||||
version: "0.0.5"
|
||||
enough_platform_widgets:
|
||||
@@ -432,6 +453,48 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "14.0.4"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.6"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.6"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -524,7 +587,7 @@ packages:
|
||||
name: flutter_inappwebview
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.4.3+8"
|
||||
version: "5.7.1"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -560,6 +623,27 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "12.0.3+1"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -998,14 +1082,14 @@ packages:
|
||||
name: permission_handler
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.2.0"
|
||||
version: "10.2.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.0.2+1"
|
||||
version: "10.2.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1110,8 +1194,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: d6c954206ac885a6f269f8be06dfd914a6393e89
|
||||
ref: demo
|
||||
resolved-ref: "70cea1832b0e93bce515d4641f96cd1b12366725"
|
||||
url: "https://github.com/linagora/rich-text-composer.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
@@ -1323,6 +1407,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
+5
-2
@@ -48,7 +48,7 @@ dependencies:
|
||||
rich_text_composer:
|
||||
git:
|
||||
url: https://github.com/linagora/rich-text-composer.git
|
||||
ref: master
|
||||
ref: demo
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
@@ -117,7 +117,7 @@ dependencies:
|
||||
device_info_plus: 4.0.2
|
||||
|
||||
# permission_handler
|
||||
permission_handler: 9.2.0
|
||||
permission_handler: 10.2.0
|
||||
|
||||
# share
|
||||
share: 2.0.4
|
||||
@@ -196,6 +196,9 @@ dependencies:
|
||||
ref: master
|
||||
|
||||
url_launcher: 6.1.5
|
||||
firebase_core: ^2.2.0
|
||||
firebase_messaging: 14.0.4
|
||||
flutter_local_notifications: ^12.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user