mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-30 22:30:13 +08:00
feat: home: show unread badge
Closes #107 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -444,6 +444,11 @@ class Api {
|
|||||||
// 获取指定分组下的up
|
// 获取指定分组下的up
|
||||||
static const String followUpGroup = '/x/relation/tag';
|
static const String followUpGroup = '/x/relation/tag';
|
||||||
|
|
||||||
|
// 获取未读私信数
|
||||||
|
// https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread
|
||||||
|
static const String msgUnread =
|
||||||
|
'${HttpString.tUrl}/session_svr/v1/session_svr/single_unread';
|
||||||
|
|
||||||
// 获取消息中心未读信息
|
// 获取消息中心未读信息
|
||||||
static const String msgFeedUnread = '/x/msgfeed/unread';
|
static const String msgFeedUnread = '/x/msgfeed/unread';
|
||||||
//https://api.bilibili.com/x/msgfeed/reply?platform=web&build=0&mobi_app=web
|
//https://api.bilibili.com/x/msgfeed/reply?platform=web&build=0&mobi_app=web
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ extension DynamicBadgeModeDesc on DynamicBadgeMode {
|
|||||||
String get description => ['隐藏', '红点', '数字'][index];
|
String get description => ['隐藏', '红点', '数字'][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DynamicBadgeModeCode on DynamicBadgeMode {
|
enum MsgUnReadType { pm, reply, at, like, sysMsg, all }
|
||||||
int get code => [0, 1, 2][index];
|
|
||||||
|
extension MsgUnReadTypeExt on MsgUnReadType {
|
||||||
|
String get title => ['私信', '回复我的', '@我', '收到的赞', '系统通知', '全部'][index];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
|
|
||||||
late bool enableSearchWord;
|
late bool enableSearchWord;
|
||||||
late RxString defaultSearch = ''.obs;
|
late RxString defaultSearch = ''.obs;
|
||||||
late int lateCheckAt = 0;
|
late int lateCheckSearchAt = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@@ -40,7 +40,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
enableSearchWord = GStorage.setting
|
enableSearchWord = GStorage.setting
|
||||||
.get(SettingBoxKey.enableSearchWord, defaultValue: true);
|
.get(SettingBoxKey.enableSearchWord, defaultValue: true);
|
||||||
if (enableSearchWord) {
|
if (enableSearchWord) {
|
||||||
lateCheckAt = DateTime.now().millisecondsSinceEpoch;
|
lateCheckSearchAt = DateTime.now().millisecondsSinceEpoch;
|
||||||
querySearchDefault();
|
querySearchDefault();
|
||||||
}
|
}
|
||||||
useSideBar =
|
useSideBar =
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/models/common/dynamic_badge_mode.dart';
|
||||||
|
import 'package:PiliPlus/pages/main/index.dart';
|
||||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -20,6 +22,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final HomeController _homeController = Get.put(HomeController());
|
||||||
late Stream<bool> stream;
|
late Stream<bool> stream;
|
||||||
|
final MainController _mainController = Get.put(MainController());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@@ -37,13 +40,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
appBar: AppBar(toolbarHeight: 0),
|
appBar: AppBar(toolbarHeight: 0),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
if (!_homeController.useSideBar)
|
if (!_homeController.useSideBar) customAppBar,
|
||||||
CustomAppBar(
|
|
||||||
stream: _homeController.hideSearchBar
|
|
||||||
? stream
|
|
||||||
: StreamController<bool>.broadcast().stream,
|
|
||||||
homeController: _homeController,
|
|
||||||
),
|
|
||||||
if (_homeController.tabs.length > 1) ...[
|
if (_homeController.tabs.length > 1) ...[
|
||||||
...[
|
...[
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -85,77 +82,57 @@ class _HomePageState extends State<HomePage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
Widget get searchBarAndUser {
|
||||||
final double height;
|
|
||||||
final Stream<bool>? stream;
|
|
||||||
final HomeController homeController;
|
|
||||||
|
|
||||||
const CustomAppBar({
|
|
||||||
super.key,
|
|
||||||
this.height = kToolbarHeight,
|
|
||||||
this.stream,
|
|
||||||
required this.homeController,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size get preferredSize => Size.fromHeight(height);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StreamBuilder(
|
|
||||||
stream: stream,
|
|
||||||
initialData: true,
|
|
||||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
|
||||||
return AnimatedOpacity(
|
|
||||||
opacity: snapshot.data ? 1 : 0,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: AnimatedContainer(
|
|
||||||
curve: Curves.easeInOutCubicEmphasized,
|
|
||||||
duration: const Duration(milliseconds: 500),
|
|
||||||
height: snapshot.data ? 52 : 0,
|
|
||||||
padding: const EdgeInsets.fromLTRB(14, 6, 14, 0),
|
|
||||||
child: SearchBarAndUser(
|
|
||||||
homeController: homeController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchBarAndUser extends StatelessWidget {
|
|
||||||
const SearchBarAndUser({
|
|
||||||
super.key,
|
|
||||||
required this.homeController,
|
|
||||||
});
|
|
||||||
|
|
||||||
final HomeController homeController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
SearchBar(homeController: homeController),
|
searchBar,
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Obx(() => homeController.userLogin.value
|
Obx(
|
||||||
? ClipRect(
|
() => _homeController.userLogin.value
|
||||||
child: IconButton(
|
? Stack(
|
||||||
tooltip: '消息',
|
clipBehavior: Clip.none,
|
||||||
onPressed: () => Get.toNamed('/whisper'),
|
alignment: Alignment.center,
|
||||||
icon: const Icon(
|
children: [
|
||||||
Icons.notifications_none,
|
IconButton(
|
||||||
),
|
tooltip: '消息',
|
||||||
),
|
onPressed: () {
|
||||||
)
|
Get.toNamed('/whisper');
|
||||||
: const SizedBox.shrink()),
|
_mainController.msgUnReadCount.value = '';
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.notifications_none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_mainController.msgBadgeMode !=
|
||||||
|
DynamicBadgeMode.hidden &&
|
||||||
|
_mainController.msgUnReadCount.value.isNotEmpty)
|
||||||
|
Positioned(
|
||||||
|
top: _mainController.msgBadgeMode ==
|
||||||
|
DynamicBadgeMode.number
|
||||||
|
? 8
|
||||||
|
: 12,
|
||||||
|
left: _mainController.msgBadgeMode ==
|
||||||
|
DynamicBadgeMode.number
|
||||||
|
? 24
|
||||||
|
: 32,
|
||||||
|
child: Badge(
|
||||||
|
label: _mainController.msgBadgeMode ==
|
||||||
|
DynamicBadgeMode.number
|
||||||
|
? Text(_mainController.msgUnReadCount.value
|
||||||
|
.toString())
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Semantics(
|
Semantics(
|
||||||
label: "我的",
|
label: "我的",
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => homeController.userLogin.value
|
() => _homeController.userLogin.value
|
||||||
? Stack(
|
? Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
@@ -163,14 +140,14 @@ class SearchBarAndUser extends StatelessWidget {
|
|||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
width: 34,
|
width: 34,
|
||||||
height: 34,
|
height: 34,
|
||||||
src: homeController.userFace.value,
|
src: _homeController.userFace.value,
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
homeController.showUserInfoDialog(context),
|
_homeController.showUserInfoDialog(context),
|
||||||
splashColor: Theme.of(context)
|
splashColor: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primaryContainer
|
.primaryContainer
|
||||||
@@ -208,100 +185,89 @@ class SearchBarAndUser extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
: DefaultUser(
|
: DefaultUser(
|
||||||
onPressed: () => homeController.showUserInfoDialog(context),
|
onPressed: () =>
|
||||||
|
_homeController.showUserInfoDialog(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class UserAndSearchVertical extends StatelessWidget {
|
Widget get customAppBar {
|
||||||
const UserAndSearchVertical({
|
return StreamBuilder(
|
||||||
super.key,
|
stream: _homeController.hideSearchBar
|
||||||
required this.ctr,
|
? stream
|
||||||
});
|
: StreamController<bool>.broadcast().stream,
|
||||||
|
initialData: true,
|
||||||
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
|
return AnimatedOpacity(
|
||||||
|
opacity: snapshot.data ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
curve: Curves.easeInOutCubicEmphasized,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
height: snapshot.data ? 52 : 0,
|
||||||
|
padding: const EdgeInsets.fromLTRB(14, 6, 14, 0),
|
||||||
|
child: searchBarAndUser,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final HomeController ctr;
|
Widget get searchBar {
|
||||||
|
return Expanded(
|
||||||
@override
|
child: Container(
|
||||||
Widget build(BuildContext context) {
|
width: 250,
|
||||||
return Column(
|
height: 44,
|
||||||
children: [
|
clipBehavior: Clip.hardEdge,
|
||||||
Semantics(
|
decoration: BoxDecoration(
|
||||||
label: "我的",
|
borderRadius: BorderRadius.circular(25),
|
||||||
child: Obx(
|
),
|
||||||
() => ctr.userLogin.value
|
child: Material(
|
||||||
? Stack(
|
color: Theme.of(context)
|
||||||
clipBehavior: Clip.none,
|
.colorScheme
|
||||||
children: [
|
.onSecondaryContainer
|
||||||
NetworkImgLayer(
|
.withOpacity(0.05),
|
||||||
type: 'avatar',
|
child: InkWell(
|
||||||
width: 34,
|
splashColor:
|
||||||
height: 34,
|
Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3),
|
||||||
src: ctr.userFace.value,
|
onTap: () => Get.toNamed(
|
||||||
|
'/search',
|
||||||
|
parameters: {
|
||||||
|
if (_homeController.enableSearchWord)
|
||||||
|
'hintText': _homeController.defaultSearch.value,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Icon(
|
||||||
|
Icons.search_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
semanticLabel: '搜索',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
if (_homeController.enableSearchWord) ...[
|
||||||
|
Expanded(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_homeController.defaultSearch.value,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
),
|
||||||
child: Material(
|
),
|
||||||
color: Colors.transparent,
|
const SizedBox(width: 2),
|
||||||
child: InkWell(
|
],
|
||||||
onTap: () => ctr.showUserInfoDialog(context),
|
],
|
||||||
splashColor: Theme.of(context)
|
),
|
||||||
.colorScheme
|
|
||||||
.primaryContainer
|
|
||||||
.withOpacity(0.3),
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(50),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: -6,
|
|
||||||
bottom: -6,
|
|
||||||
child: Obx(() => MineController.anonymity.value
|
|
||||||
? IgnorePointer(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
size: 16,
|
|
||||||
MdiIcons.incognito,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: DefaultUser(onPressed: () => ctr.showUserInfoDialog(context)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
Obx(() => ctr.userLogin.value
|
|
||||||
? IconButton(
|
|
||||||
tooltip: '消息',
|
|
||||||
onPressed: () => Get.toNamed('/whisper'),
|
|
||||||
icon: const Icon(Icons.notifications_none),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink()),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.search_outlined,
|
|
||||||
semanticLabel: '搜索',
|
|
||||||
),
|
|
||||||
onPressed: () => Get.toNamed('/search'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,154 +299,3 @@ class DefaultUser extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// class CustomTabs extends StatefulWidget {
|
|
||||||
// const CustomTabs({super.key});
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// State<CustomTabs> createState() => _CustomTabsState();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class _CustomTabsState extends State<CustomTabs> {
|
|
||||||
// final HomeController _homeController = Get.put(HomeController());
|
|
||||||
|
|
||||||
// void onTap(int index) {
|
|
||||||
// feedBack();
|
|
||||||
// if (_homeController.initialIndex.value == index) {
|
|
||||||
// _homeController.tabsCtrList[index]().animateToTop();
|
|
||||||
// }
|
|
||||||
// _homeController.initialIndex.value = index;
|
|
||||||
// _homeController.tabController.index = index;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// return Container(
|
|
||||||
// height: 44,
|
|
||||||
// margin: const EdgeInsets.only(top: 4),
|
|
||||||
// child: Obx(
|
|
||||||
// () => ListView.separated(
|
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
|
||||||
// scrollDirection: Axis.horizontal,
|
|
||||||
// itemCount: _homeController.tabs.length,
|
|
||||||
// separatorBuilder: (BuildContext context, int index) {
|
|
||||||
// return const SizedBox(width: 10);
|
|
||||||
// },
|
|
||||||
// itemBuilder: (BuildContext context, int index) {
|
|
||||||
// String label = _homeController.tabs[index]['label'];
|
|
||||||
// return Obx(
|
|
||||||
// () => CustomChip(
|
|
||||||
// onTap: () => onTap(index),
|
|
||||||
// label: label,
|
|
||||||
// selected: index == _homeController.initialIndex.value,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
class CustomChip extends StatelessWidget {
|
|
||||||
final VoidCallback onTap;
|
|
||||||
final String label;
|
|
||||||
final bool selected;
|
|
||||||
const CustomChip({
|
|
||||||
super.key,
|
|
||||||
required this.onTap,
|
|
||||||
required this.label,
|
|
||||||
required this.selected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ColorScheme colorTheme = Theme.of(context).colorScheme;
|
|
||||||
final TextStyle chipTextStyle = selected
|
|
||||||
? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)
|
|
||||||
: const TextStyle(fontSize: 13);
|
|
||||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
|
||||||
const VisualDensity visualDensity =
|
|
||||||
VisualDensity(horizontal: -4.0, vertical: -2.0);
|
|
||||||
return InputChip(
|
|
||||||
side: selected
|
|
||||||
? BorderSide(
|
|
||||||
color: colorScheme.secondary.withOpacity(0.2),
|
|
||||||
width: 2,
|
|
||||||
)
|
|
||||||
: BorderSide.none,
|
|
||||||
// backgroundColor: colorTheme.primaryContainer.withOpacity(0.1),
|
|
||||||
// selectedColor: colorTheme.secondaryContainer.withOpacity(0.8),
|
|
||||||
color: WidgetStateProperty.resolveWith<Color>((Set<WidgetState> states) {
|
|
||||||
return colorTheme.secondaryContainer.withOpacity(0.6);
|
|
||||||
}),
|
|
||||||
padding: const EdgeInsets.fromLTRB(6, 1, 6, 1),
|
|
||||||
label: Text(label, style: chipTextStyle),
|
|
||||||
onPressed: onTap,
|
|
||||||
selected: selected,
|
|
||||||
showCheckmark: false,
|
|
||||||
visualDensity: visualDensity,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchBar extends StatelessWidget {
|
|
||||||
const SearchBar({
|
|
||||||
super.key,
|
|
||||||
required this.homeController,
|
|
||||||
});
|
|
||||||
|
|
||||||
final HomeController homeController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
|
||||||
return Expanded(
|
|
||||||
child: Container(
|
|
||||||
width: 250,
|
|
||||||
height: 44,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(25),
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
color: colorScheme.onSecondaryContainer.withOpacity(0.05),
|
|
||||||
child: InkWell(
|
|
||||||
splashColor: colorScheme.primaryContainer.withOpacity(0.3),
|
|
||||||
onTap: () => Get.toNamed(
|
|
||||||
'/search',
|
|
||||||
parameters: {
|
|
||||||
if (homeController.enableSearchWord)
|
|
||||||
'hintText': homeController.defaultSearch.value,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const SizedBox(width: 14),
|
|
||||||
Icon(
|
|
||||||
Icons.search_outlined,
|
|
||||||
color: colorScheme.onSecondaryContainer,
|
|
||||||
semanticLabel: '搜索',
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
if (homeController.enableSearchWord) ...[
|
|
||||||
Expanded(
|
|
||||||
child: Obx(
|
|
||||||
() => Text(
|
|
||||||
homeController.defaultSearch.value,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(color: colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 2),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||||
|
import 'package:PiliPlus/http/api.dart';
|
||||||
import 'package:PiliPlus/http/common.dart';
|
import 'package:PiliPlus/http/common.dart';
|
||||||
|
import 'package:PiliPlus/http/init.dart';
|
||||||
import 'package:PiliPlus/pages/dynamics/view.dart';
|
import 'package:PiliPlus/pages/dynamics/view.dart';
|
||||||
import 'package:PiliPlus/pages/home/view.dart';
|
import 'package:PiliPlus/pages/home/view.dart';
|
||||||
import 'package:PiliPlus/pages/media/view.dart';
|
import 'package:PiliPlus/pages/media/view.dart';
|
||||||
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/global_data.dart';
|
import 'package:PiliPlus/utils/global_data.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -21,40 +24,135 @@ class MainController extends GetxController {
|
|||||||
late bool hideTabBar;
|
late bool hideTabBar;
|
||||||
late PageController pageController;
|
late PageController pageController;
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
RxBool userLogin = false.obs;
|
RxBool isLogin = false.obs;
|
||||||
late DynamicBadgeMode dynamicBadgeType;
|
|
||||||
late bool checkDynamic;
|
late DynamicBadgeMode dynamicBadgeMode;
|
||||||
late int dynamicPeriod;
|
late bool checkDynamic = GStorage.checkDynamic;
|
||||||
int? _lastCheckAt;
|
late int dynamicPeriod = GStorage.dynamicPeriod;
|
||||||
int? dynIndex;
|
late int _lastCheckDynamicAt = 0;
|
||||||
|
late int dynIndex = -1;
|
||||||
|
|
||||||
|
late int homeIndex = -1;
|
||||||
|
late DynamicBadgeMode msgBadgeMode = GStorage.msgBadgeMode;
|
||||||
|
late MsgUnReadType msgUnReadType = GStorage.msgUnReadType;
|
||||||
|
late final RxString msgUnReadCount = ''.obs;
|
||||||
|
late int lastCheckUnreadAt = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
checkDynamic = GStorage.checkDynamic;
|
|
||||||
dynamicPeriod = GStorage.dynamicPeriod;
|
|
||||||
hideTabBar =
|
hideTabBar =
|
||||||
GStorage.setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
GStorage.setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
||||||
dynamic userInfo = GStorage.userInfo.get('userInfoCache');
|
isLogin.value = GStorage.isLogin;
|
||||||
userLogin.value = userInfo != null;
|
dynamicBadgeMode = DynamicBadgeMode.values[GStorage.setting.get(
|
||||||
dynamicBadgeType = DynamicBadgeMode.values[GStorage.setting.get(
|
|
||||||
SettingBoxKey.dynamicBadgeMode,
|
SettingBoxKey.dynamicBadgeMode,
|
||||||
defaultValue: DynamicBadgeMode.number.code)];
|
defaultValue: DynamicBadgeMode.number.index)];
|
||||||
|
|
||||||
setNavBarConfig();
|
setNavBarConfig();
|
||||||
if (dynamicBadgeType != DynamicBadgeMode.hidden) {
|
|
||||||
dynIndex = navigationBars.indexWhere((e) => e['id'] == 1);
|
dynIndex = navigationBars.indexWhere((e) => e['id'] == 1);
|
||||||
|
if (dynamicBadgeMode != DynamicBadgeMode.hidden) {
|
||||||
if (dynIndex != -1) {
|
if (dynIndex != -1) {
|
||||||
if (checkDynamic) {
|
if (checkDynamic) {
|
||||||
_lastCheckAt = DateTime.now().millisecondsSinceEpoch;
|
_lastCheckDynamicAt = DateTime.now().millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
getUnreadDynamic();
|
getUnreadDynamic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
homeIndex = navigationBars.indexWhere((e) => e['id'] == 0);
|
||||||
|
if (msgBadgeMode != DynamicBadgeMode.hidden) {
|
||||||
|
if (homeIndex != -1) {
|
||||||
|
lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
queryUnreadMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future queryUnreadMsg() async {
|
||||||
|
if (isLogin.value.not || homeIndex == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
bool shouldCheckPM = msgUnReadType == MsgUnReadType.pm ||
|
||||||
|
msgUnReadType == MsgUnReadType.all;
|
||||||
|
bool shouldCheckFeed = msgUnReadType != MsgUnReadType.pm ||
|
||||||
|
msgUnReadType == MsgUnReadType.all;
|
||||||
|
List res = await Future.wait([
|
||||||
|
if (shouldCheckPM) _queryPMUnread(),
|
||||||
|
if (shouldCheckFeed) _queryMsgFeedUnread(),
|
||||||
|
]);
|
||||||
|
dynamic count = 0;
|
||||||
|
if (shouldCheckPM && res.firstOrNull?['status'] == true) {
|
||||||
|
count = (res.first['data'] as int?) ?? 0;
|
||||||
|
}
|
||||||
|
if ((shouldCheckPM.not && res.firstOrNull?['status'] == true) ||
|
||||||
|
(shouldCheckPM && res.getOrNull(1)?['status'] == true)) {
|
||||||
|
int index = shouldCheckPM.not ? 0 : 1;
|
||||||
|
count += (switch (msgUnReadType) {
|
||||||
|
MsgUnReadType.pm => 0,
|
||||||
|
MsgUnReadType.reply => res[index]['data']['reply'],
|
||||||
|
MsgUnReadType.at => res[index]['data']['at'],
|
||||||
|
MsgUnReadType.like => res[index]['data']['like'],
|
||||||
|
MsgUnReadType.sysMsg => res[index]['data']['sys_msg'],
|
||||||
|
MsgUnReadType.all => res[index]['data']['reply'] +
|
||||||
|
res[index]['data']['at'] +
|
||||||
|
res[index]['data']['like'] +
|
||||||
|
res[index]['data']['sys_msg'],
|
||||||
|
} as int?) ??
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
count = count == 0
|
||||||
|
? ''
|
||||||
|
: count > 99
|
||||||
|
? '99+'
|
||||||
|
: count.toString();
|
||||||
|
if (msgUnReadCount.value == count) {
|
||||||
|
msgUnReadCount.refresh();
|
||||||
|
} else {
|
||||||
|
msgUnReadCount.value = count;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('failed to get unread count: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _queryPMUnread() async {
|
||||||
|
dynamic res = await Request().get(Api.msgUnread);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': ((res.data['data']?['unfollow_unread'] as int?) ?? 0) +
|
||||||
|
((res.data['data']?['follow_unread'] as int?) ?? 0),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _queryMsgFeedUnread() async {
|
||||||
|
if (isLogin.value.not) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dynamic res = await Request().get(Api.msgFeedUnread);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void getUnreadDynamic() async {
|
void getUnreadDynamic() async {
|
||||||
if (!userLogin.value || dynIndex == -1) {
|
if (!isLogin.value || dynIndex == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (GlobalData().grpcReply) {
|
if (GlobalData().grpcReply) {
|
||||||
@@ -73,22 +171,21 @@ class MainController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setCount([int count = 0]) async {
|
void setCount([int count = 0]) async {
|
||||||
dynIndex ??= navigationBars.indexWhere((e) => e['id'] == 1);
|
if (dynIndex == -1 || navigationBars[dynIndex]['count'] == count) return;
|
||||||
if (dynIndex == -1 || navigationBars[dynIndex!]['count'] == count) return;
|
navigationBars[dynIndex]['count'] = count; // 修改 count 属性为新的值
|
||||||
navigationBars[dynIndex!]['count'] = count; // 修改 count 属性为新的值
|
|
||||||
navigationBars.refresh();
|
navigationBars.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkUnreadDynamic() {
|
void checkUnreadDynamic() {
|
||||||
if (dynIndex == -1 ||
|
if (dynIndex == -1 ||
|
||||||
!userLogin.value ||
|
!isLogin.value ||
|
||||||
dynamicBadgeType == DynamicBadgeMode.hidden ||
|
dynamicBadgeMode == DynamicBadgeMode.hidden ||
|
||||||
!checkDynamic) {
|
!checkDynamic) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int now = DateTime.now().millisecondsSinceEpoch;
|
int now = DateTime.now().millisecondsSinceEpoch;
|
||||||
if (now - (_lastCheckAt ?? 0) >= dynamicPeriod * 60 * 1000) {
|
if (now - _lastCheckDynamicAt >= dynamicPeriod * 60 * 1000) {
|
||||||
_lastCheckAt = now;
|
_lastCheckDynamicAt = now;
|
||||||
getUnreadDynamic();
|
getUnreadDynamic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/grpc/grpc_client.dart';
|
import 'package:PiliPlus/grpc/grpc_client.dart';
|
||||||
|
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -12,6 +14,7 @@ import 'package:PiliPlus/pages/home/index.dart';
|
|||||||
import 'package:PiliPlus/utils/event_bus.dart';
|
import 'package:PiliPlus/utils/event_bus.dart';
|
||||||
import 'package:PiliPlus/utils/feed_back.dart';
|
import 'package:PiliPlus/utils/feed_back.dart';
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
|
|
||||||
class MainApp extends StatefulWidget {
|
class MainApp extends StatefulWidget {
|
||||||
@@ -27,8 +30,8 @@ class MainApp extends StatefulWidget {
|
|||||||
class _MainAppState extends State<MainApp>
|
class _MainAppState extends State<MainApp>
|
||||||
with SingleTickerProviderStateMixin, RouteAware, WidgetsBindingObserver {
|
with SingleTickerProviderStateMixin, RouteAware, WidgetsBindingObserver {
|
||||||
final MainController _mainController = Get.put(MainController());
|
final MainController _mainController = Get.put(MainController());
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
late final _homeController = Get.put(HomeController());
|
||||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
late final _dynamicController = Get.put(DynamicsController());
|
||||||
|
|
||||||
int? _lastSelectTime; //上次点击时间
|
int? _lastSelectTime; //上次点击时间
|
||||||
late bool enableMYBar;
|
late bool enableMYBar;
|
||||||
@@ -57,6 +60,7 @@ class _MainAppState extends State<MainApp>
|
|||||||
void didPopNext() {
|
void didPopNext() {
|
||||||
_mainController.checkUnreadDynamic();
|
_mainController.checkUnreadDynamic();
|
||||||
_checkDefaultSearch(true);
|
_checkDefaultSearch(true);
|
||||||
|
_checkUnread(true);
|
||||||
super.didPopNext();
|
super.didPopNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,23 +69,40 @@ class _MainAppState extends State<MainApp>
|
|||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
_mainController.checkUnreadDynamic();
|
_mainController.checkUnreadDynamic();
|
||||||
_checkDefaultSearch(true);
|
_checkDefaultSearch(true);
|
||||||
|
_checkUnread(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkDefaultSearch([bool shouldCheck = false]) {
|
void _checkDefaultSearch([bool shouldCheck = false]) {
|
||||||
if (_homeController.enableSearchWord) {
|
if (_mainController.homeIndex != -1 && _homeController.enableSearchWord) {
|
||||||
if (shouldCheck &&
|
if (shouldCheck &&
|
||||||
_mainController.pages[_mainController.selectedIndex] is! HomePage) {
|
_mainController.pages[_mainController.selectedIndex] is! HomePage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int now = DateTime.now().millisecondsSinceEpoch;
|
int now = DateTime.now().millisecondsSinceEpoch;
|
||||||
if (now - _homeController.lateCheckAt >= 5 * 60 * 1000) {
|
if (now - _homeController.lateCheckSearchAt >= 5 * 60 * 1000) {
|
||||||
_homeController.lateCheckAt = now;
|
_homeController.lateCheckSearchAt = now;
|
||||||
_homeController.querySearchDefault();
|
_homeController.querySearchDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _checkUnread([bool shouldCheck = false]) {
|
||||||
|
if (_mainController.isLogin.value &&
|
||||||
|
_mainController.homeIndex != -1 &&
|
||||||
|
_mainController.msgBadgeMode != DynamicBadgeMode.hidden) {
|
||||||
|
if (shouldCheck &&
|
||||||
|
_mainController.pages[_mainController.selectedIndex] is! HomePage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
if (now - _mainController.lastCheckUnreadAt >= 5 * 60 * 1000) {
|
||||||
|
_mainController.lastCheckUnreadAt = now;
|
||||||
|
_mainController.queryUnreadMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setIndex(int value) async {
|
void setIndex(int value) async {
|
||||||
feedBack();
|
feedBack();
|
||||||
_mainController.pageController.jumpToPage(value);
|
_mainController.pageController.jumpToPage(value);
|
||||||
@@ -98,25 +119,11 @@ class _MainAppState extends State<MainApp>
|
|||||||
}
|
}
|
||||||
_homeController.flag = true;
|
_homeController.flag = true;
|
||||||
_checkDefaultSearch();
|
_checkDefaultSearch();
|
||||||
|
_checkUnread();
|
||||||
} else {
|
} else {
|
||||||
_homeController.flag = false;
|
_homeController.flag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (currentPage is RankPage) {
|
|
||||||
// if (_rankController.flag) {
|
|
||||||
// // 单击返回顶部 双击并刷新
|
|
||||||
// if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
|
||||||
// _rankController.onRefresh();
|
|
||||||
// } else {
|
|
||||||
// _rankController.animateToTop();
|
|
||||||
// }
|
|
||||||
// _lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
// }
|
|
||||||
// _rankController.flag = true;
|
|
||||||
// } else {
|
|
||||||
// _rankController.flag = false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (currentPage is DynamicsPage) {
|
if (currentPage is DynamicsPage) {
|
||||||
if (_dynamicController.flag) {
|
if (_dynamicController.flag) {
|
||||||
// 单击返回顶部 双击并刷新
|
// 单击返回顶部 双击并刷新
|
||||||
@@ -172,8 +179,8 @@ class _MainAppState extends State<MainApp>
|
|||||||
children: [
|
children: [
|
||||||
if (useSideBar) ...[
|
if (useSideBar) ...[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: context.width * 0.0387 +
|
width: context.width * 0.04 +
|
||||||
36.801 +
|
40 +
|
||||||
MediaQuery.of(context).padding.left,
|
MediaQuery.of(context).padding.left,
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => _mainController.navigationBars.length > 1
|
() => _mainController.navigationBars.length > 1
|
||||||
@@ -184,8 +191,7 @@ class _MainAppState extends State<MainApp>
|
|||||||
selectedIndex: _mainController.selectedIndex,
|
selectedIndex: _mainController.selectedIndex,
|
||||||
onDestinationSelected: setIndex,
|
onDestinationSelected: setIndex,
|
||||||
labelType: NavigationRailLabelType.none,
|
labelType: NavigationRailLabelType.none,
|
||||||
leading:
|
leading: userAndSearchVertical,
|
||||||
UserAndSearchVertical(ctr: _homeController),
|
|
||||||
destinations: _mainController.navigationBars
|
destinations: _mainController.navigationBars
|
||||||
.map((e) => NavigationRailDestination(
|
.map((e) => NavigationRailDestination(
|
||||||
icon: _buildIcon(
|
icon: _buildIcon(
|
||||||
@@ -211,8 +217,7 @@ class _MainAppState extends State<MainApp>
|
|||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minWidth: context.width * 0.0286 + 28.56,
|
minWidth: context.width * 0.0286 + 28.56,
|
||||||
),
|
),
|
||||||
child:
|
child: userAndSearchVertical,
|
||||||
UserAndSearchVertical(ctr: _homeController),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -352,10 +357,10 @@ class _MainAppState extends State<MainApp>
|
|||||||
required Widget icon,
|
required Widget icon,
|
||||||
}) =>
|
}) =>
|
||||||
id == 1 &&
|
id == 1 &&
|
||||||
_mainController.dynamicBadgeType != DynamicBadgeMode.hidden &&
|
_mainController.dynamicBadgeMode != DynamicBadgeMode.hidden &&
|
||||||
count > 0
|
count > 0
|
||||||
? Badge(
|
? Badge(
|
||||||
label: _mainController.dynamicBadgeType == DynamicBadgeMode.number
|
label: _mainController.dynamicBadgeMode == DynamicBadgeMode.number
|
||||||
? Text(count.toString())
|
? Text(count.toString())
|
||||||
: null,
|
: null,
|
||||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||||
@@ -367,4 +372,119 @@ class _MainAppState extends State<MainApp>
|
|||||||
child: icon,
|
child: icon,
|
||||||
)
|
)
|
||||||
: icon;
|
: icon;
|
||||||
|
|
||||||
|
Widget get userAndSearchVertical {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Semantics(
|
||||||
|
label: "我的",
|
||||||
|
child: Obx(
|
||||||
|
() => _homeController.userLogin.value
|
||||||
|
? Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
type: 'avatar',
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
src: _homeController.userFace.value,
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () =>
|
||||||
|
_homeController.showUserInfoDialog(context),
|
||||||
|
splashColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primaryContainer
|
||||||
|
.withOpacity(0.3),
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: -6,
|
||||||
|
bottom: -6,
|
||||||
|
child: Obx(() => MineController.anonymity.value
|
||||||
|
? IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
size: 16,
|
||||||
|
MdiIcons.incognito,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: DefaultUser(
|
||||||
|
onPressed: () =>
|
||||||
|
_homeController.showUserInfoDialog(context)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Obx(
|
||||||
|
() => _homeController.userLogin.value
|
||||||
|
? Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
tooltip: '消息',
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed('/whisper');
|
||||||
|
_mainController.msgUnReadCount.value = '';
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.notifications_none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_mainController.msgBadgeMode !=
|
||||||
|
DynamicBadgeMode.hidden &&
|
||||||
|
_mainController.msgUnReadCount.value.isNotEmpty)
|
||||||
|
Positioned(
|
||||||
|
top: _mainController.msgBadgeMode ==
|
||||||
|
DynamicBadgeMode.number
|
||||||
|
? 8
|
||||||
|
: 12,
|
||||||
|
left: _mainController.msgBadgeMode ==
|
||||||
|
DynamicBadgeMode.number
|
||||||
|
? 24
|
||||||
|
: 32,
|
||||||
|
child: Badge(
|
||||||
|
label: _mainController.msgBadgeMode ==
|
||||||
|
DynamicBadgeMode.number
|
||||||
|
? Text(_mainController.msgUnReadCount.value
|
||||||
|
.toString())
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.search_outlined,
|
||||||
|
semanticLabel: '搜索',
|
||||||
|
),
|
||||||
|
onPressed: () => Get.toNamed('/search'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class SettingPage extends StatelessWidget {
|
|||||||
if (Get.isRegistered<MainController>()) {
|
if (Get.isRegistered<MainController>()) {
|
||||||
MainController mainController =
|
MainController mainController =
|
||||||
Get.find<MainController>();
|
Get.find<MainController>();
|
||||||
mainController.userLogin.value = false;
|
mainController.isLogin.value = false;
|
||||||
}
|
}
|
||||||
await LoginUtils.refreshLoginStatus(false);
|
await LoginUtils.refreshLoginStatus(false);
|
||||||
Get.back();
|
Get.back();
|
||||||
|
|||||||
@@ -235,11 +235,11 @@ List<SettingsModel> get styleSettings => [
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
GStorage.setting.put(SettingBoxKey.dynamicBadgeMode, result.code);
|
GStorage.setting.put(SettingBoxKey.dynamicBadgeMode, result.index);
|
||||||
MainController mainController = Get.put(MainController());
|
MainController mainController = Get.put(MainController());
|
||||||
mainController.dynamicBadgeType =
|
mainController.dynamicBadgeMode =
|
||||||
DynamicBadgeMode.values[result.code];
|
DynamicBadgeMode.values[result.index];
|
||||||
if (mainController.dynamicBadgeType != DynamicBadgeMode.hidden) {
|
if (mainController.dynamicBadgeMode != DynamicBadgeMode.hidden) {
|
||||||
mainController.getUnreadDynamic();
|
mainController.getUnreadDynamic();
|
||||||
}
|
}
|
||||||
SmartDialog.showToast('设置成功');
|
SmartDialog.showToast('设置成功');
|
||||||
@@ -250,6 +250,68 @@ List<SettingsModel> get styleSettings => [
|
|||||||
leading: const Icon(Icons.motion_photos_on_outlined),
|
leading: const Icon(Icons.motion_photos_on_outlined),
|
||||||
getSubtitle: () => '当前标记样式:${GStorage.dynamicBadgeType.description}',
|
getSubtitle: () => '当前标记样式:${GStorage.dynamicBadgeType.description}',
|
||||||
),
|
),
|
||||||
|
SettingsModel(
|
||||||
|
settingsType: SettingsType.normal,
|
||||||
|
onTap: (setState) async {
|
||||||
|
DynamicBadgeMode? result = await showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return SelectDialog<DynamicBadgeMode>(
|
||||||
|
title: '消息未读标记',
|
||||||
|
value: GStorage.msgBadgeMode,
|
||||||
|
values: DynamicBadgeMode.values.map((e) {
|
||||||
|
return {'title': e.description, 'value': e};
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
GStorage.setting.put(SettingBoxKey.msgBadgeMode, result.index);
|
||||||
|
MainController mainController = Get.put(MainController());
|
||||||
|
mainController.msgBadgeMode = DynamicBadgeMode.values[result.index];
|
||||||
|
if (mainController.msgBadgeMode != DynamicBadgeMode.hidden) {
|
||||||
|
mainController.queryUnreadMsg();
|
||||||
|
} else {
|
||||||
|
mainController.msgUnReadCount.value = '';
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('设置成功');
|
||||||
|
setState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: '消息未读标记',
|
||||||
|
leading: const Icon(Icons.notifications_active_outlined),
|
||||||
|
getSubtitle: () => '当前标记样式:${GStorage.msgBadgeMode.description}',
|
||||||
|
),
|
||||||
|
SettingsModel(
|
||||||
|
settingsType: SettingsType.normal,
|
||||||
|
onTap: (setState) async {
|
||||||
|
MsgUnReadType? result = await showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return SelectDialog<MsgUnReadType>(
|
||||||
|
title: '消息未读类型',
|
||||||
|
value: GStorage.msgUnReadType,
|
||||||
|
values: MsgUnReadType.values.map((e) {
|
||||||
|
return {'title': e.title, 'value': e};
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
GStorage.setting.put(SettingBoxKey.msgUnReadType, result.index);
|
||||||
|
MainController mainController = Get.put(MainController());
|
||||||
|
mainController.msgUnReadType = MsgUnReadType.values[result.index];
|
||||||
|
if (mainController.msgBadgeMode != DynamicBadgeMode.hidden) {
|
||||||
|
mainController.queryUnreadMsg();
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('设置成功');
|
||||||
|
setState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: '消息未读类型',
|
||||||
|
leading: const Icon(Icons.notifications_active_outlined),
|
||||||
|
getSubtitle: () => '当前消息类型:${GStorage.msgUnReadType.title}',
|
||||||
|
),
|
||||||
SettingsModel(
|
SettingsModel(
|
||||||
settingsType: SettingsType.sw1tch,
|
settingsType: SettingsType.sw1tch,
|
||||||
title: '首页顶栏收起',
|
title: '首页顶栏收起',
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ extension ListExt<T> on List<T>? {
|
|||||||
if (isNullOrEmpty) {
|
if (isNullOrEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (index < 0 || index >= this!.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return this![index];
|
return this![index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,18 @@ class GStorage {
|
|||||||
static DynamicBadgeMode get dynamicBadgeType =>
|
static DynamicBadgeMode get dynamicBadgeType =>
|
||||||
DynamicBadgeMode.values[setting.get(
|
DynamicBadgeMode.values[setting.get(
|
||||||
SettingBoxKey.dynamicBadgeMode,
|
SettingBoxKey.dynamicBadgeMode,
|
||||||
defaultValue: DynamicBadgeMode.number.code,
|
defaultValue: DynamicBadgeMode.number.index,
|
||||||
|
)];
|
||||||
|
|
||||||
|
static DynamicBadgeMode get msgBadgeMode =>
|
||||||
|
DynamicBadgeMode.values[setting.get(
|
||||||
|
SettingBoxKey.msgBadgeMode,
|
||||||
|
defaultValue: DynamicBadgeMode.number.index,
|
||||||
|
)];
|
||||||
|
|
||||||
|
static MsgUnReadType get msgUnReadType => MsgUnReadType.values[setting.get(
|
||||||
|
SettingBoxKey.msgUnReadType,
|
||||||
|
defaultValue: MsgUnReadType.pm.index,
|
||||||
)];
|
)];
|
||||||
|
|
||||||
static int get defaultHomePage =>
|
static int get defaultHomePage =>
|
||||||
@@ -551,6 +562,8 @@ class SettingBoxKey {
|
|||||||
hideTabBar = 'hideTabBar', // 收起底栏
|
hideTabBar = 'hideTabBar', // 收起底栏
|
||||||
tabbarSort = 'tabbarSort', // 首页tabbar
|
tabbarSort = 'tabbarSort', // 首页tabbar
|
||||||
dynamicBadgeMode = 'dynamicBadgeMode',
|
dynamicBadgeMode = 'dynamicBadgeMode',
|
||||||
|
msgBadgeMode = 'msgBadgeMode',
|
||||||
|
msgUnReadType = 'msgUnReadType',
|
||||||
hiddenSettingUnlocked = 'hiddenSettingUnlocked',
|
hiddenSettingUnlocked = 'hiddenSettingUnlocked',
|
||||||
enableGradientBg = 'enableGradientBg',
|
enableGradientBg = 'enableGradientBg',
|
||||||
navBarSort = 'navBarSort';
|
navBarSort = 'navBarSort';
|
||||||
|
|||||||
Reference in New Issue
Block a user