143 lines
5.2 KiB
Dart
143 lines
5.2 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
|
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
|
|
class WhatsAppNotificationService {
|
|
static final WhatsAppNotificationService _instance = WhatsAppNotificationService._internal();
|
|
factory WhatsAppNotificationService() => _instance;
|
|
WhatsAppNotificationService._internal();
|
|
|
|
StreamSubscription? _subscription;
|
|
int _initTime = DateTime.now().millisecondsSinceEpoch;
|
|
bool _isListening = false;
|
|
|
|
void startListening() {
|
|
if (_isListening) return;
|
|
_isListening = true;
|
|
_initTime = DateTime.now().millisecondsSinceEpoch;
|
|
|
|
final db = FirebaseDatabase.instance.ref();
|
|
_subscription = db.child("notifikasi").orderByChild("timestamp").startAt(_initTime).onChildAdded.listen((event) {
|
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
|
if (data != null && data['timestamp'] != null) {
|
|
// Ensure it's strictly newer than initTime to avoid immediate trigger
|
|
if (data['timestamp'] > _initTime) {
|
|
if (navigatorKey.currentContext != null) {
|
|
_showNotification(navigatorKey.currentContext!, data['title'] ?? 'Pemberitahuan', data['message'] ?? '');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void stopListening() {
|
|
_subscription?.cancel();
|
|
_isListening = false;
|
|
}
|
|
|
|
void showNotification(BuildContext context, String title, String message) {
|
|
_showNotification(context, title, message);
|
|
}
|
|
|
|
void _showNotification(BuildContext context, String title, String message) {
|
|
final overlay = navigatorKey.currentState?.overlay ?? Overlay.of(context);
|
|
|
|
late OverlayEntry overlayEntry;
|
|
|
|
overlayEntry = OverlayEntry(
|
|
builder: (overlayContext) => Positioned(
|
|
top: MediaQuery.of(overlayContext).padding.top + 10,
|
|
left: 10,
|
|
right: 10,
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: TweenAnimationBuilder<Offset>(
|
|
tween: Tween<Offset>(begin: const Offset(0, -1), end: Offset.zero),
|
|
duration: const Duration(milliseconds: 400),
|
|
curve: Curves.easeOutBack,
|
|
builder: (overlayContext, offset, child) {
|
|
return FractionalTranslation(
|
|
translation: offset,
|
|
child: child,
|
|
);
|
|
},
|
|
child: GestureDetector(
|
|
onVerticalDragUpdate: (details) {
|
|
if (details.primaryDelta! < -5) {
|
|
overlayEntry.remove();
|
|
}
|
|
},
|
|
onTap: () {
|
|
overlayEntry.remove();
|
|
Navigator.pushNamed(context, '/notifikasi');
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(15),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.15),
|
|
blurRadius: 15,
|
|
offset: const Offset(0, 5),
|
|
)
|
|
]
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: title.toLowerCase().contains("bahaya") ? Colors.red.withOpacity(0.1) : const Color(0xFF2E7D5B).withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
title.toLowerCase().contains("bahaya") ? Icons.warning_rounded : Icons.info_outline,
|
|
color: title.toLowerCase().contains("bahaya") ? Colors.red : const Color(0xFF2E7D5B),
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
message,
|
|
style: const TextStyle(color: Colors.black87, fontSize: 13),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
overlay.insert(overlayEntry);
|
|
|
|
// Auto remove after 4 seconds
|
|
Future.delayed(const Duration(seconds: 4), () {
|
|
if (overlayEntry.mounted) {
|
|
overlayEntry.remove();
|
|
}
|
|
});
|
|
}
|
|
}
|