f655adfbea
Migrate app code into canonical feature slices, add phone-only AI digest scheduling and review, wire local notification/background task support, and cover the flow with tests.
106 lines
3.1 KiB
Dart
106 lines
3.1 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
const String aiDigestNotificationPayload = 'ai_digest_review_inbox';
|
|
|
|
final Provider<AiDigestNotificationIntentBus>
|
|
aiDigestNotificationIntentBusProvider = Provider<AiDigestNotificationIntentBus>(
|
|
(Ref ref) {
|
|
final AiDigestNotificationIntentBus bus = AiDigestNotificationIntentBus();
|
|
ref.onDispose(() {
|
|
unawaited(bus.close());
|
|
});
|
|
return bus;
|
|
},
|
|
);
|
|
|
|
class AiDigestNotificationIntentBus {
|
|
final StreamController<String> _controller =
|
|
StreamController<String>.broadcast();
|
|
|
|
Stream<String> get intents => _controller.stream;
|
|
|
|
void emitTap(String payload) {
|
|
if (!_controller.isClosed) {
|
|
_controller.add(payload);
|
|
}
|
|
}
|
|
|
|
Future<void> close() => _controller.close();
|
|
}
|
|
|
|
abstract interface class AiDigestNotifier {
|
|
Future<void> showDigestReady(int count);
|
|
}
|
|
|
|
class LocalAiDigestNotifier implements AiDigestNotifier {
|
|
LocalAiDigestNotifier({
|
|
FlutterLocalNotificationsPlugin? plugin,
|
|
void Function(String payload)? onTapPayload,
|
|
}) : _plugin = plugin ?? FlutterLocalNotificationsPlugin(),
|
|
_onTapPayload = onTapPayload;
|
|
|
|
final FlutterLocalNotificationsPlugin _plugin;
|
|
final void Function(String payload)? _onTapPayload;
|
|
Future<void>? _initializeFuture;
|
|
|
|
@override
|
|
Future<void> showDigestReady(int count) async {
|
|
if (!_supportsNotifications()) {
|
|
return;
|
|
}
|
|
await _ensureInitialized();
|
|
await _plugin.show(
|
|
id: 8124,
|
|
title: 'Relationship digest ready',
|
|
body:
|
|
'$count private ${count == 1 ? 'suggestion is' : 'suggestions are'} ready to review.',
|
|
notificationDetails: const NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
'relationship_saver_ai_digest',
|
|
'Relationship Digest',
|
|
channelDescription: 'Private AI digest suggestions ready for review',
|
|
importance: Importance.defaultImportance,
|
|
priority: Priority.defaultPriority,
|
|
),
|
|
iOS: DarwinNotificationDetails(),
|
|
macOS: DarwinNotificationDetails(),
|
|
),
|
|
payload: aiDigestNotificationPayload,
|
|
);
|
|
}
|
|
|
|
Future<void> _ensureInitialized() {
|
|
return _initializeFuture ??= _plugin.initialize(
|
|
settings: const InitializationSettings(
|
|
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
|
|
iOS: DarwinInitializationSettings(),
|
|
macOS: DarwinInitializationSettings(),
|
|
),
|
|
onDidReceiveNotificationResponse: (NotificationResponse response) {
|
|
final String? payload = response.payload;
|
|
if (payload != null && payload.isNotEmpty) {
|
|
_onTapPayload?.call(payload);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
bool _supportsNotifications() {
|
|
if (kIsWeb) {
|
|
return false;
|
|
}
|
|
return defaultTargetPlatform != TargetPlatform.linux;
|
|
}
|
|
}
|
|
|
|
final Provider<AiDigestNotifier> aiDigestNotifierProvider =
|
|
Provider<AiDigestNotifier>((Ref ref) {
|
|
return LocalAiDigestNotifier(
|
|
onTapPayload: ref.watch(aiDigestNotificationIntentBusProvider).emitTap,
|
|
);
|
|
});
|