From 9ad57dccb0715ca167f56e147f030ec100a91a0f Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Mon, 21 Apr 2025 11:15:21 +0800 Subject: [PATCH] opt: safearea Signed-off-by: bggRGjQaUbCoE --- lib/common/widgets/dynamic_sliver_appbar.dart | 7 +- .../bangumi/pgc_index/pgc_index_page.dart | 47 +- lib/pages/bangumi/view.dart | 12 +- lib/pages/common/common_search_page.dart | 16 +- lib/pages/dynamics/detail/view.dart | 24 +- lib/pages/fan/view.dart | 19 +- lib/pages/fav/video/fav_folder_sort_page.dart | 6 +- lib/pages/fav/view.dart | 8 +- lib/pages/fav_detail/fav_sort_page.dart | 6 +- lib/pages/fav_detail/view.dart | 631 +++++++-------- lib/pages/follow/view.dart | 22 +- lib/pages/follow/widgets/follow_list.dart | 4 +- .../follow/widgets/owner_follow_list.dart | 4 +- lib/pages/history/view.dart | 66 +- lib/pages/html/view.dart | 731 +++++++++--------- lib/pages/later/view.dart | 47 +- lib/pages/live_room/view.dart | 63 +- lib/pages/main/view.dart | 40 +- lib/pages/media/view.dart | 108 +-- lib/pages/member/controller.dart | 1 - lib/pages/member/member_page.dart | 278 ++++--- lib/pages/member/widget/user_info_card.dart | 14 +- lib/pages/member_coin/view.dart | 26 +- lib/pages/member_like/view.dart | 26 +- lib/pages/member_search/search_archive.dart | 36 +- lib/pages/member_search/search_dynamic.dart | 34 +- lib/pages/member_search/view.dart | 32 +- lib/pages/setting/pages/color_select.dart | 404 +++++----- lib/pages/setting/pages/font_size_select.dart | 95 ++- lib/pages/setting/pages/logs.dart | 117 +-- lib/pages/setting/pages/play_speed_set.dart | 6 +- lib/pages/setting/search_page.dart | 37 +- lib/pages/setting/view.dart | 37 +- lib/pages/subscription/view.dart | 28 +- lib/pages/subscription_detail/view.dart | 6 +- lib/pages/whisper/view.dart | 108 +-- lib/pages/whisper_detail/view.dart | 32 +- 37 files changed, 1660 insertions(+), 1518 deletions(-) diff --git a/lib/common/widgets/dynamic_sliver_appbar.dart b/lib/common/widgets/dynamic_sliver_appbar.dart index 5fdc2fae..db81e22a 100644 --- a/lib/common/widgets/dynamic_sliver_appbar.dart +++ b/lib/common/widgets/dynamic_sliver_appbar.dart @@ -43,10 +43,10 @@ class DynamicSliverAppBar extends StatefulWidget { this.forceMaterialTransparency = false, this.clipBehavior, this.appBarClipper, - this.hasTabBar = false, + this.callback, }); - final bool hasTabBar; + final ValueChanged? callback; final Widget? flexibleSpace; final Widget? leading; final bool automaticallyImplyLeading; @@ -113,6 +113,7 @@ class _DynamicSliverAppBarState extends State { _height = (_childKey.currentContext!.findRenderObject()! as RenderBox) .size .height; + widget.callback?.call(_height); }); }); } @@ -166,7 +167,7 @@ class _DynamicSliverAppBarState extends State { onStretchTrigger: widget.onStretchTrigger, shape: widget.shape, toolbarHeight: widget.toolbarHeight, - expandedHeight: _height + (widget.hasTabBar ? 48 : 0), + expandedHeight: _height, leadingWidth: widget.leadingWidth, toolbarTextStyle: widget.toolbarTextStyle, titleTextStyle: widget.titleTextStyle, diff --git a/lib/pages/bangumi/pgc_index/pgc_index_page.dart b/lib/pages/bangumi/pgc_index/pgc_index_page.dart index f2d7b7c8..34c45f59 100644 --- a/lib/pages/bangumi/pgc_index/pgc_index_page.dart +++ b/lib/pages/bangumi/pgc_index/pgc_index_page.dart @@ -51,30 +51,33 @@ class _PgcIndexPageState extends State int count = (data.order?.isNotEmpty == true ? 1 : 0) + (data.filter?.length ?? 0); if (count == 0) return const SizedBox.shrink(); - return CustomScrollView( - slivers: [ - if (widget.indexType != null) - SliverToBoxAdapter(child: const SizedBox(height: 12)), - SliverToBoxAdapter( - child: AnimatedSize( - curve: Curves.easeInOut, - alignment: Alignment.topCenter, - duration: const Duration(milliseconds: 200), - child: count > 5 - ? Obx(() => _buildSortWidget(count, data)) - : _buildSortWidget(count, data), + return SafeArea( + bottom: false, + child: CustomScrollView( + slivers: [ + if (widget.indexType != null) + SliverToBoxAdapter(child: const SizedBox(height: 12)), + SliverToBoxAdapter( + child: AnimatedSize( + curve: Curves.easeInOut, + alignment: Alignment.topCenter, + duration: const Duration(milliseconds: 200), + child: count > 5 + ? Obx(() => _buildSortWidget(count, data)) + : _buildSortWidget(count, data), + ), ), - ), - SliverPadding( - padding: EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - top: 12, - bottom: MediaQuery.paddingOf(context).bottom + 80, + SliverPadding( + padding: EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + top: 12, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildList(_ctr.loadingState.value)), ), - sliver: Obx(() => _buildList(_ctr.loadingState.value)), - ), - ], + ], + ), ); }), Error() => scrollErrorWidget( diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 53d37b00..21183be7 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -248,10 +248,14 @@ class _BangumiPageState extends CommonPageState length: types.length, child: Column( children: [ - TabBar( - tabs: titles - .map((title) => Tab(text: title)) - .toList()), + SafeArea( + top: false, + bottom: false, + child: TabBar( + tabs: titles + .map((title) => Tab(text: title)) + .toList()), + ), Expanded( child: tabBarView( children: types diff --git a/lib/pages/common/common_search_page.dart b/lib/pages/common/common_search_page.dart index ca029618..6786af06 100644 --- a/lib/pages/common/common_search_page.dart +++ b/lib/pages/common/common_search_page.dart @@ -48,12 +48,16 @@ abstract class CommonSearchPageState onSubmitted: (value) => controller.onRefresh(), ), ), - body: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: controller.scrollController, - slivers: [ - Obx(() => _buildBody(controller.loadingState.value)), - ], + body: SafeArea( + top: false, + bottom: false, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: controller.scrollController, + slivers: [ + Obx(() => _buildBody(controller.loadingState.value)), + ], + ), ), ); } diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 839f63e8..a7908afc 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -103,10 +103,6 @@ class _DynamicDetailPageState extends State super.initState(); // floor 1原创 2转发 init(); - // if (action == 'comment') { - // _visibleTitle = true; - // _titleStreamC.add(true); - // } _fabAnimationCtr = AnimationController( vsync: this, @@ -340,14 +336,18 @@ class _DynamicDetailPageState extends State ] : null, ), - body: context.orientation == Orientation.portrait - ? refreshIndicator( - onRefresh: () async { - await _dynamicDetailController.onRefresh(); - }, - child: _buildBody(context.orientation), - ) - : _buildBody(context.orientation), + body: SafeArea( + top: false, + bottom: false, + child: context.orientation == Orientation.portrait + ? refreshIndicator( + onRefresh: () async { + await _dynamicDetailController.onRefresh(); + }, + child: _buildBody(context.orientation), + ) + : _buildBody(context.orientation), + ), ); } diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index ca4027fc..56fc12e6 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -37,14 +37,17 @@ class _FansPageState extends State { _fansController.isOwner.value ? '我的粉丝' : '${_fansController.name}的粉丝', ), ), - body: refreshIndicator( - onRefresh: () async => await _fansController.onRefresh(), - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: _fansController.scrollController, - slivers: [ - Obx(() => _buildBody(_fansController.loadingState.value)), - ], + body: SafeArea( + bottom: false, + child: refreshIndicator( + onRefresh: () async => await _fansController.onRefresh(), + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _fansController.scrollController, + slivers: [ + Obx(() => _buildBody(_fansController.loadingState.value)), + ], + ), ), ), ); diff --git a/lib/pages/fav/video/fav_folder_sort_page.dart b/lib/pages/fav/video/fav_folder_sort_page.dart index 26e88d93..09967327 100644 --- a/lib/pages/fav/video/fav_folder_sort_page.dart +++ b/lib/pages/fav/video/fav_folder_sort_page.dart @@ -87,7 +87,11 @@ class _FavFolderSortPageState extends State { const SizedBox(width: 16), ], ), - body: _buildBody, + body: SafeArea( + top: false, + bottom: false, + child: _buildBody, + ), ); } diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index a1c355aa..69abb882 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -136,7 +136,13 @@ class _FavPageState extends State with SingleTickerProviderStateMixin { ), body: tabBarView( controller: _tabController, - children: _FavType.values.map((item) => item.page).toList(), + children: _FavType.values + .map((item) => SafeArea( + top: false, + bottom: false, + child: item.page, + )) + .toList(), ), ); } diff --git a/lib/pages/fav_detail/fav_sort_page.dart b/lib/pages/fav_detail/fav_sort_page.dart index feaa9e6c..9709a8f8 100644 --- a/lib/pages/fav_detail/fav_sort_page.dart +++ b/lib/pages/fav_detail/fav_sort_page.dart @@ -93,7 +93,11 @@ class _FavSortPageState extends State { const SizedBox(width: 16), ], ), - body: _buildBody, + body: SafeArea( + top: false, + bottom: false, + child: _buildBody, + ), ); } diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 3fa58aa2..2c5013c5 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -75,296 +75,319 @@ class _FavDetailPageState extends State { ) : const SizedBox.shrink(), ), - body: refreshIndicator( - onRefresh: () async { - await _favDetailController.onRefresh(); - }, - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: _favDetailController.scrollController, - slivers: [ - SliverAppBar( - leading: _favDetailController.enableMultiSelect.value - ? IconButton( - tooltip: '取消', - onPressed: _favDetailController.handleSelect, - icon: const Icon(Icons.close_outlined), - ) - : null, - expandedHeight: 220 - MediaQuery.of(context).padding.top, - pinned: true, - title: _favDetailController.enableMultiSelect.value - ? Text( - '已选: ${_favDetailController.checkedCount.value}', - ) - : Obx( - () => AnimatedOpacity( - opacity: - _favDetailController.titleCtr.value ? 1 : 0, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 500), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _favDetailController.item.value.title ?? '', - style: - Theme.of(context).textTheme.titleMedium, - ), - Text( - '共${_favDetailController.item.value.mediaCount}条视频', - style: - Theme.of(context).textTheme.labelMedium, - ) - ], - ), - ), - ), - actions: _favDetailController.enableMultiSelect.value - ? [ - TextButton( - style: TextButton.styleFrom( - visualDensity: - VisualDensity(horizontal: -2, vertical: -2), - ), - onPressed: () => - _favDetailController.handleSelect(true), - child: const Text('全选'), - ), - TextButton( - style: TextButton.styleFrom( - visualDensity: - VisualDensity(horizontal: -2, vertical: -2), - ), - onPressed: () { - RequestUtils.onCopyOrMove( - context: context, - isCopy: true, - ctr: _favDetailController, - mediaId: _favDetailController.mediaId, - mid: _favDetailController.mid, - ); - }, - child: Text( - '复制', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, + body: SafeArea( + top: false, + bottom: false, + child: refreshIndicator( + onRefresh: () async { + await _favDetailController.onRefresh(); + }, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _favDetailController.scrollController, + slivers: [ + SliverAppBar( + leading: _favDetailController.enableMultiSelect.value + ? IconButton( + tooltip: '取消', + onPressed: _favDetailController.handleSelect, + icon: const Icon(Icons.close_outlined), + ) + : null, + expandedHeight: 200 - MediaQuery.of(context).padding.top, + pinned: true, + title: _favDetailController.enableMultiSelect.value + ? Text( + '已选: ${_favDetailController.checkedCount.value}', + ) + : Obx( + () => AnimatedOpacity( + opacity: + _favDetailController.titleCtr.value ? 1 : 0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 500), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _favDetailController.item.value.title ?? '', + style: + Theme.of(context).textTheme.titleMedium, + ), + Text( + '共${_favDetailController.item.value.mediaCount}条视频', + style: + Theme.of(context).textTheme.labelMedium, + ) + ], ), ), ), - TextButton( - style: TextButton.styleFrom( - visualDensity: - VisualDensity(horizontal: -2, vertical: -2), - ), - onPressed: () { - RequestUtils.onCopyOrMove( - context: context, - isCopy: false, - ctr: _favDetailController, - mediaId: _favDetailController.mediaId, - mid: _favDetailController.mid, - ); - }, - child: Text( - '移动', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, + actions: _favDetailController.enableMultiSelect.value + ? [ + TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), ), + onPressed: () => + _favDetailController.handleSelect(true), + child: const Text('全选'), ), - ), - TextButton( - style: TextButton.styleFrom( - visualDensity: - VisualDensity(horizontal: -2, vertical: -2), - ), - onPressed: () => - _favDetailController.onDelChecked(context), - child: Text( - '删除', - style: TextStyle( - color: Theme.of(context).colorScheme.error), - ), - ), - const SizedBox(width: 6), - ] - : [ - IconButton( - tooltip: '搜索', - onPressed: () => Get.toNamed( - '/favSearch', - arguments: { - 'type': 0, - 'mediaId': int.parse(mediaId), - 'title': _favDetailController.item.value.title, - 'count': - _favDetailController.item.value.mediaCount, - 'isOwner': _favDetailController.isOwner.value, + TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), + onPressed: () { + RequestUtils.onCopyOrMove( + context: context, + isCopy: true, + ctr: _favDetailController, + mediaId: _favDetailController.mediaId, + mid: _favDetailController.mid, + ); }, + child: Text( + '复制', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), ), - icon: const Icon(Icons.search_outlined), - ), - Obx( - () => _favDetailController.isOwner.value - ? PopupMenuButton( - icon: const Icon(Icons.more_vert), - itemBuilder: (context) => [ - PopupMenuItem( - onTap: () { - Get.toNamed( - '/createFav', - parameters: {'mediaId': mediaId}, - )?.then((res) { - if (res is FavFolderItemData) { - _favDetailController.item.value = - res; - } - }); - }, - child: Text('编辑信息'), - ), - PopupMenuItem( - onTap: () { - UserHttp.cleanFav(mediaId: mediaId) - .then((data) { - if (data['status']) { - SmartDialog.showToast('清除成功'); - Future.delayed( - const Duration( - milliseconds: 200), () { - _favDetailController.onReload(); - }); - } else { - SmartDialog.showToast( - data['msg']); - } - }); - }, - child: Text('清除失效内容'), - ), - PopupMenuItem( - onTap: () { - if (_favDetailController.loadingState - .value is Success && - ((_favDetailController - .loadingState - .value as Success) - .response as List?) - ?.isNotEmpty == - true) { - if ((_favDetailController.item.value - .mediaCount ?? - 0) > - 1000) { - SmartDialog.showToast( - '内容太多啦!超过1000不支持排序'); - return; - } - Get.to( - FavSortPage( - favDetailController: - _favDetailController), - ); - } - }, - child: Text('排序'), - ), - if (!Utils.isDefaultFav( - _favDetailController - .item.value.attr ?? - 0)) + TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), + onPressed: () { + RequestUtils.onCopyOrMove( + context: context, + isCopy: false, + ctr: _favDetailController, + mediaId: _favDetailController.mediaId, + mid: _favDetailController.mid, + ); + }, + child: Text( + '移动', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ), + TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), + onPressed: () => + _favDetailController.onDelChecked(context), + child: Text( + '删除', + style: TextStyle( + color: Theme.of(context).colorScheme.error), + ), + ), + const SizedBox(width: 6), + ] + : [ + IconButton( + tooltip: '搜索', + onPressed: () => Get.toNamed( + '/favSearch', + arguments: { + 'type': 0, + 'mediaId': int.parse(mediaId), + 'title': + _favDetailController.item.value.title, + 'count': _favDetailController + .item.value.mediaCount, + 'isOwner': _favDetailController.isOwner.value, + }, + ), + icon: const Icon(Icons.search_outlined), + ), + Obx( + () => _favDetailController.isOwner.value + ? PopupMenuButton( + icon: const Icon(Icons.more_vert), + itemBuilder: (context) => [ PopupMenuItem( onTap: () { - showConfirmDialog( - context: context, - title: '确定删除该收藏夹?', - onConfirm: () { - UserHttp.deleteFolder( - mediaIds: [mediaId]) - .then((data) { - if (data['status']) { - SmartDialog.showToast( - '删除成功'); - Get.back(result: true); - } else { - SmartDialog.showToast( - data['msg']); - } - }); - }, - ); + Get.toNamed( + '/createFav', + parameters: {'mediaId': mediaId}, + )?.then((res) { + if (res is FavFolderItemData) { + _favDetailController + .item.value = res; + } + }); }, - child: Text( - '删除', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .error, + child: Text('编辑信息'), + ), + PopupMenuItem( + onTap: () { + UserHttp.cleanFav(mediaId: mediaId) + .then((data) { + if (data['status']) { + SmartDialog.showToast('清除成功'); + Future.delayed( + const Duration( + milliseconds: 200), () { + _favDetailController + .onReload(); + }); + } else { + SmartDialog.showToast( + data['msg']); + } + }); + }, + child: Text('清除失效内容'), + ), + PopupMenuItem( + onTap: () { + if (_favDetailController + .loadingState + .value is Success && + ((_favDetailController + .loadingState + .value + as Success) + .response as List?) + ?.isNotEmpty == + true) { + if ((_favDetailController.item + .value.mediaCount ?? + 0) > + 1000) { + SmartDialog.showToast( + '内容太多啦!超过1000不支持排序'); + return; + } + Get.to( + FavSortPage( + favDetailController: + _favDetailController), + ); + } + }, + child: Text('排序'), + ), + if (!Utils.isDefaultFav( + _favDetailController + .item.value.attr ?? + 0)) + PopupMenuItem( + onTap: () { + showConfirmDialog( + context: context, + title: '确定删除该收藏夹?', + onConfirm: () { + UserHttp.deleteFolder( + mediaIds: [mediaId]) + .then((data) { + if (data['status']) { + SmartDialog.showToast( + '删除成功'); + Get.back(result: true); + } else { + SmartDialog.showToast( + data['msg']); + } + }); + }, + ); + }, + child: Text( + '删除', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .error, + ), ), ), - ), - ], - ) - : const SizedBox.shrink(), - ), - const SizedBox(width: 6), - ], - flexibleSpace: FlexibleSpaceBar( - background: Container( - padding: EdgeInsets.only( - top: kTextTabBarHeight + - MediaQuery.of(context).padding.top + - 10, - left: 14, - right: 20, - ), - child: SizedBox( - height: 110, - child: Obx( - () => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Hero( - tag: _favDetailController.heroTag, - child: NetworkImgLayer( - width: 180, - height: 110, - src: _favDetailController.item.value.cover, + ], + ) + : const SizedBox.shrink(), + ), + const SizedBox(width: 6), + ], + flexibleSpace: FlexibleSpaceBar( + background: Container( + padding: EdgeInsets.only( + top: kTextTabBarHeight + + MediaQuery.of(context).padding.top + + 10, + left: 14, + right: 20, + ), + child: SizedBox( + height: 110, + child: Obx( + () => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Hero( + tag: _favDetailController.heroTag, + child: NetworkImgLayer( + width: 180, + height: 110, + src: _favDetailController.item.value.cover, + ), ), - ), - const SizedBox(width: 14), - Expanded( - child: SizedBox( - height: 110, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text( - _favDetailController.item.value.title ?? - '', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - fontWeight: FontWeight.bold), - ), - if (_favDetailController - .item.value.intro?.isNotEmpty == - true) + const SizedBox(width: 14), + Expanded( + child: SizedBox( + height: 110, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), Text( _favDetailController - .item.value.intro ?? + .item.value.title ?? + '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + fontWeight: FontWeight.bold), + ), + if (_favDetailController + .item.value.intro?.isNotEmpty == + true) + Text( + _favDetailController + .item.value.intro ?? + '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + const SizedBox(height: 4), + Text( + _favDetailController + .item.value.upper?.name ?? '', style: TextStyle( fontSize: Theme.of(context) @@ -375,48 +398,36 @@ class _FavDetailPageState extends State { .colorScheme .outline), ), - const SizedBox(height: 4), - Text( - _favDetailController - .item.value.upper?.name ?? - '', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: Theme.of(context) - .colorScheme - .outline), - ), - const Spacer(), - if (_favDetailController - .item.value.attr != - null) - Text( - '共${_favDetailController.item.value.mediaCount}条视频 · ${Utils.isPublicFavText(_favDetailController.item.value.attr ?? 0)}', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: Theme.of(context) - .colorScheme - .outline), - ), - ], + const Spacer(), + if (_favDetailController + .item.value.attr != + null) + Text( + '共${_favDetailController.item.value.mediaCount}条视频 · ${Utils.isPublicFavText(_favDetailController.item.value.attr ?? 0)}', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + ], + ), ), ), - ), - ], + ], + ), ), ), ), ), ), - ), - Obx(() => _buildBody(_favDetailController.loadingState.value)), - ], + Obx(() => + _buildBody(_favDetailController.loadingState.value)), + ], + ), ), ), ), diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart index d94c7d40..b7d544d5 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -76,15 +76,19 @@ class _FollowPageState extends State { if (data['status']) { return Column( children: [ - TabBar( - controller: _followController.tabController, - isScrollable: true, - tabAlignment: TabAlignment.start, - tabs: [ - for (var i in data['data']) ...[ - Tab(text: i.name), - ] - ], + SafeArea( + top: false, + bottom: false, + child: TabBar( + controller: _followController.tabController, + isScrollable: true, + tabAlignment: TabAlignment.start, + tabs: [ + for (var i in data['data']) ...[ + Tab(text: i.name), + ] + ], + ), ), Expanded( child: Material( diff --git a/lib/pages/follow/widgets/follow_list.dart b/lib/pages/follow/widgets/follow_list.dart index f73dd6a8..23ac9c37 100644 --- a/lib/pages/follow/widgets/follow_list.dart +++ b/lib/pages/follow/widgets/follow_list.dart @@ -92,12 +92,12 @@ class _FollowListState extends State { } }, ) - : errorWidget( + : scrollErrorWidget( callback: () => widget.ctr.queryFollowings('init'), ), ); } else { - return errorWidget( + return scrollErrorWidget( errMsg: data['msg'], callback: () => widget.ctr.queryFollowings('init'), ); diff --git a/lib/pages/follow/widgets/owner_follow_list.dart b/lib/pages/follow/widgets/owner_follow_list.dart index cd33a1dc..a6a2cd94 100644 --- a/lib/pages/follow/widgets/owner_follow_list.dart +++ b/lib/pages/follow/widgets/owner_follow_list.dart @@ -107,12 +107,12 @@ class _OwnerFollowListState extends State ); }, ) - : errorWidget( + : scrollErrorWidget( callback: () => widget.ctr.queryFollowings('init'), ), ); } else { - return errorWidget( + return scrollErrorWidget( errMsg: data['msg'], callback: () => widget.ctr.queryFollowings('init'), ); diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index a406d5e6..3c76ad6c 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -182,26 +182,30 @@ class _HistoryPageState extends State ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TabBar( - controller: _historyController.tabController, - onTap: (index) { - if (_historyController - .tabController!.indexIsChanging.not) { - currCtr().scrollController.animToTop(); - } else { - if (enableMultiSelect) { - currCtr(_historyController - .tabController!.previousIndex) - .handleSelect(); + SafeArea( + top: false, + bottom: false, + child: TabBar( + controller: _historyController.tabController, + onTap: (index) { + if (_historyController + .tabController!.indexIsChanging.not) { + currCtr().scrollController.animToTop(); + } else { + if (enableMultiSelect) { + currCtr(_historyController + .tabController!.previousIndex) + .handleSelect(); + } } - } - }, - tabs: [ - Tab(text: '全部'), - ..._historyController.tabs.map( - (item) => Tab(text: item.name), - ), - ], + }, + tabs: [ + Tab(text: '全部'), + ..._historyController.tabs.map( + (item) => Tab(text: item.name), + ), + ], + ), ), Expanded( child: Material( @@ -229,16 +233,20 @@ class _HistoryPageState extends State ); } - Widget get _buildPage => refreshIndicator( - onRefresh: () async { - await _historyController.onRefresh(); - }, - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: _historyController.scrollController, - slivers: [ - Obx(() => _buildBody(_historyController.loadingState.value)), - ], + Widget get _buildPage => SafeArea( + top: false, + bottom: false, + child: refreshIndicator( + onRefresh: () async { + await _historyController.onRefresh(); + }, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _historyController.scrollController, + slivers: [ + Obx(() => _buildBody(_historyController.loadingState.value)), + ], + ), ), ); diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index 26776a66..2301cf86 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -337,239 +337,292 @@ class _HtmlRenderPageState extends State const SizedBox(width: 6) ], ), - body: Stack( - children: [ - OrientationBuilder( - builder: (context, orientation) { - double padding = max(context.width / 2 - Grid.smallCardWidth, 0); - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: _ratio[0].toInt(), - child: CustomScrollView( - controller: _htmlRenderCtr.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: orientation == Orientation.portrait - ? EdgeInsets.symmetric(horizontal: padding) - : EdgeInsets.only(left: padding / 4), - sliver: SliverToBoxAdapter( - child: Obx( - () => _htmlRenderCtr.loaded.value - ? _buildHeader - : const SizedBox(), - ), - ), - ), - SliverPadding( - padding: orientation == Orientation.portrait - ? EdgeInsets.symmetric(horizontal: padding) - : EdgeInsets.only( - left: padding / 4, - bottom: - MediaQuery.paddingOf(context).bottom + 80, - ), - sliver: _buildContent, - ), - if (orientation == Orientation.portrait) ...[ - SliverPadding( - padding: EdgeInsets.symmetric(horizontal: padding), - sliver: SliverToBoxAdapter( - child: Divider( - thickness: 8, - color: Theme.of(context) - .dividerColor - .withOpacity(0.05), - ), - ), - ), - SliverPadding( - padding: EdgeInsets.symmetric(horizontal: padding), - sliver: SliverToBoxAdapter(child: replyHeader()), - ), - SliverPadding( - padding: EdgeInsets.symmetric(horizontal: padding), - sliver: Obx( - () => - replyList(_htmlRenderCtr.loadingState.value), - ), - ), - ], - ], - ), - ), - if (orientation == Orientation.landscape) ...[ - VerticalDivider( - thickness: 8, - color: Theme.of(context).dividerColor.withOpacity(0.05), - ), + body: SafeArea( + top: false, + bottom: false, + child: Stack( + children: [ + OrientationBuilder( + builder: (context, orientation) { + double padding = + max(context.width / 2 - Grid.smallCardWidth, 0); + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Expanded( - flex: _ratio[1].toInt(), - child: Scaffold( - key: _key, - backgroundColor: Colors.transparent, - body: refreshIndicator( - onRefresh: () async { - await _htmlRenderCtr.onRefresh(); - }, - child: CustomScrollView( - controller: _htmlRenderCtr.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: EdgeInsets.only(right: padding / 4), - sliver: SliverToBoxAdapter( - child: replyHeader(), + flex: _ratio[0].toInt(), + child: CustomScrollView( + controller: _htmlRenderCtr.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: orientation == Orientation.portrait + ? EdgeInsets.symmetric(horizontal: padding) + : EdgeInsets.only(left: padding / 4), + sliver: SliverToBoxAdapter( + child: Obx( + () => _htmlRenderCtr.loaded.value + ? _buildHeader + : const SizedBox(), + ), + ), + ), + SliverPadding( + padding: orientation == Orientation.portrait + ? EdgeInsets.symmetric(horizontal: padding) + : EdgeInsets.only( + left: padding / 4, + bottom: + MediaQuery.paddingOf(context).bottom + + 80, + ), + sliver: _buildContent, + ), + if (orientation == Orientation.portrait) ...[ + SliverPadding( + padding: + EdgeInsets.symmetric(horizontal: padding), + sliver: SliverToBoxAdapter( + child: Divider( + thickness: 8, + color: Theme.of(context) + .dividerColor + .withOpacity(0.05), ), ), - SliverPadding( - padding: EdgeInsets.only(right: padding / 4), - sliver: Obx( - () => replyList( - _htmlRenderCtr.loadingState.value), - ), + ), + SliverPadding( + padding: + EdgeInsets.symmetric(horizontal: padding), + sliver: SliverToBoxAdapter(child: replyHeader()), + ), + SliverPadding( + padding: + EdgeInsets.symmetric(horizontal: padding), + sliver: Obx( + () => replyList( + _htmlRenderCtr.loadingState.value), ), - ], + ), + ], + ], + ), + ), + if (orientation == Orientation.landscape) ...[ + VerticalDivider( + thickness: 8, + color: Theme.of(context).dividerColor.withOpacity(0.05), + ), + Expanded( + flex: _ratio[1].toInt(), + child: Scaffold( + key: _key, + backgroundColor: Colors.transparent, + body: refreshIndicator( + onRefresh: () async { + await _htmlRenderCtr.onRefresh(); + }, + child: CustomScrollView( + controller: _htmlRenderCtr.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only(right: padding / 4), + sliver: SliverToBoxAdapter( + child: replyHeader(), + ), + ), + SliverPadding( + padding: EdgeInsets.only(right: padding / 4), + sliver: Obx( + () => replyList( + _htmlRenderCtr.loadingState.value), + ), + ), + ], + ), ), ), ), - ), + ], ], - ], - ); - }, - ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: SlideTransition( - position: Tween( - begin: const Offset(0, 1), - end: const Offset(0, 0), - ).animate(CurvedAnimation( - parent: fabAnimationCtr, - curve: Curves.easeInOut, - )), - child: Builder( - builder: (context) { - Widget button() => FloatingActionButton( - heroTag: null, - onPressed: () { - feedBack(); - _htmlRenderCtr.onReply( - context, - oid: _htmlRenderCtr.oid.value, - replyType: ReplyType.values[type], - ); - }, - tooltip: '评论动态', - child: const Icon(Icons.reply), - ); - return _htmlRenderCtr.showDynActionBar.not - ? Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - right: 14, - bottom: - MediaQuery.of(context).padding.bottom + 14, - ), - child: button(), - ), - ) - : Obx( - () => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: EdgeInsets.only( - right: 14, - bottom: 14 + - (_htmlRenderCtr.item.value.idStr != null - ? 0 - : MediaQuery.of(context) - .padding - .bottom), - ), - child: button(), + ); + }, + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: const Offset(0, 0), + ).animate(CurvedAnimation( + parent: fabAnimationCtr, + curve: Curves.easeInOut, + )), + child: Builder( + builder: (context) { + Widget button() => FloatingActionButton( + heroTag: null, + onPressed: () { + feedBack(); + _htmlRenderCtr.onReply( + context, + oid: _htmlRenderCtr.oid.value, + replyType: ReplyType.values[type], + ); + }, + tooltip: '评论动态', + child: const Icon(Icons.reply), + ); + return _htmlRenderCtr.showDynActionBar.not + ? Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only( + right: 14, + bottom: + MediaQuery.of(context).padding.bottom + 14, ), - _htmlRenderCtr.item.value.idStr != null - ? Container( - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .surface, - border: Border( - top: BorderSide( - color: Theme.of(context) - .colorScheme - .outline - .withOpacity(0.08), + child: button(), + ), + ) + : Obx( + () => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only( + right: 14, + bottom: 14 + + (_htmlRenderCtr.item.value.idStr != null + ? 0 + : MediaQuery.of(context) + .padding + .bottom), + ), + child: button(), + ), + _htmlRenderCtr.item.value.idStr != null + ? Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .surface, + border: Border( + top: BorderSide( + color: Theme.of(context) + .colorScheme + .outline + .withOpacity(0.08), + ), ), ), - ), - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context) - .bottom), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: Builder( - builder: (btnContext) => - TextButton.icon( - onPressed: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - useSafeArea: true, - builder: (context) => - RepostPanel( - item: _htmlRenderCtr - .item.value, - callback: () { - int count = int.tryParse( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward - ?.count ?? - '0') ?? - 0; - _htmlRenderCtr + padding: EdgeInsets.only( + bottom: + MediaQuery.paddingOf(context) + .bottom), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + Expanded( + child: Builder( + builder: (btnContext) => + TextButton.icon( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + useSafeArea: true, + builder: (context) => + RepostPanel( + item: _htmlRenderCtr + .item.value, + callback: () { + int count = int.tryParse( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward + ?.count ?? + '0') ?? + 0; + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count = + (count + 1) + .toString(); + if (btnContext + .mounted) { + (btnContext + as Element?) + ?.markNeedsBuild(); + } + }, + ), + ); + }, + icon: Icon( + FontAwesomeIcons + .shareFromSquare, + size: 16, + color: Theme.of(context) + .colorScheme + .outline, + semanticLabel: "转发", + ), + style: TextButton.styleFrom( + padding: const EdgeInsets + .fromLTRB(15, 0, 15, 0), + foregroundColor: + Theme.of(context) + .colorScheme + .outline, + ), + label: Text( + _htmlRenderCtr .item .value .modules ?.moduleStat ?.forward! - .count = - (count + 1) - .toString(); - if (btnContext - .mounted) { - (btnContext - as Element?) - ?.markNeedsBuild(); - } - }, - ), - ); + .count != + null + ? Utils.numFormat( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count) + : '转发', + ), + ), + ), + ), + Expanded( + child: TextButton.icon( + onPressed: () { + Utils.shareText( + '${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}'); }, icon: Icon( - FontAwesomeIcons - .shareFromSquare, + FontAwesomeIcons.shareNodes, size: 16, color: Theme.of(context) .colorScheme .outline, - semanticLabel: "转发", + semanticLabel: "分享", ), style: TextButton.styleFrom( padding: @@ -580,178 +633,134 @@ class _HtmlRenderPageState extends State .colorScheme .outline, ), - label: Text( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward! - .count != - null - ? Utils.numFormat( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.forward! - .count) - : '转发', - ), + label: const Text('分享'), ), ), - ), - Expanded( - child: TextButton.icon( - onPressed: () { - Utils.shareText( - '${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}'); - }, - icon: Icon( - FontAwesomeIcons.shareNodes, - size: 16, - color: Theme.of(context) - .colorScheme - .outline, - semanticLabel: "分享", - ), - style: TextButton.styleFrom( - padding: - const EdgeInsets.fromLTRB( - 15, 0, 15, 0), - foregroundColor: - Theme.of(context) - .colorScheme - .outline, - ), - label: const Text('分享'), - ), - ), - Expanded( - child: Builder( - builder: (context) => - TextButton.icon( - onPressed: () => - RequestUtils.onLikeDynamic( - _htmlRenderCtr.item.value, - () { - if (context.mounted) { - (context as Element?) - ?.markNeedsBuild(); - } - }, - ), - icon: Icon( - _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.status == - true - ? FontAwesomeIcons - .solidThumbsUp - : FontAwesomeIcons - .thumbsUp, - size: 16, - color: _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.status == - true - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .outline, - semanticLabel: _htmlRenderCtr - .item - .value - .modules - ?.moduleStat - ?.like - ?.status == - true - ? "已赞" - : "点赞", - ), - style: TextButton.styleFrom( - padding: - const EdgeInsets.fromLTRB( - 15, 0, 15, 0), - foregroundColor: - Theme.of(context) - .colorScheme - .outline, - ), - label: AnimatedSwitcher( - duration: const Duration( - milliseconds: 400), - transitionBuilder: - (Widget child, - Animation - animation) { - return ScaleTransition( - scale: animation, - child: child); - }, - child: Text( + Expanded( + child: Builder( + builder: (context) => + TextButton.icon( + onPressed: () => RequestUtils + .onLikeDynamic( + _htmlRenderCtr.item.value, + () { + if (context.mounted) { + (context as Element?) + ?.markNeedsBuild(); + } + }, + ), + icon: Icon( _htmlRenderCtr .item .value .modules ?.moduleStat ?.like - ?.count != - null - ? Utils.numFormat( - _htmlRenderCtr + ?.status == + true + ? FontAwesomeIcons + .solidThumbsUp + : FontAwesomeIcons + .thumbsUp, + size: 16, + color: _htmlRenderCtr .item .value - .modules! - .moduleStat! - .like! - .count) - : '点赞', - style: TextStyle( - color: _htmlRenderCtr + .modules + ?.moduleStat + ?.like + ?.status == + true + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .outline, + semanticLabel: + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? "已赞" + : "点赞", + ), + style: TextButton.styleFrom( + padding: const EdgeInsets + .fromLTRB(15, 0, 15, 0), + foregroundColor: + Theme.of(context) + .colorScheme + .outline, + ), + label: AnimatedSwitcher( + duration: const Duration( + milliseconds: 400), + transitionBuilder: + (Widget child, + Animation + animation) { + return ScaleTransition( + scale: animation, + child: child); + }, + child: Text( + _htmlRenderCtr .item .value .modules ?.moduleStat ?.like - ?.status == - true - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .outline, + ?.count != + null + ? Utils.numFormat( + _htmlRenderCtr + .item + .value + .modules! + .moduleStat! + .like! + .count) + : '点赞', + style: TextStyle( + color: _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .outline, + ), ), ), ), ), ), - ), - ], - ), - ) - : const SizedBox.shrink(), - ], - ), - ); - }, + ], + ), + ) + : const SizedBox.shrink(), + ], + ), + ); + }, + ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index 07d69a69..c8d88bc1 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -88,24 +88,28 @@ class _LaterPageState extends State ), body: Column( children: [ - TabBar( - isScrollable: true, - controller: _tabController, - tabAlignment: TabAlignment.start, - tabs: LaterViewType.values.map((item) { - final count = _baseCtr.counts[item]; - return Tab( - text: '${item.title}${count != -1 ? '($count)' : ''}'); - }).toList(), - onTap: (_) { - if (_tabController.indexIsChanging.not) { - currCtr().scrollController.animToTop(); - } else { - if (_baseCtr.enableMultiSelect.value) { - currCtr(_tabController.previousIndex).handleSelect(); + SafeArea( + top: false, + bottom: false, + child: TabBar( + isScrollable: true, + controller: _tabController, + tabAlignment: TabAlignment.start, + tabs: LaterViewType.values.map((item) { + final count = _baseCtr.counts[item]; + return Tab( + text: '${item.title}${count != -1 ? '($count)' : ''}'); + }).toList(), + onTap: (_) { + if (_tabController.indexIsChanging.not) { + currCtr().scrollController.animToTop(); + } else { + if (_baseCtr.enableMultiSelect.value) { + currCtr(_tabController.previousIndex).handleSelect(); + } } - } - }, + }, + ), ), Expanded( child: TabBarView( @@ -113,8 +117,13 @@ class _LaterPageState extends State ? const NeverScrollableScrollPhysics() : const CustomTabBarViewScrollPhysics(), controller: _tabController, - children: - LaterViewType.values.map((item) => item.page).toList(), + children: LaterViewType.values + .map((item) => SafeArea( + top: false, + bottom: false, + child: item.page, + )) + .toList(), ), ), ], diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index f0bfd26c..8da5ae36 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -6,6 +6,7 @@ import 'package:PiliPlus/pages/live_room/send_dm_panel.dart'; import 'package:PiliPlus/pages/live_room/widgets/chat.dart'; import 'package:PiliPlus/pages/live_room/widgets/header_control.dart'; import 'package:PiliPlus/services/service_locator.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; @@ -203,7 +204,7 @@ class _LiveRoomPageState extends State ), ); } else { - return const SizedBox(); + return const SizedBox.shrink(); } }, ), @@ -228,9 +229,8 @@ class _LiveRoomPageState extends State fit: BoxFit.cover, width: Get.width, height: Get.height, - imageUrl: Utils.thumbnailImgUrl( - _liveRoomController.roomInfoH5.value.roomInfo! - .appBackground!), + imageUrl: _liveRoomController.roomInfoH5.value + .roomInfo!.appBackground!.http2https, ) : Image.asset( 'assets/images/live/default_bg.webp', @@ -239,25 +239,31 @@ class _LiveRoomPageState extends State ), ), ), - isPortrait - ? Obx( - () { - if (_liveRoomController.isPortrait.value) { - if (padding == null) { - final padding = MediaQuery.paddingOf(context); - this.padding = padding.bottom + padding.top; + SafeArea( + top: false, + left: !isFullScreen, + right: !isFullScreen, + bottom: false, + child: isPortrait + ? Obx( + () { + if (_liveRoomController.isPortrait.value) { + if (padding == null) { + final padding = MediaQuery.paddingOf(context); + this.padding = padding.bottom + padding.top; + } + return _buildPP; } - return _buildPP; - } - return _buildPH; - }, - ) - : Column( - children: [ - Obx(() => _buildAppBar), - _buildBodyH, - ], - ), + return _buildPH; + }, + ) + : Column( + children: [ + Obx(() => _buildAppBar), + _buildBodyH, + ], + ), + ), ], ), ); @@ -458,10 +464,13 @@ class _LiveRoomPageState extends State children: [ Obx( () => Container( + margin: + EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom), color: isFullScreen ? Colors.black : null, width: isFullScreen ? Get.size.width : videoWidth, height: isFullScreen ? Get.size.height : Get.size.width * 9 / 16, child: MediaQuery.removePadding( + removeTop: true, removeRight: true, context: context, child: videoPlayerPanel(fill: Colors.transparent), @@ -469,13 +478,9 @@ class _LiveRoomPageState extends State ), ), Expanded( - child: SafeArea( - left: false, - top: false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildBottomWidget, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildBottomWidget, ), ), ], diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index b3276bac..fd030906 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -173,6 +173,7 @@ class _MainAppState extends State @override Widget build(BuildContext context) { + final bool isPortrait = context.orientation == Orientation.portrait; return PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { @@ -200,8 +201,7 @@ class _MainAppState extends State body: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (useSideBar || - context.orientation == Orientation.landscape) ...[ + if (useSideBar || !isPortrait) ...[ Obx( () => _mainController.navigationBars.length > 1 ? context.isTablet @@ -298,26 +298,28 @@ class _MainAppState extends State ), ], Expanded( - child: _mainController.mainTabBarView - ? CustomTabBarView( - scrollDirection: - context.orientation == Orientation.portrait - ? Axis.horizontal - : Axis.vertical, - physics: const NeverScrollableScrollPhysics(), - controller: _mainController.controller, - children: _mainController.pages, - ) - : PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _mainController.controller, - children: _mainController.pages, - ), + child: SafeArea( + top: false, + bottom: false, + left: isPortrait, + child: _mainController.mainTabBarView + ? CustomTabBarView( + scrollDirection: + isPortrait ? Axis.horizontal : Axis.vertical, + physics: const NeverScrollableScrollPhysics(), + controller: _mainController.controller, + children: _mainController.pages, + ) + : PageView( + physics: const NeverScrollableScrollPhysics(), + controller: _mainController.controller, + children: _mainController.pages, + ), + ), ), ], ), - bottomNavigationBar: useSideBar || - context.orientation == Orientation.landscape + bottomNavigationBar: useSideBar || !isPortrait ? null : StreamBuilder( stream: _mainController.hideTabBar diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index b108fbe8..7c27d692 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -40,63 +40,67 @@ class _MediaPageState extends CommonPageState Widget build(BuildContext context) { super.build(context); Color primary = Theme.of(context).colorScheme.primary; - return Scaffold( - backgroundColor: Colors.transparent, - appBar: AppBar( - toolbarHeight: 30, - ), - body: ListView( - controller: controller.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - children: [ - ListTile( - leading: null, - title: Padding( - padding: const EdgeInsets.only(left: 20), - child: Text( - '媒体库', - style: TextStyle( - fontSize: Theme.of(context).textTheme.titleLarge!.fontSize, - fontWeight: FontWeight.bold, - ), - ), - ), - trailing: IconButton( - tooltip: '设置', - onPressed: () { - Get.toNamed('/setting'); - }, - icon: const Icon( - Icons.settings_outlined, - size: 20, - ), - ), - ), - for (var item in controller.list) + return MediaQuery.removePadding( + context: context, + removeLeft: context.orientation == Orientation.landscape, + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + toolbarHeight: 30, + ), + body: ListView( + controller: controller.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + children: [ ListTile( - onTap: item['onTap'], - dense: true, - leading: Padding( - padding: const EdgeInsets.only(left: 15), - child: Icon( - item['icon'], - color: primary, + leading: null, + title: Padding( + padding: const EdgeInsets.only(left: 20), + child: Text( + '媒体库', + style: TextStyle( + fontSize: Theme.of(context).textTheme.titleLarge!.fontSize, + fontWeight: FontWeight.bold, + ), ), ), - contentPadding: - const EdgeInsets.only(left: 15, top: 2, bottom: 2), - minLeadingWidth: 0, - title: Text( - item['title'], - style: const TextStyle(fontSize: 15), + trailing: IconButton( + tooltip: '设置', + onPressed: () { + Get.toNamed('/setting'); + }, + icon: const Icon( + Icons.settings_outlined, + size: 20, + ), ), ), - Obx( - () => controller.loadingState.value is Loading - ? const SizedBox.shrink() - : favFolder(), - ) - ], + for (var item in controller.list) + ListTile( + onTap: item['onTap'], + dense: true, + leading: Padding( + padding: const EdgeInsets.only(left: 15), + child: Icon( + item['icon'], + color: primary, + ), + ), + contentPadding: + const EdgeInsets.only(left: 15, top: 2, bottom: 2), + minLeadingWidth: 0, + title: Text( + item['title'], + style: const TextStyle(fontSize: 15), + ), + ), + Obx( + () => controller.loadingState.value is Loading + ? const SizedBox.shrink() + : favFolder(), + ) + ], + ), ), ); } diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 4b7a0769..b80f3ee2 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -33,7 +33,6 @@ class MemberControllerNew extends CommonDataController late List tabs; List? tab2; RxInt contributeInitialIndex = 0.obs; - double top = 0; bool? hasSeasonOrSeries; final fromViewAid = Get.parameters['from_view_aid']; diff --git a/lib/pages/member/member_page.dart b/lib/pages/member/member_page.dart index 81b8602c..fa6b2fd9 100644 --- a/lib/pages/member/member_page.dart +++ b/lib/pages/member/member_page.dart @@ -31,6 +31,7 @@ class _MemberPageNewState extends State { late final String _heroTag; late final MemberControllerNew _userController; final _key = GlobalKey(); + int _offset = 120; @override void initState() { @@ -45,8 +46,10 @@ class _MemberPageNewState extends State { } void listener() { - _userController.showUname.value = - _userController.scrollController.offset >= 120; + if (_userController.scrollController.hasClients) { + _userController.showUname.value = + _userController.scrollController.offset >= _offset; + } } @override @@ -57,11 +60,99 @@ class _MemberPageNewState extends State { @override Widget build(BuildContext context) { - if (_userController.top == 0) { - _userController.top = MediaQuery.of(context).padding.top; - } return Scaffold( + extendBody: true, + extendBodyBehindAppBar: true, resizeToAvoidBottomInset: false, + appBar: AppBar( + forceMaterialTransparency: true, + title: IgnorePointer( + child: Obx( + () => _userController.showUname.value && + _userController.username != null + ? Text(_userController.username!) + : const SizedBox.shrink(), + ), + ), + actions: [ + IconButton( + tooltip: '搜索', + onPressed: () => Get.toNamed( + '/memberSearch?mid=$_mid&uname=${_userController.username}'), + icon: const Icon(Icons.search_outlined), + ), + PopupMenuButton( + icon: const Icon(Icons.more_vert), + itemBuilder: (BuildContext context) => [ + if (_userController.ownerMid != _mid) ...[ + PopupMenuItem( + onTap: () => _userController.blockUser(context), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.block, size: 19), + const SizedBox(width: 10), + Text(_userController.relation.value != 128 + ? '加入黑名单' + : '移除黑名单'), + ], + ), + ) + ], + PopupMenuItem( + onTap: () => _userController.shareUser(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.share_outlined, size: 19), + const SizedBox(width: 10), + Text(_userController.ownerMid != _mid ? '分享UP主' : '分享我的主页'), + ], + ), + ), + if (_userController.ownerMid != null && + _userController.mid != _userController.ownerMid) ...[ + const PopupMenuDivider(), + PopupMenuItem( + onTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + clipBehavior: Clip.hardEdge, + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + content: ReportPanel( + name: _userController.username, + mid: _mid, + ), + ), + ); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_outline, + size: 19, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(width: 10), + Text( + '举报', + style: TextStyle( + color: Theme.of(context).colorScheme.error), + ), + ], + ), + ), + ], + ], + ), + const SizedBox(width: 4), + ], + ), body: Obx( () => _userController.loadingState.value is Success ? LayoutBuilder( @@ -70,31 +161,24 @@ class _MemberPageNewState extends State { key: _key, controller: _userController.scrollController, onlyOneScrollInBody: true, + pinnedHeaderSliverHeightBuilder: () { + return kToolbarHeight + + MediaQuery.paddingOf(this.context).top.toInt(); + }, headerSliverBuilder: (context, innerBoxIsScrolled) { return [ - SliverOverlapAbsorber( - handle: ExtendedNestedScrollView - .sliverOverlapAbsorberHandleFor(context), - sliver: _buildAppBar( - isV: constraints.maxHeight > constraints.maxWidth, - ), + _buildAppBar( + isV: constraints.maxHeight > constraints.maxWidth, ), ]; }, body: _userController.tab2?.isNotEmpty == true - ? LayoutBuilder( - builder: (context, _) { - return Padding( - padding: EdgeInsets.only( - top: ExtendedNestedScrollView - .sliverOverlapAbsorberHandleFor( - context) - .layoutExtent ?? - 0, - ), - child: _buildBody, - ); - }, + ? Column( + children: [ + if ((_userController.tab2?.length ?? 0) > 1) + _buildTab, + Expanded(child: _buildBody), + ], ) : Center(child: const Text('EMPTY')), ); @@ -109,14 +193,18 @@ class _MemberPageNewState extends State { Widget get _buildTab => Material( color: Theme.of(context).colorScheme.surface, - child: TabBar( - controller: _userController.tabController, - tabs: _userController.tabs, - onTap: (value) { - if (_userController.tabController?.indexIsChanging == false) { - _key.currentState?.outerController.animToTop(); - } - }, + child: SafeArea( + top: false, + bottom: false, + child: TabBar( + controller: _userController.tabController, + tabs: _userController.tabs, + onTap: (value) { + if (_userController.tabController?.indexIsChanging == false) { + _key.currentState?.outerController.animToTop(); + } + }, + ), ), ); @@ -150,119 +238,19 @@ class _MemberPageNewState extends State { ), ); - Widget _buildAppBar({bool needTab = true, bool isV = true}) => - DynamicSliverAppBar( - primary: false, - leading: Padding( - padding: EdgeInsets.only(top: _userController.top), - child: const BackButton(), - ), - hasTabBar: (_userController.tab2?.length ?? 0) > 1, - toolbarHeight: kToolbarHeight + _userController.top, - title: IgnorePointer( - child: Obx(() => _userController.showUname.value && - _userController.username != null - ? Padding( - padding: EdgeInsets.only(top: _userController.top), - child: Text(_userController.username!), - ) - : const SizedBox.shrink()), - ), - pinned: true, - flexibleSpace: _buildUserInfo(_userController.loadingState.value, isV), - bottom: needTab && (_userController.tab2?.length ?? -1) > 1 - ? PreferredSize( - preferredSize: Size.fromHeight(48), - child: _buildTab, - ) - : null, - actions: [ - Padding( - padding: EdgeInsets.only(top: _userController.top), - child: IconButton( - tooltip: '搜索', - onPressed: () => Get.toNamed( - '/memberSearch?mid=$_mid&uname=${_userController.username}'), - icon: const Icon(Icons.search_outlined), - ), - ), - Padding( - padding: EdgeInsets.only(top: _userController.top), - child: PopupMenuButton( - icon: const Icon(Icons.more_vert), - itemBuilder: (BuildContext context) => [ - if (_userController.ownerMid != _mid) ...[ - PopupMenuItem( - onTap: () => _userController.blockUser(context), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.block, size: 19), - const SizedBox(width: 10), - Text(_userController.relation.value != 128 - ? '加入黑名单' - : '移除黑名单'), - ], - ), - ) - ], - PopupMenuItem( - onTap: () => _userController.shareUser(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.share_outlined, size: 19), - const SizedBox(width: 10), - Text(_userController.ownerMid != _mid - ? '分享UP主' - : '分享我的主页'), - ], - ), - ), - if (_userController.ownerMid != null && - _userController.mid != _userController.ownerMid) ...[ - const PopupMenuDivider(), - PopupMenuItem( - onTap: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - clipBehavior: Clip.hardEdge, - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, - ), - content: ReportPanel( - name: _userController.username, - mid: _mid, - ), - ), - ); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.error_outline, - size: 19, - color: Theme.of(context).colorScheme.error, - ), - const SizedBox(width: 10), - Text( - '举报', - style: TextStyle( - color: Theme.of(context).colorScheme.error), - ), - ], - ), - ), - ], - ], - ), - ), - const SizedBox(width: 4), - ], - ); + Widget _buildAppBar({bool isV = true}) { + return DynamicSliverAppBar( + pinned: true, + primary: false, + automaticallyImplyLeading: false, + toolbarHeight: kToolbarHeight + MediaQuery.paddingOf(context).top, + flexibleSpace: _buildUserInfo(_userController.loadingState.value, isV), + callback: (value) { + _offset = (value - 56 - MediaQuery.paddingOf(context).top).toInt(); + listener(); + }, + ); + } Widget _errorWidget(msg) { return errorWidget( diff --git a/lib/pages/member/widget/user_info_card.dart b/lib/pages/member/widget/user_info_card.dart index db65655c..7b33ed43 100644 --- a/lib/pages/member/widget/user_info_card.dart +++ b/lib/pages/member/widget/user_info_card.dart @@ -118,6 +118,12 @@ class UserInfoCard extends StatelessWidget { onTap: () => Utils.copyText(card.name!), child: Text( card.name!, + strutStyle: StrutStyle( + height: 1, + leading: 0, + fontSize: 17, + fontWeight: FontWeight.bold, + ), style: TextStyle( height: 1, fontSize: 17, @@ -136,8 +142,7 @@ class UserInfoCard extends StatelessWidget { ), if (card.vip?.vipStatus == 1) CachedNetworkImage( - imageUrl: - Utils.thumbnailImgUrl(card.vip!.label!.image!.http2https), + imageUrl: Utils.thumbnailImgUrl(card.vip!.label!.image!, 80), height: 20, placeholder: (context, url) { return const SizedBox.shrink(); @@ -612,7 +617,10 @@ class UserInfoCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildHeader(context), + // _buildHeader(context), + SizedBox( + height: Get.mediaQuery.padding.bottom + 56, + ), SafeArea( top: false, bottom: false, diff --git a/lib/pages/member_coin/view.dart b/lib/pages/member_coin/view.dart index 58e564de..f8d7715c 100644 --- a/lib/pages/member_coin/view.dart +++ b/lib/pages/member_coin/view.dart @@ -39,18 +39,22 @@ class _MemberCoinPageState extends State { appBar: AppBar( title: Text('${widget.mid == _ownerMid ? '我' : '${widget.name}'}的最近投币'), ), - body: CustomScrollView( - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - left: StyleString.safeSpace, - right: StyleString.safeSpace, - bottom: MediaQuery.paddingOf(context).bottom + 80, + body: SafeArea( + top: false, + bottom: false, + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + left: StyleString.safeSpace, + right: StyleString.safeSpace, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_ctr.loadingState.value)), ), - sliver: Obx(() => _buildBody(_ctr.loadingState.value)), - ), - ], + ], + ), ), ); } diff --git a/lib/pages/member_like/view.dart b/lib/pages/member_like/view.dart index 5bde2469..f3bba696 100644 --- a/lib/pages/member_like/view.dart +++ b/lib/pages/member_like/view.dart @@ -39,18 +39,22 @@ class _MemberLikePageState extends State { appBar: AppBar( title: Text('${widget.mid == _ownerMid ? '我' : '${widget.name}'}的推荐'), ), - body: CustomScrollView( - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - left: StyleString.safeSpace, - right: StyleString.safeSpace, - bottom: MediaQuery.paddingOf(context).bottom + 80, + body: SafeArea( + top: false, + bottom: false, + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + left: StyleString.safeSpace, + right: StyleString.safeSpace, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_ctr.loadingState.value)), ), - sliver: Obx(() => _buildBody(_ctr.loadingState.value)), - ), - ], + ], + ), ), ); } diff --git a/lib/pages/member_search/search_archive.dart b/lib/pages/member_search/search_archive.dart index 39067b1a..2eb29df7 100644 --- a/lib/pages/member_search/search_archive.dart +++ b/lib/pages/member_search/search_archive.dart @@ -28,22 +28,26 @@ class _SearchArchiveState extends State @override Widget build(BuildContext context) { super.build(context); - return refreshIndicator( - onRefresh: () async { - await widget.ctr.refreshArchive(); - }, - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), - sliver: - Obx(() => _buildBody(context, widget.ctr.archiveState.value)), - ) - ], + return SafeArea( + top: false, + bottom: false, + child: refreshIndicator( + onRefresh: () async { + await widget.ctr.refreshArchive(); + }, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: + Obx(() => _buildBody(context, widget.ctr.archiveState.value)), + ) + ], + ), ), ); } diff --git a/lib/pages/member_search/search_dynamic.dart b/lib/pages/member_search/search_dynamic.dart index fedf9ca3..b0893748 100644 --- a/lib/pages/member_search/search_dynamic.dart +++ b/lib/pages/member_search/search_dynamic.dart @@ -30,21 +30,25 @@ class _SearchDynamicState extends State @override Widget build(BuildContext context) { super.build(context); - return refreshIndicator( - onRefresh: () async { - await widget.ctr.refreshDynamic(); - }, - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), - sliver: - Obx(() => _buildBody(context, widget.ctr.dynamicState.value)), - ) - ], + return SafeArea( + top: false, + bottom: false, + child: refreshIndicator( + onRefresh: () async { + await widget.ctr.refreshDynamic(); + }, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: + Obx(() => _buildBody(context, widget.ctr.dynamicState.value)), + ) + ], + ), ), ); } diff --git a/lib/pages/member_search/view.dart b/lib/pages/member_search/view.dart index 28666e06..6242f69e 100644 --- a/lib/pages/member_search/view.dart +++ b/lib/pages/member_search/view.dart @@ -55,22 +55,26 @@ class _MemberSearchPageState extends State { () => _memberSearchCtr.hasData.value ? Column( children: [ - TabBar( - controller: _memberSearchCtr.tabController, - tabs: [ - Obx( - () => Tab( - text: - '视频 ${_memberSearchCtr.archiveCount.value != -1 ? '${_memberSearchCtr.archiveCount.value}' : ''}', + SafeArea( + top: false, + bottom: false, + child: TabBar( + controller: _memberSearchCtr.tabController, + tabs: [ + Obx( + () => Tab( + text: + '视频 ${_memberSearchCtr.archiveCount.value != -1 ? '${_memberSearchCtr.archiveCount.value}' : ''}', + ), ), - ), - Obx( - () => Tab( - text: - '动态 ${_memberSearchCtr.dynamicCount.value != -1 ? '${_memberSearchCtr.dynamicCount.value}' : ''}', + Obx( + () => Tab( + text: + '动态 ${_memberSearchCtr.dynamicCount.value != -1 ? '${_memberSearchCtr.dynamicCount.value}' : ''}', + ), ), - ), - ], + ], + ), ), Expanded( child: tabBarView( diff --git a/lib/pages/setting/pages/color_select.dart b/lib/pages/setting/pages/color_select.dart index 67f86266..000b5566 100644 --- a/lib/pages/setting/pages/color_select.dart +++ b/lib/pages/setting/pages/color_select.dart @@ -52,220 +52,230 @@ class _ColorSelectPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('选择应用主题')), - body: ListView( - children: [ - ListTile( - onTap: () async { - ThemeType? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '主题模式', - value: ctr.themeType.value, - values: ThemeType.values - .map((e) => (e, e.description)) - .toList()); - }, - ); - if (result != null) { - try { - Get.find().themeType.value = result; - } catch (_) {} - ctr.themeType.value = result; - GStorage.setting.put(SettingBoxKey.themeMode, result.index); - Get.changeThemeMode(result.toThemeMode); - } - }, - leading: Container( - width: 40, - alignment: Alignment.center, - child: const Icon(Icons.flashlight_on_outlined), + body: SafeArea( + bottom: false, + child: ListView( + children: [ + ListTile( + onTap: () async { + ThemeType? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '主题模式', + value: ctr.themeType.value, + values: ThemeType.values + .map((e) => (e, e.description)) + .toList()); + }, + ); + if (result != null) { + try { + Get.find().themeType.value = result; + } catch (_) {} + ctr.themeType.value = result; + GStorage.setting.put(SettingBoxKey.themeMode, result.index); + Get.changeThemeMode(result.toThemeMode); + } + }, + leading: Container( + width: 40, + alignment: Alignment.center, + child: const Icon(Icons.flashlight_on_outlined), + ), + title: Text('主题模式', style: titleStyle), + subtitle: Obx(() => Text( + '当前模式:${ctr.themeType.value.description}', + style: subTitleStyle)), ), - title: Text('主题模式', style: titleStyle), - subtitle: Obx(() => Text('当前模式:${ctr.themeType.value.description}', - style: subTitleStyle)), - ), - Obx( - () => ListTile( - enabled: ctr.type.value != 0, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('调色板风格'), - PopupMenuButton( - enabled: ctr.type.value != 0, - initialValue: _dynamicSchemeVariant, - onSelected: (item) async { - _dynamicSchemeVariant = item; - await GStorage.setting - .put(SettingBoxKey.schemeVariant, item.index); - Get.forceAppUpdate(); - }, - itemBuilder: (context) => FlexSchemeVariant.values - .map((item) => PopupMenuItem( - value: item, - child: Text(item.variantName), - )) - .toList(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - _dynamicSchemeVariant.variantName, - style: TextStyle( - height: 1, - fontSize: 13, + Obx( + () => ListTile( + enabled: ctr.type.value != 0, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('调色板风格'), + PopupMenuButton( + enabled: ctr.type.value != 0, + initialValue: _dynamicSchemeVariant, + onSelected: (item) async { + _dynamicSchemeVariant = item; + await GStorage.setting + .put(SettingBoxKey.schemeVariant, item.index); + Get.forceAppUpdate(); + }, + itemBuilder: (context) => FlexSchemeVariant.values + .map((item) => PopupMenuItem( + value: item, + child: Text(item.variantName), + )) + .toList(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _dynamicSchemeVariant.variantName, + style: TextStyle( + height: 1, + fontSize: 13, + color: ctr.type.value == 0 + ? Theme.of(context) + .colorScheme + .outline + .withOpacity(0.8) + : Theme.of(context).colorScheme.secondary, + ), + strutStyle: StrutStyle(leading: 0, height: 1), + ), + Icon( + size: 20, + Icons.keyboard_arrow_right, color: ctr.type.value == 0 ? Theme.of(context) .colorScheme .outline .withOpacity(0.8) : Theme.of(context).colorScheme.secondary, - ), - strutStyle: StrutStyle(leading: 0, height: 1), - ), - Icon( - size: 20, - Icons.keyboard_arrow_right, - color: ctr.type.value == 0 - ? Theme.of(context) - .colorScheme - .outline - .withOpacity(0.8) - : Theme.of(context).colorScheme.secondary, + ) + ], + ), + ), + ], + ), + leading: Container( + width: 40, + alignment: Alignment.center, + child: Icon(Icons.palette_outlined), + ), + subtitle: Text( + _dynamicSchemeVariant.description, + style: TextStyle(fontSize: 12), + ), + ), + ), + Obx( + () => RadioListTile( + value: 0, + title: const Text('动态取色'), + groupValue: ctr.type.value, + onChanged: (dynamic val) async { + ctr.type.value = 0; + ctr.setting.put(SettingBoxKey.dynamicColor, true); + Get.forceAppUpdate(); + }, + ), + ), + Obx( + () => RadioListTile( + value: 1, + title: const Text('指定颜色'), + groupValue: ctr.type.value, + onChanged: (dynamic val) async { + ctr.type.value = 1; + ctr.setting.put(SettingBoxKey.dynamicColor, false); + Get.forceAppUpdate(); + }, + ), + ), + AnimatedSize( + curve: Curves.easeInOut, + alignment: Alignment.topCenter, + duration: const Duration(milliseconds: 200), + child: Obx( + () => SizedBox( + height: ctr.type.value == 0 ? 0 : null, + child: Padding( + padding: + const EdgeInsets.only(top: 12, left: 12, right: 12), + child: Wrap( + alignment: WrapAlignment.center, + spacing: 22, + runSpacing: 18, + children: [ + ...ctr.colorThemes.map( + (e) { + final index = ctr.colorThemes.indexOf(e); + return GestureDetector( + onTap: () { + ctr.currentColor.value = index; + ctr.setting + .put(SettingBoxKey.customColor, index); + Get.forceAppUpdate(); + }, + child: Column( + children: [ + Container( + width: 46, + height: 46, + decoration: BoxDecoration( + color: e['color'].withOpacity(0.8), + borderRadius: BorderRadius.circular(50), + border: Border.all( + width: 2, + color: ctr.currentColor.value == index + ? Colors.black + : e['color'].withOpacity(0.8), + ), + ), + child: AnimatedOpacity( + opacity: ctr.currentColor.value == index + ? 1 + : 0, + duration: + const Duration(milliseconds: 200), + child: const Icon( + Icons.done, + color: Colors.black, + size: 20, + ), + ), + ), + const SizedBox(height: 3), + Text( + e['label'], + style: TextStyle( + fontSize: 12, + color: ctr.currentColor.value != index + ? Theme.of(context) + .colorScheme + .outline + : null, + ), + ), + ], + ), + ); + }, ) ], ), ), - ], - ), - leading: Container( - width: 40, - alignment: Alignment.center, - child: Icon(Icons.palette_outlined), - ), - subtitle: Text( - _dynamicSchemeVariant.description, - style: TextStyle(fontSize: 12), - ), - ), - ), - Obx( - () => RadioListTile( - value: 0, - title: const Text('动态取色'), - groupValue: ctr.type.value, - onChanged: (dynamic val) async { - ctr.type.value = 0; - ctr.setting.put(SettingBoxKey.dynamicColor, true); - Get.forceAppUpdate(); - }, - ), - ), - Obx( - () => RadioListTile( - value: 1, - title: const Text('指定颜色'), - groupValue: ctr.type.value, - onChanged: (dynamic val) async { - ctr.type.value = 1; - ctr.setting.put(SettingBoxKey.dynamicColor, false); - Get.forceAppUpdate(); - }, - ), - ), - AnimatedSize( - curve: Curves.easeInOut, - alignment: Alignment.topCenter, - duration: const Duration(milliseconds: 200), - child: Obx( - () => SizedBox( - height: ctr.type.value == 0 ? 0 : null, - child: Padding( - padding: const EdgeInsets.only(top: 12, left: 12, right: 12), - child: Wrap( - alignment: WrapAlignment.center, - spacing: 22, - runSpacing: 18, - children: [ - ...ctr.colorThemes.map( - (e) { - final index = ctr.colorThemes.indexOf(e); - return GestureDetector( - onTap: () { - ctr.currentColor.value = index; - ctr.setting.put(SettingBoxKey.customColor, index); - Get.forceAppUpdate(); - }, - child: Column( - children: [ - Container( - width: 46, - height: 46, - decoration: BoxDecoration( - color: e['color'].withOpacity(0.8), - borderRadius: BorderRadius.circular(50), - border: Border.all( - width: 2, - color: ctr.currentColor.value == index - ? Colors.black - : e['color'].withOpacity(0.8), - ), - ), - child: AnimatedOpacity( - opacity: - ctr.currentColor.value == index ? 1 : 0, - duration: const Duration(milliseconds: 200), - child: const Icon( - Icons.done, - color: Colors.black, - size: 20, - ), - ), - ), - const SizedBox(height: 3), - Text( - e['label'], - style: TextStyle( - fontSize: 12, - color: ctr.currentColor.value != index - ? Theme.of(context).colorScheme.outline - : null, - ), - ), - ], - ), - ); - }, - ) - ], - ), ), ), ), - ), - ...[ - IgnorePointer( - child: SizedBox( - height: Get.height / 2, - width: Get.width, - child: const HomePage(), + ...[ + IgnorePointer( + child: SizedBox( + height: Get.height / 2, + width: Get.width, + child: const HomePage(), + ), ), - ), - IgnorePointer( - child: NavigationBar( - destinations: defaultNavigationBars - .map( - (item) => NavigationDestination( - icon: item['icon'], - label: item['label'], - ), - ) - .toList(), + IgnorePointer( + child: NavigationBar( + destinations: defaultNavigationBars + .map( + (item) => NavigationDestination( + icon: item['icon'], + label: item['label'], + ), + ) + .toList(), + ), ), - ), + ], ], - ], + ), ), ); } diff --git a/lib/pages/setting/pages/font_size_select.dart b/lib/pages/setting/pages/font_size_select.dart index 7f8f3ce6..45808586 100644 --- a/lib/pages/setting/pages/font_size_select.dart +++ b/lib/pages/setting/pages/font_size_select.dart @@ -48,58 +48,55 @@ class _FontSizeSelectPageState extends State { const SizedBox(width: 12) ], ), - body: Column( - children: [ - Expanded( - child: Center( - child: Text( - '当前字体大小:${currentSize == 1.0 ? '默认' : currentSize}', - style: TextStyle(fontSize: 14 * currentSize), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: Center( + child: Text( + '当前字体大小:${currentSize == 1.0 ? '默认' : currentSize}', + style: TextStyle(fontSize: 14 * currentSize), + ), ), ), - ), - Container( - width: double.infinity, - padding: EdgeInsets.only( - left: 20, - right: 20, - top: 20, - bottom: MediaQuery.of(context).padding.bottom + 20, - ), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.3))), - color: Theme.of(context).colorScheme.surface, - ), - child: Row( - children: [ - const Text('小'), - Expanded( - child: Slider( - min: minSize, - value: currentSize, - max: maxSize, - divisions: list.length - 1, - secondaryTrackValue: 1, - onChanged: (double val) { - currentSize = val.toPrecision(2); - setState(() {}); - }, + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.3))), + color: Theme.of(context).colorScheme.surface, + ), + child: Row( + children: [ + const Text('小'), + Expanded( + child: Slider( + min: minSize, + value: currentSize, + max: maxSize, + divisions: list.length - 1, + secondaryTrackValue: 1, + onChanged: (double val) { + currentSize = val.toPrecision(2); + setState(() {}); + }, + ), ), - ), - const SizedBox(width: 5), - const Text( - '大', - style: TextStyle(fontSize: 20), - ), - ], - ), - ) - ], + const SizedBox(width: 5), + const Text( + '大', + style: TextStyle(fontSize: 20), + ), + ], + ), + ) + ], + ), ), ); } diff --git a/lib/pages/setting/pages/logs.dart b/lib/pages/setting/pages/logs.dart index 0d3ebb4c..0f4142bb 100644 --- a/lib/pages/setting/pages/logs.dart +++ b/lib/pages/setting/pages/logs.dart @@ -144,62 +144,79 @@ class _LogsPageState extends State { ], ), body: logsContent.isNotEmpty - ? ListView.builder( - itemCount: logsContent.length, - itemBuilder: (context, index) { - final log = logsContent[index]; - if (log['date'] is DateTime) { - latestLog ??= log['date']; - } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( + ? SafeArea( + bottom: false, + child: ListView.separated( + itemCount: logsContent.length, + itemBuilder: (context, index) { + final log = logsContent[index]; + if (log['date'] is DateTime) { + latestLog ??= log['date']; + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - log['date'].toString(), - style: Theme.of(context).textTheme.titleMedium, + Row( + children: [ + Text( + log['date'].toString(), + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + ), + ), + const SizedBox(width: 10), + TextButton.icon( + style: TextButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), + onPressed: () async { + await Utils.copyText('```\n${log['body']}\n```', + needToast: false); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '已将 ${log['date'].toString()} 复制至剪贴板', + ), + ), + ); + } + }, + icon: const Icon(Icons.copy_outlined, size: 16), + label: const Text('复制'), + ) + ], + ), + const SizedBox(height: 5), + Card( + elevation: 1, + margin: EdgeInsets.zero, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(12.0), + child: SelectableText(log['body']), ), ), - TextButton.icon( - onPressed: () async { - await Utils.copyText('```\n${log['body']}\n```', - needToast: false); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - '已将 ${log['date'].toString()} 复制至剪贴板', - ), - ), - ); - } - }, - icon: const Icon(Icons.copy_outlined, size: 16), - label: const Text('复制'), - ) ], ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Card( - elevation: 1, - clipBehavior: Clip.antiAliasWithSaveLayer, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: SelectableText(log['body']), - ), - ), - ), - const Divider(indent: 12, endIndent: 12), - ], - ); - }, + ); + }, + separatorBuilder: (context, index) => const Divider( + indent: 12, + endIndent: 12, + height: 24, + ), + ), ) - : errorWidget(), + : scrollErrorWidget(), ); } } diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index 47d439d7..2208b111 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -210,9 +210,9 @@ class _PlaySpeedPageState extends State { const SizedBox(width: 16), ], ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + body: SafeArea( + bottom: false, + child: ListView( children: [ Padding( padding: diff --git a/lib/pages/setting/search_page.dart b/lib/pages/setting/search_page.dart index 28e545e6..7c11bec4 100644 --- a/lib/pages/setting/search_page.dart +++ b/lib/pages/setting/search_page.dart @@ -75,24 +75,27 @@ class _SettingsSearchPageState extends State { ), ), ), - body: Obx( - () => _list.isEmpty - ? CustomScrollView( - slivers: [HttpError()], - ) - : CustomScrollView( - slivers: [ - SliverWaterfallFlow.extent( - maxCrossAxisExtent: Grid.smallCardWidth * 2, - children: [ - ..._list.map((item) => item.widget), - SizedBox( - height: MediaQuery.paddingOf(context).bottom + 80, + body: SafeArea( + bottom: false, + child: CustomScrollView( + slivers: [ + Obx( + () => _list.isEmpty + ? HttpError() + : SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80, ), - ], - ), - ], - ), + sliver: SliverWaterfallFlow.extent( + maxCrossAxisExtent: Grid.smallCardWidth * 2, + children: [ + ..._list.map((item) => item.widget), + ], + ), + ), + ), + ], + ), ), ); } diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index 3e9b7433..5afb25bc 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -121,24 +121,35 @@ class _SettingPageState extends State { : Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(flex: 40, child: _buildList), + Expanded( + flex: 4, + child: MediaQuery.removePadding( + context: context, + removeRight: true, + child: _buildList, + ), + ), VerticalDivider( width: 1, color: Theme.of(context).colorScheme.outline.withOpacity(0.1), ), Expanded( - flex: 60, - child: switch (_type) { - 'privacySetting' => PrivacySetting(showAppBar: false), - 'recommendSetting' => RecommendSetting(showAppBar: false), - 'videoSetting' => VideoSetting(showAppBar: false), - 'playSetting' => PlaySetting(showAppBar: false), - 'styleSetting' => StyleSetting(showAppBar: false), - 'extraSetting' => ExtraSetting(showAppBar: false), - 'webdavSetting' => WebDavSettingPage(showAppBar: false), - 'about' => AboutPage(showAppBar: false), - _ => const SizedBox.shrink(), - }, + flex: 6, + child: MediaQuery.removePadding( + context: context, + removeLeft: true, + child: switch (_type) { + 'privacySetting' => PrivacySetting(showAppBar: false), + 'recommendSetting' => RecommendSetting(showAppBar: false), + 'videoSetting' => VideoSetting(showAppBar: false), + 'playSetting' => PlaySetting(showAppBar: false), + 'styleSetting' => StyleSetting(showAppBar: false), + 'extraSetting' => ExtraSetting(showAppBar: false), + 'webdavSetting' => WebDavSettingPage(showAppBar: false), + 'about' => AboutPage(showAppBar: false), + _ => const SizedBox.shrink(), + }, + ), ) ], ), diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 2bbcf9ca..d5949018 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -23,19 +23,23 @@ class _SubPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('我的订阅')), - body: refreshIndicator( - onRefresh: () async { - await _subController.onRefresh(); - }, - child: CustomScrollView( - slivers: [ - Obx(() => _buildBody(_subController.loadingState.value)), - SliverToBoxAdapter( - child: SizedBox( - height: MediaQuery.of(context).padding.bottom + 80, + body: SafeArea( + top: false, + bottom: false, + child: refreshIndicator( + onRefresh: () async { + await _subController.onRefresh(); + }, + child: CustomScrollView( + slivers: [ + Obx(() => _buildBody(_subController.loadingState.value)), + SliverToBoxAdapter( + child: SizedBox( + height: MediaQuery.of(context).padding.bottom + 80, + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/subscription_detail/view.dart b/lib/pages/subscription_detail/view.dart index 359da919..cbdb3659 100644 --- a/lib/pages/subscription_detail/view.dart +++ b/lib/pages/subscription_detail/view.dart @@ -103,7 +103,7 @@ class _SubDetailPageState extends State { Widget get _buildCount => SliverToBoxAdapter( child: Padding( - padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14), + padding: const EdgeInsets.only(top: 12, bottom: 8, left: 14), child: Obx( () => Text( '共${_subDetailController.mediaCount}条视频', @@ -118,7 +118,7 @@ class _SubDetailPageState extends State { ); Widget get _buildAppBar => SliverAppBar( - expandedHeight: 215 - MediaQuery.paddingOf(context).bottom, + expandedHeight: 210 - MediaQuery.paddingOf(context).top, pinned: true, title: Obx( () { @@ -158,7 +158,7 @@ class _SubDetailPageState extends State { top: kTextTabBarHeight + MediaQuery.of(context).padding.top + 15, left: 12, right: 12, - bottom: 20, + bottom: 12, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index a2256e58..27dc6d76 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -31,11 +31,7 @@ class _WhisperPageState extends State { child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ - SliverSafeArea( - top: false, - bottom: false, - sliver: _buildTopItems, - ), + _buildTopItems, Obx(() => _buildBody(_whisperController.loadingState.value)), ], ), @@ -90,59 +86,63 @@ class _WhisperPageState extends State { }; } - Widget get _buildTopItems => SliverToBoxAdapter( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: - List.generate(_whisperController.msgFeedTopItems.length, (index) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - child: Padding( - padding: const EdgeInsets.all(10), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Obx( - () => Badge( - isLabelVisible: - _whisperController.unreadCounts[index] > 0, - label: - Text(" ${_whisperController.unreadCounts[index]} "), - alignment: Alignment.topRight, - child: CircleAvatar( - radius: 22, - backgroundColor: - Theme.of(context).colorScheme.onInverseSurface, - child: Icon( - _whisperController.msgFeedTopItems[index]['icon'], - size: 20, - color: Theme.of(context).colorScheme.primary, + Widget get _buildTopItems => SliverSafeArea( + top: false, + bottom: false, + sliver: SliverToBoxAdapter( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(_whisperController.msgFeedTopItems.length, + (index) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx( + () => Badge( + isLabelVisible: + _whisperController.unreadCounts[index] > 0, + label: Text( + " ${_whisperController.unreadCounts[index]} "), + alignment: Alignment.topRight, + child: CircleAvatar( + radius: 22, + backgroundColor: + Theme.of(context).colorScheme.onInverseSurface, + child: Icon( + _whisperController.msgFeedTopItems[index]['icon'], + size: 20, + color: Theme.of(context).colorScheme.primary, + ), ), ), ), - ), - const SizedBox(height: 6), - Text( - _whisperController.msgFeedTopItems[index]['name'], - style: const TextStyle(fontSize: 13), - ), - ], + const SizedBox(height: 6), + Text( + _whisperController.msgFeedTopItems[index]['name'], + style: const TextStyle(fontSize: 13), + ), + ], + ), ), - ), - onTap: () { - if (!_whisperController.msgFeedTopItems[index]['enabled']) { - SmartDialog.showToast('已禁用'); - return; - } - _whisperController.unreadCounts[index] = 0; - Get.toNamed( - _whisperController.msgFeedTopItems[index]['route'], - ); - }, - ); - }).toList(), + onTap: () { + if (!_whisperController.msgFeedTopItems[index]['enabled']) { + SmartDialog.showToast('已禁用'); + return; + } + _whisperController.unreadCounts[index] = 0; + Get.toNamed( + _whisperController.msgFeedTopItems[index]['route'], + ); + }, + ); + }).toList(), + ), ), ); } diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index 567ae8d9..a287d135 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -94,21 +94,25 @@ class _WhisperDetailPageState ), ), ), - body: Column( - children: [ - Expanded( - child: Listener( - child: Obx(() => - _buildBody(_whisperDetailController.loadingState.value)), - onPointerDown: (event) { - // Hide panel when touch ListView. - hidePanel(); - }, + body: SafeArea( + top: false, + bottom: false, + child: Column( + children: [ + Expanded( + child: Listener( + child: Obx(() => + _buildBody(_whisperDetailController.loadingState.value)), + onPointerDown: (event) { + // Hide panel when touch ListView. + hidePanel(); + }, + ), ), - ), - _buildInputView(), - buildPanelContainer(Theme.of(context).colorScheme.onInverseSurface), - ], + _buildInputView(), + buildPanelContainer(Theme.of(context).colorScheme.onInverseSurface), + ], + ), ), ); }