feat: 首页顶栏收入侧栏,线性宽度、渐变底色扩大,“我的”页面重构为弹窗,统一替换图标

This commit is contained in:
orz12
2024-07-11 17:44:03 +08:00
parent fce011fc72
commit e9e6601e39
8 changed files with 471 additions and 494 deletions

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -6,6 +7,8 @@ import 'package:hive/hive.dart';
import 'package:PiliPalaX/models/common/tab_type.dart'; import 'package:PiliPalaX/models/common/tab_type.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import '../../http/index.dart'; import '../../http/index.dart';
import '../../utils/feed_back.dart';
import '../mine/view.dart';
class HomeController extends GetxController with GetTickerProviderStateMixin { class HomeController extends GetxController with GetTickerProviderStateMixin {
bool flag = false; bool flag = false;
@@ -27,6 +30,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
late List<String> tabbarSort; late List<String> tabbarSort;
RxString defaultSearch = ''.obs; RxString defaultSearch = ''.obs;
late bool enableGradientBg; late bool enableGradientBg;
late bool useSideBar;
@override @override
void onInit() { void onInit() {
@@ -41,6 +45,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
} }
enableGradientBg = enableGradientBg =
setting.get(SettingBoxKey.enableGradientBg, defaultValue: true); setting.get(SettingBoxKey.enableGradientBg, defaultValue: true);
useSideBar = setting.get(SettingBoxKey.useSideBar, defaultValue: false);
// 进行tabs配置 // 进行tabs配置
setTabConfig(); setTabConfig();
} }
@@ -117,4 +122,14 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
defaultSearch.value = res.data['data']['name']; defaultSearch.value = res.data['data']['name'];
} }
} }
showUserInfoDialog(context) {
feedBack();
showDialog(
context: context,
useSafeArea: true,
builder: (_) => const Dialog(
child: MinePage(),
));
}
} }

View File

@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/pages/mine/index.dart';
import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/feed_back.dart';
import '../../utils/storage.dart';
import './controller.dart'; import './controller.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@@ -30,19 +30,6 @@ class _HomePageState extends State<HomePage>
stream = _homeController.searchBarStream.stream; stream = _homeController.searchBarStream.stream;
} }
showUserBottomSheet() {
feedBack();
showModalBottomSheet(
context: context,
builder: (_) => const SizedBox(
height: 450,
child: MinePage(),
),
clipBehavior: Clip.hardEdge,
isScrollControlled: true,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
@@ -57,7 +44,7 @@ class _HomePageState extends State<HomePage>
// } // }
return Scaffold( return Scaffold(
extendBody: true, extendBody: true,
extendBodyBehindAppBar: true, // extendBodyBehindAppBar: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
appBar: AppBar( appBar: AppBar(
toolbarHeight: 0, toolbarHeight: 0,
@@ -72,13 +59,13 @@ class _HomePageState extends State<HomePage>
), ),
body: Column( body: Column(
children: [ children: [
CustomAppBar( if (!_homeController.useSideBar)
stream: _homeController.hideSearchBar CustomAppBar(
? stream stream: _homeController.hideSearchBar
: StreamController<bool>.broadcast().stream, ? stream
ctr: _homeController, : StreamController<bool>.broadcast().stream,
callback: showUserBottomSheet, ctr: _homeController,
), ),
if (_homeController.tabs.length > 1) ...[ if (_homeController.tabs.length > 1) ...[
if (_homeController.enableGradientBg) ...[ if (_homeController.enableGradientBg) ...[
const CustomTabs(), const CustomTabs(),
@@ -128,15 +115,13 @@ class _HomePageState extends State<HomePage>
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final double height; final double height;
final Stream<bool>? stream; final Stream<bool>? stream;
final HomeController? ctr; final HomeController ctr;
final Function? callback;
const CustomAppBar({ const CustomAppBar({
super.key, super.key,
this.height = kToolbarHeight, this.height = kToolbarHeight,
this.stream, this.stream,
this.ctr, required this.ctr,
this.callback,
}); });
@override @override
@@ -148,24 +133,16 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
stream: stream, stream: stream,
initialData: true, initialData: true,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
final RxBool isUserLoggedIn = ctr!.userLogin;
final double top = MediaQuery.of(context).padding.top;
return AnimatedOpacity( return AnimatedOpacity(
opacity: snapshot.data ? 1 : 0, opacity: snapshot.data ? 1 : 0,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: AnimatedContainer( child: AnimatedContainer(
curve: Curves.easeInOutCubicEmphasized, curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
height: snapshot.data ? top + 52 : top, height: snapshot.data ? 52 : 0,
padding: EdgeInsets.fromLTRB(14, top + 6, 14, 0), padding: const EdgeInsets.fromLTRB(14, 6, 14, 0),
child: Obx( child: SearchBarAndUser(
() => UserInfoWidget( ctr: ctr,
top: top,
ctr: ctr,
userLogin: isUserLoggedIn,
userFace: ctr?.userFace.value,
callback: () => callback!(),
),
), ),
), ),
); );
@@ -174,21 +151,13 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
} }
} }
class UserInfoWidget extends StatelessWidget { class SearchBarAndUser extends StatelessWidget {
const UserInfoWidget({ const SearchBarAndUser({
Key? key, Key? key,
required this.top,
required this.userLogin,
required this.userFace,
required this.callback,
required this.ctr, required this.ctr,
}) : super(key: key); }) : super(key: key);
final double top; final HomeController ctr;
final RxBool userLogin;
final String? userFace;
final VoidCallback? callback;
final HomeController? ctr;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -196,7 +165,7 @@ class UserInfoWidget extends StatelessWidget {
children: [ children: [
SearchBar(ctr: ctr), SearchBar(ctr: ctr),
const SizedBox(width: 4), const SizedBox(width: 4),
Obx(() => userLogin.value Obx(() => ctr.userLogin.value
? ClipRect( ? ClipRect(
child: IconButton( child: IconButton(
tooltip: '消息', tooltip: '消息',
@@ -211,20 +180,20 @@ class UserInfoWidget extends StatelessWidget {
Semantics( Semantics(
label: "我的", label: "我的",
child: Obx( child: Obx(
() => userLogin.value () => ctr.userLogin.value
? Stack( ? Stack(
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
type: 'avatar', type: 'avatar',
width: 34, width: 34,
height: 34, height: 34,
src: userFace, src: ctr.userFace.value,
), ),
Positioned.fill( Positioned.fill(
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () => callback?.call(), onTap: () => ctr.showUserInfoDialog(context),
splashColor: Theme.of(context) splashColor: Theme.of(context)
.colorScheme .colorScheme
.primaryContainer .primaryContainer
@@ -237,13 +206,80 @@ class UserInfoWidget extends StatelessWidget {
) )
], ],
) )
: DefaultUser(callback: () => callback!()), : DefaultUser(
callback: () => ctr.showUserInfoDialog(context)),
)), )),
], ],
); );
} }
} }
class UserAndSearchVertical extends StatelessWidget {
const UserAndSearchVertical({
Key? key,
required this.ctr,
}) : super(key: key);
final HomeController ctr;
@override
Widget build(BuildContext context) {
return Column(
children: [
Semantics(
label: "我的",
child: Obx(
() => ctr.userLogin.value
? Stack(
children: [
NetworkImgLayer(
type: 'avatar',
width: 34,
height: 34,
src: ctr.userFace.value,
),
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),
),
),
),
)
],
)
: DefaultUser(
callback: () => 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'),
),
],
);
}
}
class DefaultUser extends StatelessWidget { class DefaultUser extends StatelessWidget {
const DefaultUser({super.key, this.callback}); const DefaultUser({super.key, this.callback});
final Function? callback; final Function? callback;
@@ -334,7 +370,6 @@ class CustomChip extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ColorScheme colorTheme = Theme.of(context).colorScheme; final ColorScheme colorTheme = Theme.of(context).colorScheme;
final Color secondaryContainer = colorTheme.secondaryContainer;
final TextStyle chipTextStyle = selected final TextStyle chipTextStyle = selected
? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)
: const TextStyle(fontSize: 13); : const TextStyle(fontSize: 13);
@@ -342,16 +377,19 @@ class CustomChip extends StatelessWidget {
const VisualDensity visualDensity = const VisualDensity visualDensity =
VisualDensity(horizontal: -4.0, vertical: -2.0); VisualDensity(horizontal: -4.0, vertical: -2.0);
return InputChip( return InputChip(
side: BorderSide( side: selected
color: selected ? BorderSide(
? colorScheme.onSecondaryContainer.withOpacity(0.2) color: colorScheme.secondary.withOpacity(0.2),
: Colors.transparent, width: 2,
), )
backgroundColor: secondaryContainer, : BorderSide.none,
selectedColor: secondaryContainer, // backgroundColor: colorTheme.primaryContainer.withOpacity(0.1),
color: MaterialStateProperty.resolveWith<Color>( // selectedColor: colorTheme.secondaryContainer.withOpacity(0.8),
(Set<MaterialState> states) => secondaryContainer.withAlpha(200)), color:
padding: const EdgeInsets.fromLTRB(7, 1, 7, 1), MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
return colorTheme.secondaryContainer.withOpacity(0.6);
}),
padding: const EdgeInsets.fromLTRB(6, 1, 6, 1),
label: Text(label, style: chipTextStyle), label: Text(label, style: chipTextStyle),
onPressed: () => onTap(), onPressed: () => onTap(),
selected: selected, selected: selected,

View File

@@ -130,16 +130,16 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
Theme.of(context) Theme.of(context)
.colorScheme .colorScheme
.primary .primary
.withOpacity(0.9), .withOpacity(0.6),
Theme.of(context) Theme.of(context)
.colorScheme .colorScheme
.primary .primaryContainer
.withOpacity(0.5), .withOpacity(0.6),
Theme.of(context).colorScheme.surface Theme.of(context).colorScheme.surface
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
stops: const [0, 0.0034, 0.34]), stops: const [0.1, 0.4 ,0.7]),
), ),
), ),
), ),
@@ -150,17 +150,17 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
children: [ children: [
if (useSideBar) ...[ if (useSideBar) ...[
SizedBox( SizedBox(
width: 55 + MediaQuery.of(context).padding.left, width: context.width * 0.0387 +
36.801 +
MediaQuery.of(context).padding.left,
child: NavigationRail( child: NavigationRail(
groupAlignment: 0.0, groupAlignment: 1,
minWidth: 40.0, minWidth: context.width * 0.0286 + 28.56,
backgroundColor: Theme.of(context) backgroundColor: Colors.transparent,
.colorScheme
.surface
.withOpacity(0.2),
selectedIndex: _mainController.selectedIndex, selectedIndex: _mainController.selectedIndex,
onDestinationSelected: (value) => setIndex(value), onDestinationSelected: (value) => setIndex(value),
labelType: NavigationRailLabelType.none, labelType: NavigationRailLabelType.none,
leading: UserAndSearchVertical(ctr: _homeController),
destinations: _mainController.navigationBars destinations: _mainController.navigationBars
.map((e) => NavigationRailDestination( .map((e) => NavigationRailDestination(
icon: Badge( icon: Badge(
@@ -168,11 +168,8 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
DynamicBadgeMode.number DynamicBadgeMode.number
? Text(e['count'].toString()) ? Text(e['count'].toString())
: null, : null,
padding: EdgeInsets.fromLTRB( padding:
2 + MediaQuery.of(context).padding.left, const EdgeInsets.symmetric(horizontal: 4),
0,
2,
0),
isLabelVisible: isLabelVisible:
_mainController.dynamicBadgeType != _mainController.dynamicBadgeType !=
DynamicBadgeMode.hidden && DynamicBadgeMode.hidden &&
@@ -186,12 +183,19 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
), ),
selectedIcon: e['selectIcon'], selectedIcon: e['selectIcon'],
label: Text(e['label']), label: Text(e['label']),
padding: padding: EdgeInsets.symmetric(
const EdgeInsets.symmetric(vertical: 6), vertical: 0.01 * context.height),
)) ))
.toList(), .toList(),
trailing: SizedBox(height: 0.1 * context.height),
)), )),
], ],
VerticalDivider(
width: 1,
indent: MediaQuery.of(context).padding.top,
endIndent: MediaQuery.of(context).padding.bottom,
color: Theme.of(context).colorScheme.outline.withOpacity(0.06),
),
Expanded( Expanded(
child: PageView( child: PageView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@@ -203,6 +207,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
children: _mainController.pages, children: _mainController.pages,
), ),
), ),
if (useSideBar) SizedBox(width: context.width * 0.004),
], ],
) )
]), ]),

View File

@@ -7,6 +7,7 @@ import 'package:PiliPalaX/models/common/theme_type.dart';
import 'package:PiliPalaX/models/user/info.dart'; import 'package:PiliPalaX/models/user/info.dart';
import 'package:PiliPalaX/models/user/stat.dart'; import 'package:PiliPalaX/models/user/stat.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class MineController extends GetxController { class MineController extends GetxController {
// 用户信息 头像、昵称、lv // 用户信息 头像、昵称、lv
@@ -100,30 +101,29 @@ class MineController extends GetxController {
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
builder: (context) { builder: (context) {
return ColoredBox( return ColoredBox(
color: Theme.of(context).colorScheme.secondaryContainer, color: Theme.of(context).colorScheme.primaryContainer,
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.symmetric(vertical: 15, horizontal: 20), const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
child: child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[ Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
const Row( Row(
children: <Widget>[ children: <Widget>[
Icon( Icon(
Icons.check, MdiIcons.incognito,
color: Colors.green,
), ),
SizedBox(width: 10), const SizedBox(width: 10),
Text('已进入无痕模式', Text('已进入无痕模式',
style: TextStyle(fontSize: 15, height: 1.5)) style: Theme.of(context).textTheme.titleMedium)
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
const Text( Text(
'搜索、观看视频/直播不携带Cookie与CSRF\n' '搜索、观看视频/直播不携带身份信息(包含大会员)\n'
'不产生查询或播放记录\n' '不产生查询或播放记录\n'
'点赞等其它操作不受影响\n' '点赞等其它操作不受影响\n'
'(前往隐私设置了解详情)', '(前往隐私设置了解详情)',
style: TextStyle(fontSize: 12.5, height: 1.5)), style: Theme.of(context).textTheme.bodySmall),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
@@ -172,17 +172,17 @@ class MineController extends GetxController {
builder: (context) { builder: (context) {
return ColoredBox( return ColoredBox(
color: Theme.of(context).colorScheme.secondaryContainer, color: Theme.of(context).colorScheme.secondaryContainer,
child: const Padding( child: Padding(
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 20), padding: EdgeInsets.symmetric(vertical: 15, horizontal: 20),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Icon( Icon(
Icons.check, MdiIcons.incognitoOff,
color: Colors.green,
), ),
SizedBox(width: 10), const SizedBox(width: 10),
Text('已退出无痕模式'), Text('已退出无痕模式',
style: Theme.of(context).textTheme.titleMedium),
], ],
))); )));
}); });

View File

@@ -7,62 +7,9 @@ import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/models/common/theme_type.dart'; import 'package:PiliPalaX/models/common/theme_type.dart';
import 'package:PiliPalaX/models/user/info.dart'; import 'package:PiliPalaX/models/user/info.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'controller.dart'; import 'controller.dart';
class LeftClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path();
path.moveTo(0, 0);
path.lineTo(0, size.height);
path.lineTo(size.width / 2 - 2, size.height);
path.lineTo(size.width / 2 - 2, 0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
class RightClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path();
path.moveTo(size.width, size.height);
path.lineTo(size.width / 2 + 2, size.height);
path.lineTo(size.width / 2 + 2, 0);
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
class VerticalLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black.withOpacity(0.75)
..strokeWidth = 1.2;
canvas.drawLine(
Offset(size.width / 2, size.height-2),
Offset(size.width / 2, 3),
paint,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
class MinePage extends StatefulWidget { class MinePage extends StatefulWidget {
const MinePage({super.key}); const MinePage({super.key});
@@ -81,370 +28,333 @@ class _MinePageState extends State<MinePage> {
mineController.userLogin.listen((status) { mineController.userLogin.listen((status) {
if (mounted) { if (mounted) {
setState(() { _futureBuilderFuture = mineController.queryUserInfo();
_futureBuilderFuture = mineController.queryUserInfo(); _futureBuilderFuture.then((value) => setState(() {}));
});
} }
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( // 宽度以最长的行为准,便于两端对齐
appBar: AppBar( return IntrinsicWidth(
automaticallyImplyLeading: false, child: Column(
scrolledUnderElevation: 0, mainAxisSize: MainAxisSize.min,
elevation: 0, crossAxisAlignment: CrossAxisAlignment.center,
toolbarHeight: kTextTabBarHeight + 20, // mainAxisAlignment: MainAxisAlignment.center,
backgroundColor: Colors.transparent, children: [
centerTitle: false, const SizedBox(height: 8),
title: ExcludeSemantics( Row(
child: Row( mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const SizedBox(width: 12),
Image.asset( Image.asset(
'assets/images/logo/logo_android_2.png', 'assets/images/logo/logo_android_2.png',
width: 40, width: 35,
), ),
const SizedBox(width: 5), const SizedBox(width: 5),
Text( Text(
'PiliPalaX', 'PiliPalaX',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
], const SizedBox(width: 30),
), IconButton(
), iconSize: 40.0,
actions: [ padding: const EdgeInsets.all(8),
IconButton( // constraints: const BoxConstraints(),
tooltip: "${MineController.anonymity ? '退出' : '进入'}无痕模式", style: const ButtonStyle(
onPressed: () { tapTargetSize:
MineController.onChangeAnonymity(context); MaterialTapTargetSize.shrinkWrap, // the '2023' part
setState(() {}); ),
}, tooltip: "${MineController.anonymity ? '退出' : '进入'}无痕模式",
icon: Icon( onPressed: () {
MineController.anonymity MineController.onChangeAnonymity(context);
? CupertinoIcons.checkmark_shield setState(() {});
: CupertinoIcons.shield_slash, },
size: 22, icon: Icon(
), MineController.anonymity
), ? MdiIcons.incognito
IconButton( : MdiIcons.incognitoOff,
//system -> dark -> light -> system size: 24,
tooltip: ),
'切换至${mineController.themeType.value == ThemeType.system ? '深色' : (mineController.themeType.value == ThemeType.dark ? '浅色' : '跟随系统')}主题',
onPressed: () {
mineController.onChangeTheme();
setState(() {});
},
icon: mineController.themeType.value == ThemeType.system
? SizedBox(
width: 22,
height: 22,
child: Stack(
alignment: Alignment.center,
children: [
ClipPath(
clipper: LeftClipper(),
child: const Icon(
CupertinoIcons.moon,
size: 22,
),
),
ClipPath(
clipper: RightClipper(),
child: const Icon(
CupertinoIcons.sun_max,
size: 22,
),
),
CustomPaint(
size: const Size(22, 22),
painter: VerticalLinePainter(),
),
],
))
: Icon(
mineController.themeType.value == ThemeType.dark
? CupertinoIcons.moon
: CupertinoIcons.sun_max,
size: 22,
),
),
IconButton(
tooltip: '设置',
onPressed: () => Get.toNamed('/setting', preventDuplicates: false),
icon: const Icon(
CupertinoIcons.gear,
),
),
const SizedBox(width: 10),
],
),
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: SizedBox(
height: constraint.maxHeight,
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
return Obx(
() => userInfoBuild(mineController, context));
} else {
return userInfoBuild(mineController, context);
}
} else {
return userInfoBuild(mineController, context);
}
},
),
],
), ),
), IconButton(
); iconSize: 40.0,
}, padding: const EdgeInsets.all(8),
), constraints: const BoxConstraints(),
); style: const ButtonStyle(
tapTargetSize:
MaterialTapTargetSize.shrinkWrap, // the '2023' part
),
//system -> dark -> light -> system
tooltip:
'切换至${mineController.themeType.value == ThemeType.system ? '深色' : (mineController.themeType.value == ThemeType.dark ? '浅色' : '跟随系统')}主题',
onPressed: () {
mineController.onChangeTheme();
setState(() {});
},
icon: Icon(
mineController.themeType.value == ThemeType.system
? MdiIcons.themeLightDark
: mineController.themeType.value == ThemeType.dark
? MdiIcons.weatherSunny
: MdiIcons.weatherNight,
size: 24,
),
),
IconButton(
iconSize: 40.0,
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
style: const ButtonStyle(
tapTargetSize:
MaterialTapTargetSize.shrinkWrap, // the '2023' part
),
tooltip: '设置',
onPressed: () => {
Get.back(),
Get.toNamed('/setting', preventDuplicates: false),
},
icon: Icon(
MdiIcons.cogs,
size: 24,
),
),
const SizedBox(width: 10),
]),
const SizedBox(height: 10),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null || !snapshot.data['status']) {
return userInfoBuild(mineController, context);
}
return Obx(() => userInfoBuild(mineController, context));
} else {
return userInfoBuild(mineController, context);
}
},
),
],
));
} }
Widget userInfoBuild(_mineController, context) { Widget userInfoBuild(_mineController, context) {
return Column( LevelInfo? levelInfo = _mineController.userInfo.value.levelInfo;
children: [ TextStyle style = TextStyle(
const SizedBox(height: 5), fontSize: Theme.of(context).textTheme.titleMedium!.fontSize,
GestureDetector( color: Theme.of(context).colorScheme.primary,
onTap: () => _mineController.onLogin(), fontWeight: FontWeight.bold);
child: ClipOval( return Column(mainAxisSize: MainAxisSize.min, children: [
child: Container( Row(
width: 85, mainAxisSize: MainAxisSize.min,
height: 85, children: [
color: Theme.of(context).colorScheme.onInverseSurface, const SizedBox(width: 20),
child: Center( GestureDetector(
child: _mineController.userInfo.value.face != null onTap: () => _mineController.onLogin(),
? NetworkImgLayer( child: ClipOval(
src: _mineController.userInfo.value.face, child: Container(
semanticsLabel: '头像', width: 70,
width: 85, height: 70,
height: 85) color: Theme.of(context).colorScheme.onInverseSurface,
: Image.asset( child: Center(
'assets/images/noface.jpeg', child: _mineController.userInfo.value.face != null
semanticLabel: "默认头像", ? NetworkImgLayer(
), src: _mineController.userInfo.value.face,
semanticsLabel: '头像',
width: 70,
height: 70)
: Image.asset(
'assets/images/noface.jpeg',
semanticLabel: "默认头像",
),
),
), ),
), ),
), ),
), const SizedBox(width: 16),
const SizedBox(height: 13), IntrinsicWidth(
Row( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Text( crossAxisAlignment: CrossAxisAlignment.start,
_mineController.userInfo.value.uname ?? '点击头像登录', children: [
style: Theme.of(context).textTheme.titleMedium, Row(
), mainAxisSize: MainAxisSize.min,
const SizedBox(width: 4), children: [
Image.asset( Text(
'assets/images/lv/lv${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}.png', _mineController.userInfo.value.uname ?? '点击头像登录',
height: 10, style: Theme.of(context).textTheme.titleMedium,
semanticLabel: ),
'等级:${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}', const SizedBox(width: 4),
), Image.asset(
], 'assets/images/lv/lv${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}.png',
), height: 10,
const SizedBox(height: 8), semanticLabel:
Row( '等级:${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}',
mainAxisAlignment: MainAxisAlignment.center, ),
children: [ ],
Text.rich(TextSpan(children: [ ),
TextSpan( const SizedBox(height: 8),
text: '硬币: ', Text.rich(TextSpan(children: [
style: TextSpan(
TextStyle(color: Theme.of(context).colorScheme.outline)), text: '硬币 ',
TextSpan( style: TextStyle(
text: fontSize:
(_mineController.userInfo.value.money ?? '-').toString(), Theme.of(context).textTheme.labelSmall!.fontSize,
style: color: Theme.of(context).colorScheme.outline)),
TextStyle(color: Theme.of(context).colorScheme.primary)), TextSpan(
])) text: (_mineController.userInfo.value.money ?? '-')
], .toString(),
), style: TextStyle(
const SizedBox(height: 22), fontSize:
if (_mineController.userInfo.value.levelInfo != null) ...[ Theme.of(context).textTheme.labelSmall!.fontSize,
LayoutBuilder( fontWeight: FontWeight.bold,
builder: (context, BoxConstraints box) { color: Theme.of(context).colorScheme.primary)),
LevelInfo levelInfo = _mineController.userInfo.value.levelInfo; TextSpan(
return SizedBox( text: " 经验 ",
width: box.maxWidth, style: TextStyle(
height: 24, fontSize:
child: Stack( Theme.of(context).textTheme.labelSmall!.fontSize,
children: [ color: Theme.of(context).colorScheme.outline)),
Positioned( TextSpan(
top: 0, text: "${levelInfo?.currentExp ?? '-'}",
right: 0, semanticsLabel: "当前${levelInfo?.currentExp ?? '-'}",
bottom: 0, style: TextStyle(
child: Container( fontSize:
color: Theme.of(context).colorScheme.primary, Theme.of(context).textTheme.labelSmall!.fontSize,
height: 24, fontWeight: FontWeight.bold,
constraints: color: Theme.of(context).colorScheme.primary)),
const BoxConstraints(minWidth: 100), // 设置最小宽度为100 TextSpan(
width: box.maxWidth * text: "/${levelInfo?.nextExp ?? '-'}",
(1 - (levelInfo.currentExp! / levelInfo.nextExp!)), semanticsLabel: "升级需${levelInfo?.nextExp ?? '-'}",
child: Center( style: TextStyle(
child: Text( fontSize:
'${levelInfo.currentExp!}/${levelInfo.nextExp!}', Theme.of(context).textTheme.labelSmall!.fontSize,
style: TextStyle( color: Theme.of(context).colorScheme.outline)),
color: Theme.of(context).colorScheme.onPrimary, ])),
fontSize: 12, // const SizedBox(height: 4),
), // Text.rich(TextSpan(children: [
semanticsLabel: // ])),
'当前经验${levelInfo.currentExp!},升级需要${levelInfo.nextExp!}', // Text.rich(
), // textAlign: TextAlign.right,
), // TextSpan(children: [
), //
), // ])),
Positioned( const SizedBox(height: 4),
top: 23, LinearProgressIndicator(
left: 0, minHeight: 2,
bottom: 0, value: levelInfo != null
child: Container( ? (levelInfo.currentExp! / levelInfo.nextExp!)
width: box.maxWidth * : 0,
(_mineController backgroundColor: Theme.of(context).colorScheme.inversePrimary,
.userInfo.value.levelInfo!.currentExp! / valueColor: AlwaysStoppedAnimation<Color>(
_mineController Theme.of(context).colorScheme.primary),
.userInfo.value.levelInfo!.nextExp!), ),
height: 1, ],
decoration: BoxDecoration( )),
color: Theme.of(context).colorScheme.primary, const SizedBox(width: 20),
),
),
),
],
),
);
},
),
], ],
const SizedBox(height: 26), ),
Padding( const SizedBox(height: 10),
padding: const EdgeInsets.only(left: 12, right: 12), Container(
child: LayoutBuilder( width: 240,
builder: (context, constraints) { height: 100,
TextStyle style = TextStyle( child: GridView.count(
fontSize: Theme.of(context).textTheme.titleMedium!.fontSize, padding: EdgeInsets.zero,
color: Theme.of(context).colorScheme.primary, physics: const NeverScrollableScrollPhysics(),
fontWeight: FontWeight.bold); crossAxisCount: 3,
return SizedBox( children: [
height: constraints.maxWidth * 0.33 * 0.6, InkWell(
child: GridView.count( onTap: () => _mineController.pushDynamic(),
primary: false, borderRadius: StyleString.mdRadius,
padding: const EdgeInsets.all(0), child: Column(
crossAxisCount: 3, mainAxisAlignment: MainAxisAlignment.center,
childAspectRatio: 1.66, children: [
children: <Widget>[ AnimatedSwitcher(
InkWell( duration: const Duration(milliseconds: 400),
onTap: () => _mineController.pushDynamic(), transitionBuilder:
borderRadius: StyleString.mdRadius, (Widget child, Animation<double> animation) {
child: Column( return ScaleTransition(scale: animation, child: child);
mainAxisAlignment: MainAxisAlignment.center, },
children: [ child: Text(
AnimatedSwitcher( (_mineController.userStat.value.dynamicCount ?? '-')
duration: const Duration(milliseconds: 400), .toString(),
transitionBuilder: key: ValueKey<String>(_mineController
(Widget child, Animation<double> animation) { .userStat.value.dynamicCount
return ScaleTransition( .toString()),
scale: animation, child: child); style: style),
},
child: Text(
(_mineController.userStat.value.dynamicCount ??
'-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.dynamicCount
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'动态',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
), ),
InkWell( const SizedBox(height: 8),
onTap: () => _mineController.pushFollow(), Text(
borderRadius: StyleString.mdRadius, '动态',
child: Column( style: Theme.of(context).textTheme.labelMedium,
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
(_mineController.userStat.value.following ??
'-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.following
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'关注',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
InkWell(
onTap: () => _mineController.pushFans(),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
(_mineController.userStat.value.follower ?? '-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.follower
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'粉丝',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
), ),
], ],
), ),
); ),
}, InkWell(
), onTap: () => _mineController.pushFollow(),
), borderRadius: StyleString.mdRadius,
], child: Column(
); mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: Text(
(_mineController.userStat.value.following ?? '-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.following
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'关注',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
InkWell(
onTap: () => _mineController.pushFans(),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: Text(
(_mineController.userStat.value.follower ?? '-')
.toString(),
key: ValueKey<String>(_mineController
.userStat.value.follower
.toString()),
style: style),
),
const SizedBox(height: 8),
Text(
'粉丝',
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
],
)),
]);
} }
} }

View File

@@ -69,14 +69,14 @@ class _StyleSettingState extends State<StyleSetting> {
children: [ children: [
SetSwitchItem( SetSwitchItem(
title: '横屏适配', title: '横屏适配',
subTitle: '启用横屏布局与逻辑,适用于平板等设备', subTitle: '启用横屏布局与逻辑,适用于平板等设备;推荐全屏方向设为【不改变当前方向】',
leading: const Icon(Icons.phonelink_outlined), leading: const Icon(Icons.phonelink_outlined),
setKey: SettingBoxKey.horizontalScreen, setKey: SettingBoxKey.horizontalScreen,
defaultVal: false, defaultVal: false,
callFn: (value) { callFn: (value) {
if (value) { if (value) {
autoScreen(); autoScreen();
SmartDialog.showToast('已开启横屏适配,推荐将全屏方式设为【不改变当前方向】'); SmartDialog.showToast('已开启横屏适配');
} else { } else {
AutoOrientation.portraitUpMode(); AutoOrientation.portraitUpMode();
SmartDialog.showToast('已关闭横屏适配'); SmartDialog.showToast('已关闭横屏适配');
@@ -84,7 +84,7 @@ class _StyleSettingState extends State<StyleSetting> {
}), }),
const SetSwitchItem( const SetSwitchItem(
title: '改用侧边栏', title: '改用侧边栏',
subTitle: '开启后底栏被替换,且底栏相关设置失效', subTitle: '开启后底栏与顶栏被替换,且相关设置失效',
leading: Icon(Icons.chrome_reader_mode_outlined), leading: Icon(Icons.chrome_reader_mode_outlined),
setKey: SettingBoxKey.useSideBar, setKey: SettingBoxKey.useSideBar,
defaultVal: false, defaultVal: false,

View File

@@ -575,10 +575,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_smart_dialog name: flutter_smart_dialog
sha256: fe905154e101028e910e539ee5acb404f25cc36071ee824ebdcc7dd848c40700 sha256: a74869ad9b20e5c413c45484267619fd31ee0b4d02b976a6af15f0150e1b911b
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "4.9.7+2" version: "4.9.7+8"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -853,6 +853,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "0.5.0" version: "0.5.0"
material_design_icons_flutter:
dependency: "direct main"
description:
name: material_design_icons_flutter
sha256: "6f986b7a51f3ad4c00e33c5c84e8de1bdd140489bbcdc8b66fc1283dad4dea5a"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "7.0.7296"
media_kit: media_kit:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -78,8 +78,9 @@ dependencies:
pull_to_refresh_notification: ^3.0.1 pull_to_refresh_notification: ^3.0.1
# 图标 # 图标
font_awesome_flutter: ^10.4.0 font_awesome_flutter: ^10.4.0
material_design_icons_flutter: ^7.0.7296
# toast # toast
flutter_smart_dialog: ^4.9.4 flutter_smart_dialog: ^4.9.7+8
# 下滑关闭 # 下滑关闭
dismissible_page: ^1.0.2 dismissible_page: ^1.0.2
custom_sliding_segmented_control: ^1.7.5 custom_sliding_segmented_control: ^1.7.5