TKK_E32231105/lib/screens/notification_debug_screen.dart

346 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../services/notification_service.dart';
class NotificationDebugScreen extends StatefulWidget {
const NotificationDebugScreen({super.key});
@override
State<NotificationDebugScreen> createState() =>
_NotificationDebugScreenState();
}
class _NotificationDebugScreenState extends State<NotificationDebugScreen> {
bool _isLoading = false;
bool? _canScheduleExactNotifications;
String? _lastStatus;
List<PendingNotificationRequest> _pendingNotifications = [];
@override
void initState() {
super.initState();
_loadPendingNotifications();
}
Future<void> _setStatus(String message) async {
if (!mounted) return;
setState(() {
_lastStatus = message;
});
}
Future<void> _loadPendingNotifications() async {
setState(() {
_isLoading = true;
});
try {
await NotificationService.instance.initialize();
final canScheduleExact =
await NotificationService.instance.canScheduleExactNotifications();
final pending = await NotificationService.instance.pendingNotifications();
if (!mounted) return;
setState(() {
_pendingNotifications = pending;
_canScheduleExactNotifications = canScheduleExact;
_isLoading = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_isLoading = false;
_lastStatus = 'Gagal memuat notifikasi tertunda: $e';
});
}
}
Future<void> _sendSamplePanenPagi() async {
await _runAction(() async {
await NotificationService.instance.showPanenSummaryNotification(
jenisPanen: 'pagi',
kandangTotals: const {
'kandang 1': 15,
'kandang 2': 15,
},
);
}, 'Notif panen pagi terkirim');
}
Future<void> _sendSamplePanenSore() async {
await _runAction(() async {
await NotificationService.instance.showPanenSummaryNotification(
jenisPanen: 'sore',
kandangTotals: const {
'kandang 1': 12,
'kandang 2': 8,
},
);
}, 'Notif panen sore terkirim');
}
Future<void> _sendImmediateSimple() async {
await _runAction(() async {
await NotificationService.instance.showInstantNotification(
title: 'Tes Notifikasi',
body: 'Notifikasi lokal berhasil dikirim dari halaman debug.',
payload: 'debug:immediate',
);
}, 'Notif langsung terkirim');
}
Future<void> _scheduleThirtySecondNotification() async {
await _runAction(() async {
await NotificationService.instance.scheduleNotification(
title: 'Tes Notifikasi Terjadwal',
body:
'Notif ini dijadwalkan 30 detik sebelumnya. Coba minimize atau tutup app setelah dijadwalkan.',
delay: const Duration(seconds: 30),
prioritizeBackgroundDelivery: true,
payload: 'debug:scheduled_30s',
);
}, 'Notif 30 detik dijadwalkan');
await _loadPendingNotifications();
}
Future<void> _requestExactAlarmPermission() async {
await _runAction(() async {
final granted =
await NotificationService.instance.requestExactAlarmsPermission();
if (!granted) {
throw 'Izin exact alarm belum diberikan. Aktifkan dari settings aplikasi.';
}
}, 'Izin exact alarm berhasil diminta');
await _loadPendingNotifications();
}
Future<void> _cancelAllNotifications() async {
await _runAction(() async {
await NotificationService.instance.cancelAll();
}, 'Semua notifikasi dibatalkan');
await _loadPendingNotifications();
}
Future<void> _runAction(
Future<void> Function() action, String successText) async {
setState(() {
_isLoading = true;
});
try {
await NotificationService.instance.initialize();
await action();
await _setStatus(successText);
} catch (e) {
await _setStatus('Error: $e');
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Debug Notifikasi'),
backgroundColor: Colors.orange,
actions: [
IconButton(
onPressed: _isLoading ? null : _loadPendingNotifications,
icon: const Icon(Icons.refresh),
tooltip: 'Refresh',
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildInfoCard(),
const SizedBox(height: 16),
_buildActionCard(),
const SizedBox(height: 16),
_buildPendingCard(),
if (_lastStatus != null) ...[
const SizedBox(height: 16),
_buildStatusCard(),
],
],
),
),
);
}
Widget _buildInfoCard() {
return Card(
elevation: 2,
color: Colors.blue.shade50,
child: const Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Cara kerja notifikasi',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(
'• Notifikasi langsung akan muncul saat app masih aktif atau berjalan di background.\n'
'• Notifikasi terjadwal tetap bisa muncul walaupun app diminimize atau ditutup, selama notif sudah dijadwalkan sebelum app berhenti.\n'
'• Kalau exact alarm tidak aktif, Android bisa menunda jadwal lokal. Gunakan tombol Aktifkan Exact Alarm agar notif 30 detik lebih presisi.\n'
'• Jika yang diminta adalah notif dari event server saat app benar-benar mati total, itu perlu push notification dari backend/FCM.',
style: TextStyle(fontSize: 13, height: 1.4),
),
],
),
),
);
}
Widget _buildActionCard() {
return Card(
elevation: 3,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Aksi Debug',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: _isLoading ? null : _sendSamplePanenPagi,
icon: const Icon(Icons.wb_sunny),
label: const Text('Simulasi Panen Pagi'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _isLoading ? null : _sendSamplePanenSore,
icon: const Icon(Icons.wb_twilight),
label: const Text('Simulasi Panen Sore'),
),
const SizedBox(height: 8),
OutlinedButton.icon(
onPressed: _isLoading ? null : _sendImmediateSimple,
icon: const Icon(Icons.notifications_active),
label: const Text('Kirim Notifikasi Langsung'),
),
const SizedBox(height: 8),
OutlinedButton.icon(
onPressed: _isLoading ? null : _scheduleThirtySecondNotification,
icon: const Icon(Icons.schedule),
label: const Text('Jadwalkan Notif 30 Detik'),
),
const SizedBox(height: 8),
OutlinedButton.icon(
onPressed: _isLoading ? null : _requestExactAlarmPermission,
icon: const Icon(Icons.alarm_on),
label: const Text('Aktifkan Exact Alarm'),
),
const SizedBox(height: 8),
TextButton.icon(
onPressed: _isLoading ? null : _cancelAllNotifications,
icon: const Icon(Icons.cancel),
label: const Text('Batalkan Semua Notifikasi'),
),
],
),
),
);
}
Widget _buildPendingCard() {
return Card(
elevation: 3,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Notifikasi Tertunda',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'Exact alarm: ${_canScheduleExactNotifications == null ? 'memuat...' : (_canScheduleExactNotifications! ? 'aktif' : 'belum aktif')}',
style: TextStyle(
fontSize: 13,
color: _canScheduleExactNotifications == true
? Colors.green.shade700
: Colors.red.shade700,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
if (_pendingNotifications.isEmpty)
const Text('Belum ada notifikasi terjadwal.')
else
..._pendingNotifications.map(
(notification) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(notification.title ?? '(tanpa judul)'),
const SizedBox(height: 4),
Text(notification.body ?? '(tanpa isi)'),
const SizedBox(height: 4),
Text(
'ID: ${notification.id}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
),
),
],
),
),
);
}
Widget _buildStatusCard() {
final success = _lastStatus!.contains('terkirim') ||
_lastStatus!.contains('dijadwalkan') ||
_lastStatus!.contains('dibatalkan');
return Card(
color: success ? Colors.green.shade50 : Colors.red.shade50,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
_lastStatus!,
style: TextStyle(
fontWeight: FontWeight.w600,
color: success ? Colors.green.shade800 : Colors.red.shade800,
),
),
),
);
}
}