From e488ddec4898e5c6ffbb9166393808737c88d092 Mon Sep 17 00:00:00 2001 From: colin Date: Tue, 8 Aug 2023 18:52:29 +0800 Subject: [PATCH 1/2] feat(flutter): support navigations --- .../flutter/components/lib/widgets/index.dart | 3 +- .../components/lib/widgets/menu/index.dart | 90 ++++++++++ .../components/lib/widgets/menu/state.dart | 3 + apps/flutter/core/lib/models/common.dart | 23 +++ apps/flutter/core/lib/utils/color.utils.dart | 16 ++ apps/flutter/core/lib/utils/index.dart | 1 + apps/flutter/dev_app/lib/main.dart | 7 +- apps/flutter/dev_app/lib/main.module.dart | 3 +- .../lib/pages/public/center/controller.dart | 6 +- .../dev_app/lib/pages/public/error/index.dart | 1 + .../pages/public/error/not_found/index.dart | 1 + .../pages/public/error/not_found/view.dart | 23 +++ .../lib/pages/public/home/controller.dart | 35 ++-- .../dev_app/lib/pages/public/home/state.dart | 25 +++ .../dev_app/lib/pages/public/home/view.dart | 168 ++++++++++++++++-- .../lib/pages/public/home/widget/search.dart | 23 +-- .../dev_app/lib/pages/public/index.dart | 3 +- .../dev_app/lib/pages/public/route.name.dart | 2 + .../lib/pages/public/route.public.dart | 6 + apps/flutter/dev_app/pubspec.lock | 56 ++++++ apps/flutter/dev_app/pubspec.yaml | 1 + apps/flutter/dev_app/res/images/no_data.png | Bin 0 -> 24565 bytes apps/flutter/dev_app/res/images/profile.png | Bin 0 -> 3119 bytes apps/flutter/dev_app/res/images/setting.png | Bin 0 -> 5752 bytes apps/flutter/dev_app/res/translations/en.json | 7 + .../dev_app/res/translations/zh-Hans.json | 9 +- .../services/favorite.menu.state.service.dart | 14 +- .../lib/services/menu.state.service.dart | 14 +- 28 files changed, 494 insertions(+), 46 deletions(-) create mode 100644 apps/flutter/components/lib/widgets/menu/index.dart create mode 100644 apps/flutter/components/lib/widgets/menu/state.dart create mode 100644 apps/flutter/core/lib/utils/color.utils.dart create mode 100644 apps/flutter/dev_app/lib/pages/public/error/index.dart create mode 100644 apps/flutter/dev_app/lib/pages/public/error/not_found/index.dart create mode 100644 apps/flutter/dev_app/lib/pages/public/error/not_found/view.dart create mode 100644 apps/flutter/dev_app/res/images/no_data.png create mode 100644 apps/flutter/dev_app/res/images/profile.png create mode 100644 apps/flutter/dev_app/res/images/setting.png diff --git a/apps/flutter/components/lib/widgets/index.dart b/apps/flutter/components/lib/widgets/index.dart index a3d62f746..98afe17da 100644 --- a/apps/flutter/components/lib/widgets/index.dart +++ b/apps/flutter/components/lib/widgets/index.dart @@ -2,4 +2,5 @@ export 'avatar/index.dart'; export 'action-button/index.dart'; export 'back-to-top/index.dart'; export 'bottom-button/index.dart'; -export 'empty/index.dart'; \ No newline at end of file +export 'empty/index.dart'; +export 'menu/index.dart'; \ No newline at end of file diff --git a/apps/flutter/components/lib/widgets/menu/index.dart b/apps/flutter/components/lib/widgets/menu/index.dart new file mode 100644 index 000000000..b060f7e82 --- /dev/null +++ b/apps/flutter/components/lib/widgets/menu/index.dart @@ -0,0 +1,90 @@ +import 'package:core/models/common.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class Navigation extends StatefulWidget { + const Navigation({ + super.key, + this.activedMenu, + this.menus = const [], + this.onMenuExpanded, + }); + + final List menus; + final String? activedMenu; + final void Function(Menu menu)? onMenuExpanded; + + @override + State createState() => _NavigationState(); +} + +class _NavigationState extends State { + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Container( + child: _renderNavigations(widget.menus), + ), + ); + } + + Widget _renderNavigations(List menus) { + var mainMenus = menus.where((menu) => menu.children?.isNotEmpty == false); + var subMenus = menus.where((menu) => menu.children?.isNotEmpty == true); + + return Column( + children: [ + _renderMenus(subMenus.toList()), + ...mainMenus.map((menu) => _buildMenuItem(menu)), + ], + ); + } + + Widget _renderMenus(List menus) { + return ExpansionPanelList.radio( + initialOpenPanelValue: widget.activedMenu, + expandedHeaderPadding: const EdgeInsets.all(0), + expansionCallback: (panelIndex, isExpanded) { + if (widget.onMenuExpanded != null) { + widget.onMenuExpanded!(menus[panelIndex]); + } + }, + children: menus.map((Menu menu) { + var body = menu.children?.isNotEmpty == true + ? _renderNavigations(menu.children!) + : _buildMenuItem(menu); + return ExpansionPanelRadio( + canTapOnHeader: true, + headerBuilder: (BuildContext context, bool isExpanded) { + return SizedBox( + height: 30, + child: ListTile( + title: Text(menu.displayName.padLeft(menu.level * 4)), + ), + ); + }, + body: body, + value: menu.name, + ); + }).toList(), + ); + } + + Widget _buildMenuItem(Menu menu) { + return InkWell( + onTap: () { + Get.toNamed(menu.path); + }, + child: FractionallySizedBox( + widthFactor: 1, + child: Container( + height: 30, + margin: const EdgeInsets.only(top: 10), + child: Text( + menu.displayName.padLeft(menu.level * 8), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/apps/flutter/components/lib/widgets/menu/state.dart b/apps/flutter/components/lib/widgets/menu/state.dart new file mode 100644 index 000000000..2d3ff8350 --- /dev/null +++ b/apps/flutter/components/lib/widgets/menu/state.dart @@ -0,0 +1,3 @@ +class MenuState { + +} \ No newline at end of file diff --git a/apps/flutter/core/lib/models/common.dart b/apps/flutter/core/lib/models/common.dart index 2573eacc6..29cb93da9 100644 --- a/apps/flutter/core/lib/models/common.dart +++ b/apps/flutter/core/lib/models/common.dart @@ -42,3 +42,26 @@ class SignalrMessage { String method; List data; } + +class Menu { + Menu({ + required this.path, + required this.name, + required this.displayName, + this.id = 0, + this.level = 0, + this.description, + this.redirect, + this.meta, + this.children, + }); + String path; + String name; + String displayName; + String? description; + String? redirect; + int level; + int id; + Map? meta; + List? children; +} diff --git a/apps/flutter/core/lib/utils/color.utils.dart b/apps/flutter/core/lib/utils/color.utils.dart new file mode 100644 index 000000000..65eb7943f --- /dev/null +++ b/apps/flutter/core/lib/utils/color.utils.dart @@ -0,0 +1,16 @@ +import 'dart:ui'; + +class ColorUtils { + static Color fromHex(String hexString) { + final buffer = StringBuffer(); + if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); + buffer.write(hexString.replaceFirst('#', '')); + return Color(int.parse(buffer.toString(), radix: 16)); + } +} + +extension HexStringToColor on String { + Color toColor() { + return ColorUtils.fromHex(this); + } +} \ No newline at end of file diff --git a/apps/flutter/core/lib/utils/index.dart b/apps/flutter/core/lib/utils/index.dart index fe2eb5beb..602f60d90 100644 --- a/apps/flutter/core/lib/utils/index.dart +++ b/apps/flutter/core/lib/utils/index.dart @@ -1,3 +1,4 @@ +export 'color.utils.dart'; export 'environment.utils.dart'; export 'internal.store.dart'; export 'localization.utils.dart'; diff --git a/apps/flutter/dev_app/lib/main.dart b/apps/flutter/dev_app/lib/main.dart index f99928b23..1ee8c93b6 100644 --- a/apps/flutter/dev_app/lib/main.dart +++ b/apps/flutter/dev_app/lib/main.dart @@ -2,10 +2,12 @@ import 'package:core/dependency/index.dart'; import 'package:core/utils/theme.utils.dart'; import 'package:core/utils/logging.dart'; import 'package:dev_app/main.module.dart'; +import 'package:dev_app/pages/index.dart'; +import 'package:dev_app/pages/public/route.name.dart'; import 'package:dev_app/utils/localization.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; Future main() async { @@ -25,8 +27,9 @@ class MyApp extends StatelessWidget { theme: ThemeUtils.lightTheme, darkTheme: ThemeUtils.darkTheme, themeMode: ThemeMode.system, - initialRoute: '/', + initialRoute: PublicRoutes.main, getPages: module.getRoutes(), + unknownRoute: PublicRoute.notFound, debugShowMaterialGrid: false, enableLog: true, builder: EasyLoading.init(), diff --git a/apps/flutter/dev_app/lib/main.module.dart b/apps/flutter/dev_app/lib/main.module.dart index a0dc8c9b3..ed7fca5a9 100644 --- a/apps/flutter/dev_app/lib/main.module.dart +++ b/apps/flutter/dev_app/lib/main.module.dart @@ -1,5 +1,6 @@ import 'package:components/index.dart'; import 'package:dev_app/pages/index.dart'; +import 'package:dev_app/pages/public/route.name.dart'; import 'package:dev_app/services/index.dart'; import 'package:dev_app/utils/initial.utils.dart'; import 'package:dev_app/utils/loading.dart'; @@ -30,7 +31,7 @@ class MainModule extends Module { @override List get routes => [ GetPage( - name: '/', + name: PublicRoutes.main, page: () => const MainPage(), bindings: [ MainBinding(), diff --git a/apps/flutter/dev_app/lib/pages/public/center/controller.dart b/apps/flutter/dev_app/lib/pages/public/center/controller.dart index 653c1a1f7..c40367fd5 100644 --- a/apps/flutter/dev_app/lib/pages/public/center/controller.dart +++ b/apps/flutter/dev_app/lib/pages/public/center/controller.dart @@ -44,15 +44,15 @@ class CenterController extends GetxController { } void onClickFeedback() { - + redirectToRoute('/feedback'); } void onClickHelp() { - + redirectToRoute('/help'); } void onClickInfo() { - + redirectToRoute('/info'); } void onClickMessage() { diff --git a/apps/flutter/dev_app/lib/pages/public/error/index.dart b/apps/flutter/dev_app/lib/pages/public/error/index.dart new file mode 100644 index 000000000..aa4da5d13 --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/error/index.dart @@ -0,0 +1 @@ +export './not_found/index.dart'; \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/error/not_found/index.dart b/apps/flutter/dev_app/lib/pages/public/error/not_found/index.dart new file mode 100644 index 000000000..3205d6a40 --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/error/not_found/index.dart @@ -0,0 +1 @@ +export 'view.dart'; \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/error/not_found/view.dart b/apps/flutter/dev_app/lib/pages/public/error/not_found/view.dart new file mode 100644 index 000000000..e25c60e1a --- /dev/null +++ b/apps/flutter/dev_app/lib/pages/public/error/not_found/view.dart @@ -0,0 +1,23 @@ +import 'package:bruno/bruno.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class PageNotFound extends StatelessWidget { + const PageNotFound({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("404Message".tr) + ), + body: BrnAbnormalStateWidget( + img: Image.asset( + 'res/images/no_data.png', + scale: 3.0, + ), + content: "404MessageDetail".tr, + ), + ); + } +} \ No newline at end of file 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 0e8bd7cdd..b6884ba1c 100644 --- a/apps/flutter/dev_app/lib/pages/public/home/controller.dart +++ b/apps/flutter/dev_app/lib/pages/public/home/controller.dart @@ -1,3 +1,4 @@ +import 'package:core/models/common.dart'; import 'package:get/get.dart'; import 'package:core/dependency/index.dart'; import 'package:platforms/services/index.dart'; @@ -14,17 +15,27 @@ class HomeController extends GetxController { @override void onInit() { super.onInit(); - // _menuStateService.getMyMenus$() - // .listen((menus) { - // _state.update((val) { - // val?.menus = menus; - // }); - // }); - // _favoriteMenuStateService.getFavoriteMenus$() - // .listen((menus) { - // _state.update((val) { - // val?.favoriteMenus = menus; - // }); - // }); + _menuStateService.getMyMenus$() + .listen((menus) { + _state.update((val) { + val?.menus = menus; + }); + }); + _favoriteMenuStateService.getFavoriteMenus$() + .listen((menus) { + _state.update((val) { + val?.favoriteMenus = menus; + }); + }); + } + + void redirectToRoute(String route) { + Get.toNamed(route); + } + + void onMenuExpanded(Menu menu) { + _state.update((val) { + val?.activedMenu = menu.name; + }); } } \ No newline at end of file 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 a810abe02..cafd45fd6 100644 --- a/apps/flutter/dev_app/lib/pages/public/home/state.dart +++ b/apps/flutter/dev_app/lib/pages/public/home/state.dart @@ -1,10 +1,35 @@ +import 'package:core/models/common.dart'; import 'package:platforms/modes/menu.dto.dart'; class HomeState { HomeState({ + this.activedMenu, this.menus = const [], this.favoriteMenus = const [], }); + String? activedMenu; List menus; List favoriteMenus; + + List getMenus() => _buildTreeRecursive(menus, null, 0); + + List _buildTreeRecursive(List treeMenus, String? parentId, int level) { + List results = []; + var tempList = treeMenus.where((menu) => menu.parentId == parentId).toList(); + for (int i = 0; i < tempList.length; i++) { + var menu = Menu( + id: tempList[i].id.hashCode, + path: tempList[i].path, + name: tempList[i].name, + displayName: tempList[i].displayName, + description: tempList[i].description, + redirect: tempList[i].redirect, + meta: tempList[i].meta, + level: level + 1 + ); + menu.children = _buildTreeRecursive(treeMenus, tempList[i].id, menu.level); + results.add(menu); + } + return results; + } } \ No newline at end of file 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 5dcc92008..b6a77950c 100644 --- a/apps/flutter/dev_app/lib/pages/public/home/view.dart +++ b/apps/flutter/dev_app/lib/pages/public/home/view.dart @@ -1,6 +1,11 @@ +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'; @@ -20,26 +25,165 @@ class HomePage extends BasePage { onPressed: () { showSearch(context: context, delegate: SearchBarDelegate(menus: bloc.state.menus)); }, - child: const Row( + child: Row( children: [ - Icon(Icons.search), - Expanded(child: Text('搜索功能')) + const Icon(Icons.search), + Expanded(child: Text('Label:SearchFeatures'.tr)) ], ), ), ), - body: Column( + body: ListView( children: [ - Expanded( - child: ListView.builder( - itemCount: bloc.state.favoriteMenus.length, - itemBuilder: (context, index) { - var favoriteMenu = bloc.state.favoriteMenus[index]; - return Text(favoriteMenu.displayName ?? favoriteMenu.name); - }, + 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, + ); + }, + ), + ], + ), + ], + ), + 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, + )), + ), + ], ), - ], + ) + ), + ); + } + + Widget _buildMenu( + String name, + String path, + { + String? aliasName, + String? icon, + String? color, + String? displayName, + } + ) { + return InkWell( + onTap: () { + bloc.redirectToRoute(path); + }, + child: SizedBox( + height: 20, + width: 30, + child: Column( + children: [ + const SizedBox(height: 10), + icon != null + ? Image.asset( + icon, + height: 40, + width: 40, + color: color.isNullOrWhiteSpace() ? null : ColorUtils.fromHex(color!), + ) + : Empty.none, + Text( + displayName ?? aliasName ?? name, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 14 + ), + ) + ], + ), ), ); } diff --git a/apps/flutter/dev_app/lib/pages/public/home/widget/search.dart b/apps/flutter/dev_app/lib/pages/public/home/widget/search.dart index 99c8e4998..46baf8ae6 100644 --- a/apps/flutter/dev_app/lib/pages/public/home/widget/search.dart +++ b/apps/flutter/dev_app/lib/pages/public/home/widget/search.dart @@ -13,8 +13,7 @@ class SearchBarDelegate extends SearchDelegate { List? buildActions(BuildContext context) { Widget button = IconButton( onPressed: () { - query = ""; - showSuggestions(context); + close(context, "error"); }, icon: const Icon(Icons.clear), ); @@ -24,15 +23,17 @@ class SearchBarDelegate extends SearchDelegate { @override Widget? buildLeading(BuildContext context) { - return IconButton( - onPressed: () { - close(context, "error"); - }, - icon: AnimatedIcon( - icon: AnimatedIcons.menu_arrow, - progress: transitionAnimation, - ), - ); + // return IconButton( + // onPressed: () { + // query = ""; + // showSuggestions(context); + // }, + // icon: AnimatedIcon( + // icon: AnimatedIcons.menu_arrow, + // progress: transitionAnimation, + // ), + // ); + return null; } @override diff --git a/apps/flutter/dev_app/lib/pages/public/index.dart b/apps/flutter/dev_app/lib/pages/public/index.dart index aee3141ae..7d24fe4b5 100644 --- a/apps/flutter/dev_app/lib/pages/public/index.dart +++ b/apps/flutter/dev_app/lib/pages/public/index.dart @@ -1,3 +1,4 @@ export './center/index.dart'; export './home/index.dart'; -export './work/index.dart'; \ No newline at end of file +export './work/index.dart'; +export './error/index.dart'; \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/route.name.dart b/apps/flutter/dev_app/lib/pages/public/route.name.dart index 4361f2488..2f5830c89 100644 --- a/apps/flutter/dev_app/lib/pages/public/route.name.dart +++ b/apps/flutter/dev_app/lib/pages/public/route.name.dart @@ -1,5 +1,7 @@ class PublicRoutes { + static String main = '/main'; static String home = '/home'; static String work = '/work'; static String center = '/center'; + static String notFound = '/error/not_found'; } \ No newline at end of file diff --git a/apps/flutter/dev_app/lib/pages/public/route.public.dart b/apps/flutter/dev_app/lib/pages/public/route.public.dart index b4d6a39f5..9ade2f155 100644 --- a/apps/flutter/dev_app/lib/pages/public/route.public.dart +++ b/apps/flutter/dev_app/lib/pages/public/route.public.dart @@ -4,7 +4,13 @@ import 'index.dart'; import 'route.name.dart'; class PublicRoute { + static GetPage notFound = GetPage( + name: PublicRoutes.notFound, + page: () => const PageNotFound(), + ); + static List routes = [ + notFound, GetPage( name: PublicRoutes.home, page: () => const HomePage(), diff --git a/apps/flutter/dev_app/pubspec.lock b/apps/flutter/dev_app/pubspec.lock index bb2298370..bd7d65046 100644 --- a/apps/flutter/dev_app/pubspec.lock +++ b/apps/flutter/dev_app/pubspec.lock @@ -40,6 +40,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.11.0" + bindings_compatible: + dependency: transitive + description: + name: bindings_compatible + sha256: "5dd5189f7512aff8ec180a8a11bd59230aa34a2d743e65e427192b7292a78d87" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" boolean_selector: dependency: transitive description: @@ -48,6 +56,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" + bruno: + dependency: "direct main" + description: + name: bruno + sha256: "8bd461a658996000eab1111a93fb4826ade878103f5a9afa29a414046805448b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.4.1" build: dependency: transitive description: @@ -307,6 +323,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.5" + flutter_easyrefresh: + dependency: transitive + description: + name: flutter_easyrefresh + sha256: "5d161ee5dcac34da9065116568147d742dd25fb9bff3b10024d9054b195087ad" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.2" flutter_highlight: dependency: transitive description: @@ -514,6 +538,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" + lpinyin: + dependency: transitive + description: + name: lpinyin + sha256: "0bb843363f1f65170efd09fbdfc760c7ec34fc6354f9fcb2f89e74866a0d814a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" markdown: dependency: transitive description: @@ -608,6 +640,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.8.3" + path_drawing: + dependency: transitive + description: + name: path_drawing + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" path_provider: dependency: transitive description: @@ -664,6 +712,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.4.0" + photo_view: + dependency: transitive + description: + name: photo_view + sha256: "8036802a00bae2a78fc197af8a158e3e2f7b500561ed23b4c458107685e645bb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.14.0" platform: dependency: transitive description: diff --git a/apps/flutter/dev_app/pubspec.yaml b/apps/flutter/dev_app/pubspec.yaml index 7336089e5..db627ecaf 100644 --- a/apps/flutter/dev_app/pubspec.yaml +++ b/apps/flutter/dev_app/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: platforms: path: '../platform' + bruno: ^3.3.0 dio: ^5.2.0+1 flutter_easyloading: ^3.0.5 flutter_picker: ^2.1.0 diff --git a/apps/flutter/dev_app/res/images/no_data.png b/apps/flutter/dev_app/res/images/no_data.png new file mode 100644 index 0000000000000000000000000000000000000000..10d07379dd9a9acbc240e91b4eafc3c5c3d66750 GIT binary patch literal 24565 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 literal 0 HcmV?d00001 diff --git a/apps/flutter/dev_app/res/images/profile.png b/apps/flutter/dev_app/res/images/profile.png new file mode 100644 index 0000000000000000000000000000000000000000..0d8bf21d4c6cf6b743204c034c7a60d0dcc2504c GIT binary patch literal 3119 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 literal 0 HcmV?d00001 diff --git a/apps/flutter/dev_app/res/images/setting.png b/apps/flutter/dev_app/res/images/setting.png new file mode 100644 index 0000000000000000000000000000000000000000..46c201588562aedacea49b24649dc0b7b92ca482 GIT binary patch literal 5752 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 _state = InternalStore(state: FavoriteMenuState()); + SessionService get _sessionService => resolve(); EnvironmentService get _environmentService => resolve(); FavoriteMenuService get _favoriteMenuService => resolve(); @override void onInit() { super.onInit(); - refreshState(); + _initState(); + } + + void _initState() { + _sessionService.getToken$() + .listen((token) { + _state.patch((state) => state.menus = []); + if (token != null) { + refreshState(); + } + }); } Future refreshState() async { diff --git a/apps/flutter/platform/lib/services/menu.state.service.dart b/apps/flutter/platform/lib/services/menu.state.service.dart index d6575bb67..ac302d6d8 100644 --- a/apps/flutter/platform/lib/services/menu.state.service.dart +++ b/apps/flutter/platform/lib/services/menu.state.service.dart @@ -1,5 +1,6 @@ import 'package:core/services/environment.service.dart'; import 'package:core/services/service.base.dart'; +import 'package:core/services/session.service.dart'; import 'package:core/utils/index.dart'; import 'package:platforms/modes/state.dart'; import 'package:platforms/modes/menu.dto.dart'; @@ -11,12 +12,23 @@ class MenuStateService extends ServiceBase { final InternalStore _state = InternalStore(state: MenuState()); EnvironmentService get _environmentService => resolve(); + SessionService get _sessionService => resolve(); MenuService get _menuService => resolve(); @override void onInit() { super.onInit(); - refreshState(); + _initState(); + } + + void _initState() { + _sessionService.getToken$() + .listen((token) { + _state.patch((state) => state.menus = []); + if (token != null) { + refreshState(); + } + }); } Future refreshState() async { From 5fcd5b4b368fff217d45f324f8bd5db2a84fed6f Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 9 Aug 2023 16:04:43 +0800 Subject: [PATCH 2/2] 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); }