setup fcm
This commit is contained in:
parent
cdc65f8f8f
commit
dc09aabbe8
@ -14,6 +14,7 @@ android {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
@ -45,6 +46,8 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
|
||||
// Import the Firebase BoM
|
||||
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_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_firebase.dart' as _i857;
|
||||
import 'package:apskel_pos_flutter_v2/common/di/di_shared_preferences.dart'
|
||||
as _i135;
|
||||
import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
|
||||
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/auth/auth.dart' as _i776;
|
||||
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;
|
||||
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
|
||||
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:injectable/injectable.dart' as _i526;
|
||||
import 'package:shared_preferences/shared_preferences.dart' as _i460;
|
||||
@ -167,6 +172,7 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
final autoRouteDi = _$AutoRouteDi();
|
||||
final connectivityDi = _$ConnectivityDi();
|
||||
final dioDi = _$DioDi();
|
||||
final firebaseDi = _$FirebaseDi();
|
||||
gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc());
|
||||
gh.factory<_i96.PrinterBloc>(() => _i96.PrinterBloc());
|
||||
gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc());
|
||||
@ -179,6 +185,10 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter);
|
||||
gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity);
|
||||
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
|
||||
gh.lazySingleton<_i892.FirebaseMessaging>(() => firebaseDi.messaging);
|
||||
gh.lazySingleton<_i163.FlutterLocalNotificationsPlugin>(
|
||||
() => firebaseDi.localNotifications,
|
||||
);
|
||||
gh.lazySingleton<_i171.NetworkClient>(
|
||||
() => _i171.NetworkClient(gh<_i895.Connectivity>()),
|
||||
);
|
||||
@ -192,6 +202,12 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.factory<_i464.ProductLocalDataProvider>(
|
||||
() => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()),
|
||||
);
|
||||
gh.lazySingleton<_i312.FcmService>(
|
||||
() => _i312.FcmService(
|
||||
gh<_i892.FirebaseMessaging>(),
|
||||
gh<_i163.FlutterLocalNotificationsPlugin>(),
|
||||
),
|
||||
);
|
||||
gh.factory<_i204.AuthLocalDataProvider>(
|
||||
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
|
||||
);
|
||||
@ -397,3 +413,5 @@ class _$AutoRouteDi extends _i729.AutoRouteDi {}
|
||||
class _$ConnectivityDi extends _i807.ConnectivityDi {}
|
||||
|
||||
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:injectable/injectable.dart';
|
||||
|
||||
import 'common/service/fcm_service.dart';
|
||||
import 'injection.dart';
|
||||
import 'presentation/app_widget.dart';
|
||||
|
||||
@ -47,10 +48,12 @@ void main() async {
|
||||
kReleaseMode ? Environment.prod : Environment.dev,
|
||||
);
|
||||
|
||||
// Inisialisasi FCM setelah DI siap
|
||||
await getIt<FcmService>().initialize();
|
||||
|
||||
runApp(const AppWidget());
|
||||
},
|
||||
(error, stack) {
|
||||
// ✅ Ini udah bener
|
||||
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
||||
},
|
||||
);
|
||||
|
||||
@ -8,6 +8,8 @@ import Foundation
|
||||
import connectivity_plus
|
||||
import firebase_core
|
||||
import firebase_crashlytics
|
||||
import firebase_messaging
|
||||
import flutter_local_notifications
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
@ -16,6 +18,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
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"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
||||
56
pubspec.lock
56
pubspec.lock
@ -441,6 +441,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -526,6 +550,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1269,6 +1317,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -29,6 +29,8 @@ dependencies:
|
||||
shared_preferences: ^2.5.3
|
||||
firebase_core: ^4.2.0
|
||||
firebase_crashlytics: ^5.0.3
|
||||
firebase_messaging: ^16.0.3
|
||||
flutter_local_notifications: ^18.0.1
|
||||
another_flushbar: ^1.12.32
|
||||
flutter_spinkit: ^5.2.2
|
||||
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