diff --git a/lib/common/skeleton/msg_feed_top.dart b/lib/common/skeleton/msg_feed_top.dart index 5d2a15c83..36b195041 100644 --- a/lib/common/skeleton/msg_feed_top.dart +++ b/lib/common/skeleton/msg_feed_top.dart @@ -17,8 +17,8 @@ class MsgFeedTopSkeleton extends StatelessWidget { color: color, ), ), - title: Align( - alignment: .topLeft, + title: UnconstrainedBox( + alignment: .centerLeft, child: Container( width: 100, height: 11, diff --git a/lib/common/skeleton/whisper_item.dart b/lib/common/skeleton/whisper_item.dart index de6ceb3ab..492d06a14 100644 --- a/lib/common/skeleton/whisper_item.dart +++ b/lib/common/skeleton/whisper_item.dart @@ -17,8 +17,8 @@ class WhisperItemSkeleton extends StatelessWidget { color: color, ), ), - title: Align( - alignment: .topLeft, + title: UnconstrainedBox( + alignment: .centerLeft, child: Container( width: 100, height: 11, diff --git a/lib/common/widgets/view_insets_safe_area.dart b/lib/common/widgets/view_insets_safe_area.dart new file mode 100644 index 000000000..5e283393b --- /dev/null +++ b/lib/common/widgets/view_insets_safe_area.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class ViewInsetsSafeArea extends StatelessWidget { + const ViewInsetsSafeArea({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + final viewInsets = MediaQuery.viewInsetsOf(context); + return Padding( + padding: .only(bottom: viewInsets.bottom), + child: child, + ); + } +} diff --git a/lib/common/widgets/view_safe_area.dart b/lib/common/widgets/view_safe_area.dart index faac79184..9d9beb394 100644 --- a/lib/common/widgets/view_safe_area.dart +++ b/lib/common/widgets/view_safe_area.dart @@ -16,9 +16,9 @@ class ViewSafeArea extends StatelessWidget { @override Widget build(BuildContext context) { - EdgeInsets padding = MediaQuery.viewPaddingOf(context); + final padding = MediaQuery.viewPaddingOf(context); return Padding( - padding: EdgeInsets.only( + padding: .only( top: top ? padding.top : 0.0, left: left ? padding.left : 0.0, right: right ? padding.right : 0.0, diff --git a/lib/common/widgets/view_sliver_safe_area.dart b/lib/common/widgets/view_sliver_safe_area.dart index 455b89973..088d0448e 100644 --- a/lib/common/widgets/view_sliver_safe_area.dart +++ b/lib/common/widgets/view_sliver_safe_area.dart @@ -10,9 +10,9 @@ class ViewSliverSafeArea extends StatelessWidget { @override Widget build(BuildContext context) { - EdgeInsets padding = MediaQuery.viewPaddingOf(context); + final padding = MediaQuery.viewPaddingOf(context); return SliverPadding( - padding: EdgeInsets.only( + padding: .only( left: padding.left, right: padding.right, bottom: padding.bottom + 100, diff --git a/lib/pages/common/search/common_search_page.dart b/lib/pages/common/search/common_search_page.dart index 3855836e5..0830c74c8 100644 --- a/lib/pages/common/search/common_search_page.dart +++ b/lib/pages/common/search/common_search_page.dart @@ -1,6 +1,8 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; @@ -36,16 +38,18 @@ abstract class CommonSearchPageState } Widget _build(bool multiSelect) { - return Scaffold( + return SimpleScaffold( appBar: _buildBar(multiSelect), - body: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: controller.scrollController, - slivers: [ - ViewSliverSafeArea( - sliver: Obx(() => _buildBody(controller.loadingState.value)), - ), - ], + body: ViewInsetsSafeArea( + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: controller.scrollController, + slivers: [ + ViewSliverSafeArea( + sliver: Obx(() => _buildBody(controller.loadingState.value)), + ), + ], + ), ), ); } diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 39c0ff82e..00631b9ad 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -29,26 +29,24 @@ class _DynamicsPageState extends CommonPageState @override bool get wantKeepAlive => true; - Widget _createDynamicBtn(ThemeData theme, {bool isRight = true}) => Center( - child: Container( - width: 34, - height: 34, - margin: EdgeInsets.only(left: !isRight ? 16 : 0, right: isRight ? 16 : 0), - child: IconButton( - tooltip: '发布动态', - style: ButtonStyle( - padding: const WidgetStatePropertyAll(EdgeInsets.zero), - backgroundColor: WidgetStatePropertyAll( - theme.colorScheme.secondaryContainer, - ), - ), - onPressed: () => CreateDynPanel.onCreateDyn(context), - icon: Icon( - Icons.add, - size: 18, - color: theme.colorScheme.onSecondaryContainer, + Widget _createDynamicBtn(ThemeData theme, {bool isRight = true}) => Container( + width: 34, + height: 34, + margin: isRight ? const .only(right: 16) : const .only(left: 16), + child: IconButton( + tooltip: '发布动态', + style: ButtonStyle( + padding: const WidgetStatePropertyAll(EdgeInsets.zero), + backgroundColor: WidgetStatePropertyAll( + theme.colorScheme.secondaryContainer, ), ), + onPressed: () => CreateDynPanel.onCreateDyn(context), + icon: Icon( + Icons.add, + size: 18, + color: theme.colorScheme.onSecondaryContainer, + ), ), ); @@ -122,7 +120,7 @@ class _DynamicsPageState extends CommonPageState Widget? endDrawer; Widget? leading; - List? actions; + Widget actions; Widget child = tabBarView( controller: _dynamicsController.tabController, @@ -132,73 +130,77 @@ class _DynamicsPageState extends CommonPageState ); switch (upPanelPosition) { - case UpPanelPosition.top: + case .top: child = Column( children: [ upPanelPart(theme), Expanded(child: child), ], ); - actions = [_createDynamicBtn(theme)]; - case UpPanelPosition.leftFixed: + actions = _createDynamicBtn(theme); + case .leftFixed: child = Row( children: [ upPanelPart(theme), Expanded(child: child), ], ); - actions = [_createDynamicBtn(theme)]; - case UpPanelPosition.rightFixed: + actions = _createDynamicBtn(theme); + case .rightFixed: child = Row( children: [ Expanded(child: child), upPanelPart(theme), ], ); - actions = [_createDynamicBtn(theme)]; - case UpPanelPosition.leftDrawer: + actions = _createDynamicBtn(theme); + case .leftDrawer: drawer = upPanelPart(theme); - actions = [_createDynamicBtn(theme)]; - case UpPanelPosition.rightDrawer: + actions = _createDynamicBtn(theme); + leading = const DrawerButton(); + case .rightDrawer: endDrawer = upPanelPart(theme); leading = _createDynamicBtn(theme, isRight: false); + actions = const EndDrawerButton(); } return Scaffold( primary: false, resizeToAvoidBottomInset: false, backgroundColor: Colors.transparent, - appBar: AppBar( - primary: false, - leading: leading, - leadingWidth: 50, - toolbarHeight: 50, - backgroundColor: Colors.transparent, - title: SizedBox( - height: 50, - child: TabBar( - dividerHeight: 0, - isScrollable: true, - tabAlignment: .center, - dividerColor: Colors.transparent, - labelColor: theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - controller: _dynamicsController.tabController, - unselectedLabelColor: theme.colorScheme.onSurface, - labelStyle: - TabBarTheme.of(context).labelStyle?.copyWith(fontSize: 13) ?? - const TextStyle(fontSize: 13), - tabs: DynamicsTabType.values - .map((e) => Tab(text: e.label)) - .toList(), - onTap: (index) { - if (!_dynamicsController.tabController.indexIsChanging) { - _dynamicsController.animateToTop(); - } - }, - ), + appBar: PreferredSize( + preferredSize: const .fromHeight(50), + child: Row( + children: [ + ?leading, + Expanded( + child: TabBar( + dividerHeight: 0, + isScrollable: true, + tabAlignment: .start, + dividerColor: Colors.transparent, + labelColor: theme.colorScheme.primary, + indicatorColor: theme.colorScheme.primary, + controller: _dynamicsController.tabController, + unselectedLabelColor: theme.colorScheme.onSurface, + labelStyle: + TabBarTheme.of( + context, + ).labelStyle?.copyWith(fontSize: 13) ?? + const TextStyle(fontSize: 13), + tabs: DynamicsTabType.values + .map((e) => Tab(text: e.label)) + .toList(), + onTap: (index) { + if (!_dynamicsController.tabController.indexIsChanging) { + _dynamicsController.animateToTop(); + } + }, + ), + ), + actions, + ], ), - actions: actions, ), drawer: drawer, endDrawer: endDrawer, diff --git a/lib/pages/dynamics_mention/view.dart b/lib/pages/dynamics_mention/view.dart index ccf94fa3d..2b84be7ce 100644 --- a/lib/pages/dynamics_mention/view.dart +++ b/lib/pages/dynamics_mention/view.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart'; import 'package:PiliPlus/pages/dynamics_mention/controller.dart'; @@ -85,7 +86,6 @@ class _DynMentionPanelState Widget build(BuildContext context) { final theme = Theme.of(context); final padding = MediaQuery.paddingOf(context).bottom; - final viewInset = MediaQuery.viewInsetsOf(context).bottom; return Column( children: [ SizedBox( @@ -184,9 +184,7 @@ class _DynMentionPanelState Obx( () => _buildBody(theme, _controller.loadingState.value), ), - SliverToBoxAdapter( - child: SizedBox(height: padding + viewInset + 100), - ), + SliverToBoxAdapter(child: SizedBox(height: padding + 100)), ], ), ), @@ -194,26 +192,25 @@ class _DynMentionPanelState return Padding( padding: .only( right: kFloatingActionButtonMargin, - bottom: - padding + - kFloatingActionButtonMargin + - (_controller.showBtn.value ? viewInset : 0), + bottom: kFloatingActionButtonMargin + padding, ), - child: AnimatedSlide( - offset: _controller.showBtn.value - ? Offset.zero - : const Offset(0, 3), - duration: const Duration(milliseconds: 120), - child: FloatingActionButton( - onPressed: () { - if (_controller.mentionList.isNullOrEmpty) { + child: ViewInsetsSafeArea( + child: AnimatedSlide( + offset: _controller.showBtn.value + ? .zero + : const Offset(0, 3), + duration: const Duration(milliseconds: 120), + child: FloatingActionButton( + onPressed: () { + if (_controller.mentionList.isNullOrEmpty) { + _controller.showBtn.value = false; + return; + } + Get.back(result: _controller.mentionList); _controller.showBtn.value = false; - return; - } - Get.back(result: _controller.mentionList); - _controller.showBtn.value = false; - }, - child: const Icon(Icons.check), + }, + child: const Icon(Icons.check), + ), ), ), ); diff --git a/lib/pages/fav_create/view.dart b/lib/pages/fav_create/view.dart index 3ee894682..a276eb779 100644 --- a/lib/pages/fav_create/view.dart +++ b/lib/pages/fav_create/view.dart @@ -3,6 +3,8 @@ import 'dart:io' show File; import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/msg.dart'; @@ -72,7 +74,7 @@ class _CreateFavPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( + return SimpleScaffold( appBar: AppBar( title: Text(_mediaId != null ? '编辑' : '创建'), actions: [ @@ -177,172 +179,130 @@ class _CreateFavPageState extends State { final leadingStyle = const TextStyle(fontSize: 14); - Widget _buildBody(ThemeData theme) => SingleChildScrollView( - padding: .only(bottom: MediaQuery.viewPaddingOf(context).bottom + 25), - child: Column( - spacing: 12, - children: [ - if (_attr == null || !BiliUtils.isDefaultFav(_attr!)) - Builder( - builder: (context) { - return ListTile( - visualDensity: .standard, - tileColor: theme.colorScheme.onInverseSurface, - onTap: () { - EasyThrottle.throttle( - 'imagePicker', - const Duration(milliseconds: 500), - () { - if (_cover?.isNotEmpty == true) { - showDialog( - context: context, - builder: (_) => SimpleDialog( - clipBehavior: Clip.hardEdge, - contentPadding: const .symmetric(vertical: 12), - children: [ - DialogOption( - onPressed: () { - Get.back(); - _pickImg(context, theme); - }, - child: const Text( - '替换封面', - style: TextStyle(fontSize: 14), + Widget _buildBody(ThemeData theme) => ViewInsetsSafeArea( + child: SingleChildScrollView( + padding: .only(bottom: MediaQuery.viewPaddingOf(context).bottom + 25), + child: Column( + spacing: 12, + children: [ + if (_attr == null || !BiliUtils.isDefaultFav(_attr!)) + Builder( + builder: (context) { + return ListTile( + visualDensity: .standard, + tileColor: theme.colorScheme.onInverseSurface, + onTap: () { + EasyThrottle.throttle( + 'imagePicker', + const Duration(milliseconds: 500), + () { + if (_cover?.isNotEmpty == true) { + showDialog( + context: context, + builder: (_) => SimpleDialog( + clipBehavior: Clip.hardEdge, + contentPadding: const .symmetric(vertical: 12), + children: [ + DialogOption( + onPressed: () { + Get.back(); + _pickImg(context, theme); + }, + child: const Text( + '替换封面', + style: TextStyle(fontSize: 14), + ), ), - ), - DialogOption( - onPressed: () { - Get.back(); - _cover = null; - (context as Element).markNeedsBuild(); - }, - child: const Text( - '移除封面', - style: TextStyle(fontSize: 14), + DialogOption( + onPressed: () { + Get.back(); + _cover = null; + (context as Element).markNeedsBuild(); + }, + child: const Text( + '移除封面', + style: TextStyle(fontSize: 14), + ), ), - ), - ], - ), - ); - } else { - _pickImg(context, theme); - } - }, - ); - }, - leading: Text( - '封面', - style: leadingStyle, - ), - trailing: Row( - spacing: 10, - mainAxisSize: MainAxisSize.min, - children: [ - if (_cover?.isNotEmpty == true) - Padding( - padding: const EdgeInsets.symmetric(vertical: 5), - child: NetworkImgLayer( - src: _cover, - height: 55, - width: 88, - borderRadius: const BorderRadius.all( - Radius.circular(6), - ), - ), - ), - Icon( - Icons.keyboard_arrow_right, - color: theme.colorScheme.outline, - ), - ], - ), - ); - }, - ), - ListTile( - tileColor: theme.colorScheme.onInverseSurface, - title: Row( - children: [ - SizedBox( - width: 55, - child: Text.rich( - TextSpan( + ], + ), + ); + } else { + _pickImg(context, theme); + } + }, + ); + }, + leading: Text( + '封面', + style: leadingStyle, + ), + trailing: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, children: [ - TextSpan( - text: '*', - style: TextStyle( - fontSize: 14, - color: theme.colorScheme.error, + if (_cover?.isNotEmpty == true) + Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: NetworkImgLayer( + src: _cover, + height: 55, + width: 88, + borderRadius: const BorderRadius.all( + Radius.circular(6), + ), + ), ), - ), - const TextSpan( - text: '名称', - style: TextStyle(fontSize: 14), + Icon( + Icons.keyboard_arrow_right, + color: theme.colorScheme.outline, ), ], ), - ), - ), - Expanded( - child: TextField( - autofocus: true, - readOnly: _attr != null && BiliUtils.isDefaultFav(_attr!), - controller: _titleController, - style: TextStyle( - fontSize: 14, - color: _attr != null && BiliUtils.isDefaultFav(_attr!) - ? theme.colorScheme.outline - : null, - ), - inputFormatters: [ - LengthLimitingTextInputFormatter(20), - ], - decoration: InputDecoration( - isDense: true, - hintText: '名称', - hintStyle: TextStyle( - fontSize: 14, - color: theme.colorScheme.outline, - ), - border: const OutlineInputBorder( - borderSide: BorderSide.none, - gapPadding: 0, - ), - contentPadding: EdgeInsets.zero, - ), - ), - ), - ], - ), - ), - if (_attr == null || !BiliUtils.isDefaultFav(_attr!)) + ); + }, + ), ListTile( tileColor: theme.colorScheme.onInverseSurface, title: Row( - crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 55, - child: Text( - '简介', - style: TextStyle( - fontSize: 14, - color: theme.colorScheme.onSurfaceVariant, + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: '*', + style: TextStyle( + fontSize: 14, + color: theme.colorScheme.error, + ), + ), + const TextSpan( + text: '名称', + style: TextStyle(fontSize: 14), + ), + ], ), ), ), Expanded( child: TextField( - minLines: 6, - maxLines: 6, - controller: _introController, - style: const TextStyle(fontSize: 14), + autofocus: true, + readOnly: _attr != null && BiliUtils.isDefaultFav(_attr!), + controller: _titleController, + style: TextStyle( + fontSize: 14, + color: _attr != null && BiliUtils.isDefaultFav(_attr!) + ? theme.colorScheme.outline + : null, + ), inputFormatters: [ - LengthLimitingTextInputFormatter(200), + LengthLimitingTextInputFormatter(20), ], decoration: InputDecoration( isDense: true, - hintText: '可填写简介', + hintText: '名称', hintStyle: TextStyle( fontSize: 14, color: theme.colorScheme.outline, @@ -358,32 +318,76 @@ class _CreateFavPageState extends State { ], ), ), - Builder( - builder: (context) { - void onTap() { - _isPublic = !_isPublic; - (context as Element).markNeedsBuild(); - } - - return ListTile( - onTap: onTap, + if (_attr == null || !BiliUtils.isDefaultFav(_attr!)) + ListTile( tileColor: theme.colorScheme.onInverseSurface, - leading: Text( - '公开', - style: leadingStyle, + title: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 55, + child: Text( + '简介', + style: TextStyle( + fontSize: 14, + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + Expanded( + child: TextField( + minLines: 6, + maxLines: 6, + controller: _introController, + style: const TextStyle(fontSize: 14), + inputFormatters: [ + LengthLimitingTextInputFormatter(200), + ], + decoration: InputDecoration( + isDense: true, + hintText: '可填写简介', + hintStyle: TextStyle( + fontSize: 14, + color: theme.colorScheme.outline, + ), + border: const OutlineInputBorder( + borderSide: BorderSide.none, + gapPadding: 0, + ), + contentPadding: EdgeInsets.zero, + ), + ), + ), + ], ), - trailing: Transform.scale( - alignment: Alignment.centerRight, - scale: 0.8, - child: Switch( - value: _isPublic, - onChanged: (value) => onTap(), + ), + Builder( + builder: (context) { + void onTap() { + _isPublic = !_isPublic; + (context as Element).markNeedsBuild(); + } + + return ListTile( + onTap: onTap, + tileColor: theme.colorScheme.onInverseSurface, + leading: Text( + '公开', + style: leadingStyle, ), - ), - ); - }, - ), - ], + trailing: Transform.scale( + alignment: Alignment.centerRight, + scale: 0.8, + child: Switch( + value: _isPublic, + onChanged: (value) => onTap(), + ), + ), + ); + }, + ), + ], + ), ), ); } diff --git a/lib/pages/live_room/widgets/bottom_control.dart b/lib/pages/live_room/widgets/bottom_control.dart index c1e009701..a4d558d5f 100644 --- a/lib/pages/live_room/widgets/bottom_control.dart +++ b/lib/pages/live_room/widgets/bottom_control.dart @@ -43,13 +43,13 @@ class _BottomControlState extends State with HeaderMixin { @override Widget build(BuildContext context) { final isFullScreen = plPlayerController.isFullScreen.value; - return AppBar( - backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - primary: false, - automaticallyImplyLeading: false, - titleSpacing: 14, - title: Row( + return Padding( + padding: const .only( + left: 14, + right: 14, + bottom: (kToolbarHeight - 30) / 2, + ), + child: Row( children: [ PlayOrPauseButton(plPlayerController: plPlayerController), ComBtn( diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index ade55f0bb..604b20818 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -109,17 +109,20 @@ class _LiveHeaderControlState extends State ); } child = Expanded(child: child); - return AppBar( - backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - primary: false, - automaticallyImplyLeading: false, - titleSpacing: 14, - title: Row( + + const btnHeight = 30.0; + + return Padding( + padding: const .only( + left: 14, + right: 14, + top: (kToolbarHeight - btnHeight) / 2, + ), + child: Row( children: [ if (isFullScreen || plPlayerController.isDesktopPip) ComBtn( - height: 30, + height: btnHeight, tooltip: '返回', icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15), onTap: () { @@ -137,7 +140,7 @@ class _LiveHeaderControlState extends State Obx(() { final isAlwaysOnTop = plPlayerController.isAlwaysOnTop.value; return ComBtn( - height: 30, + height: btnHeight, tooltip: '${isAlwaysOnTop ? '取消' : ''}置顶', icon: isAlwaysOnTop ? const Icon( @@ -155,7 +158,7 @@ class _LiveHeaderControlState extends State }), if (isFullScreen || PlatformUtils.isDesktop) ComBtn( - height: 30, + height: btnHeight, tooltip: '发弹幕', icon: const Icon( size: 18, @@ -166,7 +169,7 @@ class _LiveHeaderControlState extends State ), if (Platform.isAndroid || (PlatformUtils.isDesktop && !isFullScreen)) ComBtn( - height: 30, + height: btnHeight, tooltip: '画中画', onTap: () { if (PlatformUtils.isDesktop) { @@ -185,7 +188,7 @@ class _LiveHeaderControlState extends State ), Obx( () => ComBtn( - height: 30, + height: btnHeight, tooltip: '仅播放音频', onTap: () { plPlayerController.onlyPlayAudio.toggle(); @@ -209,7 +212,7 @@ class _LiveHeaderControlState extends State final continuePlayInBackground = plPlayerController.continuePlayInBackground.value; return ComBtn( - height: 30, + height: btnHeight, tooltip: '${continuePlayInBackground ? '关闭' : ''}后台播放', onTap: plPlayerController.setContinuePlayInBackground, icon: continuePlayInBackground @@ -226,7 +229,7 @@ class _LiveHeaderControlState extends State ); }), ComBtn( - height: 30, + height: btnHeight, tooltip: '定时关闭', onTap: () => shutdownTimerService.showScheduleExitDialog( context, diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index f0e6383c2..8e1730a39 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -2,7 +2,9 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/dial_prefix.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/login/controller.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; @@ -515,7 +517,7 @@ class _LoginPageState extends State { MediaQuery.viewPaddingOf(context).copyWith(top: 0) + const EdgeInsets.only(bottom: 25); final isLandscape = !MediaQuery.sizeOf(context).isPortrait; - return Scaffold( + return SimpleScaffold( appBar: AppBar( leading: IconButton( tooltip: '关闭', @@ -586,14 +588,16 @@ class _LoginPageState extends State { } return false; }, - child: tabBarView( - controller: _loginPageCtr.tabController, - children: [ - tabViewOuter(loginByPassword(theme)), - tabViewOuter(loginBySmS(theme)), - tabViewOuter(loginByQRCode(theme)), - tabViewOuter(loginByCookie(theme)), - ], + child: ViewInsetsSafeArea( + child: tabBarView( + controller: _loginPageCtr.tabController, + children: [ + tabViewOuter(loginByPassword(theme)), + tabViewOuter(loginBySmS(theme)), + tabViewOuter(loginByQRCode(theme)), + tabViewOuter(loginByCookie(theme)), + ], + ), ), ), ); diff --git a/lib/pages/login_devices/view.dart b/lib/pages/login_devices/view.dart index f378595f9..10738fea6 100644 --- a/lib/pages/login_devices/view.dart +++ b/lib/pages/login_devices/view.dart @@ -45,27 +45,30 @@ class LoginDevicesPageState extends State { ColorScheme colorScheme, LoadingState?> loadingState, ) { - late final divider = Divider( - height: 1, - color: colorScheme.outline.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => const SliverToBoxAdapter(), - Success?>(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemBuilder: (context, index) { - return _buildItem(colorScheme, response[index]); - }, - itemCount: response.length, - separatorBuilder: (_, _) => divider, - ) - : HttpError(onReload: _controller.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return const SliverToBoxAdapter(); + case Success?>(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + height: 1, + color: colorScheme.outline.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemBuilder: (context, index) { + return _buildItem(colorScheme, response[index]); + }, + itemCount: response.length, + separatorBuilder: (_, _) => divider, + ); + } + return HttpError(onReload: _controller.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } Widget _buildItem(ColorScheme colorScheme, LoginDevice item) { diff --git a/lib/pages/msg_feed_top/at_me/view.dart b/lib/pages/msg_feed_top/at_me/view.dart index 94da53f3e..e8a5be386 100644 --- a/lib/pages/msg_feed_top/at_me/view.dart +++ b/lib/pages/msg_feed_top/at_me/view.dart @@ -73,114 +73,117 @@ class _AtMePageState extends State { ThemeData theme, LoadingState?> loadingState, ) { - late final divider = Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const MsgFeedTopSkeleton(), - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemCount: response.length, - itemBuilder: (context, int index) { - if (index == response.length - 1) { - _atMeController.onLoadMore(); + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const MsgFeedTopSkeleton(), + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemCount: response.length, + itemBuilder: (context, int index) { + if (index == response.length - 1) { + _atMeController.onLoadMore(); + } + final item = response[index]; + void onLongPress() => showConfirmDialog( + context: context, + title: const Text('确定删除该通知?'), + onConfirm: () => _atMeController.onRemove(item.id!, index), + ); + return ListTile( + safeArea: true, + onTap: () { + String? nativeUri = item.item?.nativeUri; + if (nativeUri == null || + nativeUri.isEmpty || + nativeUri.startsWith('?')) { + return; } - final item = response[index]; - void onLongPress() => showConfirmDialog( - context: context, - title: const Text('确定删除该通知?'), - onConfirm: () => _atMeController.onRemove(item.id!, index), - ); - return ListTile( - safeArea: true, - onTap: () { - String? nativeUri = item.item?.nativeUri; - if (nativeUri == null || - nativeUri.isEmpty || - nativeUri.startsWith('?')) { - return; - } - PiliScheme.routePushFromUrl(nativeUri); - }, - onLongPress: onLongPress, - onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, - leading: GestureDetector( - onTap: () => Get.toNamed('/member?mid=${item.user?.mid}'), - child: NetworkImgLayer( + PiliScheme.routePushFromUrl(nativeUri); + }, + onLongPress: onLongPress, + onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + leading: GestureDetector( + onTap: () => Get.toNamed('/member?mid=${item.user?.mid}'), + child: NetworkImgLayer( + width: 45, + height: 45, + type: ImageType.avatar, + src: item.user?.avatar, + ), + ), + title: Text.rich( + TextSpan( + children: [ + TextSpan( + text: "${item.user?.nickname}", + style: theme.textTheme.titleSmall!.copyWith( + color: theme.colorScheme.primary, + ), + ), + TextSpan( + text: " 在${item.item?.business}中@了我", + style: theme.textTheme.titleSmall!.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item.item?.sourceContent?.isNotEmpty == true) ...[ + const SizedBox(height: 4), + Text( + item.item!.sourceContent!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodyMedium!.copyWith( + color: theme.colorScheme.outline, + ), + ), + ], + const SizedBox(height: 4), + Text( + DateFormatUtils.dateFormat(item.atTime), + style: theme.textTheme.bodyMedium!.copyWith( + fontSize: 13, + color: theme.colorScheme.outline, + ), + ), + ], + ), + trailing: item.item?.image?.isNotEmpty == true + ? NetworkImgLayer( width: 45, height: 45, - type: ImageType.avatar, - src: item.user?.avatar, - ), - ), - title: Text.rich( - TextSpan( - children: [ - TextSpan( - text: "${item.user?.nickname}", - style: theme.textTheme.titleSmall!.copyWith( - color: theme.colorScheme.primary, - ), - ), - TextSpan( - text: " 在${item.item?.business}中@了我", - style: theme.textTheme.titleSmall!.copyWith( - color: theme.colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (item.item?.sourceContent?.isNotEmpty == true) ...[ - const SizedBox(height: 4), - Text( - item.item!.sourceContent!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyMedium!.copyWith( - color: theme.colorScheme.outline, - ), - ), - ], - const SizedBox(height: 4), - Text( - DateFormatUtils.dateFormat(item.atTime), - style: theme.textTheme.bodyMedium!.copyWith( - fontSize: 13, - color: theme.colorScheme.outline, - ), + src: item.item?.image, + borderRadius: const BorderRadius.all( + Radius.circular(8), ), - ], - ), - trailing: item.item?.image?.isNotEmpty == true - ? NetworkImgLayer( - width: 45, - height: 45, - src: item.item?.image, - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - ) - : null, - ); - }, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _atMeController.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _atMeController.onReload, - ), - }; + ) + : null, + ); + }, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _atMeController.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _atMeController.onReload, + ); + } } } diff --git a/lib/pages/msg_feed_top/like_detail/view.dart b/lib/pages/msg_feed_top/like_detail/view.dart index e793a96d7..78a53b115 100644 --- a/lib/pages/msg_feed_top/like_detail/view.dart +++ b/lib/pages/msg_feed_top/like_detail/view.dart @@ -55,45 +55,48 @@ class _LikeDetailPageState extends State { ThemeData theme, LoadingState?> loadingState, ) { - late final divider = Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const MsgFeedTopSkeleton(), - ), - Success(:final response) => SliverMainAxisGroup( - slivers: [ - if (_controller.card != null) ...[ - _buildCard(_controller.card!), - SliverToBoxAdapter( - child: Divider( - height: 1, - color: Colors.grey.withValues(alpha: 0.1), + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const MsgFeedTopSkeleton(), + ); + case Success(:final response): + final divider = Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withValues(alpha: 0.1), + ); + return SliverMainAxisGroup( + slivers: [ + if (_controller.card != null) ...[ + _buildCard(_controller.card!), + SliverToBoxAdapter( + child: Divider( + height: 1, + color: Colors.grey.withValues(alpha: 0.1), + ), ), + ], + SliverList.separated( + itemCount: response!.length, + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return _buildItem(theme, response[index]); + }, + separatorBuilder: (context, index) => divider, ), ], - SliverList.separated( - itemCount: response!.length, - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return _buildItem(theme, response[index]); - }, - separatorBuilder: (context, index) => divider, - ), - ], - ), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + ); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } Widget _buildCard(MsgLikeDetailCard card) { diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart index c4d7e9862..193668360 100644 --- a/lib/pages/msg_feed_top/like_me/view.dart +++ b/lib/pages/msg_feed_top/like_me/view.dart @@ -72,66 +72,69 @@ class _LikeMePageState extends State { } Widget _buildBody(ThemeData theme, LoadingState loadingState) { - late final divider = Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const MsgFeedTopSkeleton(), - ), - Success(:final response) => Builder( - builder: (context) { - Pair, List> pair = response; - List latest = pair.first; - List total = pair.second; - if (latest.isNotEmpty || total.isNotEmpty) { - return SliverMainAxisGroup( - slivers: [ - if (latest.isNotEmpty) ...[ - _buildHeader(theme, '最新'), - SliverList.separated( - itemBuilder: (context, index) { - if (total.isEmpty && index == latest.length - 1) { - _likeMeController.onLoadMore(); - } - return _buildItem(theme, latest[index], (id) { - _likeMeController.onRemove(id, index, true); - }); - }, - itemCount: latest.length, - separatorBuilder: (context, index) => divider, - ), + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const MsgFeedTopSkeleton(), + ); + case Success(:final response): + final divider = Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withValues(alpha: 0.1), + ); + return Builder( + builder: (context) { + Pair, List> pair = response; + List latest = pair.first; + List total = pair.second; + if (latest.isNotEmpty || total.isNotEmpty) { + return SliverMainAxisGroup( + slivers: [ + if (latest.isNotEmpty) ...[ + _buildHeader(theme, '最新'), + SliverList.separated( + itemBuilder: (context, index) { + if (total.isEmpty && index == latest.length - 1) { + _likeMeController.onLoadMore(); + } + return _buildItem(theme, latest[index], (id) { + _likeMeController.onRemove(id, index, true); + }); + }, + itemCount: latest.length, + separatorBuilder: (context, index) => divider, + ), + ], + if (total.isNotEmpty) ...[ + _buildHeader(theme, '累计'), + SliverList.separated( + itemBuilder: (context, index) { + if (index == total.length - 1) { + _likeMeController.onLoadMore(); + } + return _buildItem(theme, total[index], (id) { + _likeMeController.onRemove(id, index, false); + }); + }, + itemCount: total.length, + separatorBuilder: (context, index) => divider, + ), + ], ], - if (total.isNotEmpty) ...[ - _buildHeader(theme, '累计'), - SliverList.separated( - itemBuilder: (context, index) { - if (index == total.length - 1) { - _likeMeController.onLoadMore(); - } - return _buildItem(theme, total[index], (id) { - _likeMeController.onRemove(id, index, false); - }); - }, - itemCount: total.length, - separatorBuilder: (context, index) => divider, - ), - ], - ], - ); - } - return HttpError(onReload: _likeMeController.onReload); - }, - ), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _likeMeController.onReload, - ), - }; + ); + } + return HttpError(onReload: _likeMeController.onReload); + }, + ); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _likeMeController.onReload, + ); + } } Widget _buildHeader(ThemeData theme, String title) { diff --git a/lib/pages/msg_feed_top/reply_me/view.dart b/lib/pages/msg_feed_top/reply_me/view.dart index 7572407e3..b51042117 100644 --- a/lib/pages/msg_feed_top/reply_me/view.dart +++ b/lib/pages/msg_feed_top/reply_me/view.dart @@ -73,136 +73,137 @@ class _ReplyMePageState extends State { ThemeData theme, LoadingState?> loadingState, ) { - late final divider = Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const MsgFeedTopSkeleton(), - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemCount: response.length, - itemBuilder: (context, int index) { - if (index == response.length - 1) { - _replyMeController.onLoadMore(); + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const MsgFeedTopSkeleton(), + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemCount: response.length, + itemBuilder: (context, int index) { + if (index == response.length - 1) { + _replyMeController.onLoadMore(); + } + + MsgReplyItem item = response[index]; + + void onLongPress() => showConfirmDialog( + context: context, + title: const Text('确定删除该通知?'), + onConfirm: () => _replyMeController.onRemove(item.id, index), + ); + + return ListTile( + safeArea: true, + onTap: () { + String? nativeUri = item.item?.nativeUri; + if (nativeUri == null || + nativeUri.isEmpty || + nativeUri.startsWith('?')) { + return; } - - MsgReplyItem item = response[index]; - - void onLongPress() => showConfirmDialog( - context: context, - title: const Text('确定删除该通知?'), - onConfirm: () => - _replyMeController.onRemove(item.id, index), - ); - - return ListTile( - safeArea: true, - onTap: () { - String? nativeUri = item.item?.nativeUri; - if (nativeUri == null || - nativeUri.isEmpty || - nativeUri.startsWith('?')) { - return; - } - PiliScheme.routePushFromUrl( - nativeUri, - businessId: item.item?.businessId, - oid: item.item?.subjectId, - ); - }, - onLongPress: onLongPress, - onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, - leading: GestureDetector( - onTap: () => Get.toNamed('/member?mid=${item.user?.mid}'), - child: NetworkImgLayer( - width: 45, - height: 45, - type: ImageType.avatar, - src: item.user?.avatar, - ), - ), - title: Text.rich( - TextSpan( - children: [ - TextSpan( - text: "${item.user?.nickname}", - style: theme.textTheme.titleSmall!.copyWith( - color: theme.colorScheme.primary, - ), - ), - if (item.isMulti == 1) - TextSpan( - text: " 等人", - style: theme.textTheme.titleSmall!.copyWith( - fontSize: 12, - ), - ), - TextSpan( - text: - " 对我的${item.item?.business}发布了${item.counts}条评论", - style: theme.textTheme.titleSmall!.copyWith( - color: theme.colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text( - item.item?.sourceContent ?? "", - style: theme.textTheme.bodyMedium, - ), - const SizedBox(height: 4), - if (item.item?.targetReplyContent != null && - item.item?.targetReplyContent != "") - Text( - "| ${item.item?.targetReplyContent}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.labelMedium!.copyWith( - color: theme.colorScheme.outline, - height: 1.5, - ), - ), - if (item.item?.rootReplyContent != null && - item.item?.rootReplyContent != "") - Text( - " | ${item.item?.rootReplyContent}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.labelMedium!.copyWith( - color: theme.colorScheme.outline, - height: 1.5, - ), - ), - Text( - DateFormatUtils.dateFormat(item.replyTime), - style: theme.textTheme.bodyMedium!.copyWith( - fontSize: 13, - color: theme.colorScheme.outline, - ), - ), - ], - ), + PiliScheme.routePushFromUrl( + nativeUri, + businessId: item.item?.businessId, + oid: item.item?.subjectId, ); }, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _replyMeController.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _replyMeController.onReload, - ), - }; + onLongPress: onLongPress, + onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + leading: GestureDetector( + onTap: () => Get.toNamed('/member?mid=${item.user?.mid}'), + child: NetworkImgLayer( + width: 45, + height: 45, + type: ImageType.avatar, + src: item.user?.avatar, + ), + ), + title: Text.rich( + TextSpan( + children: [ + TextSpan( + text: "${item.user?.nickname}", + style: theme.textTheme.titleSmall!.copyWith( + color: theme.colorScheme.primary, + ), + ), + if (item.isMulti == 1) + TextSpan( + text: " 等人", + style: theme.textTheme.titleSmall!.copyWith( + fontSize: 12, + ), + ), + TextSpan( + text: " 对我的${item.item?.business}发布了${item.counts}条评论", + style: theme.textTheme.titleSmall!.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + item.item?.sourceContent ?? "", + style: theme.textTheme.bodyMedium, + ), + const SizedBox(height: 4), + if (item.item?.targetReplyContent != null && + item.item?.targetReplyContent != "") + Text( + "| ${item.item?.targetReplyContent}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelMedium!.copyWith( + color: theme.colorScheme.outline, + height: 1.5, + ), + ), + if (item.item?.rootReplyContent != null && + item.item?.rootReplyContent != "") + Text( + " | ${item.item?.rootReplyContent}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelMedium!.copyWith( + color: theme.colorScheme.outline, + height: 1.5, + ), + ), + Text( + DateFormatUtils.dateFormat(item.replyTime), + style: theme.textTheme.bodyMedium!.copyWith( + fontSize: 13, + color: theme.colorScheme.outline, + ), + ), + ], + ), + ); + }, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _replyMeController.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _replyMeController.onReload, + ); + } } } diff --git a/lib/pages/msg_feed_top/sys_msg/view.dart b/lib/pages/msg_feed_top/sys_msg/view.dart index d58d9fa04..1208dba18 100644 --- a/lib/pages/msg_feed_top/sys_msg/view.dart +++ b/lib/pages/msg_feed_top/sys_msg/view.dart @@ -58,79 +58,84 @@ class _SysMsgPageState extends State { ThemeData theme, LoadingState?> loadingState, ) { - late final divider = Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverSafeArea( - sliver: SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const MsgFeedSysMsgSkeleton(), - ), - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemCount: response.length, - itemBuilder: (context, int index) { - if (index == response.length - 1) { - _sysMsgController.onLoadMore(); - } - final item = response[index]; - void onLongPress() => showConfirmDialog( - context: context, - title: const Text('确定删除该通知?'), - onConfirm: () => _sysMsgController.onRemove(item.id, index), - ); - return ListTile( - safeArea: true, - onLongPress: onLongPress, - onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, - title: Text( - "${item.title}", - style: theme.textTheme.titleMedium, - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text.rich( - _buildContent(theme, item.content ?? ''), - style: TextStyle( - fontSize: 14, - color: theme.colorScheme.onSurface.withValues( - alpha: 0.85, - ), - ), + switch (loadingState) { + case Loading(): + return SliverSafeArea( + top: false, + bottom: false, + sliver: SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const MsgFeedSysMsgSkeleton(), + ), + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemCount: response.length, + itemBuilder: (context, int index) { + if (index == response.length - 1) { + _sysMsgController.onLoadMore(); + } + final item = response[index]; + void onLongPress() => showConfirmDialog( + context: context, + title: const Text('确定删除该通知?'), + onConfirm: () => _sysMsgController.onRemove(item.id, index), + ); + return ListTile( + safeArea: true, + onLongPress: onLongPress, + onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + title: Text( + "${item.title}", + style: theme.textTheme.titleMedium, + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text.rich( + _buildContent(theme, item.content ?? ''), + style: TextStyle( + fontSize: 14, + color: theme.colorScheme.onSurface.withValues( + alpha: 0.85, ), - const SizedBox(height: 5), - Align( - alignment: Alignment.centerRight, - child: Text( - "${item.timeAt}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyMedium!.copyWith( - fontSize: 13, - color: theme.colorScheme.outline, - ), - ), - ), - ], + ), ), - ); - }, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _sysMsgController.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _sysMsgController.onReload, - ), - }; + const SizedBox(height: 5), + Align( + alignment: Alignment.centerRight, + child: Text( + "${item.timeAt}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodyMedium!.copyWith( + fontSize: 13, + color: theme.colorScheme.outline, + ), + ), + ), + ], + ), + ); + }, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _sysMsgController.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _sysMsgController.onReload, + ); + } } InlineSpan _buildContent(ThemeData theme, String content) { diff --git a/lib/pages/pgc_review/child/view.dart b/lib/pages/pgc_review/child/view.dart index a4a5cc0e1..fb01b7dc6 100644 --- a/lib/pages/pgc_review/child/view.dart +++ b/lib/pages/pgc_review/child/view.dart @@ -89,34 +89,37 @@ class _PgcReviewChildPageState extends State ThemeData theme, LoadingState?> loadingState, ) { - late final divider = Divider( - height: 1, - color: theme.colorScheme.outline.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverPrototypeExtentList.builder( - prototypeItem: const VideoReplySkeleton(), - itemBuilder: (_, _) => const VideoReplySkeleton(), - itemCount: 8, - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return _itemWidget(theme, index, response[index]); - }, - itemCount: response.length, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _controller.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return SliverPrototypeExtentList.builder( + prototypeItem: const VideoReplySkeleton(), + itemBuilder: (_, _) => const VideoReplySkeleton(), + itemCount: 8, + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + height: 1, + color: theme.colorScheme.outline.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return _itemWidget(theme, index, response[index]); + }, + itemCount: response.length, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _controller.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } Widget _itemWidget(ThemeData theme, int index, PgcReviewItemModel item) { diff --git a/lib/pages/pgc_review/post/view.dart b/lib/pages/pgc_review/post/view.dart index 34a6079b1..735a84f29 100644 --- a/lib/pages/pgc_review/post/view.dart +++ b/lib/pages/pgc_review/post/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/custom_icon.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/http/pgc.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:flutter/material.dart'; @@ -179,37 +180,36 @@ class _PgcReviewPostPanelState extends State { ), ), ), - Container( - padding: EdgeInsets.only( - left: 12, - right: 12, - top: 6, - bottom: - MediaQuery.paddingOf(context).bottom + - MediaQuery.viewInsetsOf(context).bottom + - 6, - ), - width: double.infinity, - decoration: BoxDecoration( - color: theme.colorScheme.onInverseSurface, - border: Border( - top: BorderSide( - width: 0.5, - color: theme.colorScheme.outline.withValues(alpha: 0.1), - ), + ViewInsetsSafeArea( + child: Container( + padding: EdgeInsets.only( + left: 12, + right: 12, + top: 6, + bottom: MediaQuery.paddingOf(context).bottom + 6, ), - ), - child: Obx( - () => FilledButton.tonal( - style: FilledButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - padding: EdgeInsets.zero, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(6)), + width: double.infinity, + decoration: BoxDecoration( + color: theme.colorScheme.onInverseSurface, + border: Border( + top: BorderSide( + width: 0.5, + color: theme.colorScheme.outline.withValues(alpha: 0.1), ), ), - onPressed: _enablePost.value ? _onPost : null, - child: _isMod ? const Text('编辑') : const Text('发布'), + ), + child: Obx( + () => FilledButton.tonal( + style: FilledButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: EdgeInsets.zero, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(6)), + ), + ), + onPressed: _enablePost.value ? _onPost : null, + child: _isMod ? const Text('编辑') : const Text('发布'), + ), ), ), ), diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 61ef7532d..578997a3b 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -3,7 +3,9 @@ import 'dart:convert'; import 'package:PiliPlus/common/widgets/dialog/export_import.dart'; import 'package:PiliPlus/common/widgets/disabled_icon.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/sliver_wrap.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/search/search_rcmd/data.dart'; import 'package:PiliPlus/pages/search/controller.dart'; @@ -57,29 +59,31 @@ class _SearchPageState extends State { ? _buildHotSearch(isTrending: false) : null; - return Scaffold( + return SimpleScaffold( appBar: _buildAppBar, body: Padding( padding: .only(left: padding.left, right: padding.right), - child: CustomScrollView( - slivers: [ - if (_searchController.searchSuggestion) _buildSearchSuggest(), - if (isPortrait) ...[ - ?trending, - _buildHistory, - ?rcmd, - ] else if (_searchController.enableTrending || - _searchController.enableSearchRcmd) - SliverCrossAxisGroup( - slivers: [ - SliverMainAxisGroup(slivers: [?trending, ?rcmd]), - _buildHistory, - ], - ) - else - _buildHistory, - SliverPadding(padding: .only(bottom: padding.bottom)), - ], + child: ViewInsetsSafeArea( + child: CustomScrollView( + slivers: [ + if (_searchController.searchSuggestion) _buildSearchSuggest(), + if (isPortrait) ...[ + ?trending, + _buildHistory, + ?rcmd, + ] else if (_searchController.enableTrending || + _searchController.enableSearchRcmd) + SliverCrossAxisGroup( + slivers: [ + SliverMainAxisGroup(slivers: [?trending, ?rcmd]), + _buildHistory, + ], + ) + else + _buildHistory, + SliverPadding(padding: .only(bottom: padding.bottom)), + ], + ), ), ), ); diff --git a/lib/pages/search_trending/view.dart b/lib/pages/search_trending/view.dart index 8e8bf1167..6408f2664 100644 --- a/lib/pages/search_trending/view.dart +++ b/lib/pages/search_trending/view.dart @@ -129,84 +129,87 @@ class _SearchTrendingPageState extends State { ThemeData theme, LoadingState?> loadingState, ) { - late final divider = Divider( - height: 1, - indent: 48, - color: theme.colorScheme.outline.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => linearLoading, - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemCount: response.length, - itemBuilder: (context, index) { - final item = response[index]; - return ListTile( - dense: true, - onTap: () => Get.toNamed( - '/searchResult', - parameters: { - 'keyword': item.keyword!, - }, - ), - leading: index < _controller.topCount - ? const Icon( - size: 17, - Icons.vertical_align_top_outlined, - color: Color(0xFFd1403e), - ) - : Text( - '${index + 1 - _controller.topCount}', - style: TextStyle( - fontWeight: .bold, - color: ColourUtils.index2Color( - index - _controller.topCount, - theme.colorScheme.outline, - ), - fontSize: 17, - fontStyle: FontStyle.italic, - ), - ), - title: Row( - children: [ - Flexible( - child: Text( - item.keyword!, - maxLines: 1, - overflow: .ellipsis, - strutStyle: const StrutStyle(height: 1, leading: 0), - style: const TextStyle(height: 1, fontSize: 15), + switch (loadingState) { + case Loading(): + return linearLoading; + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + height: 1, + indent: 48, + color: theme.colorScheme.outline.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemCount: response.length, + itemBuilder: (context, index) { + final item = response[index]; + return ListTile( + dense: true, + onTap: () => Get.toNamed( + '/searchResult', + parameters: { + 'keyword': item.keyword!, + }, + ), + leading: index < _controller.topCount + ? const Icon( + size: 17, + Icons.vertical_align_top_outlined, + color: Color(0xFFd1403e), + ) + : Text( + '${index + 1 - _controller.topCount}', + style: TextStyle( + fontWeight: .bold, + color: ColourUtils.index2Color( + index - _controller.topCount, + theme.colorScheme.outline, ), + fontSize: 17, + fontStyle: FontStyle.italic, ), - if (item.icon?.isNotEmpty == true) ...[ - const SizedBox(width: 4), - CachedNetworkImage( - height: 16, - memCacheHeight: 16.cacheSize(context), - imageUrl: ImageUtils.thumbnailUrl(item.icon!), - placeholder: (_, _) => const SizedBox.shrink(), - ), - ] else if (item.showLiveIcon == true) ...[ - const SizedBox(width: 4), - Image.asset( - Assets.livingRect, - width: 51, - height: 16, - cacheHeight: 16.cacheSize(context), - ), - ], - ], + ), + title: Row( + children: [ + Flexible( + child: Text( + item.keyword!, + maxLines: 1, + overflow: .ellipsis, + strutStyle: const StrutStyle(height: 1, leading: 0), + style: const TextStyle(height: 1, fontSize: 15), + ), ), - ); - }, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _controller.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + if (item.icon?.isNotEmpty == true) ...[ + const SizedBox(width: 4), + CachedNetworkImage( + height: 16, + memCacheHeight: 16.cacheSize(context), + imageUrl: ImageUtils.thumbnailUrl(item.icon!), + placeholder: (_, _) => const SizedBox.shrink(), + ), + ] else if (item.showLiveIcon == true) ...[ + const SizedBox(width: 4), + Image.asset( + Assets.livingRect, + width: 51, + height: 16, + cacheHeight: 16.cacheSize(context), + ), + ], + ], + ), + ); + }, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _controller.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } } diff --git a/lib/pages/settings_search/view.dart b/lib/pages/settings_search/view.dart index ba69b26b5..e84be5c77 100644 --- a/lib/pages/settings_search/view.dart +++ b/lib/pages/settings_search/view.dart @@ -1,4 +1,6 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/pages/search/controller.dart' show DebounceStreamState; import 'package:PiliPlus/pages/setting/models/extra_settings.dart'; @@ -59,7 +61,7 @@ class _SettingsSearchPageState @override Widget build(BuildContext context) { - return Scaffold( + return SimpleScaffold( appBar: AppBar( actions: [ IconButton( @@ -88,25 +90,27 @@ class _SettingsSearchPageState ), ), ), - body: CustomScrollView( - slivers: [ - ViewSliverSafeArea( - sliver: Obx( - () => _list.isEmpty - ? const HttpError() - : SliverWaterfallFlow( - gridDelegate: - SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: Grid.smallCardWidth * 2, - ), - delegate: SliverChildBuilderDelegate( - (_, index) => _list[index].widget, - childCount: _list.length, + body: ViewInsetsSafeArea( + child: CustomScrollView( + slivers: [ + ViewSliverSafeArea( + sliver: Obx( + () => _list.isEmpty + ? const HttpError() + : SliverWaterfallFlow( + gridDelegate: + SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: Grid.smallCardWidth * 2, + ), + delegate: SliverChildBuilderDelegate( + (_, index) => _list[index].widget, + childCount: _list.length, + ), ), - ), + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/pages/share/view.dart b/lib/pages/share/view.dart index c0da274d8..6f48bc058 100644 --- a/lib/pages/share/view.dart +++ b/lib/pages/share/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/pages/contact/view.dart'; import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart'; @@ -78,190 +79,190 @@ class _SharePanelState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( - padding: - const EdgeInsets.all(12) + - MediaQuery.paddingOf(context) + - MediaQuery.viewInsetsOf(context), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('分享给'), - iconButton( - size: 32, - iconSize: 18, - tooltip: '关闭', - icon: const Icon(Icons.clear), - onPressed: Get.back, - ), - ], - ), - const SizedBox(height: 5), - Row( - children: [ - Expanded( - child: SelfSizedHorizontalList( - padding: .zero, - itemCount: _userList.length, - controller: _scrollController, - separatorBuilder: (_, _) => const SizedBox(width: 10), - itemBuilder: (context, index) { - final item = _userList[index]; - return Builder( - builder: (context) { - return GestureDetector( - onTap: () { - item.selected = !item.selected; - (context as Element).markNeedsBuild(); - }, - behavior: HitTestBehavior.opaque, - child: SizedBox( - width: 65, - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.topCenter, - children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.all(5), - child: NetworkImgLayer( - width: 40, - height: 40, - src: item.avatar, - type: ImageType.avatar, + padding: const EdgeInsets.all(12) + MediaQuery.paddingOf(context), + child: ViewInsetsSafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('分享给'), + iconButton( + size: 32, + iconSize: 18, + tooltip: '关闭', + icon: const Icon(Icons.clear), + onPressed: Get.back, + ), + ], + ), + const SizedBox(height: 5), + Row( + children: [ + Expanded( + child: SelfSizedHorizontalList( + padding: .zero, + itemCount: _userList.length, + controller: _scrollController, + separatorBuilder: (_, _) => const SizedBox(width: 10), + itemBuilder: (context, index) { + final item = _userList[index]; + return Builder( + builder: (context) { + return GestureDetector( + onTap: () { + item.selected = !item.selected; + (context as Element).markNeedsBuild(); + }, + behavior: HitTestBehavior.opaque, + child: SizedBox( + width: 65, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.topCenter, + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.all(5), + child: NetworkImgLayer( + width: 40, + height: 40, + src: item.avatar, + type: ImageType.avatar, + ), ), - ), - const SizedBox(height: 2), - Text( - item.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12), - ), - ], - ), - if (item.selected) - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: theme.colorScheme.primary - .withValues( - alpha: 0.3, - ), - shape: BoxShape.circle, - border: Border.all( - width: 1.5, - color: theme.colorScheme.primary, + const SizedBox(height: 2), + Text( + item.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 12), ), - ), - child: const Icon( - Icons.check, - size: 20, - color: Colors.white, - ), + ], ), - ], + if (item.selected) + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: theme.colorScheme.primary + .withValues( + alpha: 0.3, + ), + shape: BoxShape.circle, + border: Border.all( + width: 1.5, + color: theme.colorScheme.primary, + ), + ), + child: const Icon( + Icons.check, + size: 20, + color: Colors.white, + ), + ), + ], + ), + ), + ); + }, + ); + }, + ), + ), + GestureDetector( + onTap: () async { + _focusNode.unfocus(); + final UserModel? userModel = await Navigator.of(context) + .push( + GetPageRoute(page: () => const ContactPage()), + ); + if (userModel != null) { + _userList + ..remove(userModel) + ..insert(0, userModel); + _scrollController.jumpToTop(); + setState(() {}); + } + }, + behavior: HitTestBehavior.opaque, + child: SizedBox( + width: 65, + child: Column( + children: [ + Container( + width: 50, + height: 50, + alignment: Alignment.center, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: theme.colorScheme.onInverseSurface, + ), + child: Icon( + Icons.person_add_alt, + color: theme.colorScheme.onSurfaceVariant, ), ), - ); - }, - ); - }, - ), - ), - GestureDetector( - onTap: () async { - _focusNode.unfocus(); - final UserModel? userModel = await Navigator.of(context).push( - GetPageRoute(page: () => const ContactPage()), - ); - if (userModel != null) { - _userList - ..remove(userModel) - ..insert(0, userModel); - _scrollController.jumpToTop(); - setState(() {}); - } - }, - behavior: HitTestBehavior.opaque, - child: SizedBox( - width: 65, - child: Column( - children: [ - Container( - width: 50, - height: 50, - alignment: Alignment.center, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: theme.colorScheme.onInverseSurface, - ), - child: Icon( - Icons.person_add_alt, - color: theme.colorScheme.onSurfaceVariant, - ), ), + const SizedBox(height: 2), + const Text('更多', style: TextStyle(fontSize: 12)), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + focusNode: _focusNode, + minLines: 1, + maxLines: 2, + textInputAction: TextInputAction.newline, + decoration: InputDecoration( + hintText: '说说你的想法吧...', + visualDensity: .standard, + hintStyle: const TextStyle(fontSize: 14), + border: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(20)), ), - const SizedBox(height: 2), - const Text('更多', style: TextStyle(fontSize: 12)), - ], - ), - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - focusNode: _focusNode, - minLines: 1, - maxLines: 2, - textInputAction: TextInputAction.newline, - decoration: InputDecoration( - hintText: '说说你的想法吧...', - visualDensity: .standard, - hintStyle: const TextStyle(fontSize: 14), - border: const OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.all(Radius.circular(20)), + filled: true, + isDense: true, + contentPadding: const .symmetric( + horizontal: 12, + vertical: 6, + ), + fillColor: theme.colorScheme.onInverseSurface, ), - filled: true, - isDense: true, - contentPadding: const .symmetric( - horizontal: 12, - vertical: 6, + inputFormatters: [LengthLimitingTextInputFormatter(100)], + ), + ), + const SizedBox(width: 12), + FilledButton.tonal( + onPressed: _onSend, + style: FilledButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: const VisualDensity( + horizontal: -2, + vertical: -1, ), - fillColor: theme.colorScheme.onInverseSurface, ), - inputFormatters: [LengthLimitingTextInputFormatter(100)], + child: const Text('发送'), ), - ), - const SizedBox(width: 12), - FilledButton.tonal( - onPressed: _onSend, - style: FilledButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: const VisualDensity( - horizontal: -2, - vertical: -1, - ), - ), - child: const Text('发送'), - ), - ], - ), - ], + ], + ), + ], + ), ), ); } diff --git a/lib/pages/video/medialist/view.dart b/lib/pages/video/medialist/view.dart index 654ce0b00..cc527d097 100644 --- a/lib/pages/video/medialist/view.dart +++ b/lib/pages/video/medialist/view.dart @@ -67,42 +67,46 @@ class _MediaListPanelState extends State color: theme.colorScheme.surface, child: Column( children: [ - AppBar( - primary: false, - toolbarHeight: 45, - automaticallyImplyLeading: false, - titleSpacing: 16, - title: Text(widget.panelTitle ?? '稍后再看'), - backgroundColor: Colors.transparent, - actions: [ - iconButton( - iconSize: 20, - tooltip: widget.desc ? '顺序播放' : '倒序播放', - icon: widget.desc - ? const Icon(MdiIcons.sortAscending) - : const Icon(MdiIcons.sortDescending), - onPressed: () { - Get.back(); - widget.onReverse(); - }, - ), - iconButton( - iconSize: 20, - tooltip: '关闭', - icon: const Icon(Icons.close), - onPressed: Get.back, - ), - const SizedBox(width: 14), - ], - shape: Border( - bottom: BorderSide( - color: theme.colorScheme.outline.withValues(alpha: 0.1), + Container( + height: 45, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: theme.colorScheme.outline.withValues(alpha: 0.1), + ), ), ), + child: Row( + children: [ + const SizedBox(width: 16), + Expanded( + child: Text( + widget.panelTitle ?? '稍后再看', + style: const TextStyle(fontSize: 16), + ), + ), + iconButton( + iconSize: 20, + tooltip: widget.desc ? '顺序播放' : '倒序播放', + icon: widget.desc + ? const Icon(MdiIcons.sortAscending) + : const Icon(MdiIcons.sortDescending), + onPressed: () { + Get.back(); + widget.onReverse(); + }, + ), + iconButton( + iconSize: 20, + tooltip: '关闭', + icon: const Icon(Icons.close), + onPressed: Get.back, + ), + const SizedBox(width: 14), + ], + ), ), - Expanded( - child: enableSlide ? slideList(theme) : buildList(theme), - ), + Expanded(child: enableSlide ? slideList(theme) : buildList(theme)), ], ), ); diff --git a/lib/pages/video/member/view.dart b/lib/pages/video/member/view.dart index 18cf3ccfd..fdf0c9534 100644 --- a/lib/pages/video/member/view.dart +++ b/lib/pages/video/member/view.dart @@ -49,6 +49,7 @@ class _HorizontalMemberPageState extends State { late final HorizontalMemberPageController _controller; late final account = Accounts.main; late final String _bvid; + late ColorScheme colorScheme; @override void initState() { @@ -72,20 +73,27 @@ class _HorizontalMemberPageState extends State { } } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + colorScheme = ColorScheme.of(context); + } + @override Widget build(BuildContext context) { - final theme = Theme.of(context); - return Obx( - () => _buildUserPage(theme, _controller.userState.value), + return Material( + child: Obx( + () => _buildUserPage(_controller.userState.value), + ), ); } - Widget _buildUserPage(ThemeData theme, LoadingState userState) { + Widget _buildUserPage(LoadingState userState) { return switch (userState) { Loading() => m3eLoading, Success(:final response) => Column( children: [ - _buildUserInfo(theme, response), + _buildUserInfo(response), Expanded( child: refreshIndicator( onRefresh: _controller.onRefresh, @@ -98,10 +106,7 @@ class _HorizontalMemberPageState extends State { bottom: MediaQuery.viewPaddingOf(context).bottom + 100, ), sliver: Obx( - () => _buildVideoList( - theme, - _controller.loadingState.value, - ), + () => _buildVideoList(_controller.loadingState.value), ), ), ], @@ -121,16 +126,16 @@ class _HorizontalMemberPageState extends State { }; } - Widget _buildHeader(ThemeData theme) { + Widget _buildHeader() { return SliverPinnedHeader( - backgroundColor: theme.colorScheme.surface, + backgroundColor: colorScheme.surface, child: Padding( padding: const EdgeInsets.fromLTRB(12, 4, 6, 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ?_buildCount(), - _buildSortBtn(theme), + _buildSortBtn(), ], ), ), @@ -148,7 +153,7 @@ class _HorizontalMemberPageState extends State { return null; } - Widget _buildSortBtn(ThemeData theme) { + Widget _buildSortBtn() { return TextButton.icon( style: Style.buttonStyle, onPressed: () => _controller @@ -157,20 +162,19 @@ class _HorizontalMemberPageState extends State { icon: Icon( Icons.sort, size: 16, - color: theme.colorScheme.secondary, + color: colorScheme.secondary, ), label: Text( _controller.order.label, style: TextStyle( fontSize: 13, - color: theme.colorScheme.secondary, + color: colorScheme.secondary, ), ), ); } Widget _buildVideoList( - ThemeData theme, LoadingState?> loadingState, ) { return switch (loadingState) { @@ -183,7 +187,7 @@ class _HorizontalMemberPageState extends State { response != null && response.isNotEmpty ? SliverMainAxisGroup( slivers: [ - _buildHeader(theme), + _buildHeader(), SliverFixedExtentList.builder( itemBuilder: (context, index) { if (index == response.length - 1 && _controller.hasNext) { @@ -221,20 +225,20 @@ class _HorizontalMemberPageState extends State { }; } - Widget _buildUserInfo(ThemeData theme, MemberInfoModel memberInfoModel) { + Widget _buildUserInfo(MemberInfoModel memberInfoModel) { return Padding( padding: const .only(left: 16, top: 10, right: 16, bottom: 3), child: Row( spacing: 10, children: [ _buildAvatar(memberInfoModel.face!), - Expanded(child: _buildInfo(theme, memberInfoModel)), + Expanded(child: _buildInfo(memberInfoModel)), ], ), ); } - Column _buildInfo(ThemeData theme, MemberInfoModel memberInfoModel) => Column( + Column _buildInfo(MemberInfoModel memberInfoModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -250,7 +254,7 @@ class _HorizontalMemberPageState extends State { color: (memberInfoModel.vip?.status ?? -1) > 0 && memberInfoModel.vip?.type == 2 - ? theme.colorScheme.vipColor + ? colorScheme.vipColor : null, ), ), @@ -269,7 +273,6 @@ class _HorizontalMemberPageState extends State { children: UserInfoType.values .map( (e) => _buildChildInfo( - theme: theme, type: e, userStat: _controller.userStat, memberInfoModel: memberInfoModel, @@ -281,7 +284,7 @@ class _HorizontalMemberPageState extends State { width: 20, child: VerticalDivider( width: 1, - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ); yield child; @@ -298,10 +301,10 @@ class _HorizontalMemberPageState extends State { child: FilledButton.tonal( style: FilledButton.styleFrom( backgroundColor: memberInfoModel.isFollowed == true - ? theme.colorScheme.onInverseSurface + ? colorScheme.onInverseSurface : null, foregroundColor: memberInfoModel.isFollowed == true - ? theme.colorScheme.outline + ? colorScheme.outline : null, padding: EdgeInsets.zero, tapTargetSize: .shrinkWrap, @@ -359,7 +362,6 @@ class _HorizontalMemberPageState extends State { ); Widget _buildChildInfo({ - required ThemeData theme, required UserInfoType type, required Map userStat, required MemberInfoModel memberInfoModel, @@ -392,7 +394,7 @@ class _HorizontalMemberPageState extends State { '$num${type.title}', style: TextStyle( fontSize: 14, - color: theme.colorScheme.outline, + color: colorScheme.outline, ), ), ); diff --git a/lib/pages/video/note/view.dart b/lib/pages/video/note/view.dart index 7c8baf6c1..c34bdfa40 100644 --- a/lib/pages/video/note/view.dart +++ b/lib/pages/video/note/view.dart @@ -60,24 +60,27 @@ class _NoteListPageState extends State child: MiniScaffold( body: Column( children: [ - SizedBox( + Container( height: 45, - child: AppBar( - primary: false, - automaticallyImplyLeading: false, - titleSpacing: 16, - toolbarHeight: 45, - backgroundColor: Colors.transparent, - title: Obx(() { - final count = _controller.count.value; - return Text('笔记${count == -1 ? '' : '($count)'}'); - }), - shape: Border( + decoration: BoxDecoration( + border: Border( bottom: BorderSide( color: theme.colorScheme.outline.withValues(alpha: 0.1), ), ), - actions: [ + ), + child: Row( + children: [ + const SizedBox(width: 16), + Expanded( + child: Obx(() { + final count = _controller.count.value; + return Text( + '笔记${count == -1 ? '' : '($count)'}', + style: const TextStyle(fontSize: 16), + ); + }), + ), IconButton( tooltip: '关闭', icon: const Icon(Icons.close, size: 20), @@ -177,34 +180,37 @@ class _NoteListPageState extends State ThemeData theme, LoadingState?> loadingState, ) { - late final divider = Divider( - height: 1, - color: theme.colorScheme.outline.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverPrototypeExtentList.builder( - prototypeItem: const VideoReplySkeleton(), - itemBuilder: (_, _) => const VideoReplySkeleton(), - itemCount: 8, - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return _itemWidget(theme, response[index]); - }, - itemCount: response.length, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _controller.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return SliverPrototypeExtentList.builder( + prototypeItem: const VideoReplySkeleton(), + itemBuilder: (_, _) => const VideoReplySkeleton(), + itemCount: 8, + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + height: 1, + color: theme.colorScheme.outline.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return _itemWidget(theme, response[index]); + }, + itemCount: response.length, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _controller.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } Widget _itemWidget(ThemeData theme, VideoNoteItemModel item) { diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index a19bc69a0..d826a6ac2 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -192,44 +192,44 @@ class _PostPanelState extends State @override Widget buildPage(ThemeData theme) { return SimpleScaffold( - appBar: AppBar( - primary: false, - toolbarHeight: 45, - automaticallyImplyLeading: false, - titleSpacing: 16, - title: const Text('提交片段'), - actions: [ - iconButton( - size: 32, - context: context, - tooltip: '添加片段', - onPressed: () { - setState(() { - list.insert( - 0, - PostSegmentModel( - segment: Pair( - first: 0, - second: currentPos(), + appBar: SizedBox( + height: 45, + child: Row( + children: [ + const SizedBox(width: 16), + const Expanded(child: Text('提交片段', style: TextStyle(fontSize: 16))), + iconButton( + size: 32, + context: context, + tooltip: '添加片段', + onPressed: () { + setState(() { + list.insert( + 0, + PostSegmentModel( + segment: Pair( + first: 0, + second: currentPos(), + ), + category: SegmentType.sponsor, + actionType: ActionType.skip, ), - category: SegmentType.sponsor, - actionType: ActionType.skip, - ), - ); - }); - }, - icon: const Icon(Icons.add), - ), - const SizedBox(width: 10), - iconButton( - size: 32, - context: context, - tooltip: '关闭', - onPressed: Get.back, - icon: const Icon(Icons.close), - ), - const SizedBox(width: 16), - ], + ); + }); + }, + icon: const Icon(Icons.add), + ), + const SizedBox(width: 10), + iconButton( + size: 32, + context: context, + tooltip: '关闭', + onPressed: Get.back, + icon: const Icon(Icons.close), + ), + const SizedBox(width: 16), + ], + ), ), body: enableSlide ? slideList(theme) : buildList(theme), fab: list.isEmpty diff --git a/lib/pages/video/view_point/view.dart b/lib/pages/video/view_point/view.dart index 5f398209f..46a1d6e53 100644 --- a/lib/pages/video/view_point/view.dart +++ b/lib/pages/video/view_point/view.dart @@ -36,40 +36,38 @@ class _ViewPointsPageState extends State @override Widget buildPage(ThemeData theme) { return SimpleScaffold( - appBar: AppBar( - primary: false, - automaticallyImplyLeading: false, - titleSpacing: 16, - title: const Text('分段信息'), - toolbarHeight: 45, - actions: [ - const Text( - '分段进度条 ', - style: TextStyle(fontSize: 16), - ), - Obx( - () => Transform.scale( - alignment: Alignment.centerLeft, - scale: 0.8, - child: Switch( - value: videoDetailController.showVP.value, - onChanged: videoDetailController.showVP.call, - ), + appBar: Container( + height: 45, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: theme.colorScheme.outline.withValues(alpha: 0.1), ), ), - iconButton( - context: context, - size: 30, - icon: const Icon(Icons.clear), - tooltip: '关闭', - onPressed: Get.back, - ), - const SizedBox(width: 16), - ], - shape: Border( - bottom: BorderSide( - color: theme.colorScheme.outline.withValues(alpha: 0.1), - ), + ), + child: Row( + children: [ + const SizedBox(width: 16), + const Expanded(child: Text('分段信息', style: TextStyle(fontSize: 16))), + Obx( + () => Transform.scale( + alignment: Alignment.centerLeft, + scale: 0.8, + child: Switch( + value: videoDetailController.showVP.value, + onChanged: videoDetailController.showVP.call, + ), + ), + ), + iconButton( + context: context, + size: 30, + icon: const Icon(Icons.clear), + tooltip: '关闭', + onPressed: Get.back, + ), + const SizedBox(width: 16), + ], ), ), body: enableSlide ? slideList(theme) : buildList(theme), diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index bb80f043b..2aaa5a3a9 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -1767,363 +1767,354 @@ class HeaderControlState extends State title = const Spacer(); } - const btnWidth = 40.0; + const btnWidth = 42.0; const btnHeight = 34.0; const btnStyle = ButtonStyle(padding: WidgetStatePropertyAll(.zero)); - return AppBar( - elevation: 0, - scrolledUnderElevation: 0, - backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - primary: false, - automaticallyImplyLeading: false, - toolbarHeight: showFSActionItem ? 112 : null, - flexibleSpace: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 11), - Row( - children: [ + return Column( + mainAxisSize: .min, + children: [ + const SizedBox(height: 12), + Row( + children: [ + SizedBox( + width: btnWidth, + height: btnHeight, + child: IconButton( + tooltip: '返回', + style: btnStyle, + icon: const Icon( + FontAwesomeIcons.arrowLeft, + size: 15, + color: Colors.white, + ), + onPressed: () => + plPlayerController.onPopInvokedWithResult(false, null), + ), + ), + if (!plPlayerController.isDesktopPip && + (!isFullScreen || !isPortrait)) SizedBox( width: btnWidth, height: btnHeight, child: IconButton( - tooltip: '返回', + tooltip: '返回主页', style: btnStyle, icon: const Icon( - FontAwesomeIcons.arrowLeft, + FontAwesomeIcons.house, size: 15, color: Colors.white, ), - onPressed: () => - plPlayerController.onPopInvokedWithResult(false, null), + onPressed: plPlayerController.onCloseAll, ), ), - if (!plPlayerController.isDesktopPip && - (!isFullScreen || !isPortrait)) - SizedBox( + title, + // show current datetime + ...?timeBatteryWidgets, + if (PlatformUtils.isDesktop && !plPlayerController.isDesktopPip) + Obx(() { + final isAlwaysOnTop = plPlayerController.isAlwaysOnTop.value; + return SizedBox( width: btnWidth, height: btnHeight, child: IconButton( - tooltip: '返回主页', style: btnStyle, - icon: const Icon( - FontAwesomeIcons.house, - size: 15, - color: Colors.white, - ), - onPressed: plPlayerController.onCloseAll, + tooltip: '${isAlwaysOnTop ? '取消' : ''}置顶', + onPressed: () => + plPlayerController.setAlwaysOnTop(!isAlwaysOnTop), + icon: isAlwaysOnTop + ? const Icon( + size: 19, + Icons.push_pin, + color: Colors.white, + ) + : const Icon( + size: 19, + Icons.push_pin_outlined, + color: Colors.white, + ), ), - ), - title, - // show current datetime - ...?timeBatteryWidgets, - if (PlatformUtils.isDesktop && !plPlayerController.isDesktopPip) - Obx(() { - final isAlwaysOnTop = plPlayerController.isAlwaysOnTop.value; - return SizedBox( - width: btnWidth, - height: btnHeight, - child: IconButton( - style: btnStyle, - tooltip: '${isAlwaysOnTop ? '取消' : ''}置顶', - onPressed: () => - plPlayerController.setAlwaysOnTop(!isAlwaysOnTop), - icon: isAlwaysOnTop - ? const Icon( - size: 19, - Icons.push_pin, - color: Colors.white, - ) - : const Icon( - size: 19, - Icons.push_pin_outlined, - color: Colors.white, - ), - ), - ); - }), - if (!isFileSource) ...[ - if (!isFSOrPip) ...[ - if (videoDetailCtr.isUgc) - SizedBox( - width: btnWidth, - height: btnHeight, - child: IconButton( - tooltip: '听音频', - style: btnStyle, - onPressed: videoDetailCtr.toAudioPage, - icon: const Icon( - Icons.headphones_outlined, - size: 19, - color: Colors.white, - ), - ), - ), + ); + }), + if (!isFileSource) ...[ + if (!isFSOrPip) ...[ + if (videoDetailCtr.isUgc) SizedBox( width: btnWidth, height: btnHeight, child: IconButton( - tooltip: '投屏', + tooltip: '听音频', style: btnStyle, - onPressed: videoDetailCtr.onCast, + onPressed: videoDetailCtr.toAudioPage, icon: const Icon( - Icons.cast, + Icons.headphones_outlined, size: 19, color: Colors.white, ), ), ), - ], - if (plPlayerController.enableSponsorBlock) - SizedBox( - width: btnWidth, - height: btnHeight, - child: IconButton( - tooltip: '提交片段', - style: btnStyle, - onPressed: () => videoDetailCtr.onBlock(context), - icon: const Icon( - CustomIcons.shield_play_arrow, - size: 20, - color: Colors.white, - ), + SizedBox( + width: btnWidth, + height: btnHeight, + child: IconButton( + tooltip: '投屏', + style: btnStyle, + onPressed: videoDetailCtr.onCast, + icon: const Icon( + Icons.cast, + size: 19, + color: Colors.white, ), ), - Obx( - () => videoDetailCtr.segmentProgressList.isNotEmpty - ? SizedBox( - width: btnWidth, - height: btnHeight, - child: IconButton( - tooltip: '片段信息', - style: btnStyle, - onPressed: videoDetailCtr.showSBDetail, - icon: const Icon( - MdiIcons.advertisements, - size: 19, - color: Colors.white, - ), + ), + ], + if (plPlayerController.enableSponsorBlock) + SizedBox( + width: btnWidth, + height: btnHeight, + child: IconButton( + tooltip: '提交片段', + style: btnStyle, + onPressed: () => videoDetailCtr.onBlock(context), + icon: const Icon( + CustomIcons.shield_play_arrow, + size: 20, + color: Colors.white, + ), + ), + ), + Obx( + () => videoDetailCtr.segmentProgressList.isNotEmpty + ? SizedBox( + width: btnWidth, + height: btnHeight, + child: IconButton( + tooltip: '片段信息', + style: btnStyle, + onPressed: videoDetailCtr.showSBDetail, + icon: const Icon( + MdiIcons.advertisements, + size: 19, + color: Colors.white, ), - ) - : const SizedBox.shrink(), - ), - ], - if (!isPortrait || isFullScreen || PlatformUtils.isDesktop) ...[ - SizedBox( - width: btnWidth, - height: btnHeight, - child: IconButton( - tooltip: '发弹幕', - style: btnStyle, - onPressed: videoDetailCtr.showShootDanmakuSheet, - icon: const Icon( - Icons.comment_outlined, - size: 19, - color: Colors.white, - ), - ), - ), - SizedBox( - width: btnWidth, - height: btnHeight, - child: Obx( - () { - final enableShowDanmaku = - plPlayerController.enableShowDanmaku.value; - return IconButton( - tooltip: "${enableShowDanmaku ? '关闭' : '开启'}弹幕", - style: btnStyle, - onPressed: () { - final newVal = !enableShowDanmaku; - plPlayerController.enableShowDanmaku.value = newVal; - if (!plPlayerController.tempPlayerConf) { - setting.put( - SettingBoxKey.enableShowDanmaku, - newVal, - ); - } - }, - icon: enableShowDanmaku - ? const Icon( - size: 20, - CustomIcons.dm_on, - color: Colors.white, - ) - : const Icon( - size: 20, - CustomIcons.dm_off, - color: Colors.white, - ), - ); - }, - ), - ), - ], - SizedBox( - width: btnWidth, - height: btnHeight, - child: IconButton( - tooltip: '弹幕设置', - style: btnStyle, - onPressed: showSetDanmaku, - icon: const Icon( - size: 20, - CustomIcons.dm_settings, - color: Colors.white, - ), - ), + ), + ) + : const SizedBox.shrink(), ), - if (Platform.isAndroid || - (PlatformUtils.isDesktop && !isFullScreen)) - SizedBox( - width: btnWidth, - height: btnHeight, - child: IconButton( - tooltip: '画中画', - style: btnStyle, - onPressed: () { - if (PlatformUtils.isDesktop) { - plPlayerController.toggleDesktopPip(); - return; - } - if (AndroidHelper.isPipAvailable) { - plPlayerController.enterPip(); - } - }, - icon: const Icon( - Icons.picture_in_picture_outlined, - size: 19, - color: Colors.white, - ), - ), - ), + ], + if (!isPortrait || isFullScreen || PlatformUtils.isDesktop) ...[ SizedBox( width: btnWidth, height: btnHeight, child: IconButton( - tooltip: "更多设置", + tooltip: '发弹幕', style: btnStyle, - onPressed: showSettingSheet, + onPressed: videoDetailCtr.showShootDanmakuSheet, icon: const Icon( - Icons.more_vert_outlined, + Icons.comment_outlined, size: 19, color: Colors.white, ), ), ), + SizedBox( + width: btnWidth, + height: btnHeight, + child: Obx( + () { + final enableShowDanmaku = + plPlayerController.enableShowDanmaku.value; + return IconButton( + tooltip: "${enableShowDanmaku ? '关闭' : '开启'}弹幕", + style: btnStyle, + onPressed: () { + final newVal = !enableShowDanmaku; + plPlayerController.enableShowDanmaku.value = newVal; + if (!plPlayerController.tempPlayerConf) { + setting.put( + SettingBoxKey.enableShowDanmaku, + newVal, + ); + } + }, + icon: enableShowDanmaku + ? const Icon( + size: 20, + CustomIcons.dm_on, + color: Colors.white, + ) + : const Icon( + size: 20, + CustomIcons.dm_off, + color: Colors.white, + ), + ); + }, + ), + ), ], - ), - if (showFSActionItem) - Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: btnWidth, - height: btnHeight, - child: Obx( - () => ActionItem( - expand: false, - icon: const Icon( - FontAwesomeIcons.thumbsUp, - color: Colors.white, - ), - selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp), - selectStatus: introController.hasLike.value, - semanticsLabel: '点赞', - animation: introController.tripleAnimation, - onStartTriple: () { - plPlayerController.tripling = true; - introController.onStartTriple(); - }, - onCancelTriple: ([bool isTapUp = false]) { - plPlayerController - ..tripling = false - ..hideTaskControls(); - introController.onCancelTriple(isTapUp); - }, - ), + SizedBox( + width: btnWidth, + height: btnHeight, + child: IconButton( + tooltip: '弹幕设置', + style: btnStyle, + onPressed: showSetDanmaku, + icon: const Icon( + size: 20, + CustomIcons.dm_settings, + color: Colors.white, + ), + ), + ), + if (Platform.isAndroid || + (PlatformUtils.isDesktop && !isFullScreen)) + SizedBox( + width: btnWidth, + height: btnHeight, + child: IconButton( + tooltip: '画中画', + style: btnStyle, + onPressed: () { + if (PlatformUtils.isDesktop) { + plPlayerController.toggleDesktopPip(); + return; + } + if (AndroidHelper.isPipAvailable) { + plPlayerController.enterPip(); + } + }, + icon: const Icon( + Icons.picture_in_picture_outlined, + size: 19, + color: Colors.white, ), ), - if (introController case final UgcIntroController ugc) - SizedBox( - width: btnWidth, - height: btnHeight, - child: Obx( - () => ActionItem( - expand: false, - icon: const Icon( - FontAwesomeIcons.thumbsDown, - color: Colors.white, - ), - selectIcon: const Icon( - FontAwesomeIcons.solidThumbsDown, - ), - onTap: () => ugc.handleAction(ugc.actionDislikeVideo), - selectStatus: ugc.hasDislike.value, - semanticsLabel: '点踩', - ), - ), - ), - SizedBox( - width: btnWidth, - height: btnHeight, - child: Obx( - () => ActionItem( - expand: false, - animation: introController.tripleAnimation, - icon: const Icon( - FontAwesomeIcons.b, - color: Colors.white, - ), - selectIcon: const Icon(FontAwesomeIcons.b), - onTap: introController.actionCoinVideo, - selectStatus: introController.hasCoin, - semanticsLabel: '投币', - ), - ), + ), + SizedBox( + width: btnWidth, + height: btnHeight, + child: IconButton( + tooltip: "更多设置", + style: btnStyle, + onPressed: showSettingSheet, + icon: const Icon( + Icons.more_vert_outlined, + size: 19, + color: Colors.white, ), - SizedBox( - width: btnWidth, - height: btnHeight, - child: Obx( - () => ActionItem( - expand: false, - animation: introController.tripleAnimation, - icon: const Icon( - FontAwesomeIcons.star, - color: Colors.white, - ), - selectIcon: const Icon(FontAwesomeIcons.solidStar), - onTap: () => introController.showFavBottomSheet(context), - onLongPress: () => introController.showFavBottomSheet( - context, - isLongPress: true, - ), - selectStatus: introController.hasFav.value, - semanticsLabel: '收藏', - ), - ), - ), - SizedBox( - width: btnWidth, - height: btnHeight, - child: ActionItem( + ), + ), + ], + ), + if (showFSActionItem) + Row( + mainAxisAlignment: .end, + crossAxisAlignment: .start, + children: [ + SizedBox( + width: btnWidth, + height: btnHeight, + child: Obx( + () => ActionItem( expand: false, icon: const Icon( - FontAwesomeIcons.shareFromSquare, + FontAwesomeIcons.thumbsUp, color: Colors.white, ), - onTap: () => introController.actionShareVideo(context), - semanticsLabel: '分享', + selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp), + selectStatus: introController.hasLike.value, + semanticsLabel: '点赞', + animation: introController.tripleAnimation, + onStartTriple: () { + plPlayerController.tripling = true; + introController.onStartTriple(); + }, + onCancelTriple: ([bool isTapUp = false]) { + plPlayerController + ..tripling = false + ..hideTaskControls(); + introController.onCancelTriple(isTapUp); + }, ), ), - ], - ), - ], - ), + ), + if (introController case final UgcIntroController ugc) + SizedBox( + width: btnWidth, + height: btnHeight, + child: Obx( + () => ActionItem( + expand: false, + icon: const Icon( + FontAwesomeIcons.thumbsDown, + color: Colors.white, + ), + selectIcon: const Icon( + FontAwesomeIcons.solidThumbsDown, + ), + onTap: () => ugc.handleAction(ugc.actionDislikeVideo), + selectStatus: ugc.hasDislike.value, + semanticsLabel: '点踩', + ), + ), + ), + SizedBox( + width: btnWidth, + height: btnHeight, + child: Obx( + () => ActionItem( + expand: false, + animation: introController.tripleAnimation, + icon: const Icon( + FontAwesomeIcons.b, + color: Colors.white, + ), + selectIcon: const Icon(FontAwesomeIcons.b), + onTap: introController.actionCoinVideo, + selectStatus: introController.hasCoin, + semanticsLabel: '投币', + ), + ), + ), + SizedBox( + width: btnWidth, + height: btnHeight, + child: Obx( + () => ActionItem( + expand: false, + animation: introController.tripleAnimation, + icon: const Icon( + FontAwesomeIcons.star, + color: Colors.white, + ), + selectIcon: const Icon(FontAwesomeIcons.solidStar), + onTap: () => introController.showFavBottomSheet(context), + onLongPress: () => introController.showFavBottomSheet( + context, + isLongPress: true, + ), + selectStatus: introController.hasFav.value, + semanticsLabel: '收藏', + ), + ), + ), + SizedBox( + width: btnWidth, + height: btnHeight, + child: ActionItem( + expand: false, + icon: const Icon( + FontAwesomeIcons.shareFromSquare, + color: Colors.white, + ), + onTap: () => introController.actionShareVideo(context), + semanticsLabel: '分享', + ), + ), + ], + ), + ], ); } } diff --git a/lib/pages/webdav/view.dart b/lib/pages/webdav/view.dart index 60df06b71..e151c32b0 100644 --- a/lib/pages/webdav/view.dart +++ b/lib/pages/webdav/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/pages/webdav/webdav.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; @@ -39,115 +40,118 @@ class _WebDavSettingPageState extends State { Widget build(BuildContext context) { final showAppBar = widget.showAppBar; final padding = MediaQuery.viewPaddingOf(context); - final viewInsets = MediaQuery.viewInsetsOf(context).bottom; return SimpleScaffold( appBar: showAppBar ? AppBar(title: const Text('WebDAV 设置')) : null, - body: ListView( - padding: padding.copyWith( - top: 20, - left: 20 + (showAppBar ? padding.left : 0), - right: 20 + (showAppBar ? padding.right : 0), - bottom: padding.bottom + viewInsets + 100, + body: ViewInsetsSafeArea( + child: ListView( + padding: padding.copyWith( + top: 20, + left: 20 + (showAppBar ? padding.left : 0), + right: 20 + (showAppBar ? padding.right : 0), + bottom: padding.bottom + 100, + ), + children: [ + TextField( + controller: _uriCtr, + decoration: const InputDecoration( + labelText: '地址', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _usernameCtr, + decoration: const InputDecoration( + labelText: '用户', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _passwordCtr, + autofillHints: const [AutofillHints.password], + decoration: InputDecoration( + labelText: '密码', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + onPressed: () => setState(() => _obscureText = !_obscureText), + icon: _obscureText + ? const Icon(Icons.visibility) + : const Icon(Icons.visibility_off), + ), + ), + obscureText: _obscureText, + ), + const SizedBox(height: 20), + TextField( + controller: _directoryCtr, + decoration: const InputDecoration( + labelText: '路径', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: Style.mdRadius, + ), + ), + onPressed: WebDav().backup, + child: const Text('备份设置'), + ), + ), + const SizedBox(width: 20), + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: Style.mdRadius, + ), + ), + onPressed: WebDav().restore, + child: const Text('恢复设置'), + ), + ), + ], + ), + ], ), - children: [ - TextField( - controller: _uriCtr, - decoration: const InputDecoration( - labelText: '地址', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - TextField( - controller: _usernameCtr, - decoration: const InputDecoration( - labelText: '用户', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - TextField( - controller: _passwordCtr, - autofillHints: const [AutofillHints.password], - decoration: InputDecoration( - labelText: '密码', - border: const OutlineInputBorder(), - suffixIcon: IconButton( - onPressed: () => setState(() => _obscureText = !_obscureText), - icon: _obscureText - ? const Icon(Icons.visibility) - : const Icon(Icons.visibility_off), - ), - ), - obscureText: _obscureText, - ), - const SizedBox(height: 20), - TextField( - controller: _directoryCtr, - decoration: const InputDecoration( - labelText: '路径', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: FilledButton.tonal( - style: FilledButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: Style.mdRadius, - ), - ), - onPressed: WebDav().backup, - child: const Text('备份设置'), - ), - ), - const SizedBox(width: 20), - Expanded( - child: FilledButton.tonal( - style: FilledButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: Style.mdRadius, - ), - ), - onPressed: WebDav().restore, - child: const Text('恢复设置'), - ), - ), - ], - ), - ], ), fab: Padding( padding: .only( right: kFloatingActionButtonMargin + (showAppBar ? padding.right : 0), - bottom: kFloatingActionButtonMargin + padding.bottom + viewInsets, + bottom: kFloatingActionButtonMargin + padding.bottom, ), - child: FloatingActionButton( - child: const Icon(Icons.save), - onPressed: () async { - await GStorage.setting.putAll({ - SettingBoxKey.webdavUri: _uriCtr.text, - SettingBoxKey.webdavUsername: _usernameCtr.text, - SettingBoxKey.webdavPassword: _passwordCtr.text, - SettingBoxKey.webdavDirectory: _directoryCtr.text, - }); - if (_uriCtr.text.isEmpty) { - return; - } - try { - final res = await WebDav().init(); - if (res.first) { - SmartDialog.showToast('配置成功'); - } else { - SmartDialog.showToast('配置失败: ${res.second}'); + child: ViewInsetsSafeArea( + child: FloatingActionButton( + child: const Icon(Icons.save), + onPressed: () async { + await GStorage.setting.putAll({ + SettingBoxKey.webdavUri: _uriCtr.text, + SettingBoxKey.webdavUsername: _usernameCtr.text, + SettingBoxKey.webdavPassword: _passwordCtr.text, + SettingBoxKey.webdavDirectory: _directoryCtr.text, + }); + if (_uriCtr.text.isEmpty) { + return; } - } catch (e) { - SmartDialog.showToast('配置失败: ${e.toString()}'); - return; - } - }, + try { + final res = await WebDav().init(); + if (res.first) { + SmartDialog.showToast('配置成功'); + } else { + SmartDialog.showToast('配置失败: ${res.second}'); + } + } catch (e) { + SmartDialog.showToast('配置失败: ${e.toString()}'); + return; + } + }, + ), ), ), ); diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 5ead8064b..7cefcceec 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -109,44 +109,46 @@ class _WhisperPageState extends State { } Widget _buildBody(LoadingState?> loadingState) { - late final divider = Divider( - indent: 72, - endIndent: 20, - height: 1, - color: Colors.grey.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const WhisperItemSkeleton(), - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemCount: response.length, - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - final item = response[index]; - return WhisperSessionItem( - item: item, - onSetTop: (isTop, id) => - _controller.onSetTop(item, index, isTop, id), - onSetMute: (isMuted, talkerUid) => - _controller.onSetMute(item, isMuted, talkerUid), - onRemove: (talkerId) => - _controller.onRemove(index, talkerId), - ); - }, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _controller.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const WhisperItemSkeleton(), + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + indent: 72, + endIndent: 20, + height: 1, + color: Colors.grey.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemCount: response.length, + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + final item = response[index]; + return WhisperSessionItem( + item: item, + onSetTop: (isTop, id) => + _controller.onSetTop(item, index, isTop, id), + onSetMute: (isMuted, talkerUid) => + _controller.onSetMute(item, isMuted, talkerUid), + onRemove: (talkerId) => _controller.onRemove(index, talkerId), + ); + }, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _controller.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } Widget _buildTopItems(ThemeData theme, EdgeInsets padding) { diff --git a/lib/pages/whisper_block/view.dart b/lib/pages/whisper_block/view.dart index 27e7a2cf3..cb144398e 100644 --- a/lib/pages/whisper_block/view.dart +++ b/lib/pages/whisper_block/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; +import 'package:PiliPlus/common/widgets/view_insets_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' show KeywordBlockingItem; import 'package:PiliPlus/http/loading_state.dart'; @@ -159,71 +160,69 @@ class _WhisperBlockPageState extends State { return Padding( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12) + - EdgeInsets.only( - bottom: - MediaQuery.paddingOf(context).bottom + - MediaQuery.viewInsetsOf(context).bottom, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - '添加消息屏蔽词', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, + EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom), + child: ViewInsetsSafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '添加消息屏蔽词', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), ), - ), - GestureDetector( - onTap: Get.back, - behavior: HitTestBehavior.opaque, - child: Icon( - Icons.clear, - color: theme.colorScheme.onSurfaceVariant, + GestureDetector( + onTap: Get.back, + behavior: HitTestBehavior.opaque, + child: Icon( + Icons.clear, + color: theme.colorScheme.onSurfaceVariant, + ), ), - ), - ], - ), - const SizedBox(height: 12), - TextFormField( - autofocus: true, - maxLength: _controller.charLimit, - decoration: InputDecoration( - isDense: true, - hintText: '请输入', - visualDensity: .standard, - hintStyle: const TextStyle(fontSize: 14), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - border: const OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.all(Radius.circular(25)), - ), - filled: true, - fillColor: theme.colorScheme.onInverseSurface, + ], ), - onChanged: (value) => keyword = value, - inputFormatters: [LengthLimitingTextInputFormatter(20)], - ), - const SizedBox(height: 12), - FilledButton.tonal( - onPressed: () { - if (keyword.isNotEmpty) { - _controller.onAdd(keyword); - } - }, - child: const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [Icon(Icons.add, size: 22), Text('添加消息屏蔽词')], + const SizedBox(height: 12), + TextFormField( + autofocus: true, + maxLength: _controller.charLimit, + decoration: InputDecoration( + isDense: true, + hintText: '请输入', + visualDensity: .standard, + hintStyle: const TextStyle(fontSize: 14), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + border: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(25)), + ), + filled: true, + fillColor: theme.colorScheme.onInverseSurface, + ), + onChanged: (value) => keyword = value, + inputFormatters: [LengthLimitingTextInputFormatter(20)], ), - ), - ], + const SizedBox(height: 12), + FilledButton.tonal( + onPressed: () { + if (keyword.isNotEmpty) { + _controller.onAdd(keyword); + } + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [Icon(Icons.add, size: 22), Text('添加消息屏蔽词')], + ), + ), + ], + ), ), ); }, diff --git a/lib/pages/whisper_secondary/view.dart b/lib/pages/whisper_secondary/view.dart index 33457c0bc..e97f6c0a2 100644 --- a/lib/pages/whisper_secondary/view.dart +++ b/lib/pages/whisper_secondary/view.dart @@ -89,43 +89,45 @@ class _WhisperSecPageState extends State { } Widget _buildBody(LoadingState?> loadingState) { - late final divider = Divider( - indent: 72, - endIndent: 20, - height: 1, - color: Colors.grey.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => SliverList.builder( - itemCount: 12, - itemBuilder: (context, index) => const WhisperItemSkeleton(), - ), - Success(:final response) => - response != null && response.isNotEmpty - ? SliverList.separated( - itemCount: response.length, - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - final item = response[index]; - return WhisperSessionItem( - item: item, - onSetTop: (isTop, talkerId) => - _controller.onSetTop(item, index, isTop, talkerId), - onSetMute: (isMuted, talkerUid) => - _controller.onSetMute(item, isMuted, talkerUid), - onRemove: (talkerId) => - _controller.onRemove(index, talkerId), - ); - }, - separatorBuilder: (context, index) => divider, - ) - : HttpError(onReload: _controller.onReload), - Error(:final errMsg) => HttpError( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) => const WhisperItemSkeleton(), + ); + case Success(:final response): + if (response != null && response.isNotEmpty) { + final divider = Divider( + indent: 72, + endIndent: 20, + height: 1, + color: Colors.grey.withValues(alpha: 0.1), + ); + return SliverList.separated( + itemCount: response.length, + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + final item = response[index]; + return WhisperSessionItem( + item: item, + onSetTop: (isTop, talkerId) => + _controller.onSetTop(item, index, isTop, talkerId), + onSetMute: (isMuted, talkerUid) => + _controller.onSetMute(item, isMuted, talkerUid), + onRemove: (talkerId) => _controller.onRemove(index, talkerId), + ); + }, + separatorBuilder: (context, index) => divider, + ); + } + return HttpError(onReload: _controller.onReload); + case Error(:final errMsg): + return HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } } diff --git a/lib/pages/whisper_settings/view.dart b/lib/pages/whisper_settings/view.dart index 79d1fedd5..c41765700 100644 --- a/lib/pages/whisper_settings/view.dart +++ b/lib/pages/whisper_settings/view.dart @@ -153,37 +153,40 @@ class _WhisperSettingsPageState extends State { ThemeData theme, LoadingState> loadingState, ) { - late final divider = Divider( - height: 1, - color: theme.colorScheme.outline.withValues(alpha: 0.1), - ); - return switch (loadingState) { - Loading() => const SizedBox.shrink(), - Success>(:final response) => Builder( - builder: (context) { - final keys = response.keys.toList()..sort(); - return ListView.separated( - padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, - ), - itemCount: keys.length, - itemBuilder: (context, index) { - final key = keys[index]; - final item = response[key]!; - return ImSettingsItem( - item: item, - onSet: () => onSet(key, response, item), - onRedirect: () => onRedirect(theme, key, response, item), - ); - }, - separatorBuilder: (context, index) => divider, - ); - }, - ), - Error(:final errMsg) => scrollErrorWidget( - errMsg: errMsg, - onReload: _controller.onReload, - ), - }; + switch (loadingState) { + case Loading(): + return const SizedBox.shrink(); + case Success>(:final response): + final divider = Divider( + height: 1, + color: theme.colorScheme.outline.withValues(alpha: 0.1), + ); + return Builder( + builder: (context) { + final keys = response.keys.toList()..sort(); + return ListView.separated( + padding: EdgeInsets.only( + bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + ), + itemCount: keys.length, + itemBuilder: (context, index) { + final key = keys[index]; + final item = response[key]!; + return ImSettingsItem( + item: item, + onSet: () => onSet(key, response, item), + onRedirect: () => onRedirect(theme, key, response, item), + ); + }, + separatorBuilder: (context, index) => divider, + ); + }, + ); + case Error(:final errMsg): + return scrollErrorWidget( + errMsg: errMsg, + onReload: _controller.onReload, + ); + } } }