setup fcm
This commit is contained in:
parent
cdc65f8f8f
commit
dc09aabbe8
@ -14,6 +14,7 @@ android {
|
|||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@ -45,6 +46,8 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||||
|
|
||||||
// Import the Firebase BoM
|
// Import the Firebase BoM
|
||||||
implementation(platform("com.google.firebase:firebase-bom:34.4.0"))
|
implementation(platform("com.google.firebase:firebase-bom:34.4.0"))
|
||||||
|
|
||||||
|
|||||||
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 846 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 431 B |
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
13
lib/common/di/di_firebase.dart
Normal file
13
lib/common/di/di_firebase.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
@module
|
||||||
|
abstract class FirebaseDi {
|
||||||
|
@lazySingleton
|
||||||
|
FirebaseMessaging get messaging => FirebaseMessaging.instance;
|
||||||
|
|
||||||
|
@lazySingleton
|
||||||
|
FlutterLocalNotificationsPlugin get localNotifications =>
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
}
|
||||||
87
lib/common/service/fcm_example_usage.dart
Normal file
87
lib/common/service/fcm_example_usage.dart
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// CONTOH PENGGUNAAN FCM SERVICE
|
||||||
|
// File ini hanya untuk referensi, tidak perlu diimport ke app
|
||||||
|
|
||||||
|
import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart';
|
||||||
|
import 'package:apskel_pos_flutter_v2/injection.dart';
|
||||||
|
|
||||||
|
/// Contoh 1: Ambil FCM token
|
||||||
|
Future<void> exampleGetToken() async {
|
||||||
|
final fcmService = getIt<FcmService>();
|
||||||
|
final token = await fcmService.getToken();
|
||||||
|
print('FCM Token: $token');
|
||||||
|
|
||||||
|
// Kirim token ke server untuk push notification
|
||||||
|
// await apiClient.sendTokenToServer(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contoh 2: Subscribe ke topic
|
||||||
|
Future<void> exampleSubscribeToTopic() async {
|
||||||
|
final fcmService = getIt<FcmService>();
|
||||||
|
|
||||||
|
// Subscribe ke topic "all_users"
|
||||||
|
await fcmService.subscribeToTopic('all_users');
|
||||||
|
|
||||||
|
// Subscribe ke topic berdasarkan outlet_id
|
||||||
|
await fcmService.subscribeToTopic('outlet_123');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contoh 3: Unsubscribe dari topic
|
||||||
|
Future<void> exampleUnsubscribeFromTopic() async {
|
||||||
|
final fcmService = getIt<FcmService>();
|
||||||
|
|
||||||
|
await fcmService.unsubscribeFromTopic('outlet_123');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contoh 4: Gunakan di BLoC
|
||||||
|
///
|
||||||
|
/// class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
||||||
|
/// final FcmService _fcmService;
|
||||||
|
///
|
||||||
|
/// NotificationBloc(this._fcmService) : super(NotificationInitial()) {
|
||||||
|
/// on<GetFcmToken>(_onGetFcmToken);
|
||||||
|
/// on<SubscribeToTopic>(_onSubscribeToTopic);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Future<void> _onGetFcmToken(
|
||||||
|
/// GetFcmToken event,
|
||||||
|
/// Emitter<NotificationState> emit,
|
||||||
|
/// ) async {
|
||||||
|
/// final token = await _fcmService.getToken();
|
||||||
|
/// emit(NotificationTokenLoaded(token));
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Future<void> _onSubscribeToTopic(
|
||||||
|
/// SubscribeToTopic event,
|
||||||
|
/// Emitter<NotificationState> emit,
|
||||||
|
/// ) async {
|
||||||
|
/// await _fcmService.subscribeToTopic(event.topic);
|
||||||
|
/// emit(NotificationTopicSubscribed(event.topic));
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
|
||||||
|
/// Contoh 5: Gunakan di Repository
|
||||||
|
///
|
||||||
|
/// @LazySingleton(as: IAuthRepository)
|
||||||
|
/// class AuthRepository implements IAuthRepository {
|
||||||
|
/// final FcmService _fcmService;
|
||||||
|
/// final ApiClient _apiClient;
|
||||||
|
///
|
||||||
|
/// AuthRepository(this._fcmService, this._apiClient);
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// Future<Either<Failure, User>> login(String email, String password) async {
|
||||||
|
/// try {
|
||||||
|
/// final response = await _apiClient.login(email, password);
|
||||||
|
///
|
||||||
|
/// // Kirim FCM token ke server setelah login berhasil
|
||||||
|
/// final fcmToken = await _fcmService.getToken();
|
||||||
|
/// if (fcmToken != null) {
|
||||||
|
/// await _apiClient.updateFcmToken(fcmToken);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// return Right(response);
|
||||||
|
/// } catch (e) {
|
||||||
|
/// return Left(ServerFailure(e.toString()));
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
191
lib/common/service/fcm_service.dart
Normal file
191
lib/common/service/fcm_service.dart
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
/// Background message handler — harus top-level function
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
|
log('[FCM] Background message: ${message.messageId}');
|
||||||
|
}
|
||||||
|
|
||||||
|
@lazySingleton
|
||||||
|
class FcmService {
|
||||||
|
FcmService(this._messaging, this._localNotifications);
|
||||||
|
|
||||||
|
final FirebaseMessaging _messaging;
|
||||||
|
final FlutterLocalNotificationsPlugin _localNotifications;
|
||||||
|
|
||||||
|
/// Inisialisasi FCM: minta permission, set handler, ambil token
|
||||||
|
Future<void> initialize() async {
|
||||||
|
// Setup local notifications
|
||||||
|
await _setupLocalNotifications();
|
||||||
|
|
||||||
|
// Register background handler
|
||||||
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||||
|
|
||||||
|
// Minta permission notifikasi (iOS & Android 13+)
|
||||||
|
final settings = await _messaging.requestPermission(
|
||||||
|
alert: true,
|
||||||
|
badge: true,
|
||||||
|
sound: true,
|
||||||
|
provisional: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
log('[FCM] Permission status: ${settings.authorizationStatus}');
|
||||||
|
|
||||||
|
if (settings.authorizationStatus == AuthorizationStatus.authorized ||
|
||||||
|
settings.authorizationStatus == AuthorizationStatus.provisional) {
|
||||||
|
await _setupTokenHandling();
|
||||||
|
_setupForegroundHandler();
|
||||||
|
_setupOpenedAppHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setupLocalNotifications() async {
|
||||||
|
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
|
const iosSettings = DarwinInitializationSettings(
|
||||||
|
requestAlertPermission: true,
|
||||||
|
requestBadgePermission: true,
|
||||||
|
requestSoundPermission: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const initSettings = InitializationSettings(
|
||||||
|
android: androidSettings,
|
||||||
|
iOS: iosSettings,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _localNotifications.initialize(
|
||||||
|
initSettings,
|
||||||
|
onDidReceiveNotificationResponse: _onNotificationTapped,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Android notification channel
|
||||||
|
const channel = AndroidNotificationChannel(
|
||||||
|
'high_importance_channel',
|
||||||
|
'High Importance Notifications',
|
||||||
|
description: 'This channel is used for important notifications.',
|
||||||
|
importance: Importance.high,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _localNotifications
|
||||||
|
.resolvePlatformSpecificImplementation<
|
||||||
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
|
?.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onNotificationTapped(NotificationResponse response) {
|
||||||
|
log('[FCM] Notification tapped: ${response.payload}');
|
||||||
|
// TODO: handle navigation berdasarkan payload
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setupTokenHandling() async {
|
||||||
|
final token = await getToken();
|
||||||
|
log('========================================');
|
||||||
|
log('[FCM] TOKEN: $token');
|
||||||
|
log('========================================');
|
||||||
|
// TODO: kirim token ke server jika diperlukan
|
||||||
|
|
||||||
|
_messaging.onTokenRefresh.listen((newToken) {
|
||||||
|
log('========================================');
|
||||||
|
log('[FCM] TOKEN REFRESHED: $newToken');
|
||||||
|
log('========================================');
|
||||||
|
// TODO: kirim token baru ke server jika diperlukan
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupForegroundHandler() {
|
||||||
|
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||||
|
log('[FCM] Foreground message: ${message.messageId}');
|
||||||
|
log('[FCM] Title: ${message.notification?.title}');
|
||||||
|
log('[FCM] Body: ${message.notification?.body}');
|
||||||
|
log('[FCM] Data: ${message.data}');
|
||||||
|
|
||||||
|
// Tampilkan notifikasi lokal saat app di foreground
|
||||||
|
final notification = message.notification;
|
||||||
|
if (notification != null) {
|
||||||
|
_showLocalNotification(
|
||||||
|
id: message.hashCode,
|
||||||
|
title: notification.title ?? '',
|
||||||
|
body: notification.body ?? '',
|
||||||
|
payload: message.data.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupOpenedAppHandler() {
|
||||||
|
// App dibuka dari notifikasi saat terminated
|
||||||
|
_messaging.getInitialMessage().then((RemoteMessage? message) {
|
||||||
|
if (message != null) {
|
||||||
|
log('[FCM] App opened from terminated via: ${message.messageId}');
|
||||||
|
_handleMessageNavigation(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// App dibuka dari notifikasi saat background
|
||||||
|
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
|
||||||
|
log('[FCM] App opened from background via: ${message.messageId}');
|
||||||
|
_handleMessageNavigation(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleMessageNavigation(RemoteMessage message) {
|
||||||
|
// TODO: tambahkan logika navigasi berdasarkan message.data
|
||||||
|
log('[FCM] Handle navigation for: ${message.data}');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showLocalNotification({
|
||||||
|
required int id,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
const androidDetails = AndroidNotificationDetails(
|
||||||
|
'high_importance_channel',
|
||||||
|
'High Importance Notifications',
|
||||||
|
channelDescription: 'This channel is used for important notifications.',
|
||||||
|
importance: Importance.high,
|
||||||
|
priority: Priority.high,
|
||||||
|
showWhen: true,
|
||||||
|
icon: 'ic_notification',
|
||||||
|
);
|
||||||
|
|
||||||
|
const iosDetails = DarwinNotificationDetails(
|
||||||
|
presentAlert: true,
|
||||||
|
presentBadge: true,
|
||||||
|
presentSound: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const details = NotificationDetails(
|
||||||
|
android: androidDetails,
|
||||||
|
iOS: iosDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _localNotifications.show(id, title, body, details, payload: payload);
|
||||||
|
log('[FCM] Local notification shown: $title');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ambil FCM token perangkat
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
try {
|
||||||
|
return await _messaging.getToken();
|
||||||
|
} catch (e) {
|
||||||
|
log('[FCM] Failed to get token: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe ke topic tertentu
|
||||||
|
Future<void> subscribeToTopic(String topic) async {
|
||||||
|
await _messaging.subscribeToTopic(topic);
|
||||||
|
log('[FCM] Subscribed to topic: $topic');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unsubscribe dari topic tertentu
|
||||||
|
Future<void> unsubscribeFromTopic(String topic) async {
|
||||||
|
await _messaging.unsubscribeFromTopic(topic);
|
||||||
|
log('[FCM] Unsubscribed from topic: $topic');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -80,10 +80,12 @@ import 'package:apskel_pos_flutter_v2/common/di/di_auto_route.dart' as _i729;
|
|||||||
import 'package:apskel_pos_flutter_v2/common/di/di_connectivity.dart' as _i807;
|
import 'package:apskel_pos_flutter_v2/common/di/di_connectivity.dart' as _i807;
|
||||||
import 'package:apskel_pos_flutter_v2/common/di/di_database.dart' as _i209;
|
import 'package:apskel_pos_flutter_v2/common/di/di_database.dart' as _i209;
|
||||||
import 'package:apskel_pos_flutter_v2/common/di/di_dio.dart' as _i86;
|
import 'package:apskel_pos_flutter_v2/common/di/di_dio.dart' as _i86;
|
||||||
|
import 'package:apskel_pos_flutter_v2/common/di/di_firebase.dart' as _i857;
|
||||||
import 'package:apskel_pos_flutter_v2/common/di/di_shared_preferences.dart'
|
import 'package:apskel_pos_flutter_v2/common/di/di_shared_preferences.dart'
|
||||||
as _i135;
|
as _i135;
|
||||||
import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
|
import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
|
||||||
as _i171;
|
as _i171;
|
||||||
|
import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart' as _i312;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/analytic/analytic.dart' as _i346;
|
import 'package:apskel_pos_flutter_v2/domain/analytic/analytic.dart' as _i346;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
|
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
|
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
|
||||||
@ -148,6 +150,9 @@ import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
|
|||||||
as _i800;
|
as _i800;
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
|
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
|
||||||
import 'package:dio/dio.dart' as _i361;
|
import 'package:dio/dio.dart' as _i361;
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart' as _i892;
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
|
||||||
|
as _i163;
|
||||||
import 'package:get_it/get_it.dart' as _i174;
|
import 'package:get_it/get_it.dart' as _i174;
|
||||||
import 'package:injectable/injectable.dart' as _i526;
|
import 'package:injectable/injectable.dart' as _i526;
|
||||||
import 'package:shared_preferences/shared_preferences.dart' as _i460;
|
import 'package:shared_preferences/shared_preferences.dart' as _i460;
|
||||||
@ -167,6 +172,7 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
final autoRouteDi = _$AutoRouteDi();
|
final autoRouteDi = _$AutoRouteDi();
|
||||||
final connectivityDi = _$ConnectivityDi();
|
final connectivityDi = _$ConnectivityDi();
|
||||||
final dioDi = _$DioDi();
|
final dioDi = _$DioDi();
|
||||||
|
final firebaseDi = _$FirebaseDi();
|
||||||
gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc());
|
gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc());
|
||||||
gh.factory<_i96.PrinterBloc>(() => _i96.PrinterBloc());
|
gh.factory<_i96.PrinterBloc>(() => _i96.PrinterBloc());
|
||||||
gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc());
|
gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc());
|
||||||
@ -179,6 +185,10 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter);
|
gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter);
|
||||||
gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity);
|
gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity);
|
||||||
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
|
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
|
||||||
|
gh.lazySingleton<_i892.FirebaseMessaging>(() => firebaseDi.messaging);
|
||||||
|
gh.lazySingleton<_i163.FlutterLocalNotificationsPlugin>(
|
||||||
|
() => firebaseDi.localNotifications,
|
||||||
|
);
|
||||||
gh.lazySingleton<_i171.NetworkClient>(
|
gh.lazySingleton<_i171.NetworkClient>(
|
||||||
() => _i171.NetworkClient(gh<_i895.Connectivity>()),
|
() => _i171.NetworkClient(gh<_i895.Connectivity>()),
|
||||||
);
|
);
|
||||||
@ -192,6 +202,12 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i464.ProductLocalDataProvider>(
|
gh.factory<_i464.ProductLocalDataProvider>(
|
||||||
() => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()),
|
() => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()),
|
||||||
);
|
);
|
||||||
|
gh.lazySingleton<_i312.FcmService>(
|
||||||
|
() => _i312.FcmService(
|
||||||
|
gh<_i892.FirebaseMessaging>(),
|
||||||
|
gh<_i163.FlutterLocalNotificationsPlugin>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
gh.factory<_i204.AuthLocalDataProvider>(
|
gh.factory<_i204.AuthLocalDataProvider>(
|
||||||
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
|
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
|
||||||
);
|
);
|
||||||
@ -397,3 +413,5 @@ class _$AutoRouteDi extends _i729.AutoRouteDi {}
|
|||||||
class _$ConnectivityDi extends _i807.ConnectivityDi {}
|
class _$ConnectivityDi extends _i807.ConnectivityDi {}
|
||||||
|
|
||||||
class _$DioDi extends _i86.DioDi {}
|
class _$DioDi extends _i86.DioDi {}
|
||||||
|
|
||||||
|
class _$FirebaseDi extends _i857.FirebaseDi {}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import 'common/service/fcm_service.dart';
|
||||||
import 'injection.dart';
|
import 'injection.dart';
|
||||||
import 'presentation/app_widget.dart';
|
import 'presentation/app_widget.dart';
|
||||||
|
|
||||||
@ -47,10 +48,12 @@ void main() async {
|
|||||||
kReleaseMode ? Environment.prod : Environment.dev,
|
kReleaseMode ? Environment.prod : Environment.dev,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Inisialisasi FCM setelah DI siap
|
||||||
|
await getIt<FcmService>().initialize();
|
||||||
|
|
||||||
runApp(const AppWidget());
|
runApp(const AppWidget());
|
||||||
},
|
},
|
||||||
(error, stack) {
|
(error, stack) {
|
||||||
// ✅ Ini udah bener
|
|
||||||
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import Foundation
|
|||||||
import connectivity_plus
|
import connectivity_plus
|
||||||
import firebase_core
|
import firebase_core
|
||||||
import firebase_crashlytics
|
import firebase_crashlytics
|
||||||
|
import firebase_messaging
|
||||||
|
import flutter_local_notifications
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
@ -16,6 +18,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
|
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
|
||||||
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
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"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
|||||||
56
pubspec.lock
56
pubspec.lock
@ -441,6 +441,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.8.14"
|
version: "3.8.14"
|
||||||
|
firebase_messaging:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_messaging
|
||||||
|
sha256: "5021279acd1cb5ccaceaa388e616e82cc4a2e4d862f02637df0e8ab766e6900a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "16.0.3"
|
||||||
|
firebase_messaging_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_platform_interface
|
||||||
|
sha256: f3a16c51f02055ace2a7c16ccb341c1f1b36b67c13270a48bcef68c1d970bbe8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.7.3"
|
||||||
|
firebase_messaging_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_web
|
||||||
|
sha256: "3eb9a1382caeb95b370f21e36d4a460496af777c9c2ef5df9b90d4803982c069"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.3"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -526,6 +550,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.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_spinkit:
|
flutter_spinkit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1269,6 +1317,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.5"
|
||||||
|
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:
|
||||||
|
|||||||
@ -29,6 +29,8 @@ dependencies:
|
|||||||
shared_preferences: ^2.5.3
|
shared_preferences: ^2.5.3
|
||||||
firebase_core: ^4.2.0
|
firebase_core: ^4.2.0
|
||||||
firebase_crashlytics: ^5.0.3
|
firebase_crashlytics: ^5.0.3
|
||||||
|
firebase_messaging: ^16.0.3
|
||||||
|
flutter_local_notifications: ^18.0.1
|
||||||
another_flushbar: ^1.12.32
|
another_flushbar: ^1.12.32
|
||||||
flutter_spinkit: ^5.2.2
|
flutter_spinkit: ^5.2.2
|
||||||
bloc: ^9.1.0
|
bloc: ^9.1.0
|
||||||
|
|||||||
47
tool/generate_notification_icon.dart
Normal file
47
tool/generate_notification_icon.dart
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
/// Jalankan dengan: dart run tool/generate_notification_icon.dart
|
||||||
|
void main() async {
|
||||||
|
final inputPath = 'assets/images/logo_white.png';
|
||||||
|
final outputBase = 'android/app/src/main/res';
|
||||||
|
|
||||||
|
final sizes = {
|
||||||
|
'drawable-mdpi': 24,
|
||||||
|
'drawable-hdpi': 36,
|
||||||
|
'drawable-xhdpi': 48,
|
||||||
|
'drawable-xxhdpi': 72,
|
||||||
|
'drawable-xxxhdpi': 96,
|
||||||
|
};
|
||||||
|
|
||||||
|
final inputFile = File(inputPath);
|
||||||
|
if (!inputFile.existsSync()) {
|
||||||
|
print('ERROR: File tidak ditemukan: $inputPath');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final originalBytes = inputFile.readAsBytesSync();
|
||||||
|
final original = img.decodeImage(originalBytes);
|
||||||
|
if (original == null) {
|
||||||
|
print('ERROR: Gagal decode image');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
print('Source: $inputPath (${original.width}x${original.height})');
|
||||||
|
|
||||||
|
for (final entry in sizes.entries) {
|
||||||
|
final folder = '$outputBase/${entry.key}';
|
||||||
|
final outputPath = '$folder/ic_notification.png';
|
||||||
|
|
||||||
|
Directory(folder).createSync(recursive: true);
|
||||||
|
|
||||||
|
final size = entry.value;
|
||||||
|
final resized = img.copyResize(original, width: size, height: size);
|
||||||
|
final pngBytes = img.encodePng(resized);
|
||||||
|
|
||||||
|
File(outputPath).writeAsBytesSync(pngBytes);
|
||||||
|
print('Generated: $outputPath (${size}x${size})');
|
||||||
|
}
|
||||||
|
|
||||||
|
print('\nDone! Gunakan icon: "ic_notification" di fcm_service.dart');
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user