feat: custom check unReadDynamic

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-11-18 20:52:40 +08:00
parent 15c1449998
commit a0c54ced96
7 changed files with 250 additions and 142 deletions

View File

@@ -251,6 +251,7 @@ class MyApp extends StatelessWidget {
navigatorObservers: [ navigatorObservers: [
VideoDetailPage.routeObserver, VideoDetailPage.routeObserver,
SearchPage.routeObserver, SearchPage.routeObserver,
MainApp.routeObserver,
], ],
); );
}), }),

View File

@@ -1,7 +1,9 @@
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/http/msg.dart'; import 'package:PiliPalaX/http/msg.dart';
import 'package:PiliPalaX/pages/common/common_controller.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:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../../http/dynamics.dart'; import '../../../http/dynamics.dart';
@@ -10,6 +12,7 @@ class DynamicsTabController extends CommonController {
final String dynamicsType; final String dynamicsType;
String offset = ''; String offset = '';
int mid = -1; int mid = -1;
late final MainController mainController = Get.find<MainController>();
@override @override
void onInit() { void onInit() {
@@ -19,6 +22,11 @@ class DynamicsTabController extends CommonController {
@override @override
Future onRefresh() async { Future onRefresh() async {
if (dynamicsType == 'all') {
if (mainController.navigationBars[1]['count'] != 0) {
mainController.clearUnread();
}
}
offset = ''; offset = '';
await queryData(); await queryData();
} }

View File

@@ -31,10 +31,15 @@ class MainController extends GetxController {
Box userInfoCache = GStorage.userInfo; Box userInfoCache = GStorage.userInfo;
RxBool userLogin = false.obs; RxBool userLogin = false.obs;
late DynamicBadgeMode dynamicBadgeType; late DynamicBadgeMode dynamicBadgeType;
late bool checkDynamic;
late int dynamicPeriod;
int? _lastCheckAt;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
checkDynamic = GStorage.checkDynamic;
dynamicPeriod = GStorage.dynamicPeriod;
if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) { if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) {
Utils.checkUpdate(); Utils.checkUpdate();
} }
@@ -49,6 +54,9 @@ class MainController extends GetxController {
SettingBoxKey.dynamicBadgeMode, SettingBoxKey.dynamicBadgeMode,
defaultValue: DynamicBadgeMode.number.code)]; defaultValue: DynamicBadgeMode.number.code)];
if (dynamicBadgeType != DynamicBadgeMode.hidden) { if (dynamicBadgeType != DynamicBadgeMode.hidden) {
if (checkDynamic) {
_lastCheckAt = DateTime.now().millisecondsSinceEpoch;
}
getUnreadDynamic(); getUnreadDynamic();
} }
} }
@@ -78,23 +86,34 @@ class MainController extends GetxController {
if (!userLogin.value) { if (!userLogin.value) {
return; return;
} }
int dynamicItemIndex = // not needed yet
navigationBars.indexWhere((item) => item['label'] == "动态"); // int dynamicItemIndex =
// navigationBars.indexWhere((item) => item['label'] == "动态");
// if (dynamicItemIndex == -1) return;
var res = await CommonHttp.unReadDynamic(); var res = await CommonHttp.unReadDynamic();
var data = res['data']; var data = res['data'];
if (dynamicItemIndex != -1) { navigationBars[1]['count'] =
navigationBars[dynamicItemIndex]['count'] = data == null ? 0 : data.length; // 修改 count 属性为新的值
data == null ? 0 : data.length; // 修改 count 属性为新的值
}
navigationBars.refresh(); navigationBars.refresh();
} }
void clearUnread() async { void clearUnread() async {
int dynamicItemIndex = // not needed yet
navigationBars.indexWhere((item) => item['label'] == "动态"); // int dynamicItemIndex =
if (dynamicItemIndex != -1) { // navigationBars.indexWhere((item) => item['label'] == "动态");
navigationBars[dynamicItemIndex]['count'] = 0; // 修改 count 属性为新的值 // if (dynamicItemIndex == -1) return;
} navigationBars[1]['count'] = 0; // 修改 count 属性为新的值
navigationBars.refresh(); 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();
}
}
} }

View File

@@ -17,9 +17,13 @@ class MainApp extends StatefulWidget {
@override @override
State<MainApp> createState() => _MainAppState(); State<MainApp> createState() => _MainAppState();
static final RouteObserver<PageRoute> routeObserver =
RouteObserver<PageRoute>();
} }
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin { class _MainAppState extends State<MainApp>
with SingleTickerProviderStateMixin, RouteAware, WidgetsBindingObserver {
final MainController _mainController = Get.put(MainController()); final MainController _mainController = Get.put(MainController());
final HomeController _homeController = Get.put(HomeController()); final HomeController _homeController = Get.put(HomeController());
final DynamicsController _dynamicController = Get.put(DynamicsController()); final DynamicsController _dynamicController = Get.put(DynamicsController());
@@ -37,6 +41,26 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
PageController(initialPage: _mainController.selectedIndex); PageController(initialPage: _mainController.selectedIndex);
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
useSideBar = setting.get(SettingBoxKey.useSideBar, defaultValue: false); 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 { void setIndex(int value) async {
@@ -92,6 +116,8 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
@override @override
void dispose() async { void dispose() async {
MainApp.routeObserver.unsubscribe(this);
WidgetsBinding.instance.removeObserver(this);
await GrpcClient.instance.shutdown(); await GrpcClient.instance.shutdown();
await GStorage.close(); await GStorage.close();
EventBus().off(EventName.loginEvent); EventBus().off(EventName.loginEvent);
@@ -125,72 +151,59 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
width: context.width * 0.0387 + width: context.width * 0.0387 +
36.801 + 36.801 +
MediaQuery.of(context).padding.left, MediaQuery.of(context).padding.left,
child: NavigationRail( child: Obx(
groupAlignment: 1, () => NavigationRail(
minWidth: context.width * 0.0286 + 28.56, groupAlignment: 1,
backgroundColor: Colors.transparent, minWidth: context.width * 0.0286 + 28.56,
selectedIndex: _mainController.selectedIndex, backgroundColor: Colors.transparent,
onDestinationSelected: (value) => setIndex(value), selectedIndex: _mainController.selectedIndex,
labelType: NavigationRailLabelType.none, onDestinationSelected: (value) => setIndex(value),
leading: UserAndSearchVertical(ctr: _homeController), labelType: NavigationRailLabelType.none,
destinations: _mainController.navigationBars leading: UserAndSearchVertical(ctr: _homeController),
.map((e) => NavigationRailDestination( destinations: _mainController.navigationBars
icon: Badge( .map((e) => NavigationRailDestination(
label: _mainController.dynamicBadgeType == icon: _buildIcon(
DynamicBadgeMode.number id: e['id'],
? Text(e['count'].toString()) count: e['count'],
: null, icon: e['icon'],
padding: ),
const EdgeInsets.symmetric(horizontal: 4), selectedIcon: _buildIcon(
isLabelVisible: id: e['id'],
_mainController.dynamicBadgeType != count: e['count'],
DynamicBadgeMode.hidden && icon: e['selectIcon'],
e['count'] > 0, ),
child: e['icon'], label: Text(e['label']),
backgroundColor: padding: EdgeInsets.symmetric(
Theme.of(context).colorScheme.primary, vertical: 0.01 * context.height),
textColor: Theme.of(context) ))
.colorScheme .toList(),
.onInverseSurface, trailing: SizedBox(height: 0.1 * context.height),
), ),
selectedIcon: 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) ] else if (!isPortait)
NavigationRail( Obx(
onDestinationSelected: (value) => setIndex(value), () => NavigationRail(
selectedIndex: _mainController.selectedIndex, onDestinationSelected: (value) => setIndex(value),
destinations: _mainController.navigationBars selectedIndex: _mainController.selectedIndex,
.map( destinations: _mainController.navigationBars
(e) => NavigationRailDestination( .map(
icon: Badge( (e) => NavigationRailDestination(
label: _mainController.dynamicBadgeType == icon: _buildIcon(
DynamicBadgeMode.number id: e['id'],
? Text(e['count'].toString()) count: e['count'],
: null, icon: e['icon'],
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0), ),
isLabelVisible: selectedIcon: _buildIcon(
_mainController.dynamicBadgeType != id: e['id'],
DynamicBadgeMode.hidden && count: e['count'],
e['count'] > 0, icon: e['selectIcon'],
child: e['icon'], ),
backgroundColor: label: Text(e['label']),
Theme.of(context).colorScheme.primary,
textColor: Theme.of(context)
.colorScheme
.onInverseSurface,
), ),
selectedIcon: e['selectIcon'], )
label: Text(e['label']), .toList(),
), ),
)
.toList(),
), ),
VerticalDivider( VerticalDivider(
width: 1, width: 1,
@@ -226,78 +239,61 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1), offset: Offset(0, snapshot.data ? 0 : 1),
child: enableMYBar child: enableMYBar
? NavigationBar( ? Obx(
onDestinationSelected: (value) => () => NavigationBar(
setIndex(value), onDestinationSelected: (value) =>
selectedIndex: _mainController.selectedIndex, setIndex(value),
destinations: _mainController.navigationBars selectedIndex: _mainController.selectedIndex,
.map( destinations:
(e) => NavigationDestination( _mainController.navigationBars.map(
icon: Badge( (e) {
label: _mainController return NavigationDestination(
.dynamicBadgeType == icon: _buildIcon(
DynamicBadgeMode.number id: e['id'],
? Text(e['count'].toString()) count: e['count'],
: null, icon: e['icon'],
padding: const EdgeInsets.fromLTRB( ),
6, 0, 6, 0), selectedIcon: _buildIcon(
isLabelVisible: _mainController id: e['id'],
.dynamicBadgeType != count: e['count'],
DynamicBadgeMode.hidden && icon: e['selectIcon'],
e['count'] > 0,
child: e['icon'],
backgroundColor: Theme.of(context)
.colorScheme
.primary,
textColor: Theme.of(context)
.colorScheme
.onInverseSurface,
), ),
selectedIcon: e['selectIcon'],
label: e['label'], label: e['label'],
), );
) },
.toList(), ).toList(),
),
) )
: BottomNavigationBar( : Obx(
currentIndex: _mainController.selectedIndex, () => BottomNavigationBar(
onTap: (value) => setIndex(value), currentIndex: _mainController.selectedIndex,
iconSize: 16, onTap: (value) => setIndex(value),
selectedFontSize: 12, iconSize: 16,
unselectedFontSize: 12, selectedFontSize: 12,
type: BottomNavigationBarType.fixed, unselectedFontSize: 12,
// selectedItemColor: type: BottomNavigationBarType.fixed,
// Theme.of(context).colorScheme.primary, // 选中项的颜色 // selectedItemColor:
// unselectedItemColor: // Theme.of(context).colorScheme.primary, // 选中项的颜色
// Theme.of(context).colorScheme.onSurface, // unselectedItemColor:
items: _mainController.navigationBars // Theme.of(context).colorScheme.onSurface,
.map( items: _mainController.navigationBars
(e) => BottomNavigationBarItem( .map(
icon: Badge( (e) => BottomNavigationBarItem(
label: _mainController icon: _buildIcon(
.dynamicBadgeType == id: e['id'],
DynamicBadgeMode.number count: e['count'],
? Text(e['count'].toString()) icon: e['icon'],
: null, ),
padding: const EdgeInsets.fromLTRB( activeIcon: _buildIcon(
6, 0, 6, 0), id: e['id'],
isLabelVisible: _mainController count: e['count'],
.dynamicBadgeType != icon: e['selectIcon'],
DynamicBadgeMode.hidden && ),
e['count'] > 0, label: e['label'],
child: e['icon'],
backgroundColor: Theme.of(context)
.colorScheme
.primary,
textColor: Theme.of(context)
.colorScheme
.onInverseSurface,
), ),
activeIcon: e['selectIcon'], )
label: e['label'], .toList(),
), ),
)
.toList(),
), ),
); );
}, },
@@ -307,4 +303,26 @@ class _MainAppState extends State<MainApp> 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;
} }

View File

@@ -27,6 +27,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
@override @override
void dispose() { void dispose() {
_searchController.searchFocusNode.dispose(); _searchController.searchFocusNode.dispose();
SearchPage.routeObserver.unsubscribe(this);
super.dispose(); super.dispose();
} }

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/pages/main/controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@@ -147,6 +148,58 @@ class _ExtraSettingState extends State<ExtraSetting> {
defaultVal: false, defaultVal: false,
onTap: () => Get.toNamed('/sponsorBlock'), onTap: () => Get.toNamed('/sponsorBlock'),
), ),
SetSwitchItem(
title: '检查未读动态',
subTitle: '点击设置检查周期(min)',
leading: Icon(Icons.notifications_none),
setKey: SettingBoxKey.checkDynamic,
defaultVal: true,
callFn: (value) {
Get.find<MainController>().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<MainController>().dynamicPeriod =
dynamicPeriod;
},
child: Text('确定'),
)
],
);
},
);
},
),
Obx( Obx(
() => ListTile( () => ListTile(
enableFeedback: true, enableFeedback: true,

View File

@@ -70,6 +70,12 @@ class GStorage {
static bool get blockTrack => static bool get blockTrack =>
setting.get(SettingBoxKey.blockTrack, defaultValue: true); 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 { static ThemeMode get themeMode {
switch (setting.get(SettingBoxKey.themeMode, switch (setting.get(SettingBoxKey.themeMode,
defaultValue: ThemeType.system.code)) { defaultValue: ThemeType.system.code)) {
@@ -251,6 +257,8 @@ class SettingBoxKey {
blockServer = 'blockServer', blockServer = 'blockServer',
blockTrack = 'blockTrack', blockTrack = 'blockTrack',
previewQuality = 'previewQuality', previewQuality = 'previewQuality',
checkDynamic = 'checkDynamic',
dynamicPeriod = 'dynamicPeriod',
// 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细 // 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细
danmakuWeight = 'danmakuWeight', danmakuWeight = 'danmakuWeight',