mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-05 10:10:14 +08:00
@@ -6,6 +6,7 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart';
|
||||
import 'package:PiliPlus/pages/fav/pgc/controller.dart';
|
||||
import 'package:PiliPlus/pages/fav/pgc/pgc_layout.dart';
|
||||
import 'package:PiliPlus/pages/fav/pgc/widget/item.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -43,128 +44,118 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
|
||||
super.build(context);
|
||||
final theme = Theme.of(context);
|
||||
final padding = MediaQuery.viewPaddingOf(context);
|
||||
final bottomH = 50 + padding.bottom;
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
refreshIndicator(
|
||||
onRefresh: _favPgcController.onRefresh,
|
||||
child: CustomScrollView(
|
||||
controller: _favPgcController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: padding.bottom + 100),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_favPgcController.loadingState.value),
|
||||
return PgcLayout(
|
||||
body: refreshIndicator(
|
||||
onRefresh: _favPgcController.onRefresh,
|
||||
child: CustomScrollView(
|
||||
controller: _favPgcController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: padding.bottom + 100),
|
||||
sliver: Obx(
|
||||
() => _buildBody(_favPgcController.loadingState.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
toolbar: Obx(
|
||||
() => AnimatedSlide(
|
||||
offset: _favPgcController.enableMultiSelect.value
|
||||
? const Offset(0, -1)
|
||||
: Offset.zero,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
padding: .only(bottom: padding.bottom),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 0.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: -bottomH,
|
||||
child: Obx(
|
||||
() => AnimatedSlide(
|
||||
offset: _favPgcController.enableMultiSelect.value
|
||||
? const Offset(0, -1)
|
||||
: Offset.zero,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
height: bottomH,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 0.5,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
iconButton(
|
||||
size: 32,
|
||||
tooltip: '取消',
|
||||
context: context,
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: _favPgcController.onDisable,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
iconButton(
|
||||
size: 32,
|
||||
tooltip: '取消',
|
||||
context: context,
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: _favPgcController.onDisable,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Obx(
|
||||
() => Checkbox(
|
||||
value: _favPgcController.allSelected.value,
|
||||
onChanged: (value) {
|
||||
_favPgcController.handleSelect(
|
||||
checked: !_favPgcController.allSelected.value,
|
||||
disableSelect: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _favPgcController.handleSelect(
|
||||
const SizedBox(width: 12),
|
||||
Obx(
|
||||
() => Checkbox(
|
||||
value: _favPgcController.allSelected.value,
|
||||
onChanged: (value) {
|
||||
_favPgcController.handleSelect(
|
||||
checked: !_favPgcController.allSelected.value,
|
||||
disableSelect: false,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 14,
|
||||
bottom: 14,
|
||||
right: 12,
|
||||
),
|
||||
child: Text('全选'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _favPgcController.handleSelect(
|
||||
checked: !_favPgcController.allSelected.value,
|
||||
disableSelect: false,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 14,
|
||||
bottom: 14,
|
||||
right: 12,
|
||||
),
|
||||
const Spacer(),
|
||||
...const [
|
||||
(followStatus: 1, title: '想看'),
|
||||
(followStatus: 2, title: '在看'),
|
||||
(followStatus: 3, title: '看过'),
|
||||
]
|
||||
.where(
|
||||
(item) => item.followStatus != widget.followStatus,
|
||||
)
|
||||
.map(
|
||||
(item) => Padding(
|
||||
padding: const EdgeInsets.only(left: 25),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_favPgcController.checkedCount != 0) {
|
||||
_favPgcController.onUpdateList(
|
||||
item.followStatus,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 5,
|
||||
),
|
||||
child: Text(
|
||||
'标记为${item.title}',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
child: Text('全选'),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...const [
|
||||
(followStatus: 1, title: '想看'),
|
||||
(followStatus: 2, title: '在看'),
|
||||
(followStatus: 3, title: '看过'),
|
||||
]
|
||||
.where(
|
||||
(item) => item.followStatus != widget.followStatus,
|
||||
)
|
||||
.map(
|
||||
(item) => Padding(
|
||||
padding: const EdgeInsets.only(left: 25),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_favPgcController.checkedCount != 0) {
|
||||
_favPgcController.onUpdateList(
|
||||
item.followStatus,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 5,
|
||||
),
|
||||
child: Text(
|
||||
'标记为${item.title}',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
86
lib/pages/fav/pgc/pgc_layout.dart
Normal file
86
lib/pages/fav/pgc/pgc_layout.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart' show BoxHitTestResult, BoxParentData;
|
||||
|
||||
enum PgcType { toolbar, body }
|
||||
|
||||
class PgcLayout
|
||||
extends SlottedMultiChildRenderObjectWidget<PgcType, RenderBox> {
|
||||
const PgcLayout({
|
||||
super.key,
|
||||
required this.body,
|
||||
required this.toolbar,
|
||||
});
|
||||
|
||||
final Widget body;
|
||||
final Widget toolbar;
|
||||
|
||||
@override
|
||||
Iterable<PgcType> get slots => PgcType.values;
|
||||
|
||||
@override
|
||||
Widget childForSlot(slot) => switch (slot) {
|
||||
.toolbar => toolbar,
|
||||
.body => body,
|
||||
};
|
||||
|
||||
@override
|
||||
SlottedContainerRenderObjectMixin<PgcType, RenderBox> createRenderObject(
|
||||
BuildContext context,
|
||||
) {
|
||||
return _RenderPgcLayout();
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderPgcLayout extends RenderBox
|
||||
with SlottedContainerRenderObjectMixin<PgcType, RenderBox> {
|
||||
RenderBox get body => childForSlot(.body)!;
|
||||
RenderBox get toolbar => childForSlot(.toolbar)!;
|
||||
|
||||
Offset _getOffset(RenderBox child) {
|
||||
return (child.parentData as BoxParentData).offset;
|
||||
}
|
||||
|
||||
void _setOffset(RenderBox child, Offset offset) {
|
||||
(child.parentData as BoxParentData).offset = offset;
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
final constraints = this.constraints;
|
||||
size = constraints.biggest;
|
||||
|
||||
final body = this.body..layout(constraints);
|
||||
_setOffset(body, .zero);
|
||||
|
||||
final toolbar = this.toolbar
|
||||
..layout(BoxConstraints.tightFor(width: constraints.maxWidth));
|
||||
_setOffset(toolbar, Offset(0, constraints.maxHeight));
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
void doPaint(RenderBox child) {
|
||||
context.paintChild(child, _getOffset(child) + offset);
|
||||
}
|
||||
|
||||
doPaint(body);
|
||||
doPaint(toolbar);
|
||||
}
|
||||
|
||||
@override
|
||||
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
|
||||
for (final child in children) {
|
||||
final bool isHit = result.addWithPaintOffset(
|
||||
offset: _getOffset(child),
|
||||
position: position,
|
||||
hitTest: (BoxHitTestResult result, Offset transformed) {
|
||||
return child.hitTest(result, position: transformed);
|
||||
},
|
||||
);
|
||||
if (isHit) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user