mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: home: show unread badge
Closes #107 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -27,7 +27,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
|
||||
late bool enableSearchWord;
|
||||
late RxString defaultSearch = ''.obs;
|
||||
late int lateCheckAt = 0;
|
||||
late int lateCheckSearchAt = 0;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -40,7 +40,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
enableSearchWord = GStorage.setting
|
||||
.get(SettingBoxKey.enableSearchWord, defaultValue: true);
|
||||
if (enableSearchWord) {
|
||||
lateCheckAt = DateTime.now().millisecondsSinceEpoch;
|
||||
lateCheckSearchAt = DateTime.now().millisecondsSinceEpoch;
|
||||
querySearchDefault();
|
||||
}
|
||||
useSideBar =
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -20,6 +22,7 @@ class _HomePageState extends State<HomePage>
|
||||
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
late Stream<bool> stream;
|
||||
final MainController _mainController = Get.put(MainController());
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@@ -37,13 +40,7 @@ class _HomePageState extends State<HomePage>
|
||||
appBar: AppBar(toolbarHeight: 0),
|
||||
body: Column(
|
||||
children: [
|
||||
if (!_homeController.useSideBar)
|
||||
CustomAppBar(
|
||||
stream: _homeController.hideSearchBar
|
||||
? stream
|
||||
: StreamController<bool>.broadcast().stream,
|
||||
homeController: _homeController,
|
||||
),
|
||||
if (!_homeController.useSideBar) customAppBar,
|
||||
if (_homeController.tabs.length > 1) ...[
|
||||
...[
|
||||
const SizedBox(height: 4),
|
||||
@@ -85,77 +82,57 @@ class _HomePageState extends State<HomePage>
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
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) {
|
||||
Widget get searchBarAndUser {
|
||||
return Row(
|
||||
children: [
|
||||
SearchBar(homeController: homeController),
|
||||
searchBar,
|
||||
const SizedBox(width: 4),
|
||||
Obx(() => homeController.userLogin.value
|
||||
? ClipRect(
|
||||
child: IconButton(
|
||||
tooltip: '消息',
|
||||
onPressed: () => Get.toNamed('/whisper'),
|
||||
icon: const Icon(
|
||||
Icons.notifications_none,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
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(),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Semantics(
|
||||
label: "我的",
|
||||
child: Obx(
|
||||
() => homeController.userLogin.value
|
||||
() => _homeController.userLogin.value
|
||||
? Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
@@ -163,14 +140,14 @@ class SearchBarAndUser extends StatelessWidget {
|
||||
type: 'avatar',
|
||||
width: 34,
|
||||
height: 34,
|
||||
src: homeController.userFace.value,
|
||||
src: _homeController.userFace.value,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
homeController.showUserInfoDialog(context),
|
||||
_homeController.showUserInfoDialog(context),
|
||||
splashColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
@@ -208,100 +185,89 @@ class SearchBarAndUser extends StatelessWidget {
|
||||
],
|
||||
)
|
||||
: DefaultUser(
|
||||
onPressed: () => homeController.showUserInfoDialog(context),
|
||||
onPressed: () =>
|
||||
_homeController.showUserInfoDialog(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserAndSearchVertical extends StatelessWidget {
|
||||
const UserAndSearchVertical({
|
||||
super.key,
|
||||
required this.ctr,
|
||||
});
|
||||
Widget get customAppBar {
|
||||
return StreamBuilder(
|
||||
stream: _homeController.hideSearchBar
|
||||
? 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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Semantics(
|
||||
label: "我的",
|
||||
child: Obx(
|
||||
() => ctr.userLogin.value
|
||||
? Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
width: 34,
|
||||
height: 34,
|
||||
src: ctr.userFace.value,
|
||||
Widget get searchBar {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
width: 250,
|
||||
height: 44,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: Material(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.05),
|
||||
child: InkWell(
|
||||
splashColor:
|
||||
Theme.of(context).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: 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,
|
||||
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(width: 2),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user