diff --git a/apps/flutter/core/lib/config/env.config.dart b/apps/flutter/core/lib/config/env.config.dart index c054b0141..3dfa1aeb8 100644 --- a/apps/flutter/core/lib/config/env.config.dart +++ b/apps/flutter/core/lib/config/env.config.dart @@ -15,6 +15,7 @@ class EnvConfig { this.staticFilesUrl, this.tenantKey = '__tenant', this.defaultLanguage = 'en', + this.notifications, }); String clientId; String? clientSecret; @@ -24,10 +25,56 @@ class EnvConfig { String? staticFilesUrl; String? tenantKey; String? defaultLanguage; + NotificationConfig? notifications; factory EnvConfig.fromJson(Map json) => _$EnvConfigFromJson(json); } +@JsonSerializable(createToJson: false) +class NotificationConfig { + NotificationConfig({ + this.android, + this.darwin, + this.linux, + }); + AndroidNotification? android; + DarwinNotification? darwin; + LinuxNotification? linux; + + factory NotificationConfig.fromJson(Map json) => _$NotificationConfigFromJson(json); +} + +@JsonSerializable(createToJson: false) +class LinuxNotification { + LinuxNotification({ + required this.defaultActionName, + }); + String defaultActionName; + + factory LinuxNotification.fromJson(Map json) => _$LinuxNotificationFromJson(json); +} + +@JsonSerializable(createToJson: false) +class AndroidNotification { + AndroidNotification({ + required this.channelId, + required this.channelName, + this.channelDescription, + }); + String channelId; + String channelName; + String? channelDescription; + + factory AndroidNotification.fromJson(Map json) => _$AndroidNotificationFromJson(json); +} + +@JsonSerializable(createToJson: false) +class DarwinNotification { + DarwinNotification(); + + factory DarwinNotification.fromJson(Map json) => _$DarwinNotificationFromJson(json); +} + enum Env { development('DEV', 'Development'), profile('PROF', 'Profile'), @@ -53,7 +100,7 @@ class Environment { fromJson: EnvConfig.fromJson, ); - Environment.current = envlib.Environment.instance().config; + Environment.current = envlib.Environment.instance().config; } static late EnvConfig current; diff --git a/apps/flutter/core/lib/config/env.config.g.dart b/apps/flutter/core/lib/config/env.config.g.dart index 19b0a34fc..3db6ffc35 100644 --- a/apps/flutter/core/lib/config/env.config.g.dart +++ b/apps/flutter/core/lib/config/env.config.g.dart @@ -13,6 +13,39 @@ EnvConfig _$EnvConfigFromJson(Map json) => EnvConfig( baseUrl: json['baseUrl'] as String, uploadFilesUrl: json['uploadFilesUrl'] as String?, staticFilesUrl: json['staticFilesUrl'] as String?, - tenantKey: json['tenantKey'] as String?, - defaultLanguage: json['defaultLanguage'] as String?, + tenantKey: json['tenantKey'] as String? ?? '__tenant', + defaultLanguage: json['defaultLanguage'] as String? ?? 'en', + notifications: json['notifications'] == null + ? null + : NotificationConfig.fromJson( + json['notifications'] as Map), ); + +NotificationConfig _$NotificationConfigFromJson(Map json) => + NotificationConfig( + android: json['android'] == null + ? null + : AndroidNotification.fromJson( + json['android'] as Map), + darwin: json['darwin'] == null + ? null + : DarwinNotification.fromJson(json['darwin'] as Map), + linux: json['linux'] == null + ? null + : LinuxNotification.fromJson(json['linux'] as Map), + ); + +LinuxNotification _$LinuxNotificationFromJson(Map json) => + LinuxNotification( + defaultActionName: json['defaultActionName'] as String, + ); + +AndroidNotification _$AndroidNotificationFromJson(Map json) => + AndroidNotification( + channelId: json['channelId'] as String, + channelName: json['channelName'] as String, + channelDescription: json['channelDescription'] as String?, + ); + +DarwinNotification _$DarwinNotificationFromJson(Map json) => + DarwinNotification(); diff --git a/apps/flutter/core/lib/models/abp.dto.dart b/apps/flutter/core/lib/models/abp.dto.dart index 3dcf1e647..e218ffe54 100644 --- a/apps/flutter/core/lib/models/abp.dto.dart +++ b/apps/flutter/core/lib/models/abp.dto.dart @@ -2,18 +2,22 @@ class LocalizableStringInfo { LocalizableStringInfo({ required this.resourceName, required this.name, + this.values, }); String resourceName; String name; + Map? values; factory LocalizableStringInfo.fromJson(Map json) => LocalizableStringInfo( resourceName: json['resourceName'] as String, name: json['name'] as String, + values: json['values'] as Map?, ); Map toJson() => { 'resourceName': resourceName, 'name': name, + 'values': values, }; } diff --git a/apps/flutter/core/lib/services/service.base.dart b/apps/flutter/core/lib/services/service.base.dart index 2cf9f7374..c270ed807 100644 --- a/apps/flutter/core/lib/services/service.base.dart +++ b/apps/flutter/core/lib/services/service.base.dart @@ -1,5 +1,5 @@ import 'package:get/get.dart'; abstract class ServiceBase extends GetxService { - T find() => Get.find(); + T find({ String? tag}) => Get.find(tag: tag); } \ No newline at end of file diff --git a/apps/flutter/core/lib/utils/localization.utils.dart b/apps/flutter/core/lib/utils/localization.utils.dart index c6734fbde..9f72389b3 100644 --- a/apps/flutter/core/lib/utils/localization.utils.dart +++ b/apps/flutter/core/lib/utils/localization.utils.dart @@ -1,4 +1,5 @@ import 'package:core/utils/string.extensions.dart'; +import 'package:get/get.dart'; import '../proxy/volo/abp/asp-net-core/mvc/index.dart'; @@ -64,4 +65,16 @@ class LocalizedText { String? resourceName; String? key; String? localized; +} + +extension AbpTranslationsExtensions on String { + String trFormat([Map params = const {}]) { + var trans = tr; + if (params.isNotEmpty) { + params.forEach((key, value) { + trans = trans.replaceAll('{$key}', value); + }); + } + return trans; + } } \ No newline at end of file diff --git a/apps/flutter/dev_app/README.md b/apps/flutter/dev_app/README.md index f6c8d16bb..31b40a3a7 100644 --- a/apps/flutter/dev_app/README.md +++ b/apps/flutter/dev_app/README.md @@ -14,3 +14,45 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +## Environment + +* **baseUrl**: 服务器连接地址(必输) +* **clientId**: 客户端标识(必输) +* **clientSecret**: 客户端密钥(可选) +* **authority**: 身份认证服务器地址(必输) +* **uploadFilesUrl**: 上传文件路径(可选) +* **staticFilesUrl**: 静态文件路径(可选) +* **tenantKey**: 多租户标识(可选) +* **defaultLanguage**: 应用程序默认语言, 优先级低于用户配置(可选) +* **notifications**: 通知相关设置(可选) +* ***android***: android端通知设置(可选) +* ***channelId***: 通道标识(配置了android节点后为必输) +* ***channelName***: 通道名称(配置了android节点后为必输) +* ***channelDescription***: 通道说明(可选) +* ***linux***: linux端通知设置(可选) +* ***defaultActionName***: 默认点击方法名称(配置了linux节点后为必输) +* ***darwin***: iOS/Mac os端通知设置(可选) + +```json +{ + "baseUrl": "http://127.0.0.1:30000", + "clientId": "abp-flutter", + "clientSecret": "1q2w3e*", + "authority": "http://127.0.0.1:30000", + "uploadFilesUrl": "", + "staticFilesUrl": "", + "tenantKey": "__tenant", + "defaultLanguage": "en", + "notifications": { + "android": { + "channelId": "abp-flutter", + "channelName": "abp-flutter", + "channelDescription": "适用于Android端的通知通道定义" + }, + "linux": { + "defaultActionName": "Open notification" + } + } +} +``` \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/main/controller.dart b/apps/flutter/dev_app/lib/pages/main/controller.dart index 2dc45ad36..9c08eb7f6 100644 --- a/apps/flutter/dev_app/lib/pages/main/controller.dart +++ b/apps/flutter/dev_app/lib/pages/main/controller.dart @@ -1,52 +1,65 @@ +import 'package:core/services/notification.send.service.dart'; import 'package:get/get.dart'; import 'package:core/config/index.dart'; -import 'package:core/services/config.state.service.dart'; import 'package:core/services/session.service.dart'; import 'package:core/services/signalr.service.dart'; import 'package:core/services/subscription.service.dart'; import 'package:core/tokens/index.dart'; import 'package:core/utils/index.dart'; -import 'package:notifications/models/notification.dart'; +import 'package:notifications/models/index.dart'; class MainController extends GetxController { final RxInt _pageIndex = RxInt(0); int get currentIndex => _pageIndex.value; - SessionService get sessionService => Get.find(); - ConfigStateService get configStateService => Get.find(); - SubscriptionService get subscriptionService => Get.find(tag: NotificationTokens.consumer); - SignalrService get signalrService => Get.find(tag: NotificationTokens.producer); + SessionService get _sessionService => Get.find(); + SubscriptionService get _subscriptionService => Get.find(tag: NotificationTokens.consumer); + SignalrService get _signalrService => Get.find(tag: NotificationTokens.producer); + NotificationSendService get _notificationSendService => Get.find(); @override void onInit() async { super.onInit(); - subscriptionService.addOne(signalrService.onClose(logger.debug)); - subscriptionService.addOne(signalrService.onReconnected(logger.debug)); - subscriptionService.addOne(signalrService.onReconnecting(logger.debug)); - subscriptionService.subscribe( - signalrService.subscribe(NotificationTokens.receiver), - (message) { - var notification = NotificationInfo.fromJson(message.data.first as dynamic); - logger.debug(notification); + _subscriptionService.addOne(_signalrService.onClose(logger.debug)); + _subscriptionService.addOne(_signalrService.onReconnected(logger.debug)); + _subscriptionService.addOne(_signalrService.onReconnecting(logger.debug)); + // 在SignalR Hub之上再次订阅,用于全局通知启用按钮来管理 + _subscriptionService.subscribe( + // 订阅SignalR Hub + _signalrService.subscribe(NotificationTokens.receiver), + (message) async { + for (var data in message.data) { + if (data == null) continue; + // 解析通知数据 + var notification = NotificationInfo.fromJson(data as dynamic); + // 格式化为移动端可识别通知数据 + var payload = NotificationPaylod.fromData(notification.data); + // 发布本地通知 + await _notificationSendService.send( + payload.title, + payload.body, + payload.payload, + ); + } }, ); - sessionService.getToken$() + _sessionService.getToken$() .listen((token) async { if (token == null) { - await signalrService.stop(); + await _signalrService.stop(); } else { - await signalrService.start(); + await _signalrService.start(); } }); - if (sessionService.currentLanguage.isNullOrWhiteSpace()) { - sessionService.setLanguage(Environment.current.defaultLanguage ?? 'en'); + if (_sessionService.currentLanguage.isNullOrWhiteSpace()) { + _sessionService.setLanguage(Environment.current.defaultLanguage ?? 'en'); } } @override void onClose() { - subscriptionService.closeAll() - .whenComplete(() => signalrService.stop()); + _subscriptionService.closeAll() + .whenComplete(() => _signalrService.stop()); super.onClose(); } diff --git a/apps/flutter/dev_app/lib/services/notification.send.local.service.dart b/apps/flutter/dev_app/lib/services/notification.send.local.service.dart index 311f29a95..9c1ea36b2 100644 --- a/apps/flutter/dev_app/lib/services/notification.send.local.service.dart +++ b/apps/flutter/dev_app/lib/services/notification.send.local.service.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:core/config/index.dart'; import 'package:get/get.dart'; import 'package:rxdart/rxdart.dart' hide Notification; @@ -11,11 +12,12 @@ class FlutterLocalNotificationsSendService extends NotificationSendService { final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final Subject _notifications$ = BehaviorSubject(); final Subject _selectedNotifications$ = BehaviorSubject(); + final EnvConfig _env = Environment.current; Future initAsync() async { const initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/logo'); - const initializationSettingsLinux = LinuxInitializationSettings( - defaultActionName: 'Open notification', + var initializationSettingsLinux = LinuxInitializationSettings( + defaultActionName: _env.notifications?.linux?.defaultActionName ?? 'Open notification', ); var initializationSettingsDarwin = DarwinInitializationSettings( onDidReceiveLocalNotification: (id, title, body, payload) { @@ -46,17 +48,28 @@ class FlutterLocalNotificationsSendService extends NotificationSendService { @override Future send(String title, [String? body, String? payload]) async { nid.value += 1; - const androidDetails = AndroidNotificationDetails( - 'abp-flutter', - 'abp-flutter'); - const details = NotificationDetails( + + await _flutterLocalNotificationsPlugin.show( + nid.value, title, body, _buildDetails(), payload: payload); + } + + NotificationDetails _buildDetails() { + var androidDetails = AndroidNotificationDetails( + _env.notifications?.android?.channelId ?? 'abp-flutter', + _env.notifications?.android?.channelName ?? 'abp-flutter', + channelDescription: _env.notifications?.android?.channelDescription); + + const darwinDetails = DarwinNotificationDetails(); + + const linuxDetails = LinuxNotificationDetails(); + + var details = NotificationDetails( android: androidDetails, + iOS: darwinDetails, + macOS: darwinDetails, + linux: linuxDetails, ); - await _flutterLocalNotificationsPlugin.show( - nid.value, - title, - body, - details, - payload: payload); + + return details; } } \ No newline at end of file diff --git a/apps/flutter/dev_app/res/config/demo.json b/apps/flutter/dev_app/res/config/demo.json index 2bf590388..34fdd37b1 100644 --- a/apps/flutter/dev_app/res/config/demo.json +++ b/apps/flutter/dev_app/res/config/demo.json @@ -6,5 +6,15 @@ "uploadFilesUrl": "", "staticFilesUrl": "", "tenantKey": "__tenant", - "defaultLanguage": "en" + "defaultLanguage": "en", + "notifications": { + "android": { + "channelId": "abp-flutter", + "channelName": "abp-flutter", + "channelDescription": "适用于Android端的通知通道定义" + }, + "linux": { + "defaultActionName": "Open notification" + } + } } \ No newline at end of file diff --git a/apps/flutter/notifications/lib/models/common.dart b/apps/flutter/notifications/lib/models/common.dart new file mode 100644 index 000000000..177c30fa8 --- /dev/null +++ b/apps/flutter/notifications/lib/models/common.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; + +import 'package:core/models/abp.dto.dart'; +import 'package:core/utils/localization.utils.dart'; + +import 'notification.dart'; + +class NotificationPaylod { + NotificationPaylod({ + this.id, + required this.title, + required this.body, + this.payload, + }); + int? id; + String title; + String body; + String? payload; + + factory NotificationPaylod.fromData(NotificationData data) { + if (data.extraProperties['L'] == true) { + var title = LocalizableStringInfo.fromJson(data.extraProperties['title']); + var message = LocalizableStringInfo.fromJson(data.extraProperties['message']); + return NotificationPaylod( + title: title.name.trFormat(title.values?.map((key, value) => MapEntry(key, value?.toString() ?? '')) ?? {}), + body: message.name.trFormat(message.values?.map((key, value) => MapEntry(key, value?.toString() ?? '')) ?? {}), + payload: jsonEncode(data.extraProperties), + ); + } + return NotificationPaylod( + title: data.extraProperties['title'] as String, + body: data.extraProperties['message'] as String, + payload: jsonEncode(data.extraProperties), + ); + } +} \ No newline at end of file diff --git a/apps/flutter/notifications/lib/models/index.dart b/apps/flutter/notifications/lib/models/index.dart new file mode 100644 index 000000000..889faa445 --- /dev/null +++ b/apps/flutter/notifications/lib/models/index.dart @@ -0,0 +1,3 @@ +export 'common.dart'; +export 'notification.dart'; +export 'notification.state.dart'; \ No newline at end of file diff --git a/apps/flutter/notifications/lib/pages/notifier/state.dart b/apps/flutter/notifications/lib/models/notification.state.dart similarity index 51% rename from apps/flutter/notifications/lib/pages/notifier/state.dart rename to apps/flutter/notifications/lib/models/notification.state.dart index 2cad9fe67..e2f88e0dd 100644 --- a/apps/flutter/notifications/lib/pages/notifier/state.dart +++ b/apps/flutter/notifications/lib/models/notification.state.dart @@ -1,21 +1,37 @@ import 'package:get/get.dart'; +import 'package:json_annotation/json_annotation.dart'; -import '../../models/notification.dart'; +import 'notification.dart'; -class NotifierManageState { - NotifierManageState({ +part 'notification.state.g.dart'; + +@JsonSerializable() +class NotificationState { + NotificationState({ required this.isEnabled, required this.groups, }); bool isEnabled; List groups; - NotificationGroup? find(String name) { + NotificationGroup? findGroup(String name) { return groups.firstWhereOrNull((item) => item.name == name); } + + Notification? findNotification(String name) { + for (var group in groups) { + var find = group.find(name); + if (find != null) return find; + } + return null; + } + + factory NotificationState.fromJson(Map json) => _$NotificationStateFromJson(json); + Map toJson() => _$NotificationStateToJson(this); } +@JsonSerializable() class NotificationGroup { NotificationGroup({ required this.name, @@ -29,8 +45,12 @@ class NotificationGroup { Notification? find(String name) { return notifications.firstWhereOrNull((item) => item.name == name); } + + factory NotificationGroup.fromJson(Map json) => _$NotificationGroupFromJson(json); + Map toJson() => _$NotificationGroupToJson(this); } +@JsonSerializable() class Notification { Notification({ required this.name, @@ -50,4 +70,7 @@ class Notification { NotificationLifetime lifetime; NotificationType type; NotificationContentType contentType; + + factory Notification.fromJson(Map json) => _$NotificationFromJson(json); + Map toJson() => _$NotificationToJson(this); } diff --git a/apps/flutter/notifications/lib/models/notification.state.g.dart b/apps/flutter/notifications/lib/models/notification.state.g.dart new file mode 100644 index 000000000..b8c74f142 --- /dev/null +++ b/apps/flutter/notifications/lib/models/notification.state.g.dart @@ -0,0 +1,80 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notification.state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NotificationState _$NotificationStateFromJson(Map json) => + NotificationState( + isEnabled: json['isEnabled'] as bool, + groups: (json['groups'] as List) + .map((e) => NotificationGroup.fromJson(e as Map)) + .toList(), + ); + +Map _$NotificationStateToJson(NotificationState instance) => + { + 'isEnabled': instance.isEnabled, + 'groups': instance.groups, + }; + +NotificationGroup _$NotificationGroupFromJson(Map json) => + NotificationGroup( + name: json['name'] as String, + displayName: json['displayName'] as String, + notifications: (json['notifications'] as List) + .map((e) => Notification.fromJson(e as Map)) + .toList(), + ); + +Map _$NotificationGroupToJson(NotificationGroup instance) => + { + 'name': instance.name, + 'displayName': instance.displayName, + 'notifications': instance.notifications, + }; + +Notification _$NotificationFromJson(Map json) => Notification( + name: json['name'] as String, + groupName: json['groupName'] as String, + displayName: json['displayName'] as String, + isSubscribed: json['isSubscribed'] as bool, + description: json['description'] as String?, + lifetime: $enumDecode(_$NotificationLifetimeEnumMap, json['lifetime']), + type: $enumDecode(_$NotificationTypeEnumMap, json['type']), + contentType: + $enumDecode(_$NotificationContentTypeEnumMap, json['contentType']), + ); + +Map _$NotificationToJson(Notification instance) => + { + 'name': instance.name, + 'groupName': instance.groupName, + 'displayName': instance.displayName, + 'isSubscribed': instance.isSubscribed, + 'description': instance.description, + 'lifetime': _$NotificationLifetimeEnumMap[instance.lifetime]!, + 'type': _$NotificationTypeEnumMap[instance.type]!, + 'contentType': _$NotificationContentTypeEnumMap[instance.contentType]!, + }; + +const _$NotificationLifetimeEnumMap = { + NotificationLifetime.persistent: 0, + NotificationLifetime.onlyOne: 1, +}; + +const _$NotificationTypeEnumMap = { + NotificationType.application: 0, + NotificationType.system: 10, + NotificationType.user: 20, + NotificationType.serviceCallback: 30, +}; + +const _$NotificationContentTypeEnumMap = { + NotificationContentType.text: 0, + NotificationContentType.html: 1, + NotificationContentType.markdown: 2, + NotificationContentType.json: 3, +}; diff --git a/apps/flutter/notifications/lib/notifications.module.dart b/apps/flutter/notifications/lib/notifications.module.dart index fce7bb9dd..4c26a2046 100644 --- a/apps/flutter/notifications/lib/notifications.module.dart +++ b/apps/flutter/notifications/lib/notifications.module.dart @@ -12,5 +12,6 @@ class NotificationsModule extends Module { void configureServices() { inject(this); lazyInject(() => NotificationService(), fenix: true); + lazyInject(() => NotificationStateService(), fenix: true); } } \ No newline at end of file diff --git a/apps/flutter/notifications/lib/pages/notifier/controller.dart b/apps/flutter/notifications/lib/pages/notifier/controller.dart index 30190accf..aa14313d5 100644 --- a/apps/flutter/notifications/lib/pages/notifier/controller.dart +++ b/apps/flutter/notifications/lib/pages/notifier/controller.dart @@ -1,86 +1,37 @@ -import 'package:core/services/index.dart'; import 'package:get/get.dart'; +import '../../models/index.dart'; import '../../services/index.dart'; -import '../../tokens/index.dart'; -import './state.dart'; class NotifierManageController extends GetxController { - NotificationService get notificationService => Get.find(); - SignalrService get signalrService => Get.find(tag: NotificationTokens.producer); - SessionService get sessionService => Get.find(); + NotificationStateService get stateService => Get.find(); - final Rx _state = Rx(NotifierManageState( + final Rx _state = Rx(NotificationState( isEnabled: true, groups: [])); - NotifierManageState get state => _state.value; - - void _initNotifierConfig() async { - if (!sessionService.isAuthenticated) return; - var notifiers = await notificationService.getAssignableNotifiersAsync(); - var subscres = await notificationService.getMySubscribedListAsync(); - - List groups = []; - for (var notifierGroup in notifiers.items) { - if (notifierGroup.notifications == null && notifierGroup.notifications?.isEmpty == true) { - continue; - } - - List notifications = []; - for (var notifier in notifierGroup.notifications!) { - notifications.add(Notification( - name: notifier.name, - groupName: notifierGroup.name, - displayName: notifier.displayName ?? notifier.name, - description: notifier.description, - type: notifier.type, - contentType: notifier.contentType, - lifetime: notifier.lifetime, - isSubscribed: subscres.items.any((item) => item.name == notifier.name), - )); - } - - groups.add(NotificationGroup( - name: notifierGroup.name, - displayName: notifierGroup.displayName ?? notifierGroup.name, - notifications: notifications, - )); - } - - _state.update((val) { - val!.groups = groups; - }); + NotificationState get state => _state.value; + + void _updateState() async { + _state.value = stateService.state; + stateService.getNotificationState$() + .listen((state$) { + _state.update((val) { + val = state$; + }); + }); } void onSubscribed(Notification notification, bool isSubscribe) async { - if (!isSubscribe) { - await notificationService.unSubscribe(notification.name); - } else { - await notificationService.subscribe(notification.name); - } - _state.update((val) { - var group = val!.find(notification.groupName); - if (group == null) return; - var findNotification = group.find(notification.name); - if (findNotification == null) return; - findNotification.isSubscribed = isSubscribe; - }); + await stateService.subscribe(notification, isSubscribe); } void onNotificationEnabled(bool isEnabled) { - if (!isEnabled) { - signalrService.unsubscribe(NotificationTokens.receiver); - } else { - signalrService.subscribe(NotificationTokens.receiver); - } - _state.update((val) { - val!.isEnabled = isEnabled; - }); + stateService.subscribeAll(isEnabled); } @override void onInit() { super.onInit(); - _initNotifierConfig(); + _updateState(); } } \ No newline at end of file diff --git a/apps/flutter/notifications/lib/pages/notifier/widget/notifier_card.dart b/apps/flutter/notifications/lib/pages/notifier/widget/notifier_card.dart index e0dfa991b..16f860eab 100644 --- a/apps/flutter/notifications/lib/pages/notifier/widget/notifier_card.dart +++ b/apps/flutter/notifications/lib/pages/notifier/widget/notifier_card.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart' hide Notification; -import '../state.dart'; +import '../../../models/index.dart'; class NotifierCard extends StatelessWidget { const NotifierCard({ diff --git a/apps/flutter/notifications/lib/services/index.dart b/apps/flutter/notifications/lib/services/index.dart index beef5d17b..a562143c2 100644 --- a/apps/flutter/notifications/lib/services/index.dart +++ b/apps/flutter/notifications/lib/services/index.dart @@ -1 +1,2 @@ -export 'notification.service.dart'; \ No newline at end of file +export 'notification.service.dart'; +export 'notification.state.service.dart'; \ No newline at end of file diff --git a/apps/flutter/notifications/lib/services/notification.state.service.dart b/apps/flutter/notifications/lib/services/notification.state.service.dart new file mode 100644 index 000000000..29c8997a4 --- /dev/null +++ b/apps/flutter/notifications/lib/services/notification.state.service.dart @@ -0,0 +1,124 @@ +import 'dart:convert'; + +import 'package:core/services/session.service.dart'; +import 'package:core/services/service.base.dart'; +import 'package:core/services/signalr.service.dart'; +import 'package:core/services/storage.service.dart'; +import 'package:core/utils/internal.store.dart'; +import 'package:notifications/models/notification.state.dart'; + +import '../tokens/notifications.token.dart'; +import 'notification.service.dart'; + +class NotificationStateService extends ServiceBase { + static const String configKey = '_abp_notification_'; + SessionService get _sessionService => find(); + NotificationService get _notificationService => find(); + StorageService get _storageService => find(); + SignalrService get _signalrService => find(tag: NotificationTokens.producer); + + final InternalStore _store = InternalStore( + state: _initState() + ); + + NotificationState get state => _store.state; + + void subscribeAll(bool isEnabled) { + if (isEnabled) { + _signalrService.subscribe(NotificationTokens.receiver); + } else { + _signalrService.unsubscribe(NotificationTokens.receiver); + } + _store.patch((val) { + val.isEnabled = isEnabled; + }); + } + + Stream getNotificationState$() { + return _store.sliceUpdate((state) => state); + } + + NotificationGroup? findGroup(String name) { + return _store.state.findGroup(name); + } + + Notification? findNotification(String name) { + return _store.state.findNotification(name); + } + + Future subscribe(Notification notification, bool isSubscribe) async { + if (isSubscribe) { + await _notificationService.subscribe(notification.name); + } else { + await _notificationService.unSubscribe(notification.name); + } + _changeSubscribedState(notification, isSubscribe); + } + + @override + void onInit() { + super.onInit(); + _sessionService.getProfile$() + .listen((profile) async { + if (_sessionService.isAuthenticated) { + await _refreshState(); + } + }); + var notification$ = _store.sliceUpdate((state) => state); + notification$.listen((notification) { + _storageService.setItem(configKey, jsonEncode(notification.toJson())); + }); + } + + static NotificationState _initState() { + var configState = StorageService.initStorage(configKey, + (value) => NotificationState.fromJson(jsonDecode(value))); + return configState ?? NotificationState(isEnabled: true, groups: []); + } + + Future _refreshState() async { + var notifiers = await _notificationService.getAssignableNotifiersAsync(); + var subscres = await _notificationService.getMySubscribedListAsync(); + List groups = []; + + for (var notifierGroup in notifiers.items) { + if (notifierGroup.notifications == null && notifierGroup.notifications?.isEmpty == true) { + continue; + } + + List notifications = []; + for (var notifier in notifierGroup.notifications!) { + notifications.add(Notification( + name: notifier.name, + groupName: notifierGroup.name, + displayName: notifier.displayName ?? notifier.name, + description: notifier.description, + type: notifier.type, + contentType: notifier.contentType, + lifetime: notifier.lifetime, + isSubscribed: subscres.items.any((item) => item.name == notifier.name), + )); + } + + groups.add(NotificationGroup( + name: notifierGroup.name, + displayName: notifierGroup.displayName ?? notifierGroup.name, + notifications: notifications, + )); + } + + _store.patch((state) { + state.groups = groups; + }); + } + + void _changeSubscribedState(Notification notification, bool isSubscribe) { + _store.patch((val) { + var group = val.findGroup(notification.groupName); + if (group == null) return; + var findNotification = group.find(notification.name); + if (findNotification == null) return; + findNotification.isSubscribed = true; + }); + } +} \ No newline at end of file