From a0c54ced966af449797db25b3bd58f84b1a4aafe Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Mon, 18 Nov 2024 20:52:40 +0800 Subject: [PATCH] feat: custom check unReadDynamic Signed-off-by: bggRGjQaUbCoE --- lib/main.dart | 1 + lib/pages/dynamics/tab/controller.dart | 8 + lib/pages/main/controller.dart | 41 +++- lib/pages/main/view.dart | 280 +++++++++++++------------ lib/pages/search/view.dart | 1 + lib/pages/setting/extra_setting.dart | 53 +++++ lib/utils/storage.dart | 8 + 7 files changed, 250 insertions(+), 142 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ea4ee983..0bde77d8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -251,6 +251,7 @@ class MyApp extends StatelessWidget { navigatorObservers: [ VideoDetailPage.routeObserver, SearchPage.routeObserver, + MainApp.routeObserver, ], ); }), diff --git a/lib/pages/dynamics/tab/controller.dart b/lib/pages/dynamics/tab/controller.dart index 73bf6d25..6f173325 100644 --- a/lib/pages/dynamics/tab/controller.dart +++ b/lib/pages/dynamics/tab/controller.dart @@ -1,7 +1,9 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/msg.dart'; import 'package:PiliPalaX/pages/common/common_controller.dart'; +import 'package:PiliPalaX/pages/main/controller.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; import '../../../http/dynamics.dart'; @@ -10,6 +12,7 @@ class DynamicsTabController extends CommonController { final String dynamicsType; String offset = ''; int mid = -1; + late final MainController mainController = Get.find(); @override void onInit() { @@ -19,6 +22,11 @@ class DynamicsTabController extends CommonController { @override Future onRefresh() async { + if (dynamicsType == 'all') { + if (mainController.navigationBars[1]['count'] != 0) { + mainController.clearUnread(); + } + } offset = ''; await queryData(); } diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index b98a65d0..a28f05f8 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -31,10 +31,15 @@ class MainController extends GetxController { Box userInfoCache = GStorage.userInfo; RxBool userLogin = false.obs; late DynamicBadgeMode dynamicBadgeType; + late bool checkDynamic; + late int dynamicPeriod; + int? _lastCheckAt; @override void onInit() { super.onInit(); + checkDynamic = GStorage.checkDynamic; + dynamicPeriod = GStorage.dynamicPeriod; if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) { Utils.checkUpdate(); } @@ -49,6 +54,9 @@ class MainController extends GetxController { SettingBoxKey.dynamicBadgeMode, defaultValue: DynamicBadgeMode.number.code)]; if (dynamicBadgeType != DynamicBadgeMode.hidden) { + if (checkDynamic) { + _lastCheckAt = DateTime.now().millisecondsSinceEpoch; + } getUnreadDynamic(); } } @@ -78,23 +86,34 @@ class MainController extends GetxController { if (!userLogin.value) { return; } - int dynamicItemIndex = - navigationBars.indexWhere((item) => item['label'] == "动态"); + // not needed yet + // int dynamicItemIndex = + // navigationBars.indexWhere((item) => item['label'] == "动态"); + // if (dynamicItemIndex == -1) return; var res = await CommonHttp.unReadDynamic(); var data = res['data']; - if (dynamicItemIndex != -1) { - navigationBars[dynamicItemIndex]['count'] = - data == null ? 0 : data.length; // 修改 count 属性为新的值 - } + navigationBars[1]['count'] = + data == null ? 0 : data.length; // 修改 count 属性为新的值 navigationBars.refresh(); } void clearUnread() async { - int dynamicItemIndex = - navigationBars.indexWhere((item) => item['label'] == "动态"); - if (dynamicItemIndex != -1) { - navigationBars[dynamicItemIndex]['count'] = 0; // 修改 count 属性为新的值 - } + // not needed yet + // int dynamicItemIndex = + // navigationBars.indexWhere((item) => item['label'] == "动态"); + // if (dynamicItemIndex == -1) return; + navigationBars[1]['count'] = 0; // 修改 count 属性为新的值 navigationBars.refresh(); } + + void checkUnreadDynamic() { + if (!userLogin.value || + dynamicBadgeType == DynamicBadgeMode.hidden || + !checkDynamic) return; + int now = DateTime.now().millisecondsSinceEpoch; + if (now - (_lastCheckAt ?? 0) >= dynamicPeriod * 60 * 1000) { + _lastCheckAt = now; + getUnreadDynamic(); + } + } } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 69e8600e..04617525 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -17,9 +17,13 @@ class MainApp extends StatefulWidget { @override State createState() => _MainAppState(); + + static final RouteObserver routeObserver = + RouteObserver(); } -class _MainAppState extends State with SingleTickerProviderStateMixin { +class _MainAppState extends State + with SingleTickerProviderStateMixin, RouteAware, WidgetsBindingObserver { final MainController _mainController = Get.put(MainController()); final HomeController _homeController = Get.put(HomeController()); final DynamicsController _dynamicController = Get.put(DynamicsController()); @@ -37,6 +41,26 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { PageController(initialPage: _mainController.selectedIndex); enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); useSideBar = setting.get(SettingBoxKey.useSideBar, defaultValue: false); + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + MainApp.routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); + } + + @override + void didPopNext() { + _mainController.checkUnreadDynamic(); + super.didPopNext(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _mainController.checkUnreadDynamic(); + } } void setIndex(int value) async { @@ -92,6 +116,8 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { @override void dispose() async { + MainApp.routeObserver.unsubscribe(this); + WidgetsBinding.instance.removeObserver(this); await GrpcClient.instance.shutdown(); await GStorage.close(); EventBus().off(EventName.loginEvent); @@ -125,72 +151,59 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { width: context.width * 0.0387 + 36.801 + MediaQuery.of(context).padding.left, - child: NavigationRail( - groupAlignment: 1, - minWidth: context.width * 0.0286 + 28.56, - backgroundColor: Colors.transparent, - selectedIndex: _mainController.selectedIndex, - onDestinationSelected: (value) => setIndex(value), - labelType: NavigationRailLabelType.none, - leading: UserAndSearchVertical(ctr: _homeController), - destinations: _mainController.navigationBars - .map((e) => NavigationRailDestination( - icon: Badge( - label: _mainController.dynamicBadgeType == - DynamicBadgeMode.number - ? Text(e['count'].toString()) - : null, - padding: - const EdgeInsets.symmetric(horizontal: 4), - isLabelVisible: - _mainController.dynamicBadgeType != - DynamicBadgeMode.hidden && - e['count'] > 0, - child: e['icon'], - backgroundColor: - Theme.of(context).colorScheme.primary, - textColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - selectedIcon: e['selectIcon'], - label: Text(e['label']), - padding: EdgeInsets.symmetric( - vertical: 0.01 * context.height), - )) - .toList(), - trailing: SizedBox(height: 0.1 * context.height), + child: Obx( + () => NavigationRail( + groupAlignment: 1, + minWidth: context.width * 0.0286 + 28.56, + backgroundColor: Colors.transparent, + selectedIndex: _mainController.selectedIndex, + onDestinationSelected: (value) => setIndex(value), + labelType: NavigationRailLabelType.none, + leading: UserAndSearchVertical(ctr: _homeController), + destinations: _mainController.navigationBars + .map((e) => NavigationRailDestination( + icon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['icon'], + ), + selectedIcon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['selectIcon'], + ), + label: Text(e['label']), + padding: EdgeInsets.symmetric( + vertical: 0.01 * context.height), + )) + .toList(), + trailing: SizedBox(height: 0.1 * context.height), + ), ), ), ] else if (!isPortait) - NavigationRail( - onDestinationSelected: (value) => setIndex(value), - selectedIndex: _mainController.selectedIndex, - destinations: _mainController.navigationBars - .map( - (e) => NavigationRailDestination( - icon: Badge( - label: _mainController.dynamicBadgeType == - DynamicBadgeMode.number - ? Text(e['count'].toString()) - : null, - padding: const EdgeInsets.fromLTRB(6, 0, 6, 0), - isLabelVisible: - _mainController.dynamicBadgeType != - DynamicBadgeMode.hidden && - e['count'] > 0, - child: e['icon'], - backgroundColor: - Theme.of(context).colorScheme.primary, - textColor: Theme.of(context) - .colorScheme - .onInverseSurface, + Obx( + () => NavigationRail( + onDestinationSelected: (value) => setIndex(value), + selectedIndex: _mainController.selectedIndex, + destinations: _mainController.navigationBars + .map( + (e) => NavigationRailDestination( + icon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['icon'], + ), + selectedIcon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['selectIcon'], + ), + label: Text(e['label']), ), - selectedIcon: e['selectIcon'], - label: Text(e['label']), - ), - ) - .toList(), + ) + .toList(), + ), ), VerticalDivider( width: 1, @@ -226,78 +239,61 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { duration: const Duration(milliseconds: 500), offset: Offset(0, snapshot.data ? 0 : 1), child: enableMYBar - ? NavigationBar( - onDestinationSelected: (value) => - setIndex(value), - selectedIndex: _mainController.selectedIndex, - destinations: _mainController.navigationBars - .map( - (e) => NavigationDestination( - icon: Badge( - label: _mainController - .dynamicBadgeType == - DynamicBadgeMode.number - ? Text(e['count'].toString()) - : null, - padding: const EdgeInsets.fromLTRB( - 6, 0, 6, 0), - isLabelVisible: _mainController - .dynamicBadgeType != - DynamicBadgeMode.hidden && - e['count'] > 0, - child: e['icon'], - backgroundColor: Theme.of(context) - .colorScheme - .primary, - textColor: Theme.of(context) - .colorScheme - .onInverseSurface, + ? Obx( + () => NavigationBar( + onDestinationSelected: (value) => + setIndex(value), + selectedIndex: _mainController.selectedIndex, + destinations: + _mainController.navigationBars.map( + (e) { + return NavigationDestination( + icon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['icon'], + ), + selectedIcon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['selectIcon'], ), - selectedIcon: e['selectIcon'], label: e['label'], - ), - ) - .toList(), + ); + }, + ).toList(), + ), ) - : BottomNavigationBar( - currentIndex: _mainController.selectedIndex, - onTap: (value) => setIndex(value), - iconSize: 16, - selectedFontSize: 12, - unselectedFontSize: 12, - type: BottomNavigationBarType.fixed, - // selectedItemColor: - // Theme.of(context).colorScheme.primary, // 选中项的颜色 - // unselectedItemColor: - // Theme.of(context).colorScheme.onSurface, - items: _mainController.navigationBars - .map( - (e) => BottomNavigationBarItem( - icon: Badge( - label: _mainController - .dynamicBadgeType == - DynamicBadgeMode.number - ? Text(e['count'].toString()) - : null, - padding: const EdgeInsets.fromLTRB( - 6, 0, 6, 0), - isLabelVisible: _mainController - .dynamicBadgeType != - DynamicBadgeMode.hidden && - e['count'] > 0, - child: e['icon'], - backgroundColor: Theme.of(context) - .colorScheme - .primary, - textColor: Theme.of(context) - .colorScheme - .onInverseSurface, + : Obx( + () => BottomNavigationBar( + currentIndex: _mainController.selectedIndex, + onTap: (value) => setIndex(value), + iconSize: 16, + selectedFontSize: 12, + unselectedFontSize: 12, + type: BottomNavigationBarType.fixed, + // selectedItemColor: + // Theme.of(context).colorScheme.primary, // 选中项的颜色 + // unselectedItemColor: + // Theme.of(context).colorScheme.onSurface, + items: _mainController.navigationBars + .map( + (e) => BottomNavigationBarItem( + icon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['icon'], + ), + activeIcon: _buildIcon( + id: e['id'], + count: e['count'], + icon: e['selectIcon'], + ), + label: e['label'], ), - activeIcon: e['selectIcon'], - label: e['label'], - ), - ) - .toList(), + ) + .toList(), + ), ), ); }, @@ -307,4 +303,26 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { ), ); } + + Widget _buildIcon({ + required int id, + required int count, + required Widget icon, + }) => + id == 1 && + _mainController.dynamicBadgeType != DynamicBadgeMode.hidden && + count > 0 + ? Badge( + label: _mainController.dynamicBadgeType == DynamicBadgeMode.number + ? Text(count.toString()) + : null, + padding: const EdgeInsets.fromLTRB(6, 0, 6, 0), + // isLabelVisible: + // _mainController.dynamicBadgeType != DynamicBadgeMode.hidden && + // count > 0, + // backgroundColor: Theme.of(context).colorScheme.primary, + // textColor: Theme.of(context).colorScheme.onInverseSurface, + child: icon, + ) + : icon; } diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index d8379ed5..e60e12e3 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -27,6 +27,7 @@ class _SearchPageState extends State with RouteAware { @override void dispose() { _searchController.searchFocusNode.dispose(); + SearchPage.routeObserver.unsubscribe(this); super.dispose(); } diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index 54487bfb..5c144b92 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -1,3 +1,4 @@ +import 'package:PiliPalaX/pages/main/controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; @@ -147,6 +148,58 @@ class _ExtraSettingState extends State { defaultVal: false, onTap: () => Get.toNamed('/sponsorBlock'), ), + SetSwitchItem( + title: '检查未读动态', + subTitle: '点击设置检查周期(min)', + leading: Icon(Icons.notifications_none), + setKey: SettingBoxKey.checkDynamic, + defaultVal: true, + callFn: (value) { + Get.find().checkDynamic = value; + }, + onTap: () { + int dynamicPeriod = GStorage.dynamicPeriod; + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('检查周期', style: TextStyle(fontSize: 18)), + content: TextFormField( + autofocus: true, + initialValue: dynamicPeriod.toString(), + keyboardType: + TextInputType.numberWithOptions(decimal: true), + onChanged: (value) { + dynamicPeriod = int.tryParse(value) ?? 5; + }, + decoration: InputDecoration(suffixText: 'min'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + GStorage.setting + .put(SettingBoxKey.dynamicPeriod, dynamicPeriod); + Get.find().dynamicPeriod = + dynamicPeriod; + }, + child: Text('确定'), + ) + ], + ); + }, + ); + }, + ), Obx( () => ListTile( enableFeedback: true, diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 3b8ed510..8a1f5a7d 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -70,6 +70,12 @@ class GStorage { static bool get blockTrack => setting.get(SettingBoxKey.blockTrack, defaultValue: true); + static bool get checkDynamic => + setting.get(SettingBoxKey.checkDynamic, defaultValue: true); + + static int get dynamicPeriod => + setting.get(SettingBoxKey.dynamicPeriod, defaultValue: 5); + static ThemeMode get themeMode { switch (setting.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code)) { @@ -251,6 +257,8 @@ class SettingBoxKey { blockServer = 'blockServer', blockTrack = 'blockTrack', previewQuality = 'previewQuality', + checkDynamic = 'checkDynamic', + dynamicPeriod = 'dynamicPeriod', // 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细 danmakuWeight = 'danmakuWeight',