opt: safearea

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-21 11:15:21 +08:00
parent 95caf111ae
commit 9ad57dccb0
37 changed files with 1660 additions and 1518 deletions

View File

@@ -43,10 +43,10 @@ class DynamicSliverAppBar extends StatefulWidget {
this.forceMaterialTransparency = false, this.forceMaterialTransparency = false,
this.clipBehavior, this.clipBehavior,
this.appBarClipper, this.appBarClipper,
this.hasTabBar = false, this.callback,
}); });
final bool hasTabBar; final ValueChanged<double>? callback;
final Widget? flexibleSpace; final Widget? flexibleSpace;
final Widget? leading; final Widget? leading;
final bool automaticallyImplyLeading; final bool automaticallyImplyLeading;
@@ -113,6 +113,7 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
_height = (_childKey.currentContext!.findRenderObject()! as RenderBox) _height = (_childKey.currentContext!.findRenderObject()! as RenderBox)
.size .size
.height; .height;
widget.callback?.call(_height);
}); });
}); });
} }
@@ -166,7 +167,7 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
onStretchTrigger: widget.onStretchTrigger, onStretchTrigger: widget.onStretchTrigger,
shape: widget.shape, shape: widget.shape,
toolbarHeight: widget.toolbarHeight, toolbarHeight: widget.toolbarHeight,
expandedHeight: _height + (widget.hasTabBar ? 48 : 0), expandedHeight: _height,
leadingWidth: widget.leadingWidth, leadingWidth: widget.leadingWidth,
toolbarTextStyle: widget.toolbarTextStyle, toolbarTextStyle: widget.toolbarTextStyle,
titleTextStyle: widget.titleTextStyle, titleTextStyle: widget.titleTextStyle,

View File

@@ -51,30 +51,33 @@ class _PgcIndexPageState extends State<PgcIndexPage>
int count = (data.order?.isNotEmpty == true ? 1 : 0) + int count = (data.order?.isNotEmpty == true ? 1 : 0) +
(data.filter?.length ?? 0); (data.filter?.length ?? 0);
if (count == 0) return const SizedBox.shrink(); if (count == 0) return const SizedBox.shrink();
return CustomScrollView( return SafeArea(
slivers: [ bottom: false,
if (widget.indexType != null) child: CustomScrollView(
SliverToBoxAdapter(child: const SizedBox(height: 12)), slivers: [
SliverToBoxAdapter( if (widget.indexType != null)
child: AnimatedSize( SliverToBoxAdapter(child: const SizedBox(height: 12)),
curve: Curves.easeInOut, SliverToBoxAdapter(
alignment: Alignment.topCenter, child: AnimatedSize(
duration: const Duration(milliseconds: 200), curve: Curves.easeInOut,
child: count > 5 alignment: Alignment.topCenter,
? Obx(() => _buildSortWidget(count, data)) duration: const Duration(milliseconds: 200),
: _buildSortWidget(count, data), child: count > 5
? Obx(() => _buildSortWidget(count, data))
: _buildSortWidget(count, data),
),
), ),
), SliverPadding(
SliverPadding( padding: EdgeInsets.only(
padding: EdgeInsets.only( left: StyleString.safeSpace,
left: StyleString.safeSpace, right: StyleString.safeSpace,
right: StyleString.safeSpace, top: 12,
top: 12, bottom: MediaQuery.paddingOf(context).bottom + 80,
bottom: MediaQuery.paddingOf(context).bottom + 80, ),
sliver: Obx(() => _buildList(_ctr.loadingState.value)),
), ),
sliver: Obx(() => _buildList(_ctr.loadingState.value)), ],
), ),
],
); );
}), }),
Error() => scrollErrorWidget( Error() => scrollErrorWidget(

View File

@@ -248,10 +248,14 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
length: types.length, length: types.length,
child: Column( child: Column(
children: [ children: [
TabBar( SafeArea(
tabs: titles top: false,
.map((title) => Tab(text: title)) bottom: false,
.toList()), child: TabBar(
tabs: titles
.map((title) => Tab(text: title))
.toList()),
),
Expanded( Expanded(
child: tabBarView( child: tabBarView(
children: types children: types

View File

@@ -48,12 +48,16 @@ abstract class CommonSearchPageState<S extends CommonSearchPage, R, T>
onSubmitted: (value) => controller.onRefresh(), onSubmitted: (value) => controller.onRefresh(),
), ),
), ),
body: CustomScrollView( body: SafeArea(
physics: const AlwaysScrollableScrollPhysics(), top: false,
controller: controller.scrollController, bottom: false,
slivers: [ child: CustomScrollView(
Obx(() => _buildBody(controller.loadingState.value)), physics: const AlwaysScrollableScrollPhysics(),
], controller: controller.scrollController,
slivers: [
Obx(() => _buildBody(controller.loadingState.value)),
],
),
), ),
); );
} }

View File

@@ -103,10 +103,6 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
super.initState(); super.initState();
// floor 1原创 2转发 // floor 1原创 2转发
init(); init();
// if (action == 'comment') {
// _visibleTitle = true;
// _titleStreamC.add(true);
// }
_fabAnimationCtr = AnimationController( _fabAnimationCtr = AnimationController(
vsync: this, vsync: this,
@@ -340,14 +336,18 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
] ]
: null, : null,
), ),
body: context.orientation == Orientation.portrait body: SafeArea(
? refreshIndicator( top: false,
onRefresh: () async { bottom: false,
await _dynamicDetailController.onRefresh(); child: context.orientation == Orientation.portrait
}, ? refreshIndicator(
child: _buildBody(context.orientation), onRefresh: () async {
) await _dynamicDetailController.onRefresh();
: _buildBody(context.orientation), },
child: _buildBody(context.orientation),
)
: _buildBody(context.orientation),
),
); );
} }

View File

@@ -37,14 +37,17 @@ class _FansPageState extends State<FansPage> {
_fansController.isOwner.value ? '我的粉丝' : '${_fansController.name}的粉丝', _fansController.isOwner.value ? '我的粉丝' : '${_fansController.name}的粉丝',
), ),
), ),
body: refreshIndicator( body: SafeArea(
onRefresh: () async => await _fansController.onRefresh(), bottom: false,
child: CustomScrollView( child: refreshIndicator(
physics: const AlwaysScrollableScrollPhysics(), onRefresh: () async => await _fansController.onRefresh(),
controller: _fansController.scrollController, child: CustomScrollView(
slivers: [ physics: const AlwaysScrollableScrollPhysics(),
Obx(() => _buildBody(_fansController.loadingState.value)), controller: _fansController.scrollController,
], slivers: [
Obx(() => _buildBody(_fansController.loadingState.value)),
],
),
), ),
), ),
); );

View File

@@ -87,7 +87,11 @@ class _FavFolderSortPageState extends State<FavFolderSortPage> {
const SizedBox(width: 16), const SizedBox(width: 16),
], ],
), ),
body: _buildBody, body: SafeArea(
top: false,
bottom: false,
child: _buildBody,
),
); );
} }

View File

@@ -136,7 +136,13 @@ class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
), ),
body: tabBarView( body: tabBarView(
controller: _tabController, controller: _tabController,
children: _FavType.values.map((item) => item.page).toList(), children: _FavType.values
.map((item) => SafeArea(
top: false,
bottom: false,
child: item.page,
))
.toList(),
), ),
); );
} }

View File

@@ -93,7 +93,11 @@ class _FavSortPageState extends State<FavSortPage> {
const SizedBox(width: 16), const SizedBox(width: 16),
], ],
), ),
body: _buildBody, body: SafeArea(
top: false,
bottom: false,
child: _buildBody,
),
); );
} }

View File

@@ -75,296 +75,319 @@ class _FavDetailPageState extends State<FavDetailPage> {
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
), ),
body: refreshIndicator( body: SafeArea(
onRefresh: () async { top: false,
await _favDetailController.onRefresh(); bottom: false,
}, child: refreshIndicator(
child: CustomScrollView( onRefresh: () async {
physics: const AlwaysScrollableScrollPhysics(), await _favDetailController.onRefresh();
controller: _favDetailController.scrollController, },
slivers: [ child: CustomScrollView(
SliverAppBar( physics: const AlwaysScrollableScrollPhysics(),
leading: _favDetailController.enableMultiSelect.value controller: _favDetailController.scrollController,
? IconButton( slivers: [
tooltip: '取消', SliverAppBar(
onPressed: _favDetailController.handleSelect, leading: _favDetailController.enableMultiSelect.value
icon: const Icon(Icons.close_outlined), ? IconButton(
) tooltip: '取消',
: null, onPressed: _favDetailController.handleSelect,
expandedHeight: 220 - MediaQuery.of(context).padding.top, icon: const Icon(Icons.close_outlined),
pinned: true, )
title: _favDetailController.enableMultiSelect.value : null,
? Text( expandedHeight: 200 - MediaQuery.of(context).padding.top,
'已选: ${_favDetailController.checkedCount.value}', pinned: true,
) title: _favDetailController.enableMultiSelect.value
: Obx( ? Text(
() => AnimatedOpacity( '已选: ${_favDetailController.checkedCount.value}',
opacity: )
_favDetailController.titleCtr.value ? 1 : 0, : Obx(
curve: Curves.easeOut, () => AnimatedOpacity(
duration: const Duration(milliseconds: 500), opacity:
child: Column( _favDetailController.titleCtr.value ? 1 : 0,
crossAxisAlignment: CrossAxisAlignment.start, curve: Curves.easeOut,
children: [ duration: const Duration(milliseconds: 500),
Text( child: Column(
_favDetailController.item.value.title ?? '', crossAxisAlignment: CrossAxisAlignment.start,
style: children: [
Theme.of(context).textTheme.titleMedium, Text(
), _favDetailController.item.value.title ?? '',
Text( style:
'${_favDetailController.item.value.mediaCount}条视频', Theme.of(context).textTheme.titleMedium,
style: ),
Theme.of(context).textTheme.labelMedium, 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<FavDetailData,
FavDetailItemData>(
context: context,
isCopy: true,
ctr: _favDetailController,
mediaId: _favDetailController.mediaId,
mid: _favDetailController.mid,
);
},
child: Text(
'复制',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
), ),
), ),
), ),
TextButton( actions: _favDetailController.enableMultiSelect.value
style: TextButton.styleFrom( ? [
visualDensity: TextButton(
VisualDensity(horizontal: -2, vertical: -2), style: TextButton.styleFrom(
), visualDensity:
onPressed: () { VisualDensity(horizontal: -2, vertical: -2),
RequestUtils.onCopyOrMove<FavDetailData,
FavDetailItemData>(
context: context,
isCopy: false,
ctr: _favDetailController,
mediaId: _favDetailController.mediaId,
mid: _favDetailController.mid,
);
},
child: Text(
'移动',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
), ),
onPressed: () =>
_favDetailController.handleSelect(true),
child: const Text('全选'),
), ),
), TextButton(
TextButton( style: TextButton.styleFrom(
style: TextButton.styleFrom( visualDensity:
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
VisualDensity(horizontal: -2, vertical: -2), ),
), onPressed: () {
onPressed: () => RequestUtils.onCopyOrMove<FavDetailData,
_favDetailController.onDelChecked(context), FavDetailItemData>(
child: Text( context: context,
'删除', isCopy: true,
style: TextStyle( ctr: _favDetailController,
color: Theme.of(context).colorScheme.error), mediaId: _favDetailController.mediaId,
), mid: _favDetailController.mid,
), );
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,
}, },
child: Text(
'复制',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
), ),
icon: const Icon(Icons.search_outlined), TextButton(
), style: TextButton.styleFrom(
Obx( visualDensity:
() => _favDetailController.isOwner.value VisualDensity(horizontal: -2, vertical: -2),
? PopupMenuButton( ),
icon: const Icon(Icons.more_vert), onPressed: () {
itemBuilder: (context) => [ RequestUtils.onCopyOrMove<FavDetailData,
PopupMenuItem( FavDetailItemData>(
onTap: () { context: context,
Get.toNamed( isCopy: false,
'/createFav', ctr: _favDetailController,
parameters: {'mediaId': mediaId}, mediaId: _favDetailController.mediaId,
)?.then((res) { mid: _favDetailController.mid,
if (res is FavFolderItemData) { );
_favDetailController.item.value = },
res; child: Text(
} '移动',
}); style: TextStyle(
}, color: Theme.of(context)
child: Text('编辑信息'), .colorScheme
), .onSurfaceVariant,
PopupMenuItem( ),
onTap: () { ),
UserHttp.cleanFav(mediaId: mediaId) ),
.then((data) { TextButton(
if (data['status']) { style: TextButton.styleFrom(
SmartDialog.showToast('清除成功'); visualDensity:
Future.delayed( VisualDensity(horizontal: -2, vertical: -2),
const Duration( ),
milliseconds: 200), () { onPressed: () =>
_favDetailController.onReload(); _favDetailController.onDelChecked(context),
}); child: Text(
} else { '删除',
SmartDialog.showToast( style: TextStyle(
data['msg']); color: Theme.of(context).colorScheme.error),
} ),
}); ),
}, const SizedBox(width: 6),
child: Text('清除失效内容'), ]
), : [
PopupMenuItem( IconButton(
onTap: () { tooltip: '搜索',
if (_favDetailController.loadingState onPressed: () => Get.toNamed(
.value is Success && '/favSearch',
((_favDetailController arguments: {
.loadingState 'type': 0,
.value as Success) 'mediaId': int.parse(mediaId),
.response as List?) 'title':
?.isNotEmpty == _favDetailController.item.value.title,
true) { 'count': _favDetailController
if ((_favDetailController.item.value .item.value.mediaCount,
.mediaCount ?? 'isOwner': _favDetailController.isOwner.value,
0) > },
1000) { ),
SmartDialog.showToast( icon: const Icon(Icons.search_outlined),
'内容太多啦超过1000不支持排序'); ),
return; Obx(
} () => _favDetailController.isOwner.value
Get.to( ? PopupMenuButton(
FavSortPage( icon: const Icon(Icons.more_vert),
favDetailController: itemBuilder: (context) => [
_favDetailController),
);
}
},
child: Text('排序'),
),
if (!Utils.isDefaultFav(
_favDetailController
.item.value.attr ??
0))
PopupMenuItem( PopupMenuItem(
onTap: () { onTap: () {
showConfirmDialog( Get.toNamed(
context: context, '/createFav',
title: '确定删除该收藏夹?', parameters: {'mediaId': mediaId},
onConfirm: () { )?.then((res) {
UserHttp.deleteFolder( if (res is FavFolderItemData) {
mediaIds: [mediaId]) _favDetailController
.then((data) { .item.value = res;
if (data['status']) { }
SmartDialog.showToast( });
'删除成功');
Get.back(result: true);
} else {
SmartDialog.showToast(
data['msg']);
}
});
},
);
}, },
child: Text( child: Text('编辑信息'),
'删除', ),
style: TextStyle( PopupMenuItem(
color: Theme.of(context) onTap: () {
.colorScheme UserHttp.cleanFav(mediaId: mediaId)
.error, .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.shrink(), ),
), const SizedBox(width: 6),
const SizedBox(width: 6), ],
], flexibleSpace: FlexibleSpaceBar(
flexibleSpace: FlexibleSpaceBar( background: Container(
background: Container( padding: EdgeInsets.only(
padding: EdgeInsets.only( top: kTextTabBarHeight +
top: kTextTabBarHeight + MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.top + 10,
10, left: 14,
left: 14, right: 20,
right: 20, ),
), child: SizedBox(
child: SizedBox( height: 110,
height: 110, child: Obx(
child: Obx( () => Row(
() => Row( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Hero(
Hero( tag: _favDetailController.heroTag,
tag: _favDetailController.heroTag, child: NetworkImgLayer(
child: NetworkImgLayer( width: 180,
width: 180, height: 110,
height: 110, src: _favDetailController.item.value.cover,
src: _favDetailController.item.value.cover, ),
), ),
), const SizedBox(width: 14),
const SizedBox(width: 14), Expanded(
Expanded( child: SizedBox(
child: SizedBox( height: 110,
height: 110, child: Column(
child: Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, mainAxisAlignment:
mainAxisAlignment: MainAxisAlignment.start, MainAxisAlignment.start,
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 4), 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)
Text( Text(
_favDetailController _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( style: TextStyle(
fontSize: Theme.of(context) fontSize: Theme.of(context)
@@ -375,48 +398,36 @@ class _FavDetailPageState extends State<FavDetailPage> {
.colorScheme .colorScheme
.outline), .outline),
), ),
const SizedBox(height: 4), const Spacer(),
Text( if (_favDetailController
_favDetailController .item.value.attr !=
.item.value.upper?.name ?? null)
'', Text(
style: TextStyle( '${_favDetailController.item.value.mediaCount}条视频 · ${Utils.isPublicFavText(_favDetailController.item.value.attr ?? 0)}',
fontSize: Theme.of(context) style: TextStyle(
.textTheme fontSize: Theme.of(context)
.labelSmall! .textTheme
.fontSize, .labelSmall!
color: Theme.of(context) .fontSize,
.colorScheme color: Theme.of(context)
.outline), .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(() =>
Obx(() => _buildBody(_favDetailController.loadingState.value)), _buildBody(_favDetailController.loadingState.value)),
], ],
),
), ),
), ),
), ),

View File

@@ -76,15 +76,19 @@ class _FollowPageState extends State<FollowPage> {
if (data['status']) { if (data['status']) {
return Column( return Column(
children: [ children: [
TabBar( SafeArea(
controller: _followController.tabController, top: false,
isScrollable: true, bottom: false,
tabAlignment: TabAlignment.start, child: TabBar(
tabs: [ controller: _followController.tabController,
for (var i in data['data']) ...[ isScrollable: true,
Tab(text: i.name), tabAlignment: TabAlignment.start,
] tabs: [
], for (var i in data['data']) ...[
Tab(text: i.name),
]
],
),
), ),
Expanded( Expanded(
child: Material( child: Material(

View File

@@ -92,12 +92,12 @@ class _FollowListState extends State<FollowList> {
} }
}, },
) )
: errorWidget( : scrollErrorWidget(
callback: () => widget.ctr.queryFollowings('init'), callback: () => widget.ctr.queryFollowings('init'),
), ),
); );
} else { } else {
return errorWidget( return scrollErrorWidget(
errMsg: data['msg'], errMsg: data['msg'],
callback: () => widget.ctr.queryFollowings('init'), callback: () => widget.ctr.queryFollowings('init'),
); );

View File

@@ -107,12 +107,12 @@ class _OwnerFollowListState extends State<OwnerFollowList>
); );
}, },
) )
: errorWidget( : scrollErrorWidget(
callback: () => widget.ctr.queryFollowings('init'), callback: () => widget.ctr.queryFollowings('init'),
), ),
); );
} else { } else {
return errorWidget( return scrollErrorWidget(
errMsg: data['msg'], errMsg: data['msg'],
callback: () => widget.ctr.queryFollowings('init'), callback: () => widget.ctr.queryFollowings('init'),
); );

View File

@@ -182,26 +182,30 @@ class _HistoryPageState extends State<HistoryPage>
? Column( ? Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
TabBar( SafeArea(
controller: _historyController.tabController, top: false,
onTap: (index) { bottom: false,
if (_historyController child: TabBar(
.tabController!.indexIsChanging.not) { controller: _historyController.tabController,
currCtr().scrollController.animToTop(); onTap: (index) {
} else { if (_historyController
if (enableMultiSelect) { .tabController!.indexIsChanging.not) {
currCtr(_historyController currCtr().scrollController.animToTop();
.tabController!.previousIndex) } else {
.handleSelect(); if (enableMultiSelect) {
currCtr(_historyController
.tabController!.previousIndex)
.handleSelect();
}
} }
} },
}, tabs: [
tabs: [ Tab(text: '全部'),
Tab(text: '全部'), ..._historyController.tabs.map(
..._historyController.tabs.map( (item) => Tab(text: item.name),
(item) => Tab(text: item.name), ),
), ],
], ),
), ),
Expanded( Expanded(
child: Material( child: Material(
@@ -229,16 +233,20 @@ class _HistoryPageState extends State<HistoryPage>
); );
} }
Widget get _buildPage => refreshIndicator( Widget get _buildPage => SafeArea(
onRefresh: () async { top: false,
await _historyController.onRefresh(); bottom: false,
}, child: refreshIndicator(
child: CustomScrollView( onRefresh: () async {
physics: const AlwaysScrollableScrollPhysics(), await _historyController.onRefresh();
controller: _historyController.scrollController, },
slivers: [ child: CustomScrollView(
Obx(() => _buildBody(_historyController.loadingState.value)), physics: const AlwaysScrollableScrollPhysics(),
], controller: _historyController.scrollController,
slivers: [
Obx(() => _buildBody(_historyController.loadingState.value)),
],
),
), ),
); );

View File

@@ -337,239 +337,292 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
const SizedBox(width: 6) const SizedBox(width: 6)
], ],
), ),
body: Stack( body: SafeArea(
children: [ top: false,
OrientationBuilder( bottom: false,
builder: (context, orientation) { child: Stack(
double padding = max(context.width / 2 - Grid.smallCardWidth, 0); children: [
return Row( OrientationBuilder(
crossAxisAlignment: CrossAxisAlignment.start, builder: (context, orientation) {
children: [ double padding =
Expanded( max(context.width / 2 - Grid.smallCardWidth, 0);
flex: _ratio[0].toInt(), return Row(
child: CustomScrollView( crossAxisAlignment: CrossAxisAlignment.start,
controller: _htmlRenderCtr.scrollController, children: [
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),
),
Expanded( Expanded(
flex: _ratio[1].toInt(), flex: _ratio[0].toInt(),
child: Scaffold( child: CustomScrollView(
key: _key, controller: _htmlRenderCtr.scrollController,
backgroundColor: Colors.transparent, physics: const AlwaysScrollableScrollPhysics(),
body: refreshIndicator( slivers: [
onRefresh: () async { SliverPadding(
await _htmlRenderCtr.onRefresh(); padding: orientation == Orientation.portrait
}, ? EdgeInsets.symmetric(horizontal: padding)
child: CustomScrollView( : EdgeInsets.only(left: padding / 4),
controller: _htmlRenderCtr.scrollController, sliver: SliverToBoxAdapter(
physics: const AlwaysScrollableScrollPhysics(), child: Obx(
slivers: [ () => _htmlRenderCtr.loaded.value
SliverPadding( ? _buildHeader
padding: EdgeInsets.only(right: padding / 4), : const SizedBox(),
sliver: SliverToBoxAdapter( ),
child: replyHeader(), ),
),
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), SliverPadding(
sliver: Obx( padding:
() => replyList( EdgeInsets.symmetric(horizontal: padding),
_htmlRenderCtr.loadingState.value), 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(
Positioned( left: 0,
left: 0, right: 0,
right: 0, bottom: 0,
bottom: 0, child: SlideTransition(
child: SlideTransition( position: Tween<Offset>(
position: Tween<Offset>( begin: const Offset(0, 1),
begin: const Offset(0, 1), end: const Offset(0, 0),
end: const Offset(0, 0), ).animate(CurvedAnimation(
).animate(CurvedAnimation( parent: fabAnimationCtr,
parent: fabAnimationCtr, curve: Curves.easeInOut,
curve: Curves.easeInOut, )),
)), child: Builder(
child: Builder( builder: (context) {
builder: (context) { Widget button() => FloatingActionButton(
Widget button() => FloatingActionButton( heroTag: null,
heroTag: null, onPressed: () {
onPressed: () { feedBack();
feedBack(); _htmlRenderCtr.onReply(
_htmlRenderCtr.onReply( context,
context, oid: _htmlRenderCtr.oid.value,
oid: _htmlRenderCtr.oid.value, replyType: ReplyType.values[type],
replyType: ReplyType.values[type], );
); },
}, tooltip: '评论动态',
tooltip: '评论动态', child: const Icon(Icons.reply),
child: const Icon(Icons.reply), );
); return _htmlRenderCtr.showDynActionBar.not
return _htmlRenderCtr.showDynActionBar.not ? Align(
? Align( alignment: Alignment.bottomRight,
alignment: Alignment.bottomRight, child: Padding(
child: Padding( padding: EdgeInsets.only(
padding: EdgeInsets.only( right: 14,
right: 14, bottom:
bottom: MediaQuery.of(context).padding.bottom + 14,
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(),
), ),
_htmlRenderCtr.item.value.idStr != null child: button(),
? Container( ),
decoration: BoxDecoration( )
color: Theme.of(context) : Obx(
.colorScheme () => Column(
.surface, mainAxisSize: MainAxisSize.min,
border: Border( crossAxisAlignment: CrossAxisAlignment.end,
top: BorderSide( children: [
color: Theme.of(context) Padding(
.colorScheme padding: EdgeInsets.only(
.outline right: 14,
.withOpacity(0.08), 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(
padding: EdgeInsets.only( bottom:
bottom: MediaQuery.paddingOf(context) MediaQuery.paddingOf(context)
.bottom), .bottom),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.spaceAround, MainAxisAlignment.spaceAround,
children: [ children: [
Expanded( Expanded(
child: Builder( child: Builder(
builder: (btnContext) => builder: (btnContext) =>
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
useSafeArea: true, useSafeArea: true,
builder: (context) => builder: (context) =>
RepostPanel( RepostPanel(
item: _htmlRenderCtr item: _htmlRenderCtr
.item.value, .item.value,
callback: () { callback: () {
int count = int.tryParse( int count = int.tryParse(
_htmlRenderCtr _htmlRenderCtr
.item .item
.value .value
.modules .modules
?.moduleStat ?.moduleStat
?.forward ?.forward
?.count ?? ?.count ??
'0') ?? '0') ??
0; 0;
_htmlRenderCtr _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 .item
.value .value
.modules .modules
?.moduleStat ?.moduleStat
?.forward! ?.forward!
.count = .count !=
(count + 1) null
.toString(); ? Utils.numFormat(
if (btnContext _htmlRenderCtr
.mounted) { .item
(btnContext .value
as Element?) .modules
?.markNeedsBuild(); ?.moduleStat
} ?.forward!
}, .count)
), : '转发',
); ),
),
),
),
Expanded(
child: TextButton.icon(
onPressed: () {
Utils.shareText(
'${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}');
}, },
icon: Icon( icon: Icon(
FontAwesomeIcons FontAwesomeIcons.shareNodes,
.shareFromSquare,
size: 16, size: 16,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.outline, .outline,
semanticLabel: "转发", semanticLabel: "分享",
), ),
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: padding:
@@ -580,178 +633,134 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
.colorScheme .colorScheme
.outline, .outline,
), ),
label: Text( label: const Text('分享'),
_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.forward!
.count !=
null
? Utils.numFormat(
_htmlRenderCtr
.item
.value
.modules
?.moduleStat
?.forward!
.count)
: '转发',
),
), ),
), ),
), Expanded(
Expanded( child: Builder(
child: TextButton.icon( builder: (context) =>
onPressed: () { TextButton.icon(
Utils.shareText( onPressed: () => RequestUtils
'${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}'); .onLikeDynamic(
}, _htmlRenderCtr.item.value,
icon: Icon( () {
FontAwesomeIcons.shareNodes, if (context.mounted) {
size: 16, (context as Element?)
color: Theme.of(context) ?.markNeedsBuild();
.colorScheme }
.outline, },
semanticLabel: "分享", ),
), icon: Icon(
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<double>
animation) {
return ScaleTransition(
scale: animation,
child: child);
},
child: Text(
_htmlRenderCtr _htmlRenderCtr
.item .item
.value .value
.modules .modules
?.moduleStat ?.moduleStat
?.like ?.like
?.count != ?.status ==
null true
? Utils.numFormat( ? FontAwesomeIcons
_htmlRenderCtr .solidThumbsUp
: FontAwesomeIcons
.thumbsUp,
size: 16,
color: _htmlRenderCtr
.item .item
.value .value
.modules! .modules
.moduleStat! ?.moduleStat
.like! ?.like
.count) ?.status ==
: '点赞', true
style: TextStyle( ? Theme.of(context)
color: _htmlRenderCtr .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<double>
animation) {
return ScaleTransition(
scale: animation,
child: child);
},
child: Text(
_htmlRenderCtr
.item .item
.value .value
.modules .modules
?.moduleStat ?.moduleStat
?.like ?.like
?.status == ?.count !=
true null
? Theme.of(context) ? Utils.numFormat(
.colorScheme _htmlRenderCtr
.primary .item
: Theme.of(context) .value
.colorScheme .modules!
.outline, .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(), ],
], ),
), );
); },
}, ),
), ),
), ),
), ],
], ),
), ),
); );
} }

View File

@@ -88,24 +88,28 @@ class _LaterPageState extends State<LaterPage>
), ),
body: Column( body: Column(
children: [ children: [
TabBar( SafeArea(
isScrollable: true, top: false,
controller: _tabController, bottom: false,
tabAlignment: TabAlignment.start, child: TabBar(
tabs: LaterViewType.values.map((item) { isScrollable: true,
final count = _baseCtr.counts[item]; controller: _tabController,
return Tab( tabAlignment: TabAlignment.start,
text: '${item.title}${count != -1 ? '($count)' : ''}'); tabs: LaterViewType.values.map((item) {
}).toList(), final count = _baseCtr.counts[item];
onTap: (_) { return Tab(
if (_tabController.indexIsChanging.not) { text: '${item.title}${count != -1 ? '($count)' : ''}');
currCtr().scrollController.animToTop(); }).toList(),
} else { onTap: (_) {
if (_baseCtr.enableMultiSelect.value) { if (_tabController.indexIsChanging.not) {
currCtr(_tabController.previousIndex).handleSelect(); currCtr().scrollController.animToTop();
} else {
if (_baseCtr.enableMultiSelect.value) {
currCtr(_tabController.previousIndex).handleSelect();
}
} }
} },
}, ),
), ),
Expanded( Expanded(
child: TabBarView( child: TabBarView(
@@ -113,8 +117,13 @@ class _LaterPageState extends State<LaterPage>
? const NeverScrollableScrollPhysics() ? const NeverScrollableScrollPhysics()
: const CustomTabBarViewScrollPhysics(), : const CustomTabBarViewScrollPhysics(),
controller: _tabController, controller: _tabController,
children: children: LaterViewType.values
LaterViewType.values.map((item) => item.page).toList(), .map((item) => SafeArea(
top: false,
bottom: false,
child: item.page,
))
.toList(),
), ),
), ),
], ],

View File

@@ -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/chat.dart';
import 'package:PiliPlus/pages/live_room/widgets/header_control.dart'; import 'package:PiliPlus/pages/live_room/widgets/header_control.dart';
import 'package:PiliPlus/services/service_locator.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/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
@@ -203,7 +204,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
), ),
); );
} else { } else {
return const SizedBox(); return const SizedBox.shrink();
} }
}, },
), ),
@@ -228,9 +229,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
fit: BoxFit.cover, fit: BoxFit.cover,
width: Get.width, width: Get.width,
height: Get.height, height: Get.height,
imageUrl: Utils.thumbnailImgUrl( imageUrl: _liveRoomController.roomInfoH5.value
_liveRoomController.roomInfoH5.value.roomInfo! .roomInfo!.appBackground!.http2https,
.appBackground!),
) )
: Image.asset( : Image.asset(
'assets/images/live/default_bg.webp', 'assets/images/live/default_bg.webp',
@@ -239,25 +239,31 @@ class _LiveRoomPageState extends State<LiveRoomPage>
), ),
), ),
), ),
isPortrait SafeArea(
? Obx( top: false,
() { left: !isFullScreen,
if (_liveRoomController.isPortrait.value) { right: !isFullScreen,
if (padding == null) { bottom: false,
final padding = MediaQuery.paddingOf(context); child: isPortrait
this.padding = padding.bottom + padding.top; ? 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;
} },
return _buildPH; )
}, : Column(
) children: [
: Column( Obx(() => _buildAppBar),
children: [ _buildBodyH,
Obx(() => _buildAppBar), ],
_buildBodyH, ),
], ),
),
], ],
), ),
); );
@@ -458,10 +464,13 @@ class _LiveRoomPageState extends State<LiveRoomPage>
children: [ children: [
Obx( Obx(
() => Container( () => Container(
margin:
EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom),
color: isFullScreen ? Colors.black : null, color: isFullScreen ? Colors.black : null,
width: isFullScreen ? Get.size.width : videoWidth, width: isFullScreen ? Get.size.width : videoWidth,
height: isFullScreen ? Get.size.height : Get.size.width * 9 / 16, height: isFullScreen ? Get.size.height : Get.size.width * 9 / 16,
child: MediaQuery.removePadding( child: MediaQuery.removePadding(
removeTop: true,
removeRight: true, removeRight: true,
context: context, context: context,
child: videoPlayerPanel(fill: Colors.transparent), child: videoPlayerPanel(fill: Colors.transparent),
@@ -469,13 +478,9 @@ class _LiveRoomPageState extends State<LiveRoomPage>
), ),
), ),
Expanded( Expanded(
child: SafeArea( child: Column(
left: false, crossAxisAlignment: CrossAxisAlignment.start,
top: false, children: _buildBottomWidget,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildBottomWidget,
),
), ),
), ),
], ],

View File

@@ -173,6 +173,7 @@ class _MainAppState extends State<MainApp>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isPortrait = context.orientation == Orientation.portrait;
return PopScope( return PopScope(
canPop: false, canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) async { onPopInvokedWithResult: (bool didPop, Object? result) async {
@@ -200,8 +201,7 @@ class _MainAppState extends State<MainApp>
body: Row( body: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (useSideBar || if (useSideBar || !isPortrait) ...[
context.orientation == Orientation.landscape) ...[
Obx( Obx(
() => _mainController.navigationBars.length > 1 () => _mainController.navigationBars.length > 1
? context.isTablet ? context.isTablet
@@ -298,26 +298,28 @@ class _MainAppState extends State<MainApp>
), ),
], ],
Expanded( Expanded(
child: _mainController.mainTabBarView child: SafeArea(
? CustomTabBarView( top: false,
scrollDirection: bottom: false,
context.orientation == Orientation.portrait left: isPortrait,
? Axis.horizontal child: _mainController.mainTabBarView
: Axis.vertical, ? CustomTabBarView(
physics: const NeverScrollableScrollPhysics(), scrollDirection:
controller: _mainController.controller, isPortrait ? Axis.horizontal : Axis.vertical,
children: _mainController.pages, physics: const NeverScrollableScrollPhysics(),
) controller: _mainController.controller,
: PageView( children: _mainController.pages,
physics: const NeverScrollableScrollPhysics(), )
controller: _mainController.controller, : PageView(
children: _mainController.pages, physics: const NeverScrollableScrollPhysics(),
), controller: _mainController.controller,
children: _mainController.pages,
),
),
), ),
], ],
), ),
bottomNavigationBar: useSideBar || bottomNavigationBar: useSideBar || !isPortrait
context.orientation == Orientation.landscape
? null ? null
: StreamBuilder( : StreamBuilder(
stream: _mainController.hideTabBar stream: _mainController.hideTabBar

View File

@@ -40,63 +40,67 @@ class _MediaPageState extends CommonPageState<MediaPage, MediaController>
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
Color primary = Theme.of(context).colorScheme.primary; Color primary = Theme.of(context).colorScheme.primary;
return Scaffold( return MediaQuery.removePadding(
backgroundColor: Colors.transparent, context: context,
appBar: AppBar( removeLeft: context.orientation == Orientation.landscape,
toolbarHeight: 30, child: Scaffold(
), backgroundColor: Colors.transparent,
body: ListView( appBar: AppBar(
controller: controller.scrollController, toolbarHeight: 30,
physics: const AlwaysScrollableScrollPhysics(), ),
children: [ body: ListView(
ListTile( controller: controller.scrollController,
leading: null, physics: const AlwaysScrollableScrollPhysics(),
title: Padding( children: [
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)
ListTile( ListTile(
onTap: item['onTap'], leading: null,
dense: true, title: Padding(
leading: Padding( padding: const EdgeInsets.only(left: 20),
padding: const EdgeInsets.only(left: 15), child: Text(
child: Icon( '媒体库',
item['icon'], style: TextStyle(
color: primary, fontSize: Theme.of(context).textTheme.titleLarge!.fontSize,
fontWeight: FontWeight.bold,
),
), ),
), ),
contentPadding: trailing: IconButton(
const EdgeInsets.only(left: 15, top: 2, bottom: 2), tooltip: '设置',
minLeadingWidth: 0, onPressed: () {
title: Text( Get.toNamed('/setting');
item['title'], },
style: const TextStyle(fontSize: 15), icon: const Icon(
Icons.settings_outlined,
size: 20,
),
), ),
), ),
Obx( for (var item in controller.list)
() => controller.loadingState.value is Loading ListTile(
? const SizedBox.shrink() onTap: item['onTap'],
: favFolder(), 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(),
)
],
),
), ),
); );
} }

View File

@@ -33,7 +33,6 @@ class MemberControllerNew extends CommonDataController<Data, dynamic>
late List<Tab> tabs; late List<Tab> tabs;
List<Tab2>? tab2; List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs; RxInt contributeInitialIndex = 0.obs;
double top = 0;
bool? hasSeasonOrSeries; bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid']; final fromViewAid = Get.parameters['from_view_aid'];

View File

@@ -31,6 +31,7 @@ class _MemberPageNewState extends State<MemberPageNew> {
late final String _heroTag; late final String _heroTag;
late final MemberControllerNew _userController; late final MemberControllerNew _userController;
final _key = GlobalKey<ExtendedNestedScrollViewState>(); final _key = GlobalKey<ExtendedNestedScrollViewState>();
int _offset = 120;
@override @override
void initState() { void initState() {
@@ -45,8 +46,10 @@ class _MemberPageNewState extends State<MemberPageNew> {
} }
void listener() { void listener() {
_userController.showUname.value = if (_userController.scrollController.hasClients) {
_userController.scrollController.offset >= 120; _userController.showUname.value =
_userController.scrollController.offset >= _offset;
}
} }
@override @override
@@ -57,11 +60,99 @@ class _MemberPageNewState extends State<MemberPageNew> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_userController.top == 0) {
_userController.top = MediaQuery.of(context).padding.top;
}
return Scaffold( return Scaffold(
extendBody: true,
extendBodyBehindAppBar: true,
resizeToAvoidBottomInset: false, 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) => <PopupMenuEntry>[
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( body: Obx(
() => _userController.loadingState.value is Success () => _userController.loadingState.value is Success
? LayoutBuilder( ? LayoutBuilder(
@@ -70,31 +161,24 @@ class _MemberPageNewState extends State<MemberPageNew> {
key: _key, key: _key,
controller: _userController.scrollController, controller: _userController.scrollController,
onlyOneScrollInBody: true, onlyOneScrollInBody: true,
pinnedHeaderSliverHeightBuilder: () {
return kToolbarHeight +
MediaQuery.paddingOf(this.context).top.toInt();
},
headerSliverBuilder: (context, innerBoxIsScrolled) { headerSliverBuilder: (context, innerBoxIsScrolled) {
return [ return [
SliverOverlapAbsorber( _buildAppBar(
handle: ExtendedNestedScrollView isV: constraints.maxHeight > constraints.maxWidth,
.sliverOverlapAbsorberHandleFor(context),
sliver: _buildAppBar(
isV: constraints.maxHeight > constraints.maxWidth,
),
), ),
]; ];
}, },
body: _userController.tab2?.isNotEmpty == true body: _userController.tab2?.isNotEmpty == true
? LayoutBuilder( ? Column(
builder: (context, _) { children: [
return Padding( if ((_userController.tab2?.length ?? 0) > 1)
padding: EdgeInsets.only( _buildTab,
top: ExtendedNestedScrollView Expanded(child: _buildBody),
.sliverOverlapAbsorberHandleFor( ],
context)
.layoutExtent ??
0,
),
child: _buildBody,
);
},
) )
: Center(child: const Text('EMPTY')), : Center(child: const Text('EMPTY')),
); );
@@ -109,14 +193,18 @@ class _MemberPageNewState extends State<MemberPageNew> {
Widget get _buildTab => Material( Widget get _buildTab => Material(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: TabBar( child: SafeArea(
controller: _userController.tabController, top: false,
tabs: _userController.tabs, bottom: false,
onTap: (value) { child: TabBar(
if (_userController.tabController?.indexIsChanging == false) { controller: _userController.tabController,
_key.currentState?.outerController.animToTop(); tabs: _userController.tabs,
} onTap: (value) {
}, if (_userController.tabController?.indexIsChanging == false) {
_key.currentState?.outerController.animToTop();
}
},
),
), ),
); );
@@ -150,119 +238,19 @@ class _MemberPageNewState extends State<MemberPageNew> {
), ),
); );
Widget _buildAppBar({bool needTab = true, bool isV = true}) => Widget _buildAppBar({bool isV = true}) {
DynamicSliverAppBar( return DynamicSliverAppBar(
primary: false, pinned: true,
leading: Padding( primary: false,
padding: EdgeInsets.only(top: _userController.top), automaticallyImplyLeading: false,
child: const BackButton(), toolbarHeight: kToolbarHeight + MediaQuery.paddingOf(context).top,
), flexibleSpace: _buildUserInfo(_userController.loadingState.value, isV),
hasTabBar: (_userController.tab2?.length ?? 0) > 1, callback: (value) {
toolbarHeight: kToolbarHeight + _userController.top, _offset = (value - 56 - MediaQuery.paddingOf(context).top).toInt();
title: IgnorePointer( listener();
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) => <PopupMenuEntry>[
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 _errorWidget(msg) { Widget _errorWidget(msg) {
return errorWidget( return errorWidget(

View File

@@ -118,6 +118,12 @@ class UserInfoCard extends StatelessWidget {
onTap: () => Utils.copyText(card.name!), onTap: () => Utils.copyText(card.name!),
child: Text( child: Text(
card.name!, card.name!,
strutStyle: StrutStyle(
height: 1,
leading: 0,
fontSize: 17,
fontWeight: FontWeight.bold,
),
style: TextStyle( style: TextStyle(
height: 1, height: 1,
fontSize: 17, fontSize: 17,
@@ -136,8 +142,7 @@ class UserInfoCard extends StatelessWidget {
), ),
if (card.vip?.vipStatus == 1) if (card.vip?.vipStatus == 1)
CachedNetworkImage( CachedNetworkImage(
imageUrl: imageUrl: Utils.thumbnailImgUrl(card.vip!.label!.image!, 80),
Utils.thumbnailImgUrl(card.vip!.label!.image!.http2https),
height: 20, height: 20,
placeholder: (context, url) { placeholder: (context, url) {
return const SizedBox.shrink(); return const SizedBox.shrink();
@@ -612,7 +617,10 @@ class UserInfoCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeader(context), // _buildHeader(context),
SizedBox(
height: Get.mediaQuery.padding.bottom + 56,
),
SafeArea( SafeArea(
top: false, top: false,
bottom: false, bottom: false,

View File

@@ -39,18 +39,22 @@ class _MemberCoinPageState extends State<MemberCoinPage> {
appBar: AppBar( appBar: AppBar(
title: Text('${widget.mid == _ownerMid ? '' : '${widget.name}'}'), title: Text('${widget.mid == _ownerMid ? '' : '${widget.name}'}'),
), ),
body: CustomScrollView( body: SafeArea(
slivers: [ top: false,
SliverPadding( bottom: false,
padding: EdgeInsets.only( child: CustomScrollView(
top: StyleString.safeSpace - 5, slivers: [
left: StyleString.safeSpace, SliverPadding(
right: StyleString.safeSpace, padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80, 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)), ],
), ),
],
), ),
); );
} }

View File

@@ -39,18 +39,22 @@ class _MemberLikePageState extends State<MemberLikePage> {
appBar: AppBar( appBar: AppBar(
title: Text('${widget.mid == _ownerMid ? '' : '${widget.name}'}'), title: Text('${widget.mid == _ownerMid ? '' : '${widget.name}'}'),
), ),
body: CustomScrollView( body: SafeArea(
slivers: [ top: false,
SliverPadding( bottom: false,
padding: EdgeInsets.only( child: CustomScrollView(
top: StyleString.safeSpace - 5, slivers: [
left: StyleString.safeSpace, SliverPadding(
right: StyleString.safeSpace, padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80, 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)), ],
), ),
],
), ),
); );
} }

View File

@@ -28,22 +28,26 @@ class _SearchArchiveState extends State<SearchArchive>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return refreshIndicator( return SafeArea(
onRefresh: () async { top: false,
await widget.ctr.refreshArchive(); bottom: false,
}, child: refreshIndicator(
child: CustomScrollView( onRefresh: () async {
physics: const AlwaysScrollableScrollPhysics(), await widget.ctr.refreshArchive();
slivers: [ },
SliverPadding( child: CustomScrollView(
padding: EdgeInsets.only( physics: const AlwaysScrollableScrollPhysics(),
top: StyleString.safeSpace - 5, slivers: [
bottom: MediaQuery.paddingOf(context).bottom + 80, SliverPadding(
), padding: EdgeInsets.only(
sliver: top: StyleString.safeSpace - 5,
Obx(() => _buildBody(context, widget.ctr.archiveState.value)), bottom: MediaQuery.paddingOf(context).bottom + 80,
) ),
], sliver:
Obx(() => _buildBody(context, widget.ctr.archiveState.value)),
)
],
),
), ),
); );
} }

View File

@@ -30,21 +30,25 @@ class _SearchDynamicState extends State<SearchDynamic>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return refreshIndicator( return SafeArea(
onRefresh: () async { top: false,
await widget.ctr.refreshDynamic(); bottom: false,
}, child: refreshIndicator(
child: CustomScrollView( onRefresh: () async {
physics: const AlwaysScrollableScrollPhysics(), await widget.ctr.refreshDynamic();
slivers: [ },
SliverPadding( child: CustomScrollView(
padding: EdgeInsets.only( physics: const AlwaysScrollableScrollPhysics(),
bottom: MediaQuery.paddingOf(context).bottom + 80, slivers: [
), SliverPadding(
sliver: padding: EdgeInsets.only(
Obx(() => _buildBody(context, widget.ctr.dynamicState.value)), bottom: MediaQuery.paddingOf(context).bottom + 80,
) ),
], sliver:
Obx(() => _buildBody(context, widget.ctr.dynamicState.value)),
)
],
),
), ),
); );
} }

View File

@@ -55,22 +55,26 @@ class _MemberSearchPageState extends State<MemberSearchPage> {
() => _memberSearchCtr.hasData.value () => _memberSearchCtr.hasData.value
? Column( ? Column(
children: [ children: [
TabBar( SafeArea(
controller: _memberSearchCtr.tabController, top: false,
tabs: [ bottom: false,
Obx( child: TabBar(
() => Tab( controller: _memberSearchCtr.tabController,
text: tabs: [
'视频 ${_memberSearchCtr.archiveCount.value != -1 ? '${_memberSearchCtr.archiveCount.value}' : ''}', Obx(
() => Tab(
text:
'视频 ${_memberSearchCtr.archiveCount.value != -1 ? '${_memberSearchCtr.archiveCount.value}' : ''}',
),
), ),
), Obx(
Obx( () => Tab(
() => Tab( text:
text: '动态 ${_memberSearchCtr.dynamicCount.value != -1 ? '${_memberSearchCtr.dynamicCount.value}' : ''}',
'动态 ${_memberSearchCtr.dynamicCount.value != -1 ? '${_memberSearchCtr.dynamicCount.value}' : ''}', ),
), ),
), ],
], ),
), ),
Expanded( Expanded(
child: tabBarView( child: tabBarView(

View File

@@ -52,220 +52,230 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('选择应用主题')), appBar: AppBar(title: const Text('选择应用主题')),
body: ListView( body: SafeArea(
children: [ bottom: false,
ListTile( child: ListView(
onTap: () async { children: [
ThemeType? result = await showDialog( ListTile(
context: context, onTap: () async {
builder: (context) { ThemeType? result = await showDialog(
return SelectDialog<ThemeType>( context: context,
title: '主题模式', builder: (context) {
value: ctr.themeType.value, return SelectDialog<ThemeType>(
values: ThemeType.values title: '主题模式',
.map((e) => (e, e.description)) value: ctr.themeType.value,
.toList()); values: ThemeType.values
}, .map((e) => (e, e.description))
); .toList());
if (result != null) { },
try { );
Get.find<MineController>().themeType.value = result; if (result != null) {
} catch (_) {} try {
ctr.themeType.value = result; Get.find<MineController>().themeType.value = result;
GStorage.setting.put(SettingBoxKey.themeMode, result.index); } catch (_) {}
Get.changeThemeMode(result.toThemeMode); ctr.themeType.value = result;
} GStorage.setting.put(SettingBoxKey.themeMode, result.index);
}, Get.changeThemeMode(result.toThemeMode);
leading: Container( }
width: 40, },
alignment: Alignment.center, leading: Container(
child: const Icon(Icons.flashlight_on_outlined), 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), Obx(
subtitle: Obx(() => Text('当前模式:${ctr.themeType.value.description}', () => ListTile(
style: subTitleStyle)), enabled: ctr.type.value != 0,
), title: Row(
Obx( mainAxisAlignment: MainAxisAlignment.spaceBetween,
() => ListTile( children: [
enabled: ctr.type.value != 0, const Text('调色板风格'),
title: Row( PopupMenuButton(
mainAxisAlignment: MainAxisAlignment.spaceBetween, enabled: ctr.type.value != 0,
children: [ initialValue: _dynamicSchemeVariant,
const Text('调色板风格'), onSelected: (item) async {
PopupMenuButton( _dynamicSchemeVariant = item;
enabled: ctr.type.value != 0, await GStorage.setting
initialValue: _dynamicSchemeVariant, .put(SettingBoxKey.schemeVariant, item.index);
onSelected: (item) async { Get.forceAppUpdate();
_dynamicSchemeVariant = item; },
await GStorage.setting itemBuilder: (context) => FlexSchemeVariant.values
.put(SettingBoxKey.schemeVariant, item.index); .map((item) => PopupMenuItem<FlexSchemeVariant>(
Get.forceAppUpdate(); value: item,
}, child: Text(item.variantName),
itemBuilder: (context) => FlexSchemeVariant.values ))
.map((item) => PopupMenuItem<FlexSchemeVariant>( .toList(),
value: item, child: Row(
child: Text(item.variantName), mainAxisSize: MainAxisSize.min,
)) children: [
.toList(), Text(
child: Row( _dynamicSchemeVariant.variantName,
mainAxisSize: MainAxisSize.min, style: TextStyle(
children: [ height: 1,
Text( fontSize: 13,
_dynamicSchemeVariant.variantName, color: ctr.type.value == 0
style: TextStyle( ? Theme.of(context)
height: 1, .colorScheme
fontSize: 13, .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 color: ctr.type.value == 0
? Theme.of(context) ? Theme.of(context)
.colorScheme .colorScheme
.outline .outline
.withOpacity(0.8) .withOpacity(0.8)
: Theme.of(context).colorScheme.secondary, : Theme.of(context).colorScheme.secondary,
), )
strutStyle: StrutStyle(leading: 0, height: 1), ],
), ),
Icon( ),
size: 20, ],
Icons.keyboard_arrow_right, ),
color: ctr.type.value == 0 leading: Container(
? Theme.of(context) width: 40,
.colorScheme alignment: Alignment.center,
.outline child: Icon(Icons.palette_outlined),
.withOpacity(0.8) ),
: Theme.of(context).colorScheme.secondary, 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(
IgnorePointer( child: SizedBox(
child: SizedBox( height: Get.height / 2,
height: Get.height / 2, width: Get.width,
width: Get.width, child: const HomePage(),
child: const HomePage(), ),
), ),
), IgnorePointer(
IgnorePointer( child: NavigationBar(
child: NavigationBar( destinations: defaultNavigationBars
destinations: defaultNavigationBars .map(
.map( (item) => NavigationDestination(
(item) => NavigationDestination( icon: item['icon'],
icon: item['icon'], label: item['label'],
label: item['label'], ),
), )
) .toList(),
.toList(), ),
), ),
), ],
], ],
], ),
), ),
); );
} }

View File

@@ -48,58 +48,55 @@ class _FontSizeSelectPageState extends State<FontSizeSelectPage> {
const SizedBox(width: 12) const SizedBox(width: 12)
], ],
), ),
body: Column( body: SafeArea(
children: [ child: Column(
Expanded( children: [
child: Center( Expanded(
child: Text( child: Center(
'当前字体大小:${currentSize == 1.0 ? '默认' : currentSize}', child: Text(
style: TextStyle(fontSize: 14 * currentSize), '当前字体大小:${currentSize == 1.0 ? '默认' : currentSize}',
style: TextStyle(fontSize: 14 * currentSize),
),
), ),
), ),
), Container(
Container( width: double.infinity,
width: double.infinity, padding: EdgeInsets.all(20),
padding: EdgeInsets.only( decoration: BoxDecoration(
left: 20, border: Border(
right: 20, top: BorderSide(
top: 20, color: Theme.of(context)
bottom: MediaQuery.of(context).padding.bottom + 20, .colorScheme
), .primary
decoration: BoxDecoration( .withOpacity(0.3))),
border: Border( color: Theme.of(context).colorScheme.surface,
top: BorderSide( ),
color: Theme.of(context) child: Row(
.colorScheme children: [
.primary const Text(''),
.withOpacity(0.3))), Expanded(
color: Theme.of(context).colorScheme.surface, child: Slider(
), min: minSize,
child: Row( value: currentSize,
children: [ max: maxSize,
const Text(''), divisions: list.length - 1,
Expanded( secondaryTrackValue: 1,
child: Slider( onChanged: (double val) {
min: minSize, currentSize = val.toPrecision(2);
value: currentSize, setState(() {});
max: maxSize, },
divisions: list.length - 1, ),
secondaryTrackValue: 1,
onChanged: (double val) {
currentSize = val.toPrecision(2);
setState(() {});
},
), ),
), const SizedBox(width: 5),
const SizedBox(width: 5), const Text(
const Text( '',
'', style: TextStyle(fontSize: 20),
style: TextStyle(fontSize: 20), ),
), ],
], ),
), )
) ],
], ),
), ),
); );
} }

View File

@@ -144,62 +144,79 @@ class _LogsPageState extends State<LogsPage> {
], ],
), ),
body: logsContent.isNotEmpty body: logsContent.isNotEmpty
? ListView.builder( ? SafeArea(
itemCount: logsContent.length, bottom: false,
itemBuilder: (context, index) { child: ListView.separated(
final log = logsContent[index]; itemCount: logsContent.length,
if (log['date'] is DateTime) { itemBuilder: (context, index) {
latestLog ??= log['date']; final log = logsContent[index];
} if (log['date'] is DateTime) {
return Column( latestLog ??= log['date'];
crossAxisAlignment: CrossAxisAlignment.start, }
mainAxisSize: MainAxisSize.min, return Padding(
children: [ padding: const EdgeInsets.symmetric(horizontal: 16),
Row( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( Row(
padding: const EdgeInsets.all(8.0), children: [
child: Text( Text(
log['date'].toString(), log['date'].toString(),
style: Theme.of(context).textTheme.titleMedium, 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( separatorBuilder: (context, index) => const Divider(
elevation: 1, indent: 12,
clipBehavior: Clip.antiAliasWithSaveLayer, endIndent: 12,
child: Padding( height: 24,
padding: const EdgeInsets.all(12.0), ),
child: SelectableText(log['body']), ),
),
),
),
const Divider(indent: 12, endIndent: 12),
],
);
},
) )
: errorWidget(), : scrollErrorWidget(),
); );
} }
} }

View File

@@ -210,9 +210,9 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
const SizedBox(width: 16), const SizedBox(width: 16),
], ],
), ),
body: SingleChildScrollView( body: SafeArea(
child: Column( bottom: false,
crossAxisAlignment: CrossAxisAlignment.start, child: ListView(
children: [ children: [
Padding( Padding(
padding: padding:

View File

@@ -75,24 +75,27 @@ class _SettingsSearchPageState extends State<SettingsSearchPage> {
), ),
), ),
), ),
body: Obx( body: SafeArea(
() => _list.isEmpty bottom: false,
? CustomScrollView( child: CustomScrollView(
slivers: [HttpError()], slivers: [
) Obx(
: CustomScrollView( () => _list.isEmpty
slivers: [ ? HttpError()
SliverWaterfallFlow.extent( : SliverPadding(
maxCrossAxisExtent: Grid.smallCardWidth * 2, padding: EdgeInsets.only(
children: [ bottom: MediaQuery.paddingOf(context).bottom + 80,
..._list.map((item) => item.widget),
SizedBox(
height: MediaQuery.paddingOf(context).bottom + 80,
), ),
], sliver: SliverWaterfallFlow.extent(
), maxCrossAxisExtent: Grid.smallCardWidth * 2,
], children: [
), ..._list.map((item) => item.widget),
],
),
),
),
],
),
), ),
); );
} }

View File

@@ -121,24 +121,35 @@ class _SettingPageState extends State<SettingPage> {
: Row( : Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded(flex: 40, child: _buildList), Expanded(
flex: 4,
child: MediaQuery.removePadding(
context: context,
removeRight: true,
child: _buildList,
),
),
VerticalDivider( VerticalDivider(
width: 1, width: 1,
color: Theme.of(context).colorScheme.outline.withOpacity(0.1), color: Theme.of(context).colorScheme.outline.withOpacity(0.1),
), ),
Expanded( Expanded(
flex: 60, flex: 6,
child: switch (_type) { child: MediaQuery.removePadding(
'privacySetting' => PrivacySetting(showAppBar: false), context: context,
'recommendSetting' => RecommendSetting(showAppBar: false), removeLeft: true,
'videoSetting' => VideoSetting(showAppBar: false), child: switch (_type) {
'playSetting' => PlaySetting(showAppBar: false), 'privacySetting' => PrivacySetting(showAppBar: false),
'styleSetting' => StyleSetting(showAppBar: false), 'recommendSetting' => RecommendSetting(showAppBar: false),
'extraSetting' => ExtraSetting(showAppBar: false), 'videoSetting' => VideoSetting(showAppBar: false),
'webdavSetting' => WebDavSettingPage(showAppBar: false), 'playSetting' => PlaySetting(showAppBar: false),
'about' => AboutPage(showAppBar: false), 'styleSetting' => StyleSetting(showAppBar: false),
_ => const SizedBox.shrink(), 'extraSetting' => ExtraSetting(showAppBar: false),
}, 'webdavSetting' => WebDavSettingPage(showAppBar: false),
'about' => AboutPage(showAppBar: false),
_ => const SizedBox.shrink(),
},
),
) )
], ],
), ),

View File

@@ -23,19 +23,23 @@ class _SubPageState extends State<SubPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('我的订阅')), appBar: AppBar(title: const Text('我的订阅')),
body: refreshIndicator( body: SafeArea(
onRefresh: () async { top: false,
await _subController.onRefresh(); bottom: false,
}, child: refreshIndicator(
child: CustomScrollView( onRefresh: () async {
slivers: [ await _subController.onRefresh();
Obx(() => _buildBody(_subController.loadingState.value)), },
SliverToBoxAdapter( child: CustomScrollView(
child: SizedBox( slivers: [
height: MediaQuery.of(context).padding.bottom + 80, Obx(() => _buildBody(_subController.loadingState.value)),
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).padding.bottom + 80,
),
), ),
), ],
], ),
), ),
), ),
); );

View File

@@ -103,7 +103,7 @@ class _SubDetailPageState extends State<SubDetailPage> {
Widget get _buildCount => SliverToBoxAdapter( Widget get _buildCount => SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14), padding: const EdgeInsets.only(top: 12, bottom: 8, left: 14),
child: Obx( child: Obx(
() => Text( () => Text(
'${_subDetailController.mediaCount}条视频', '${_subDetailController.mediaCount}条视频',
@@ -118,7 +118,7 @@ class _SubDetailPageState extends State<SubDetailPage> {
); );
Widget get _buildAppBar => SliverAppBar( Widget get _buildAppBar => SliverAppBar(
expandedHeight: 215 - MediaQuery.paddingOf(context).bottom, expandedHeight: 210 - MediaQuery.paddingOf(context).top,
pinned: true, pinned: true,
title: Obx( title: Obx(
() { () {
@@ -158,7 +158,7 @@ class _SubDetailPageState extends State<SubDetailPage> {
top: kTextTabBarHeight + MediaQuery.of(context).padding.top + 15, top: kTextTabBarHeight + MediaQuery.of(context).padding.top + 15,
left: 12, left: 12,
right: 12, right: 12,
bottom: 20, bottom: 12,
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -31,11 +31,7 @@ class _WhisperPageState extends State<WhisperPage> {
child: CustomScrollView( child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
SliverSafeArea( _buildTopItems,
top: false,
bottom: false,
sliver: _buildTopItems,
),
Obx(() => _buildBody(_whisperController.loadingState.value)), Obx(() => _buildBody(_whisperController.loadingState.value)),
], ],
), ),
@@ -90,59 +86,63 @@ class _WhisperPageState extends State<WhisperPage> {
}; };
} }
Widget get _buildTopItems => SliverToBoxAdapter( Widget get _buildTopItems => SliverSafeArea(
child: Row( top: false,
mainAxisAlignment: MainAxisAlignment.spaceEvenly, bottom: false,
children: sliver: SliverToBoxAdapter(
List.generate(_whisperController.msgFeedTopItems.length, (index) { child: Row(
return GestureDetector( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
behavior: HitTestBehavior.opaque, children: List.generate(_whisperController.msgFeedTopItems.length,
child: Padding( (index) {
padding: const EdgeInsets.all(10), return GestureDetector(
child: Column( behavior: HitTestBehavior.opaque,
mainAxisSize: MainAxisSize.min, child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.all(10),
mainAxisAlignment: MainAxisAlignment.center, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
Obx( crossAxisAlignment: CrossAxisAlignment.center,
() => Badge( mainAxisAlignment: MainAxisAlignment.center,
isLabelVisible: children: [
_whisperController.unreadCounts[index] > 0, Obx(
label: () => Badge(
Text(" ${_whisperController.unreadCounts[index]} "), isLabelVisible:
alignment: Alignment.topRight, _whisperController.unreadCounts[index] > 0,
child: CircleAvatar( label: Text(
radius: 22, " ${_whisperController.unreadCounts[index]} "),
backgroundColor: alignment: Alignment.topRight,
Theme.of(context).colorScheme.onInverseSurface, child: CircleAvatar(
child: Icon( radius: 22,
_whisperController.msgFeedTopItems[index]['icon'], backgroundColor:
size: 20, Theme.of(context).colorScheme.onInverseSurface,
color: Theme.of(context).colorScheme.primary, child: Icon(
_whisperController.msgFeedTopItems[index]['icon'],
size: 20,
color: Theme.of(context).colorScheme.primary,
),
), ),
), ),
), ),
), const SizedBox(height: 6),
const SizedBox(height: 6), Text(
Text( _whisperController.msgFeedTopItems[index]['name'],
_whisperController.msgFeedTopItems[index]['name'], style: const TextStyle(fontSize: 13),
style: const TextStyle(fontSize: 13), ),
), ],
], ),
), ),
), onTap: () {
onTap: () { if (!_whisperController.msgFeedTopItems[index]['enabled']) {
if (!_whisperController.msgFeedTopItems[index]['enabled']) { SmartDialog.showToast('已禁用');
SmartDialog.showToast('已禁用'); return;
return; }
} _whisperController.unreadCounts[index] = 0;
_whisperController.unreadCounts[index] = 0; Get.toNamed(
Get.toNamed( _whisperController.msgFeedTopItems[index]['route'],
_whisperController.msgFeedTopItems[index]['route'], );
); },
}, );
); }).toList(),
}).toList(), ),
), ),
); );
} }

View File

@@ -94,21 +94,25 @@ class _WhisperDetailPageState
), ),
), ),
), ),
body: Column( body: SafeArea(
children: [ top: false,
Expanded( bottom: false,
child: Listener( child: Column(
child: Obx(() => children: [
_buildBody(_whisperDetailController.loadingState.value)), Expanded(
onPointerDown: (event) { child: Listener(
// Hide panel when touch ListView. child: Obx(() =>
hidePanel(); _buildBody(_whisperDetailController.loadingState.value)),
}, onPointerDown: (event) {
// Hide panel when touch ListView.
hidePanel();
},
),
), ),
), _buildInputView(),
_buildInputView(), buildPanelContainer(Theme.of(context).colorScheme.onInverseSurface),
buildPanelContainer(Theme.of(context).colorScheme.onInverseSurface), ],
], ),
), ),
); );
} }