From 5fcd5b4b368fff217d45f324f8bd5db2a84fed6f Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 9 Aug 2023 16:04:43 +0800 Subject: [PATCH] feat(flutter): add menu drawer --- .../components/lib/widgets/menu/index.dart | 11 +- apps/flutter/core/lib/models/environment.dart | 2 + .../core/lib/models/environment.g.dart | 6 + apps/flutter/dev_app/.gitignore | 6 +- .../dev_app/lib/pages/main/controller.dart | 13 +- .../lib/pages/public/home/controller.dart | 14 ++ .../dev_app/lib/pages/public/home/state.dart | 3 + .../dev_app/lib/pages/public/home/view.dart | 143 +++++------------- .../lib/pages/public/home/widget/index.dart | 5 + .../pages/public/home/widget/menu_drawer.dart | 76 ++++++++++ .../pages/public/home/widget/my_favorite.dart | 42 +++++ .../public/home/widget/notification_bar.dart | 55 +++++++ .../public/home/widget/quick_navigation.dart | 33 ++++ .../notification.send.local.service.dart | 17 +++ .../translation.service.res.service.dart | 24 ++- apps/flutter/dev_app/res/config/demo.json | 14 ++ .../dev_app/res/images/notification.png | Bin 0 -> 6508 bytes .../lib/models/notification.state.dart | 1 + .../services/notification.state.service.dart | 11 ++ .../services/favorite.menu.state.service.dart | 2 +- .../lib/services/menu.state.service.dart | 2 +- 21 files changed, 348 insertions(+), 132 deletions(-) create mode 100644 apps/flutter/dev_app/lib/pages/public/home/widget/index.dart create mode 100644 apps/flutter/dev_app/lib/pages/public/home/widget/menu_drawer.dart create mode 100644 apps/flutter/dev_app/lib/pages/public/home/widget/my_favorite.dart create mode 100644 apps/flutter/dev_app/lib/pages/public/home/widget/notification_bar.dart create mode 100644 apps/flutter/dev_app/lib/pages/public/home/widget/quick_navigation.dart create mode 100644 apps/flutter/dev_app/res/images/notification.png diff --git a/apps/flutter/components/lib/widgets/menu/index.dart b/apps/flutter/components/lib/widgets/menu/index.dart index b060f7e82..ff9f64846 100644 --- a/apps/flutter/components/lib/widgets/menu/index.dart +++ b/apps/flutter/components/lib/widgets/menu/index.dart @@ -21,11 +21,7 @@ class Navigation extends StatefulWidget { class _NavigationState extends State { @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Container( - child: _renderNavigations(widget.menus), - ), - ); + return _renderNavigations(widget.menus); } Widget _renderNavigations(List menus) { @@ -59,7 +55,8 @@ class _NavigationState extends State { return SizedBox( height: 30, child: ListTile( - title: Text(menu.displayName.padLeft(menu.level * 4)), + title: Text( + (menu.meta?['displayName']?.toString().tr ?? menu.displayName).padLeft(menu.level * 4)), ), ); }, @@ -81,7 +78,7 @@ class _NavigationState extends State { height: 30, margin: const EdgeInsets.only(top: 10), child: Text( - menu.displayName.padLeft(menu.level * 8), + (menu.meta?['displayName']?.toString().tr ?? menu.displayName).padLeft(menu.level * 8), ), ), ), diff --git a/apps/flutter/core/lib/models/environment.dart b/apps/flutter/core/lib/models/environment.dart index 0ac766486..f99127ebd 100644 --- a/apps/flutter/core/lib/models/environment.dart +++ b/apps/flutter/core/lib/models/environment.dart @@ -177,10 +177,12 @@ class LocalizationConfig { this.defaultLanguage, this.useLocalResources = true, this.supportedLocales = const [], + this.translationFiles = const {}, }); String? defaultLanguage; bool? useLocalResources; List? supportedLocales; + Map>? translationFiles; factory LocalizationConfig.fromJson(Map json) => _$LocalizationConfigFromJson(json); Map toJson() => _$LocalizationConfigToJson(this); diff --git a/apps/flutter/core/lib/models/environment.g.dart b/apps/flutter/core/lib/models/environment.g.dart index 07efcf04e..45c7cf6fc 100644 --- a/apps/flutter/core/lib/models/environment.g.dart +++ b/apps/flutter/core/lib/models/environment.g.dart @@ -102,6 +102,11 @@ LocalizationConfig _$LocalizationConfigFromJson(Map json) => supportedLocales: json['supportedLocales'] != null ? (json['supportedLocales'] as List).map((e) => LanguageInfo.fromJson(e)).toList() : null, + translationFiles: json['translationFiles'] != null + ? (json['translationFiles'] as Map) + .map((key, value) => MapEntry(key, (value as List) + .map((e) => e as String).toList())) + : null, ); Map _$LocalizationConfigToJson(LocalizationConfig instance) => @@ -109,6 +114,7 @@ Map _$LocalizationConfigToJson(LocalizationConfig instance) => 'defaultLanguage': instance.defaultLanguage, 'useLocalResources': instance.useLocalResources, 'supportedLocales': instance.supportedLocales, + 'translationFiles': instance.translationFiles, }; RemoteService _$RemoteServiceFromJson(Map json) => diff --git a/apps/flutter/dev_app/.gitignore b/apps/flutter/dev_app/.gitignore index bceef1d48..0333a3f0e 100644 --- a/apps/flutter/dev_app/.gitignore +++ b/apps/flutter/dev_app/.gitignore @@ -44,4 +44,8 @@ app.*.map.json /android/app/release # Environment config -/res/config/development.json \ No newline at end of file +/res/config/development.json + +# Ignored translations +/res/translations/merge-en.json +/res/translations/merge-zh-Hans.json \ 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 ccfccf474..7cd35ca98 100644 --- a/apps/flutter/dev_app/lib/pages/main/controller.dart +++ b/apps/flutter/dev_app/lib/pages/main/controller.dart @@ -1,13 +1,13 @@ import 'package:core/dependency/index.dart'; import 'package:core/abstracts/signalr.service.dart'; import 'package:core/services/environment.service.dart'; -import 'package:core/services/notification.send.service.dart'; import 'package:dev_app/handlers/index.dart'; import 'package:get/get.dart'; import 'package:core/services/session.service.dart'; import 'package:core/services/subscription.service.dart'; import 'package:core/utils/index.dart'; import 'package:notifications/models/index.dart'; +import 'package:notifications/services/notification.state.service.dart'; import 'package:notifications/tokens/index.dart'; class MainController extends GetxController { @@ -17,7 +17,7 @@ class MainController extends GetxController { SessionService get _sessionService => injector.get(); SubscriptionService get _subscriptionService => injector.get(tag: NotificationTokens.consumer); SignalrService get _signalrService => injector.get(tag: NotificationTokens.producer); - NotificationSendService get _notificationSendService => injector.get(); + NotificationStateService get _notificationStateService => injector.get(); EnvironmentService get _environmentService => injector.get(); ErrorHandler get _errorHandler => injector.get(); @@ -37,14 +37,7 @@ class MainController extends GetxController { if (data == null) continue; // 解析通知数据 var notification = NotificationInfo.fromJson(data as dynamic); - // 格式化为移动端可识别通知数据 - var payload = NotificationPaylod.fromNotification(notification); - // 发布本地通知 - await _notificationSendService.send( - payload.title, - payload.body, - payload.payload, - ); + _notificationStateService.addNotification(notification); } }, ); diff --git a/apps/flutter/dev_app/lib/pages/public/home/controller.dart b/apps/flutter/dev_app/lib/pages/public/home/controller.dart index b6884ba1c..9b272b415 100644 --- a/apps/flutter/dev_app/lib/pages/public/home/controller.dart +++ b/apps/flutter/dev_app/lib/pages/public/home/controller.dart @@ -1,6 +1,7 @@ import 'package:core/models/common.dart'; import 'package:get/get.dart'; import 'package:core/dependency/index.dart'; +import 'package:notifications/services/notification.state.service.dart'; import 'package:platforms/services/index.dart'; import 'state.dart'; @@ -8,6 +9,7 @@ import 'state.dart'; class HomeController extends GetxController { MenuStateService get _menuStateService => injector.get(); FavoriteMenuStateService get _favoriteMenuStateService => injector.get(); + NotificationStateService get _notificationStateService => injector.get(); final Rx _state = Rx(HomeState()); HomeState get state => _state.value; @@ -27,6 +29,18 @@ class HomeController extends GetxController { val?.favoriteMenus = menus; }); }); + _notificationStateService.getNotifications$() + .listen((payload) { + var notifications = state.notifications.reversed.take(5).toList(); + notifications.add(payload); + _state.update((val) { + val?.notifications = notifications; + }); + }); + } + + Future refreshMenus() async { + await _menuStateService.refreshState(); } void redirectToRoute(String route) { diff --git a/apps/flutter/dev_app/lib/pages/public/home/state.dart b/apps/flutter/dev_app/lib/pages/public/home/state.dart index cafd45fd6..4ddaa2a60 100644 --- a/apps/flutter/dev_app/lib/pages/public/home/state.dart +++ b/apps/flutter/dev_app/lib/pages/public/home/state.dart @@ -1,4 +1,5 @@ import 'package:core/models/common.dart'; +import 'package:notifications/models/common.dart'; import 'package:platforms/modes/menu.dto.dart'; class HomeState { @@ -6,10 +7,12 @@ class HomeState { this.activedMenu, this.menus = const [], this.favoriteMenus = const [], + this.notifications = const [], }); String? activedMenu; List menus; List favoriteMenus; + List notifications; List getMenus() => _buildTreeRecursive(menus, null, 0); diff --git a/apps/flutter/dev_app/lib/pages/public/home/view.dart b/apps/flutter/dev_app/lib/pages/public/home/view.dart index b6a77950c..b55edaa17 100644 --- a/apps/flutter/dev_app/lib/pages/public/home/view.dart +++ b/apps/flutter/dev_app/lib/pages/public/home/view.dart @@ -1,13 +1,13 @@ import 'package:account/pages/route.name.dart'; import 'package:components/index.dart'; import 'package:core/utils/index.dart'; -import 'package:dev_app/pages/public/home/widget/search.dart'; import 'package:dev_app/pages/system/route.name.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'controller.dart'; +import './widget/index.dart'; class HomePage extends BasePage { const HomePage({super.key}); @@ -35,114 +35,47 @@ class HomePage extends BasePage { ), body: ListView( children: [ - ExpansionTile( - initiallyExpanded: true, - title: Text('Label:QuickNavigation'.tr, - style: Theme.of(context).textTheme.titleMedium, - ), - children: [ - SizedBox( - height: 120, - child: GridView.count( - shrinkWrap: true, - crossAxisCount: 4, - crossAxisSpacing: 5, - physics: const NeverScrollableScrollPhysics(), - children: [ - _buildMenu( - SystemRoutes.settings, - SystemRoutes.settings, - icon: 'res/images/setting.png', - displayName: "Label:SystemSettings".tr, - color: Colors.red.hex), - _buildMenu( - AccountRoutes.profile, - AccountRoutes.profile, - icon: 'res/images/profile.png', - displayName: "Page:UserProfile".tr, - color: const Color.fromARGB(255, 68, 160, 206).hex), - ], - ), - ), - ], - ), - ExpansionTile( - initiallyExpanded: true, - title: Text('Label:MyFavorite'.tr, - style: Theme.of(context).textTheme.titleMedium, - ), - children: [ - GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: bloc.state.favoriteMenus.length, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - crossAxisSpacing: 5, - ), - itemBuilder: (BuildContext context, int index) { - if (index >= bloc.state.favoriteMenus.length) { - return Empty.none; - } - var favoriteMenu = bloc.state.favoriteMenus[index]; - return _buildMenu( - favoriteMenu.name, - favoriteMenu.path, - aliasName: favoriteMenu.aliasName, - //icon: favoriteMenu.icon, - // TODO: 需要各个模块自行提供本地图标 - icon: 'res/images/setting.png', - color: favoriteMenu.color, - displayName: favoriteMenu.displayName, - ); - }, - ), - ], + Obx(() => NotificationBar(notifications: bloc.state.notifications)), + QuickNavigation( + menus: [ + _buildMenu( + SystemRoutes.settings, + SystemRoutes.settings, + icon: 'res/images/setting.png', + displayName: "Label:SystemSettings".tr, + color: Colors.red.hex), + _buildMenu( + AccountRoutes.profile, + AccountRoutes.profile, + icon: 'res/images/profile.png', + displayName: "Page:UserProfile".tr, + color: const Color.fromARGB(255, 68, 160, 206).hex), + ], ), + Obx(() => MyFavorite( + favoriteMenus: bloc.state.favoriteMenus, + favoriteMenuBuilder: (favoriteMenu) { + return _buildMenu( + favoriteMenu.name, + favoriteMenu.path, + aliasName: favoriteMenu.aliasName, + //icon: favoriteMenu.icon, + // TODO: 需要各个模块自行提供本地图标 + icon: 'res/images/setting.png', + color: favoriteMenu.color, + displayName: favoriteMenu.displayName, + ); + }, + )), ], ), drawer: SafeArea( - child: Container( - width: 260, - color: const Color.fromARGB(255, 44, 115, 141), - child: Column( - children: [ - Container( - height: 24, - margin: const EdgeInsets.all(10), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: Image.asset( - 'res/images/logo.png', - height: 20, - width: 20, - ), - ), - const Padding( - padding: EdgeInsets.only(left: 10), - child: Text( - 'abp flutter', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w400, - ), - ), - ), - ], - ), - ), - Expanded( - child: Obx(() => Navigation( - activedMenu: bloc.state.activedMenu, - menus: bloc.state.getMenus(), - onMenuExpanded: bloc.onMenuExpanded, - )), - ), - ], - ), - ) + child: Obx(() => MenuDrawer( + activedMenu: bloc.state.activedMenu, + menus: bloc.state.getMenus(), + onMenuExpanded: bloc.onMenuExpanded, + onMenuRefresh: bloc.refreshMenus, + )), ), ); } diff --git a/apps/flutter/dev_app/lib/pages/public/home/widget/index.dart b/apps/flutter/dev_app/lib/pages/public/home/widget/index.dart new file mode 100644 index 000000000..769be9109 --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/home/widget/index.dart @@ -0,0 +1,5 @@ +export 'search.dart'; +export 'menu_drawer.dart'; +export 'my_favorite.dart'; +export 'notification_bar.dart'; +export 'quick_navigation.dart'; \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/home/widget/menu_drawer.dart b/apps/flutter/dev_app/lib/pages/public/home/widget/menu_drawer.dart new file mode 100644 index 000000000..2fd2a49f4 --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/home/widget/menu_drawer.dart @@ -0,0 +1,76 @@ +import 'package:components/widgets/menu/index.dart'; +import 'package:core/models/common.dart'; +import 'package:flutter/material.dart'; + +class MenuDrawer extends StatelessWidget { + const MenuDrawer({ + super.key, + this.activedMenu, + this.menus = const [], + required this.onMenuRefresh, + this.onMenuExpanded, + }); + + final String? activedMenu; + final List menus; + final void Function(Menu menu)? onMenuExpanded; + final Future Function() onMenuRefresh; + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + onRefresh: onMenuRefresh, + child: Drawer( + width: 260, + child: Column( + children: [ + _buildLogo(), + Expanded( + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + children: [ + Navigation( + activedMenu: activedMenu, + menus: menus, + onMenuExpanded: onMenuExpanded, + ), + ], + ), + ) + ), + ], + ), + ), + ); + } + + Widget _buildLogo() { + return Container( + height: 24, + margin: const EdgeInsets.all(10), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Image.asset( + 'res/images/logo.png', + height: 20, + width: 20, + ), + ), + const Padding( + padding: EdgeInsets.only(left: 10), + child: Text( + 'abp flutter', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/home/widget/my_favorite.dart b/apps/flutter/dev_app/lib/pages/public/home/widget/my_favorite.dart new file mode 100644 index 000000000..ef2c0984d --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/home/widget/my_favorite.dart @@ -0,0 +1,42 @@ +import 'package:components/widgets/empty/index.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:platforms/modes/menu.dto.dart'; + +class MyFavorite extends StatelessWidget { + const MyFavorite({ + super.key, + required this.favoriteMenus, + required this.favoriteMenuBuilder, + }); + + final List favoriteMenus; + final Widget Function(UserFavoriteMenuDto favoriteMenu) favoriteMenuBuilder; + + @override + Widget build(BuildContext context) { + return ExpansionTile( + initiallyExpanded: true, + title: Text('Label:MyFavorite'.tr, + style: Theme.of(context).textTheme.titleMedium, + ), + children: [ + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: favoriteMenus.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + crossAxisSpacing: 5, + ), + itemBuilder: (BuildContext context, int index) { + if (index >= favoriteMenus.length) { + return Empty.none; + } + return favoriteMenuBuilder(favoriteMenus[index]); + }, + ), + ], + ); + } +} \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/home/widget/notification_bar.dart b/apps/flutter/dev_app/lib/pages/public/home/widget/notification_bar.dart new file mode 100644 index 000000000..b7272fc64 --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/home/widget/notification_bar.dart @@ -0,0 +1,55 @@ +import 'package:bruno/bruno.dart'; +import 'package:components/widgets/empty/index.dart'; +import 'package:flutter/material.dart'; +import 'package:notifications/models/common.dart'; +import 'package:notifications/models/notification.dart'; + +class NotificationBar extends StatelessWidget { + const NotificationBar({ + super.key, + required this.notifications + }); + + final List notifications; + + @override + Widget build(BuildContext context) { + if (notifications.isEmpty) { + return Empty.none; + } + return SizedBox( + height: 40, + child: SingleChildScrollView( + child: Column( + children: notifications.map((payload) { + return BrnNoticeBar( + padding: const EdgeInsets.only(left: 5, right: 5, top: 3), + leftWidget: Image.asset( + 'res/images/notification.png', + height: 30, + width: 30, + ), + content: payload.title, + marquee: true, + noticeStyle: _mapNoticeStyles(payload.severity), + ); + }).toList(), + ), + ), + ); + } + + NoticeStyle _mapNoticeStyles(NotificationSeverity? severity) { + if (severity == null) return NoticeStyles.normalNoticeWithArrow; + switch (severity) { + case NotificationSeverity.info: + case NotificationSeverity.success: + return NoticeStyles.succeedWithArrow; + case NotificationSeverity.fatal: + case NotificationSeverity.error: + return NoticeStyles.failWithArrow; + case NotificationSeverity.warn: + return NoticeStyles.warningWithArrow; + } + } +} \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/home/widget/quick_navigation.dart b/apps/flutter/dev_app/lib/pages/public/home/widget/quick_navigation.dart new file mode 100644 index 000000000..929bb3c10 --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/home/widget/quick_navigation.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class QuickNavigation extends StatelessWidget { + const QuickNavigation({ + super.key, + this.menus = const [], + }); + + final List menus; + + @override + Widget build(BuildContext context) { + return ExpansionTile( + initiallyExpanded: true, + title: Text('Label:QuickNavigation'.tr, + style: Theme.of(context).textTheme.titleMedium, + ), + children: [ + SizedBox( + height: 120, + child: GridView.count( + shrinkWrap: true, + crossAxisCount: 4, + crossAxisSpacing: 5, + physics: const NeverScrollableScrollPhysics(), + children: menus, + ), + ), + ], + ); + } +} \ No newline at end of file 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 beddf7184..de9191cc8 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,6 +1,7 @@ import 'dart:async'; import 'package:core/index.dart'; import 'package:get/get.dart'; +import 'package:notifications/services/notification.state.service.dart'; import 'package:rxdart/rxdart.dart' hide Notification; import 'package:core/models/notifications.dart'; @@ -14,6 +15,22 @@ class FlutterLocalNotificationsSendService extends NotificationSendService { final Subject _selectedNotifications$ = BehaviorSubject(); EnvironmentService get _environmentService => resolve(); + NotificationStateService get _notificationStateService => resolve(); + + @override + void onInit() { + super.onInit(); + _notificationStateService + .getNotifications$() + .listen((payload) async { + // 发布本地通知 + await send( + payload.title, + payload.body, + payload.payload, + ); + }); + } Future initAsync() async { var environment = _environmentService.getEnvironment(); diff --git a/apps/flutter/dev_app/lib/services/translation.service.res.service.dart b/apps/flutter/dev_app/lib/services/translation.service.res.service.dart index 82a9dfe84..28ef8e23f 100644 --- a/apps/flutter/dev_app/lib/services/translation.service.res.service.dart +++ b/apps/flutter/dev_app/lib/services/translation.service.res.service.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:core/services/environment.service.dart'; import 'package:core/services/localization.service.dart'; import 'package:core/services/service.base.dart'; import 'package:core/services/session.service.dart'; @@ -17,6 +18,7 @@ class TranslationResService extends ServiceBase implements TranslationService { final InternalStore _store = InternalStore(state: TranslationState()); SessionService get _sessionService => resolve(); + EnvironmentService get _environmentService => resolve(); LocalizationService get _localizationService => resolve(); @override @@ -44,13 +46,21 @@ class TranslationResService extends ServiceBase implements TranslationService { Future _mapTranslationsMap(String language) async { Map> translationsMap = {}; - var filePath = 'res/translations/$language.json'; - var content = await rootBundle.loadString(filePath); - var translationsObject = jsonDecode(content) as Map; - translationsMap.putIfAbsent( - language, - () => translationsObject.map((key, value) => MapEntry(key, value)) - ); + var environment = _environmentService.getEnvironment(); + var translationFiles = environment.localization.translationFiles?[language] ?? ['$language.json']; + + for (var translationFile in translationFiles) { + try { + var filePath = 'res/translations/$translationFile'; + var content = await rootBundle.loadString(filePath); + var translationsObject = jsonDecode(content) as Map; + var translations = translationsMap[language] ?? {}; + translations.addAll(translationsObject.map((key, value) => MapEntry(key, value))); + translationsMap.putIfAbsent(language, () => translations); + } catch (e) { + logger.error(e); + } + } return TranslationState( language: language, translations: translationsMap, diff --git a/apps/flutter/dev_app/res/config/demo.json b/apps/flutter/dev_app/res/config/demo.json index 49b54960f..840abdf4e 100644 --- a/apps/flutter/dev_app/res/config/demo.json +++ b/apps/flutter/dev_app/res/config/demo.json @@ -13,6 +13,20 @@ "localization": { "useLocalResources": true, "defaultLanguage": "zh-Hans", + "translationFiles": { + "zh-Hans": [ + "zh-Hans.json" + ], + "zh_CN": [ + "zh-Hans.json" + ], + "en": [ + "en.json" + ], + "en_US": [ + "en.json" + ] + }, "supportedLocales": [ { "cultureName": "en", diff --git a/apps/flutter/dev_app/res/images/notification.png b/apps/flutter/dev_app/res/images/notification.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3be84b1e46768bd11ee23b995047322ae9b5ac GIT binary patch literal 6508 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 literal 0 HcmV?d00001 diff --git a/apps/flutter/notifications/lib/models/notification.state.dart b/apps/flutter/notifications/lib/models/notification.state.dart index 2b7882188..a45bfc9ac 100644 --- a/apps/flutter/notifications/lib/models/notification.state.dart +++ b/apps/flutter/notifications/lib/models/notification.state.dart @@ -14,6 +14,7 @@ class NotificationState { }); bool isEnabled; List groups; + NotificationGroup? findGroup(String name) { return groups.firstWhereOrNull((item) => item.name == name); diff --git a/apps/flutter/notifications/lib/services/notification.state.service.dart b/apps/flutter/notifications/lib/services/notification.state.service.dart index 037a588d5..3cfa8eb73 100644 --- a/apps/flutter/notifications/lib/services/notification.state.service.dart +++ b/apps/flutter/notifications/lib/services/notification.state.service.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:notifications/models/common.dart'; import 'package:rxdart/rxdart.dart' hide Notification; import 'package:notifications/models/notification.dart'; import 'package:core/services/session.service.dart'; @@ -20,6 +21,8 @@ class NotificationStateService extends ServiceBase { NotificationService get _notificationService => resolve(); SignalrService get _signalrService => resolve(tag: NotificationTokens.producer); + final BehaviorSubject _notifications = BehaviorSubject(); + final InternalStore _store = InternalStore( state: _initState() ); @@ -41,6 +44,10 @@ class NotificationStateService extends ServiceBase { return _store.sliceUpdate((state) => state); } + Stream getNotifications$() { + return _notifications; + } + NotificationGroup? findGroup(String name) { return _store.state.findGroup(name); } @@ -75,6 +82,10 @@ class NotificationStateService extends ServiceBase { return configState ?? NotificationState(isEnabled: true, groups: []); } + void addNotification(NotificationInfo notification) { + _notifications.add(NotificationPaylod.fromNotification(notification)); + } + Future> getGroupAndCombineWithNotification(List groupItems) { return _notificationService.getMySubscribedListAsync() .then((subscres) { diff --git a/apps/flutter/platform/lib/services/favorite.menu.state.service.dart b/apps/flutter/platform/lib/services/favorite.menu.state.service.dart index eb9e6b272..c29255709 100644 --- a/apps/flutter/platform/lib/services/favorite.menu.state.service.dart +++ b/apps/flutter/platform/lib/services/favorite.menu.state.service.dart @@ -33,7 +33,7 @@ class FavoriteMenuStateService extends ServiceBase { Future refreshState() async { var environment = _environmentService.getEnvironment(); - var framework = environment.application.framework ?? 'flutter'; + var framework = environment.application.framework ?? 'abp-flutter'; var result = await _favoriteMenuService.getMyFavoriteMenuList(framework); _state.patch((state) => state.menus = result.items); } diff --git a/apps/flutter/platform/lib/services/menu.state.service.dart b/apps/flutter/platform/lib/services/menu.state.service.dart index ac302d6d8..8729a72e8 100644 --- a/apps/flutter/platform/lib/services/menu.state.service.dart +++ b/apps/flutter/platform/lib/services/menu.state.service.dart @@ -33,7 +33,7 @@ class MenuStateService extends ServiceBase { Future refreshState() async { var environment = _environmentService.getEnvironment(); - var framework = environment.application.framework ?? 'flutter'; + var framework = environment.application.framework ?? 'abp-flutter'; var result = await _menuService.getCurrentUserMenuList(framework); _state.patch((state) => state.menus = result.items); }