diff --git a/lib/common/widgets/draggable_sheet/dyn.dart b/lib/common/widgets/draggable_sheet/dyn.dart index e2fc95ba9..01e91f2d4 100644 --- a/lib/common/widgets/draggable_sheet/dyn.dart +++ b/lib/common/widgets/draggable_sheet/dyn.dart @@ -1,4 +1,3 @@ - import 'package:flutter/gestures.dart' show Drag; import 'package:flutter/material.dart'; @@ -25,7 +24,9 @@ class DynDraggableScrollableSheet extends DraggableScrollableSheet { class _DynDraggableScrollableSheetState extends DraggableScrollableSheetState { @override void initScrollController() { - scrollController = _DynDraggableScrollableSheetScrollController(extent: extent); + scrollController = _DynDraggableScrollableSheetScrollController( + extent: extent, + ); } } diff --git a/lib/common/widgets/image_grid/image_grid_view.dart b/lib/common/widgets/image_grid/image_grid_view.dart index de226dc99..a40f40bbc 100644 --- a/lib/common/widgets/image_grid/image_grid_view.dart +++ b/lib/common/widgets/image_grid/image_grid_view.dart @@ -22,10 +22,13 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_grid/image_grid_builder.dart'; +import 'package:PiliPlus/common/widgets/image_viewer/gallery_viewer.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; +import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -94,13 +97,17 @@ class ImageGridView extends StatelessWidget { !fullScreen && Get.currentRoute.startsWith(_regex) && !context.mediaQuerySize.isPortrait) { - final scaffoldState = Scaffold.maybeOf(context); + final scaffoldState = MiniScaffold.maybeOf(context); if (scaffoldState != null) { onViewImage?.call(); - PageUtils.onHorizontalPreviewState( - scaffoldState, - imgList, - index, + scaffoldState.showBottomSheet( + constraints: const BoxConstraints(), + (context) => GalleryViewer( + sources: imgList, + initIndex: index, + quality: GlobalData().imgQuality, + ), + enableDrag: false, ); return; } diff --git a/lib/common/widgets/main_layout.dart b/lib/common/widgets/main_layout.dart index fda45f1b7..a00f534d6 100644 --- a/lib/common/widgets/main_layout.dart +++ b/lib/common/widgets/main_layout.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart' show BoxParentData, BoxHitTestResult, ChildLayoutHelper; -enum MainType { sideBar, body, bottomNav } +enum MainType { sideBar, bottomNav, body } class MainLayout extends SlottedMultiChildRenderObjectWidget { @@ -37,6 +37,10 @@ class MainLayout class _RenderMainLayout extends RenderBox with SlottedContainerRenderObjectMixin { + RenderBox? get sideBar => childForSlot(.sideBar); + RenderBox? get bottomNav => childForSlot(.bottomNav); + RenderBox get body => childForSlot(.body)!; + Offset _getOffset(RenderBox child) { return (child.parentData as BoxParentData).offset; } @@ -53,7 +57,7 @@ class _RenderMainLayout extends RenderBox final Offset bodyOffset; final BoxConstraints bodyConstraints; - final sideBar = childForSlot(.sideBar); + final sideBar = this.sideBar; if (sideBar != null) { final sideBarWidth = ChildLayoutHelper.layoutChild( sideBar, @@ -67,7 +71,7 @@ class _RenderMainLayout extends RenderBox height: constraints.maxHeight, ); } else { - final bottomNav = childForSlot(.bottomNav); + final bottomNav = this.bottomNav; if (bottomNav != null) { final bottomNavSize = ChildLayoutHelper.layoutChild( bottomNav, @@ -89,20 +93,26 @@ class _RenderMainLayout extends RenderBox ); } - final body = childForSlot(.body)!..layout(bodyConstraints); + final body = this.body..layout(bodyConstraints); _setOffset(body, bodyOffset); } @override void paint(PaintingContext context, Offset offset) { - for (final child in children) { - context.paintChild(child, _getOffset(child) + offset); + void doPaint(RenderBox? child) { + if (child != null) { + context.paintChild(child, _getOffset(child) + offset); + } } + + doPaint(sideBar); + doPaint(body); + doPaint(bottomNav); } @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { - for (final type in MainType.values.reversed) { + for (final type in MainType.values) { final child = childForSlot(type); if (child == null) continue; final bool isHit = result.addWithPaintOffset( diff --git a/lib/common/widgets/scaffold/bottom_sheet.dart b/lib/common/widgets/scaffold/bottom_sheet.dart new file mode 100644 index 000000000..22224e5da --- /dev/null +++ b/lib/common/widgets/scaffold/bottom_sheet.dart @@ -0,0 +1,80 @@ +import 'package:flutter/gestures.dart' show VerticalDragGestureRecognizer; +import 'package:flutter/material.dart'; + +// ignore: camel_case_types +class BottomSheet_ extends BottomSheet { + const BottomSheet_({ + super.key, + super.animationController, + super.enableDrag = true, + super.onDragStart, + super.onDragEnd, + super.constraints, + required super.onClosing, + required super.builder, + }); + + @override + BottomSheetState createState() => _MiniBottomSheetState(); +} + +class _MiniBottomSheetState extends BottomSheetState { + VerticalDragGestureRecognizer? _verticalDragGestureRecognizer; + + VerticalDragGestureRecognizer get verticalDragGestureRecognizer => + _verticalDragGestureRecognizer ??= + VerticalDragGestureRecognizer(debugOwner: this) + ..onStart = handleDragStart + ..onUpdate = handleDragUpdate + ..onEnd = handleDragEnd + ..gestureSettings = MediaQuery.maybeGestureSettingsOf(context) + ..onlyAcceptDragOnThreshold = true; + + @override + void dispose() { + super.dispose(); + if (widget.enableDrag) { + _verticalDragGestureRecognizer?.dispose(); + _verticalDragGestureRecognizer = null; + } + } + + void _onPointerDown(PointerDownEvent event) { + verticalDragGestureRecognizer.addPointer(event); + } + + @override + Widget build(BuildContext context) { + final bool useMaterial3 = Theme.of(context).useMaterial3; + late final defaults = useMaterial3 + ? BottomSheetDefaultsM3(context) + : const BottomSheetThemeData(); + late final bottomSheetTheme = Theme.of(context).bottomSheetTheme; + final BoxConstraints? constraints = + widget.constraints ?? + bottomSheetTheme.constraints ?? + defaults.constraints; + + Widget bottomSheet = KeyedSubtree( + key: childKey, + child: widget.builder(context), + ); + + if (constraints != null && constraints != const BoxConstraints()) { + bottomSheet = Align( + alignment: Alignment.bottomCenter, + heightFactor: 1.0, + child: ConstrainedBox(constraints: constraints, child: bottomSheet), + ); + } + + if (widget.enableDrag) { + return Listener( + onPointerDown: _onPointerDown, + child: bottomSheet, + ); + } + + return bottomSheet; + } +} diff --git a/lib/common/widgets/scaffold/mini_scaffold.dart b/lib/common/widgets/scaffold/mini_scaffold.dart new file mode 100644 index 000000000..667fafae0 --- /dev/null +++ b/lib/common/widgets/scaffold/mini_scaffold.dart @@ -0,0 +1,308 @@ +import 'dart:async' show Completer; + +import 'package:PiliPlus/common/widgets/scaffold/bottom_sheet.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' + show RenderStack, BoxHitTestResult, StackParentData; + +class MiniScaffold extends StatefulWidget { + const MiniScaffold({ + super.key, + required this.body, + }); + + final Widget body; + + static MiniScaffoldState of(BuildContext context) { + return context.findAncestorStateOfType()!; + } + + static MiniScaffoldState? maybeOf(BuildContext context) { + return context.findAncestorStateOfType(); + } + + @override + State createState() => MiniScaffoldState(); +} + +class MiniScaffoldState extends State + with TickerProviderStateMixin { + final _dismissedBottomSheets = []; + PersistentBottomSheetController? _currentBottomSheet; + LocalHistoryEntry? _persistentSheetHistoryEntry; + + void _closeCurrentBottomSheet() { + if (_currentBottomSheet != null) { + if (!_currentBottomSheet!.isLocalHistoryEntry) { + _currentBottomSheet!.close(); + } + assert(() { + _currentBottomSheet?.completer.future.whenComplete(() { + assert(_currentBottomSheet == null); + }); + return true; + }()); + } + } + + PersistentBottomSheetController _buildBottomSheet( + WidgetBuilder builder, { + required bool isPersistent, + required AnimationController animationController, + BoxConstraints? constraints, + bool? enableDrag, + bool shouldDisposeAnimationController = true, + }) { + final completer = Completer(); + final bottomSheetKey = GlobalKey(); + late StandardBottomSheet bottomSheet; + + var removedEntry = false; + var doingDispose = false; + + void removePersistentSheetHistoryEntryIfNeeded() { + assert(isPersistent); + if (_persistentSheetHistoryEntry != null) { + _persistentSheetHistoryEntry!.remove(); + _persistentSheetHistoryEntry = null; + } + } + + void removeCurrentBottomSheet() { + removedEntry = true; + if (_currentBottomSheet == null) { + return; + } + assert(_currentBottomSheet!.widget == bottomSheet); + assert(bottomSheetKey.currentState != null); + + if (isPersistent) { + removePersistentSheetHistoryEntryIfNeeded(); + } + + bottomSheetKey.currentState!.close(); + setState(() { + _currentBottomSheet = null; + }); + + if (!animationController.isDismissed) { + _dismissedBottomSheets.add(bottomSheet); + } + completer.complete(); + } + + final LocalHistoryEntry? entry = isPersistent + ? null + : LocalHistoryEntry( + onRemove: () { + if (!removedEntry && + _currentBottomSheet?.widget == bottomSheet && + !doingDispose) { + removeCurrentBottomSheet(); + } + }, + ); + + void removeEntryIfNeeded() { + if (!isPersistent && !removedEntry) { + assert(entry != null); + entry!.remove(); + removedEntry = true; + } + } + + bottomSheet = _StandardBottomSheet( + key: bottomSheetKey, + animationController: animationController, + enableDrag: enableDrag ?? !isPersistent, + onClosing: () { + if (_currentBottomSheet == null) { + return; + } + assert(_currentBottomSheet!.widget == bottomSheet); + removeEntryIfNeeded(); + }, + onDismissed: () { + if (_dismissedBottomSheets.contains(bottomSheet)) { + setState(() { + _dismissedBottomSheets.remove(bottomSheet); + }); + } + }, + onDispose: () { + doingDispose = true; + removeEntryIfNeeded(); + if (shouldDisposeAnimationController) { + animationController.dispose(); + } + }, + builder: builder, + isPersistent: isPersistent, + constraints: constraints, + ); + + if (!isPersistent) { + ModalRoute.of(context)!.addLocalHistoryEntry(entry!); + } + + return PersistentBottomSheetController( + bottomSheet, + completer, + entry != null ? entry.remove : removeCurrentBottomSheet, + (VoidCallback fn) { + bottomSheetKey.currentState?.setState(fn); + }, + !isPersistent, + ); + } + + PersistentBottomSheetController showBottomSheet( + WidgetBuilder builder, { + BoxConstraints? constraints, + bool? enableDrag, + AnimationController? transitionAnimationController, + AnimationStyle? sheetAnimationStyle, + }) { + assert(debugCheckHasMediaQuery(context)); + + _closeCurrentBottomSheet(); + final AnimationController controller = + (transitionAnimationController ?? + BottomSheet.createAnimationController( + this, + sheetAnimationStyle: sheetAnimationStyle, + )) + ..forward(); + setState(() { + _currentBottomSheet = _buildBottomSheet( + builder, + isPersistent: false, + animationController: controller, + constraints: constraints, + enableDrag: enableDrag, + shouldDisposeAnimationController: transitionAnimationController == null, + ); + }); + return _currentBottomSheet!; + } + + @override + Widget build(BuildContext context) { + return BottomSheetStack( + clipBehavior: .none, + alignment: .bottomCenter, + children: [ + widget.body, + ..._dismissedBottomSheets, + ?_currentBottomSheet?.widget, + ], + ); + } +} + +class _StandardBottomSheet extends StandardBottomSheet { + const _StandardBottomSheet({ + super.key, + required super.animationController, + super.enableDrag, + required super.onClosing, + required super.onDismissed, + required super.builder, + super.isPersistent, + super.constraints, + super.onDispose, + }); + + @override + StandardBottomSheetState createState() => _StandardBottomSheetState(); +} + +class _StandardBottomSheetState extends StandardBottomSheetState { + @override + Widget build(BuildContext context) { + final child = BottomSheet_( + animationController: widget.animationController, + enableDrag: widget.enableDrag, + onDragStart: handleDragStart, + onDragEnd: handleDragEnd, + onClosing: widget.onClosing!, + builder: widget.builder, + constraints: widget.constraints, + ); + if (widget.enableDrag) { + return AnimatedBuilder( + animation: widget.animationController, + builder: (context, child) => Align( + alignment: AlignmentDirectional.topStart, + heightFactor: animationCurve.transform( + widget.animationController.value, + ), + child: child, + ), + child: child, + ); + } + return AnimatedBuilder( + animation: widget.animationController, + builder: (context, child) => Opacity( + opacity: widget.animationController.value, + child: child, + ), + child: child, + ); + } +} + +class BottomSheetStack extends Stack { + const BottomSheetStack({ + super.key, + super.alignment, + super.textDirection, + super.fit, + super.clipBehavior, + super.children, + }); + + @override + RenderBottomSheetStack createRenderObject(BuildContext context) { + return RenderBottomSheetStack( + alignment: alignment, + textDirection: textDirection ?? Directionality.maybeOf(context), + fit: fit, + clipBehavior: clipBehavior, + ); + } +} + +class RenderBottomSheetStack extends RenderStack { + RenderBottomSheetStack({ + super.children, + super.alignment, + super.textDirection, + super.fit, + super.clipBehavior, + }); + + /// HitTest lastChild only + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? child = lastChild; + if (child != null) { + final childParentData = child.parentData! as StackParentData; + return result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - childParentData.offset); + final isHit = child.hitTest(result, position: transformed); + if (childParentData.previousSibling != null) { + return false; + } else { + return isHit; + } + }, + ); + } + return false; + } +} diff --git a/lib/common/widgets/scaffold/simple_scaffold.dart b/lib/common/widgets/scaffold/simple_scaffold.dart new file mode 100644 index 000000000..183e787ef --- /dev/null +++ b/lib/common/widgets/scaffold/simple_scaffold.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' + show BoxParentData, BoxHitTestResult, ChildLayoutHelper; + +class SimpleScaffold extends StatelessWidget { + const SimpleScaffold({ + super.key, + this.backgroundColor, + this.fab, + this.appBar, + required this.body, + }); + + final Color? backgroundColor; + final Widget? fab; + final Widget? appBar; + final Widget body; + + @override + Widget build(BuildContext context) { + return Material( + color: backgroundColor, + child: ScaffoldLayout( + fab: fab, + appBar: appBar, + body: body, + ), + ); + } +} + +enum ScaffoldType { fab, appBar, body } + +class ScaffoldLayout + extends SlottedMultiChildRenderObjectWidget { + const ScaffoldLayout({ + super.key, + this.fab, + this.appBar, + required this.body, + }); + + final Widget? fab; + final Widget? appBar; + final Widget body; + + @override + Iterable get slots => ScaffoldType.values; + + @override + Widget? childForSlot(slot) => switch (slot) { + .fab => fab, + .appBar => appBar, + .body => body, + }; + + @override + SlottedContainerRenderObjectMixin createRenderObject( + BuildContext context, + ) { + return _RenderScaffoldLayout(); + } +} + +class _RenderScaffoldLayout extends RenderBox + with SlottedContainerRenderObjectMixin { + RenderBox? get fab => childForSlot(.fab); + RenderBox? get appBar => childForSlot(.appBar); + RenderBox get body => childForSlot(.body)!; + + 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 Offset bodyOffset; + final BoxConstraints bodyConstraints; + + final appBar = this.appBar; + if (appBar != null) { + final appBarHeight = ChildLayoutHelper.layoutChild( + appBar, + BoxConstraints.tightFor(width: constraints.maxWidth), + ).height; + _setOffset(appBar, .zero); + + bodyOffset = Offset(0, appBarHeight); + bodyConstraints = BoxConstraints.tightFor( + width: constraints.maxWidth, + height: constraints.maxHeight - appBarHeight, + ); + } else { + bodyOffset = .zero; + bodyConstraints = constraints; + } + + final body = this.body..layout(bodyConstraints); + _setOffset(body, bodyOffset); + + final fab = this.fab; + if (fab != null) { + final fabSize = ChildLayoutHelper.layoutChild(fab, constraints.loosen()); + _setOffset( + fab, + Offset( + constraints.maxWidth - fabSize.width, + constraints.maxHeight - fabSize.height, + ), + ); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + void doPaint(RenderBox? child) { + if (child != null) { + context.paintChild(child, _getOffset(child) + offset); + } + } + + doPaint(appBar); + doPaint(body); + doPaint(fab); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + for (final type in ScaffoldType.values) { + final child = childForSlot(type); + if (child == null) continue; + 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; + } +} diff --git a/lib/common/widgets/simple_colored_box.dart b/lib/common/widgets/simple_colored_box.dart new file mode 100644 index 000000000..4966e54b5 --- /dev/null +++ b/lib/common/widgets/simple_colored_box.dart @@ -0,0 +1,68 @@ +import 'package:flutter/rendering.dart' show RenderProxyBox; +import 'package:flutter/widgets.dart'; + +class SimpleColoredBox extends ColoredBox { + const SimpleColoredBox({ + required super.color, + super.isAntiAlias, + super.child, + super.key, + }); + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderSimpleColoredBox(color: color, isAntiAlias: isAntiAlias); + } + + @override + void updateRenderObject( + BuildContext context, + RenderSimpleColoredBox renderObject, + ) { + renderObject + ..color = color + ..isAntiAlias = isAntiAlias; + } +} + +class RenderSimpleColoredBox extends RenderProxyBox { + RenderSimpleColoredBox({ + required this._color, + required this._isAntiAlias, + }); + + Color get color => _color; + Color _color; + set color(Color value) { + if (value == _color) { + return; + } + _color = value; + markNeedsPaint(); + } + + bool get isAntiAlias => _isAntiAlias; + bool _isAntiAlias; + set isAntiAlias(bool value) { + if (value == _isAntiAlias) { + return; + } + _isAntiAlias = value; + markNeedsPaint(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (size > Size.zero) { + context.canvas.drawRect( + offset & size, + Paint() + ..isAntiAlias = isAntiAlias + ..color = color, + ); + } + if (child != null) { + context.paintChild(child!, offset); + } + } +} diff --git a/lib/pages/about/view.dart b/lib/pages/about/view.dart index a07c4b685..e1e2e4b56 100644 --- a/lib/pages/about/view.dart +++ b/lib/pages/about/view.dart @@ -9,6 +9,7 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/dialog/export_import.dart'; import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/services/logger.dart'; import 'package:PiliPlus/utils/accounts.dart'; @@ -89,9 +90,8 @@ class _AboutPageState extends State { final subTitleStyle = TextStyle(fontSize: 13, color: outline); final showAppBar = widget.showAppBar; final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( + return SimpleScaffold( appBar: showAppBar ? AppBar(title: const Text('关于')) : null, - resizeToAvoidBottomInset: false, body: ListView( padding: EdgeInsets.only( left: showAppBar ? padding.left : 0, diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index 60fbdb463..ab9077063 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -5,6 +5,8 @@ import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarScrollPhysics; import 'package:PiliPlus/common/widgets/sliver/sliver_to_box_adapter.dart'; @@ -54,20 +56,19 @@ class _ArticlePageState extends CommonDynPageState { @override Widget build(BuildContext context) { - final child = Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(), - body: Padding( - padding: EdgeInsets.only(left: padding.left, right: padding.right), - child: _buildPage(), - ), - floatingActionButtonLocation: floatingActionButtonLocation, - floatingActionButton: SlideTransition( - position: fabAnimation, - child: _buildBottom(), + return fabAnimWrapper( + child: SimpleScaffold( + appBar: _buildAppBar(), + body: Padding( + padding: .only(left: padding.left, right: padding.right), + child: _buildPage(), + ), + fab: SlideTransition( + position: fabAnimation, + child: _buildBottom(), + ), ), ); - return fabAnimWrapper(child: child); } Widget _buildPage() { @@ -137,9 +138,7 @@ class _ArticlePageState extends CommonDynPageState { flex: flex1, child: Padding( padding: .only(right: padding), - child: Scaffold( - backgroundColor: Colors.transparent, - resizeToAvoidBottomInset: false, + child: MiniScaffold( body: refreshIndicator( onRefresh: controller.onRefresh, child: CustomScrollView( diff --git a/lib/pages/audio/view.dart b/lib/pages/audio/view.dart index e73c41a82..bbef52f73 100644 --- a/lib/pages/audio/view.dart +++ b/lib/pages/audio/view.dart @@ -9,6 +9,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; import 'package:PiliPlus/common/widgets/progress_bar/audio_video_progress_bar.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show platformClampingPhysics; import 'package:PiliPlus/common/widgets/selection_text.dart'; @@ -96,8 +97,7 @@ class _AudioPageState extends State { final colorScheme = ColorScheme.of(context); final isPortrait = MediaQuery.sizeOf(context).isPortrait; final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( actions: [ if (_controller.isUgc && _controller.enableSponsorBlock) diff --git a/lib/pages/blacklist/view.dart b/lib/pages/blacklist/view.dart index 584e07bc2..4f06c1012 100644 --- a/lib/pages/blacklist/view.dart +++ b/lib/pages/blacklist/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/blacklist/list.dart'; @@ -34,8 +35,7 @@ class _BlackListPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Obx( () => Text( diff --git a/lib/pages/bubble/view.dart b/lib/pages/bubble/view.dart index 8e2954514..52c2f95d8 100644 --- a/lib/pages/bubble/view.dart +++ b/lib/pages/bubble/view.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/bubble/dyn_list.dart'; @@ -70,67 +71,65 @@ class _BubblePageState extends State if (widget.categoryId != null) { return child; } else { - child = Stack( - clipBehavior: .none, - children: [ - child, - Positioned( + child = ScaffoldLayout( + body: child, + fab: Padding( + padding: .only( right: kFloatingActionButtonMargin, bottom: kFloatingActionButtonMargin + padding.bottom, - child: Obx( - () { - final sortInfo = _controller.sortInfo.value; - if (sortInfo == null || sortInfo.showSort != true) { - return const SizedBox.shrink(); - } - final item = sortInfo.sortItems?.firstWhereOrNull( - (e) => e.sortType == sortInfo.curSortType, - ); - if (item != null) { - return FloatingActionButton.extended( - tooltip: '排序', - onPressed: () => showDialog( - context: context, - builder: (context) => SimpleDialog( - clipBehavior: .hardEdge, - contentPadding: const .symmetric(vertical: 12), - children: sortInfo.sortItems!.map( - (e) { - final isSelected = item.sortType == e.sortType; - return ListTile( - dense: true, - enabled: !isSelected, - onTap: () { - Get.back(); - if (!isSelected) { - _controller.onSort(e.sortType); - } - }, - title: Text( - e.text!, - style: const TextStyle(fontSize: 14), - ), - trailing: isSelected - ? const Icon(size: 22, Icons.check) - : null, - ); - }, - ).toList(), - ), - ), - icon: const Icon(Icons.sort, size: 20), - label: Text(item.text!), - ); - } - return const SizedBox.shrink(); - }, - ), ), - ], + child: Obx( + () { + final sortInfo = _controller.sortInfo.value; + if (sortInfo == null || sortInfo.showSort != true) { + return const SizedBox.shrink(); + } + final item = sortInfo.sortItems?.firstWhereOrNull( + (e) => e.sortType == sortInfo.curSortType, + ); + if (item != null) { + return FloatingActionButton.extended( + tooltip: '排序', + onPressed: () => showDialog( + context: context, + builder: (context) => SimpleDialog( + clipBehavior: .hardEdge, + contentPadding: const .symmetric(vertical: 12), + children: sortInfo.sortItems!.map( + (e) { + final isSelected = item.sortType == e.sortType; + return ListTile( + dense: true, + enabled: !isSelected, + onTap: () { + Get.back(); + if (!isSelected) { + _controller.onSort(e.sortType); + } + }, + title: Text( + e.text!, + style: const TextStyle(fontSize: 14), + ), + trailing: isSelected + ? const Icon(size: 22, Icons.check) + : null, + ); + }, + ).toList(), + ), + ), + icon: const Icon(Icons.sort, size: 20), + label: Text(item.text!), + ); + } + return const SizedBox.shrink(); + }, + ), + ), ); } - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Obx(() { final tribeName = _controller.tribeName.value; diff --git a/lib/pages/common/dyn/common_dyn_page.dart b/lib/pages/common/dyn/common_dyn_page.dart index 3a2315d29..9f845c85a 100644 --- a/lib/pages/common/dyn/common_dyn_page.dart +++ b/lib/pages/common/dyn/common_dyn_page.dart @@ -2,6 +2,8 @@ import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' @@ -235,8 +237,7 @@ mixin CommonDynPageMixin ), ); if (showBackBtn) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('评论详情'), shape: Border( @@ -258,11 +259,10 @@ mixin CommonDynPageMixin arguments: arguments, ); } else { - final scaffoldState = Scaffold.maybeOf(context); + final scaffoldState = MiniScaffold.maybeOf(context); if (scaffoldState != null) { hideFab(); scaffoldState.showBottomSheet( - backgroundColor: Colors.transparent, (context) => replyReplyPage(showBackBtn: false), ); } else { @@ -320,7 +320,10 @@ mixin CommonDynPageMixin : const NoBottomPaddingFabLocation(); Widget get fabButton => Padding( - padding: .only(bottom: padding.bottom + kFloatingActionButtonMargin), + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, + ), child: replyButton, ); diff --git a/lib/pages/contact/view.dart b/lib/pages/contact/view.dart index 95cb0c7e7..203e486d9 100644 --- a/lib/pages/contact/view.dart +++ b/lib/pages/contact/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/pages/fan/view.dart'; import 'package:PiliPlus/pages/follow/child/child_view.dart'; @@ -39,8 +40,7 @@ class _ContactPageState extends State @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('通讯录'), bottom: TabBar( diff --git a/lib/pages/danmaku_block/view.dart b/lib/pages/danmaku_block/view.dart index 9e8d92bbd..e5d644297 100644 --- a/lib/pages/danmaku_block/view.dart +++ b/lib/pages/danmaku_block/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/models/common/dm_block_type.dart'; import 'package:PiliPlus/models/user/danmaku_block.dart'; @@ -25,6 +26,7 @@ class DanmakuBlockPage extends StatefulWidget { class _DanmakuBlockPageState extends State { final DanmakuBlockController _controller = Get.put(DanmakuBlockController()); late PlPlayerController plPlayerController; + late EdgeInsets padding; @override void initState() { @@ -32,6 +34,12 @@ class _DanmakuBlockPageState extends State { plPlayerController = Get.arguments as PlPlayerController; } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + padding = MediaQuery.viewPaddingOf(context); + } + @override void dispose() { final ruleFilter = RuleFilter.fromRuleTypeEntries(_controller.rules); @@ -42,8 +50,7 @@ class _DanmakuBlockPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('弹幕屏蔽'), bottom: TabBar( @@ -71,11 +78,18 @@ class _DanmakuBlockPageState extends State { ) .toList(), ), - floatingActionButton: FloatingActionButton( - tooltip: '添加', - onPressed: () => - _showAddDialog(DmBlockType.values[_controller.tabController.index]), - child: const Icon(Icons.add), + fab: Padding( + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, + ), + child: FloatingActionButton( + tooltip: '添加', + onPressed: () => _showAddDialog( + DmBlockType.values[_controller.tabController.index], + ), + child: const Icon(Icons.add), + ), ), ); } @@ -86,9 +100,7 @@ class _DanmakuBlockPageState extends State { } return ListView.builder( itemCount: list.length, - padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, - ), + padding: .only(bottom: padding.bottom + 100), itemBuilder: (context, itemIndex) { final SimpleRule item = list[itemIndex]; final child = iconButton( diff --git a/lib/pages/dlna/view.dart b/lib/pages/dlna/view.dart index 39ed53944..eca3a66ac 100644 --- a/lib/pages/dlna/view.dart +++ b/lib/pages/dlna/view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:dlna_dart/dlna.dart'; import 'package:flutter/material.dart'; @@ -70,8 +71,7 @@ class _DLNAPageState extends State { @override Widget build(BuildContext context) { final colorScheme = ColorScheme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('投屏'), actions: [ diff --git a/lib/pages/download/detail/view.dart b/lib/pages/download/detail/view.dart index 1ff40cf8f..3c4775667 100644 --- a/lib/pages/download/detail/view.dart +++ b/lib/pages/download/detail/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart' @@ -93,8 +94,7 @@ class _DownloadDetailPageState extends State handleSelect(); } }, - child: Scaffold( - resizeToAvoidBottomInset: false, + child: SimpleScaffold( appBar: MultiSelectAppBarWidget( ctr: this, actions: [ diff --git a/lib/pages/download/downloading/view.dart b/lib/pages/download/downloading/view.dart index 4483c69ec..6acde5ef3 100644 --- a/lib/pages/download/downloading/view.dart +++ b/lib/pages/download/downloading/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart' @@ -41,8 +42,7 @@ class _DownloadingPageState extends State handleSelect(); } }, - child: Scaffold( - resizeToAvoidBottomInset: false, + child: SimpleScaffold( appBar: MultiSelectAppBarWidget( ctr: this, child: AppBar( diff --git a/lib/pages/download/view.dart b/lib/pages/download/view.dart index b1fbc27ce..f123f7761 100644 --- a/lib/pages/download/view.dart +++ b/lib/pages/download/view.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models_new/download/download_info.dart'; @@ -57,8 +58,7 @@ class _DownloadPageState extends State with GridMixin { _controller.handleSelect(); } }, - child: Scaffold( - resizeToAvoidBottomInset: false, + child: SimpleScaffold( appBar: MultiSelectAppBarWidget( ctr: _controller, actions: [ diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 1f2abe4a8..39c0ff82e 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -165,6 +165,7 @@ class _DynamicsPageState extends CommonPageState } return Scaffold( + primary: false, resizeToAvoidBottomInset: false, backgroundColor: Colors.transparent, appBar: AppBar( diff --git a/lib/pages/dynamics_create_reserve/view.dart b/lib/pages/dynamics_create_reserve/view.dart index 408f42ad6..1762bde9a 100644 --- a/lib/pages/dynamics_create_reserve/view.dart +++ b/lib/pages/dynamics_create_reserve/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/time_picker.dart'; import 'package:PiliPlus/pages/dynamics_create_reserve/controller.dart'; import 'package:PiliPlus/utils/date_utils.dart'; @@ -46,7 +47,7 @@ class _CreateReservePageState extends State { ), const SizedBox(height: 10), ]; - return Scaffold( + return SimpleScaffold( appBar: AppBar(title: const Text('添加直播预约')), body: ListView( padding: EdgeInsets.only( diff --git a/lib/pages/dynamics_create_vote/view.dart b/lib/pages/dynamics_create_vote/view.dart index 1e1198017..f4c0f834e 100644 --- a/lib/pages/dynamics_create_vote/view.dart +++ b/lib/pages/dynamics_create_vote/view.dart @@ -2,6 +2,7 @@ import 'dart:io' show File; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/time_picker.dart'; import 'package:PiliPlus/models/dynamics/vote_model.dart'; import 'package:PiliPlus/pages/dynamics_create_vote/controller.dart'; @@ -53,7 +54,7 @@ class _CreateVotePageState extends State { thickness: 1, color: theme.colorScheme.outline.withValues(alpha: 0.1), ); - return Scaffold( + return SimpleScaffold( appBar: AppBar( title: Text('${_controller.voteId != null ? '' : '发起'}投票'), ), diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index bf729cc2e..635ceb427 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -6,6 +6,8 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_behavior.dart' show NoOverscrollIndicator; import 'package:PiliPlus/common/widgets/scroll_physics.dart' @@ -60,7 +62,9 @@ class _DynamicDetailPageState void _startRefresh() { _isRefreshing.value = true; - _refreshController.repeat(); + _refreshController + ..value = 0 + ..repeat(); } void _stopRefresh() { @@ -119,23 +123,20 @@ class _DynamicDetailPageState @override Widget build(BuildContext context) { - final child = Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(), - body: Padding( - padding: EdgeInsets.only(left: padding.left, right: padding.right), - child: _buildBody(), - ), - floatingActionButtonLocation: floatingActionButtonLocation, - floatingActionButton: SlideTransition( - position: fabAnimation, - child: _buildBottom(), - ), - ); return SelectionTapRegionSurface( /// apply `lib/scripts/scrollable.patch` isScrolling: () => _scrollable?.shouldIgnorePointer ?? false, - child: child, + child: SimpleScaffold( + appBar: _buildAppBar(), + body: Padding( + padding: EdgeInsets.only(left: padding.left, right: padding.right), + child: _buildBody(), + ), + fab: SlideTransition( + position: fabAnimation, + child: _buildBottom(), + ), + ), ); } @@ -482,9 +483,7 @@ class _DynamicDetailPageState flex: flex1, child: Padding( padding: EdgeInsets.only(right: padding), - child: Scaffold( - backgroundColor: Colors.transparent, - resizeToAvoidBottomInset: false, + child: MiniScaffold( body: Column( children: [ _buildTabBar(), diff --git a/lib/pages/dynamics_mention/view.dart b/lib/pages/dynamics_mention/view.dart index 35b7cef63..5ae4e350d 100644 --- a/lib/pages/dynamics_mention/view.dart +++ b/lib/pages/dynamics_mention/view.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart'; @@ -85,142 +86,142 @@ class _DynMentionPanelState final theme = Theme.of(context); final padding = MediaQuery.paddingOf(context).bottom; final viewInset = MediaQuery.viewInsetsOf(context).bottom; - return Column( - children: [ - SizedBox( - height: 35, - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: theme.colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3)), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 5), - child: TextField( - focusNode: _controller.focusNode, - controller: _controller.controller, - onChanged: ctr!.add, - decoration: InputDecoration( - visualDensity: .standard, - border: const OutlineInputBorder( - gapPadding: 0, - borderSide: BorderSide.none, - borderRadius: BorderRadius.all(Radius.circular(25)), - ), - isDense: true, - filled: true, - fillColor: theme.colorScheme.onInverseSurface, - hintText: '输入你想@的人', - hintStyle: const TextStyle(fontSize: 14), - prefixIcon: const Padding( - padding: EdgeInsets.only(left: 12, right: 4), - child: Icon(Icons.search, size: 20), - ), - prefixIconConstraints: const .new(minHeight: 0, minWidth: 0), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 6, - ), - suffixIcon: Obx( - () => _controller.enableClear.value - ? Padding( - padding: const EdgeInsets.only(right: 12), - child: GestureDetector( - child: Container( - padding: const EdgeInsetsDirectional.all(2), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: theme.colorScheme.secondaryContainer, - ), - child: Icon( - Icons.clear, - size: 16, - color: theme.colorScheme.onSecondaryContainer, - ), - ), - onTap: () => _controller - ..enableClear.value = false - ..controller.clear() - ..onRefresh().whenComplete( - () => - WidgetsBinding.instance.addPostFrameCallback( - (_) => widget.scrollController?.jumpToTop(), - ), - ), - ), - ) - : const SizedBox.shrink(), - ), - suffixIconConstraints: const BoxConstraints( - minHeight: 0, - minWidth: 0, - ), - ), - ), - ), - Expanded( - child: Stack( - clipBehavior: Clip.none, - children: [ - NotificationListener( - onNotification: (notification) { - if (notification is UserScrollNotification) { - if (_controller.focusNode.hasFocus) { - _controller.focusNode.unfocus(); - } - } else if (notification is ScrollEndNotification) { - widget.onCachePos?.call(notification.metrics.pixels); - } - return false; - }, - child: CustomScrollView( - controller: widget.scrollController, - slivers: [ - Obx( - () => _buildBody(theme, _controller.loadingState.value), - ), - SliverToBoxAdapter( - child: SizedBox(height: padding + viewInset + 100), - ), - ], + return SimpleScaffold( + body: Column( + children: [ + SizedBox( + height: 35, + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: theme.colorScheme.outline, + borderRadius: const BorderRadius.all(Radius.circular(3)), ), ), - Obx(() { - return Positioned( - right: kFloatingActionButtonMargin, - bottom: - padding + - kFloatingActionButtonMargin + - (_controller.showBtn.value ? viewInset : 0), - child: AnimatedSlide( - offset: _controller.showBtn.value - ? Offset.zero - : const Offset(0, 3), - duration: const Duration(milliseconds: 120), - child: FloatingActionButton( - onPressed: () { - if (_controller.mentionList.isNullOrEmpty) { - _controller.showBtn.value = false; - return; - } - Get.back(result: _controller.mentionList); - _controller.showBtn.value = false; - }, - child: const Icon(Icons.check), - ), - ), - ); - }), - ], + ), ), - ), - ], + Padding( + padding: const EdgeInsets.only(left: 16, right: 16, bottom: 5), + child: TextField( + focusNode: _controller.focusNode, + controller: _controller.controller, + onChanged: ctr!.add, + decoration: InputDecoration( + visualDensity: .standard, + border: const OutlineInputBorder( + gapPadding: 0, + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(25)), + ), + isDense: true, + filled: true, + fillColor: theme.colorScheme.onInverseSurface, + hintText: '输入你想@的人', + hintStyle: const TextStyle(fontSize: 14), + prefixIcon: const Padding( + padding: EdgeInsets.only(left: 12, right: 4), + child: Icon(Icons.search, size: 20), + ), + prefixIconConstraints: const .new(minHeight: 0, minWidth: 0), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 6, + ), + suffixIcon: Obx( + () => _controller.enableClear.value + ? Padding( + padding: const EdgeInsets.only(right: 12), + child: GestureDetector( + child: Container( + padding: const EdgeInsetsDirectional.all(2), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: theme.colorScheme.secondaryContainer, + ), + child: Icon( + Icons.clear, + size: 16, + color: theme.colorScheme.onSecondaryContainer, + ), + ), + onTap: () => _controller + ..enableClear.value = false + ..controller.clear() + ..onRefresh().whenComplete( + () => WidgetsBinding.instance + .addPostFrameCallback( + (_) => + widget.scrollController?.jumpToTop(), + ), + ), + ), + ) + : const SizedBox.shrink(), + ), + suffixIconConstraints: const BoxConstraints( + minHeight: 0, + minWidth: 0, + ), + ), + ), + ), + Expanded( + child: NotificationListener( + onNotification: (notification) { + if (notification is UserScrollNotification) { + if (_controller.focusNode.hasFocus) { + _controller.focusNode.unfocus(); + } + } else if (notification is ScrollEndNotification) { + widget.onCachePos?.call(notification.metrics.pixels); + } + return false; + }, + child: CustomScrollView( + controller: widget.scrollController, + slivers: [ + Obx( + () => _buildBody(theme, _controller.loadingState.value), + ), + SliverToBoxAdapter( + child: SizedBox(height: padding + viewInset + 100), + ), + ], + ), + ), + ), + ], + ), + fab: Obx(() { + return Padding( + padding: .only( + right: kFloatingActionButtonMargin, + bottom: + padding + + kFloatingActionButtonMargin + + (_controller.showBtn.value ? viewInset : 0), + ), + child: AnimatedSlide( + offset: _controller.showBtn.value + ? Offset.zero + : const Offset(0, 3), + duration: const Duration(milliseconds: 120), + child: FloatingActionButton( + onPressed: () { + if (_controller.mentionList.isNullOrEmpty) { + _controller.showBtn.value = false; + return; + } + Get.back(result: _controller.mentionList); + _controller.showBtn.value = false; + }, + child: const Icon(Icons.check), + ), + ), + ); + }), ); } diff --git a/lib/pages/dynamics_topic/view.dart b/lib/pages/dynamics_topic/view.dart index b74be8ead..dc24c677a 100644 --- a/lib/pages/dynamics_topic/view.dart +++ b/lib/pages/dynamics_topic/view.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; import 'package:PiliPlus/http/constants.dart'; @@ -59,121 +60,112 @@ class _DynTopicPageState extends State @override Widget build(BuildContext context) { - return Material( - child: Stack( - clipBehavior: .none, - children: [ - refreshIndicator( - onRefresh: _controller.onRefresh, - child: fabAnimWrapper( - child: CustomScrollView( - controller: _controller.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - Obx( - () => _buildAppBar( - colorScheme, - padding, - _controller.topState.value, - ), - ), - Obx(() { - final allSortBy = - _controller.topicSortByConf.value?.allSortBy; - if (allSortBy != null && allSortBy.isNotEmpty) { - return SliverPinnedHeader( - backgroundColor: colorScheme.surface, - child: Padding( - padding: EdgeInsets.only( - left: 12 + padding.left, - top: 6, - bottom: 6, - ), - child: Builder( - builder: (context) { - return ToggleButtons( - fillColor: colorScheme.secondaryContainer, - selectedColor: colorScheme.onSecondaryContainer, - constraints: const BoxConstraints( - minWidth: 54, - minHeight: 24, - ), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - borderRadius: const .all(.circular(25)), - onPressed: (index) { - _controller.onSort(allSortBy[index].sortBy!); - (context as Element).markNeedsBuild(); - }, - isSelected: allSortBy - .map((e) => e.sortBy == _controller.sortBy) - .toList(), - children: allSortBy.map((e) { - return Text( - e.sortName!, - style: const TextStyle( - fontSize: 13, - height: 1, - ), - strutStyle: const StrutStyle( - height: 1, - leading: 0, - fontSize: 13, - ), - textScaler: TextScaler.noScaling, - ); - }).toList(), - ); + return SimpleScaffold( + body: refreshIndicator( + onRefresh: _controller.onRefresh, + child: fabAnimWrapper( + child: CustomScrollView( + controller: _controller.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + Obx( + () => _buildAppBar( + colorScheme, + padding, + _controller.topState.value, + ), + ), + Obx(() { + final allSortBy = _controller.topicSortByConf.value?.allSortBy; + if (allSortBy != null && allSortBy.isNotEmpty) { + return SliverPinnedHeader( + backgroundColor: colorScheme.surface, + child: Padding( + padding: EdgeInsets.only( + left: 12 + padding.left, + top: 6, + bottom: 6, + ), + child: Builder( + builder: (context) { + return ToggleButtons( + fillColor: colorScheme.secondaryContainer, + selectedColor: colorScheme.onSecondaryContainer, + constraints: const BoxConstraints( + minWidth: 54, + minHeight: 24, + ), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + borderRadius: const .all(.circular(25)), + onPressed: (index) { + _controller.onSort(allSortBy[index].sortBy!); + (context as Element).markNeedsBuild(); }, - ), - ), - ); - } - return const SliverToBoxAdapter(); - }), - SliverPadding( - padding: EdgeInsets.only( - left: padding.left, - right: padding.right, - bottom: padding.bottom + 100, - ), - sliver: buildPage( - Obx(() => _buildBody(_controller.loadingState.value)), + isSelected: allSortBy + .map((e) => e.sortBy == _controller.sortBy) + .toList(), + children: allSortBy.map((e) { + return Text( + e.sortName!, + style: const TextStyle( + fontSize: 13, + height: 1, + ), + strutStyle: const StrutStyle( + height: 1, + leading: 0, + fontSize: 13, + ), + textScaler: TextScaler.noScaling, + ); + }).toList(), + ); + }, + ), ), + ); + } + return const SliverToBoxAdapter(); + }), + SliverPadding( + padding: EdgeInsets.only( + left: padding.left, + right: padding.right, + bottom: padding.bottom + 100, + ), + sliver: buildPage( + Obx(() => _buildBody(_controller.loadingState.value)), + ), + ), + ], + ), + ), + ), + fab: SlideTransition( + position: fabAnimation, + child: Padding( + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, + ), + child: FloatingActionButton.extended( + onPressed: () { + if (_controller.isLogin) { + CreateDynPanel.onCreateDyn( + context, + topic: Pair( + first: int.parse(_controller.topicId), + second: _controller.topicName, ), - ], - ), - ), + ); + } else { + SmartDialog.showToast('账号未登录'); + } + }, + icon: const Icon(CustomIcons.topic_tag, size: 20), + label: const Text('参与话题'), ), - Positioned( - right: padding.right + kFloatingActionButtonMargin, - bottom: 0, - child: SlideTransition( - position: fabAnimation, - child: Padding( - padding: .only( - bottom: padding.bottom + kFloatingActionButtonMargin, - ), - child: FloatingActionButton.extended( - onPressed: () { - if (_controller.isLogin) { - CreateDynPanel.onCreateDyn( - context, - topic: Pair( - first: int.parse(_controller.topicId), - second: _controller.topicName, - ), - ); - } else { - SmartDialog.showToast('账号未登录'); - } - }, - icon: const Icon(CustomIcons.topic_tag, size: 20), - label: const Text('参与话题'), - ), - ), - ), - ), - ], + ), ), ); } diff --git a/lib/pages/dynamics_topic_rcmd/view.dart b/lib/pages/dynamics_topic_rcmd/view.dart index f80c09806..88cee7366 100644 --- a/lib/pages/dynamics_topic_rcmd/view.dart +++ b/lib/pages/dynamics_topic_rcmd/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; @@ -21,8 +22,7 @@ class _DynTopicRcmdPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('话题')), body: refreshIndicator( onRefresh: _controller.onRefresh, diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 90288627e..2787f276e 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -51,8 +52,7 @@ class _FavPageState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('我的收藏'), actions: [ diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index c541a4bf6..60a2a4de7 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -4,13 +4,12 @@ import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/fav_order_type.dart'; import 'package:PiliPlus/models_new/fav/fav_detail/media.dart'; import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; -import 'package:PiliPlus/pages/common/fab_mixin.dart' - show NoRightMarginFabLocation; import 'package:PiliPlus/pages/dynamics_repost/view.dart'; import 'package:PiliPlus/pages/fav_detail/controller.dart'; import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart'; @@ -60,12 +59,11 @@ class _FavDetailPageState extends State with GridMixin { _favDetailController.handleSelect(); } }, - child: Scaffold( - resizeToAvoidBottomInset: false, - floatingActionButtonLocation: const NoRightMarginFabLocation(), - floatingActionButton: Padding( - padding: const EdgeInsets.only( - right: kFloatingActionButtonMargin, + child: SimpleScaffold( + fab: Padding( + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, ), child: Obx( () => _favDetailController.folderInfo.value.mediaCount > 0 diff --git a/lib/pages/fav_folder_sort/view.dart b/lib/pages/fav_folder_sort/view.dart index c06bdaa6a..4e9936ef9 100644 --- a/lib/pages/fav_folder_sort/view.dart +++ b/lib/pages/fav_folder_sort/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/reorder_mixin.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; @@ -27,8 +28,7 @@ class _FavFolderSortPageState extends State @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('收藏夹排序'), actions: [ diff --git a/lib/pages/fav_sort/view.dart b/lib/pages/fav_sort/view.dart index 67674afb1..dde9db1de 100644 --- a/lib/pages/fav_sort/view.dart +++ b/lib/pages/fav_sort/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/reorder_mixin.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/fav/fav_detail/media.dart'; @@ -46,8 +47,7 @@ class _FavSortPageState extends State with ReorderMixin { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text('排序: ${_favDetailController.folderInfo.value.title}'), actions: [ diff --git a/lib/pages/follow/child/child_view.dart b/lib/pages/follow/child/child_view.dart index bf4e69946..501ea30c4 100644 --- a/lib/pages/follow/child/child_view.dart +++ b/lib/pages/follow/child/child_view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/button/more_btn.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/follow_order_type.dart'; import 'package:PiliPlus/models_new/follow/list.dart'; @@ -109,36 +110,30 @@ class _FollowChildPageState extends State ); if (widget.onSelect != null || (widget.controller?.isOwner == true && widget.tagid == null)) { - return Stack( - clipBehavior: Clip.none, - children: [ - fabAnimWrapper(child: child), - Positioned( - right: kFloatingActionButtonMargin + padding.right, - bottom: 0, - child: SlideTransition( - position: fabAnimation, - child: Padding( - padding: .only( - bottom: kFloatingActionButtonMargin + padding.bottom, - ), - child: FloatingActionButton.extended( - onPressed: () => _followController - ..setOrderType( - _followController.orderType.value == FollowOrderType.def - ? FollowOrderType.attention - : FollowOrderType.def, - ) - ..onReload(), - icon: const Icon(Icons.format_list_bulleted, size: 20), - label: Obx( - () => Text(_followController.orderType.value.title), - ), - ), + return ScaffoldLayout( + body: fabAnimWrapper(child: child), + fab: SlideTransition( + position: fabAnimation, + child: Padding( + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, + ), + child: FloatingActionButton.extended( + onPressed: () => _followController + ..setOrderType( + _followController.orderType.value == FollowOrderType.def + ? FollowOrderType.attention + : FollowOrderType.def, + ) + ..onReload(), + icon: const Icon(Icons.format_list_bulleted, size: 20), + label: Obx( + () => Text(_followController.orderType.value.title), ), ), ), - ], + ), ); } return child; diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart index c4d235f61..ec15a3d96 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -48,8 +49,7 @@ class _FollowPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: _buildAppBar, body: _followController.isOwner ? Obx(() => _buildBody(_followController.followState.value)) diff --git a/lib/pages/follow_tag_sort/view.dart b/lib/pages/follow_tag_sort/view.dart index 481e073f5..2dd8cf49e 100644 --- a/lib/pages/follow_tag_sort/view.dart +++ b/lib/pages/follow_tag_sort/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/reorder_mixin.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/follow.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/member/tags.dart'; @@ -36,8 +37,7 @@ class _FollowTagSortPageState extends State @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('关注分组排序'), actions: _customTags.isNotEmpty diff --git a/lib/pages/follow_type/view.dart b/lib/pages/follow_type/view.dart index 1fa199ae6..ba7fab19d 100644 --- a/lib/pages/follow_type/view.dart +++ b/lib/pages/follow_type/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/follow/list.dart'; @@ -19,8 +20,7 @@ abstract class FollowTypePageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context).colorScheme; - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: appBar, body: refreshIndicator( onRefresh: controller.onRefresh, diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 2f9d237b0..70c01bded 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarScrollPhysics; import 'package:PiliPlus/http/loading_state.dart'; @@ -92,8 +93,7 @@ class _HistoryPageState extends State currCtr().handleSelect(); } }, - child: Scaffold( - resizeToAvoidBottomInset: false, + child: SimpleScaffold( appBar: MultiSelectAppBarWidget( visible: enableMultiSelect, ctr: currCtr(), diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index 472dad6b0..7456a3b6c 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -85,8 +86,7 @@ class _HotPageState extends State homeController.tabController.animateTo(index); } else { Get.to( - Scaffold( - resizeToAvoidBottomInset: false, + SimpleScaffold( appBar: AppBar(title: const Text('排行榜')), body: const ViewSafeArea(child: RankPage()), ), diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index 44d526393..62418f7e8 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -1,13 +1,12 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarScrollPhysics; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/models/common/later_view_type.dart'; import 'package:PiliPlus/models_new/later/list.dart'; -import 'package:PiliPlus/pages/common/fab_mixin.dart' - show NoRightMarginFabLocation; import 'package:PiliPlus/pages/later/base_controller.dart'; import 'package:PiliPlus/pages/later/controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; @@ -63,6 +62,7 @@ class _LaterPageState extends State @override Widget build(BuildContext context) { + final padding = MediaQuery.viewPaddingOf(context); return Obx( () { final enableMultiSelect = _baseCtr.enableMultiSelect.value; @@ -73,12 +73,13 @@ class _LaterPageState extends State currCtr().handleSelect(); } }, - child: Scaffold( - resizeToAvoidBottomInset: false, + child: SimpleScaffold( appBar: _buildAppbar(enableMultiSelect), - floatingActionButtonLocation: const NoRightMarginFabLocation(), - floatingActionButton: Padding( - padding: const .only(right: kFloatingActionButtonMargin), + fab: Padding( + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, + ), child: Obx( () => currCtr().loadingState.value.isSuccess ? AnimatedSlide( diff --git a/lib/pages/live_area/view.dart b/lib/pages/live_area/view.dart index 404048e1b..e5994d078 100644 --- a/lib/pages/live_area/view.dart +++ b/lib/pages/live_area/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -30,8 +31,7 @@ class _LiveAreaPageState extends State { Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('全部标签'), actions: _controller.isLogin diff --git a/lib/pages/live_area_detail/view.dart b/lib/pages/live_area_detail/view.dart index 56043997e..1e940d7fa 100644 --- a/lib/pages/live_area_detail/view.dart +++ b/lib/pages/live_area_detail/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -43,8 +44,7 @@ class _LiveAreaDetailPageState extends State { Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text(widget.parentName), actions: [ diff --git a/lib/pages/live_dm_block/view.dart b/lib/pages/live_dm_block/view.dart index 49783b376..c4297fc88 100644 --- a/lib/pages/live_dm_block/view.dart +++ b/lib/pages/live_dm_block/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_behavior.dart' show NoOverscrollIndicator; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; @@ -85,75 +86,71 @@ class _LiveDmBlockPageState extends State { ), ); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('弹幕屏蔽')), body: Padding( - padding: EdgeInsets.only(left: padding.left, right: padding.right), - child: Stack( - clipBehavior: Clip.none, - children: [ - isPortrait - ? ExtendedNestedScrollView( - onlyOneScrollInBody: true, - scrollBehavior: const NoOverscrollIndicator(), - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - SliverToBoxAdapter(child: left), - SliverOverlapAbsorber( - handle: - ExtendedNestedScrollView.sliverOverlapAbsorberHandleFor( - context, - ), - sliver: SliverPinnedHeader(child: tabBar), - ), - ]; - }, - body: LayoutBuilder( - builder: (context, _) { - return Padding( - padding: EdgeInsets.only( - top: - ExtendedNestedScrollView.sliverOverlapAbsorberHandleFor( - context, - ).layoutExtent ?? - 0, + padding: .only(left: padding.left, right: padding.right), + child: isPortrait + ? ExtendedNestedScrollView( + onlyOneScrollInBody: true, + scrollBehavior: const NoOverscrollIndicator(), + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverToBoxAdapter(child: left), + SliverOverlapAbsorber( + handle: + ExtendedNestedScrollView.sliverOverlapAbsorberHandleFor( + context, ), - child: view, - ); - }, + sliver: SliverPinnedHeader(child: tabBar), ), - ) - : Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: left), - VerticalDivider( - width: 1, - color: theme.colorScheme.outline.withValues(alpha: 0.1), + ]; + }, + body: LayoutBuilder( + builder: (context, _) { + return Padding( + padding: EdgeInsets.only( + top: + ExtendedNestedScrollView.sliverOverlapAbsorberHandleFor( + context, + ).layoutExtent ?? + 0, ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title, - tabBar, - Expanded(child: view), - ], - ), - ), - ], + child: view, + ); + }, + ), + ) + : Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: left), + VerticalDivider( + width: 1, + color: theme.colorScheme.outline.withValues(alpha: 0.1), ), - Positioned( - right: kFloatingActionButtonMargin, - bottom: kFloatingActionButtonMargin + padding.bottom, - child: FloatingActionButton( - tooltip: '添加', - onPressed: _addShieldKeyword, - child: const Icon(Icons.add), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title, + tabBar, + Expanded(child: view), + ], + ), + ), + ], ), - ), - ], + ), + fab: Padding( + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, + ), + child: FloatingActionButton( + tooltip: '添加', + onPressed: _addShieldKeyword, + child: const Icon(Icons.add), ), ), ); diff --git a/lib/pages/live_follow/view.dart b/lib/pages/live_follow/view.dart index 0ea623ebb..fd16ce467 100644 --- a/lib/pages/live_follow/view.dart +++ b/lib/pages/live_follow/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/video_card_v.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/live/live_follow/item.dart'; import 'package:PiliPlus/pages/live_follow/controller.dart'; @@ -23,8 +24,7 @@ class _LiveFollowPageState extends State { @override Widget build(BuildContext context) { final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Obx( () { diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 6cfe5cf44..2bb16c64d 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -13,6 +13,7 @@ import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recogniz import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarScrollPhysics; import 'package:PiliPlus/models/common/live/live_contribution_rank_type.dart'; @@ -413,10 +414,7 @@ class _LiveRoomPageState extends State ); }, ), - Scaffold( - primary: !plPlayerController.removeSafeArea, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, + ScaffoldLayout( appBar: isWindowMode && isFullScreen && !isPortrait ? null : _buildAppBar(isFullScreen), diff --git a/lib/pages/live_search/view.dart b/lib/pages/live_search/view.dart index cf3229ce5..0d4af6de2 100644 --- a/lib/pages/live_search/view.dart +++ b/lib/pages/live_search/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/pages/live_search/child/view.dart'; @@ -21,8 +22,7 @@ class _LiveSearchPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( actions: [ IconButton( diff --git a/lib/pages/log_table/view.dart b/lib/pages/log_table/view.dart index f1d0658d3..ec405168b 100644 --- a/lib/pages/log_table/view.dart +++ b/lib/pages/log_table/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/log_table/controller.dart'; import 'package:PiliPlus/utils/extension/widget_ext.dart'; @@ -19,8 +20,7 @@ class _LogPageState extends State> { @override Widget build(BuildContext context) { final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: Text(_controller.title)), body: CustomScrollView( slivers: [ diff --git a/lib/pages/login_devices/view.dart b/lib/pages/login_devices/view.dart index 1c130f119..f378595f9 100644 --- a/lib/pages/login_devices/view.dart +++ b/lib/pages/login_devices/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/login_devices/device.dart'; @@ -22,8 +23,7 @@ class LoginDevicesPageState extends State { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('登录设备')), body: refreshIndicator( onRefresh: _controller.onRefresh, diff --git a/lib/pages/main_reply/view.dart b/lib/pages/main_reply/view.dart index 371bfdc44..e6637a601 100644 --- a/lib/pages/main_reply/view.dart +++ b/lib/pages/main_reply/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' @@ -57,8 +58,7 @@ class _MainReplyPageState extends State @override Widget build(BuildContext context) { final colorScheme = ColorScheme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('查看评论')), body: fabAnimWrapper( child: refreshIndicator( @@ -80,11 +80,13 @@ class _MainReplyPageState extends State ), ).constraintWidth(), ), - floatingActionButtonLocation: const NoBottomPaddingFabLocation(), - floatingActionButton: SlideTransition( + fab: SlideTransition( position: fabAnimation, child: Padding( - padding: .only(bottom: padding.bottom + kFloatingActionButtonMargin), + padding: .only( + right: kFloatingActionButtonMargin + padding.right, + bottom: kFloatingActionButtonMargin + padding.bottom, + ), child: FloatingActionButton( heroTag: null, onPressed: () { @@ -211,8 +213,7 @@ class _MainReplyPageState extends State int oid = replyItem.oid.toInt(); int rpid = replyItem.id.toInt(); Get.to( - Scaffold( - resizeToAvoidBottomInset: false, + SimpleScaffold( appBar: AppBar( title: const Text('评论详情'), shape: Border( diff --git a/lib/pages/match_info/view.dart b/lib/pages/match_info/view.dart index c72a1623b..4868f38d0 100644 --- a/lib/pages/match_info/view.dart +++ b/lib/pages/match_info/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; @@ -8,8 +9,6 @@ import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/match/match_info/contest.dart'; import 'package:PiliPlus/models_new/match/match_info/team.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart'; -import 'package:PiliPlus/pages/common/fab_mixin.dart' - show NoBottomPaddingFabLocation; import 'package:PiliPlus/pages/match_info/controller.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; import 'package:PiliPlus/utils/date_utils.dart'; @@ -40,29 +39,28 @@ class _MatchInfoPageState extends CommonDynPageState { @override Widget build(BuildContext context) { - final child = Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar(title: const Text('比赛详情')), - body: ViewSafeArea( - child: refreshIndicator( - onRefresh: controller.onRefresh, - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - Obx(() => _buildInfo(controller.infoState.value)), - buildReplyHeader(), - Obx(() => replyList(controller.loadingState.value)), - ], + return fabAnimWrapper( + child: SimpleScaffold( + appBar: AppBar(title: const Text('比赛详情')), + body: ViewSafeArea( + child: refreshIndicator( + onRefresh: controller.onRefresh, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + Obx(() => _buildInfo(controller.infoState.value)), + buildReplyHeader(), + Obx(() => replyList(controller.loadingState.value)), + ], + ), ), + ).constraintWidth(), + fab: SlideTransition( + position: fabAnimation, + child: fabButton, ), - ).constraintWidth(), - floatingActionButtonLocation: const NoBottomPaddingFabLocation(), - floatingActionButton: SlideTransition( - position: fabAnimation, - child: fabButton, ), ); - return fabAnimWrapper(child: child); } Widget _buildInfo(LoadingState infoState) { @@ -192,8 +190,7 @@ class _MatchInfoPageState extends CommonDynPageState { int oid = replyItem.oid.toInt(); int rpid = replyItem.id.toInt(); Get.to( - Scaffold( - resizeToAvoidBottomInset: false, + SimpleScaffold( appBar: AppBar( title: const Text('评论详情'), shape: Border( diff --git a/lib/pages/member/widget/header_layout_widget.dart b/lib/pages/member/widget/header_layout_widget.dart index 19a922182..6bf7bd062 100644 --- a/lib/pages/member/widget/header_layout_widget.dart +++ b/lib/pages/member/widget/header_layout_widget.dart @@ -32,7 +32,7 @@ const double _kActionsTopPadding = 140.0; const double _kActionsLeftPadding = 160.0; const double _kActionsRightPadding = 15.0; -enum HeaderType { header, avatar, actions } +enum HeaderType { avatar, actions, header } class HeaderLayoutWidget extends SlottedMultiChildRenderObjectWidget { @@ -52,9 +52,9 @@ class HeaderLayoutWidget @override Widget childForSlot(HeaderType slot) => switch (slot) { - .header => header, .avatar => avatar, .actions => actions, + .header => header, }; @override @@ -65,6 +65,10 @@ class HeaderLayoutWidget class RenderHeaderWidget extends RenderBox with SlottedContainerRenderObjectMixin { + RenderBox get header => childForSlot(.header)!; + RenderBox get avatar => childForSlot(.avatar)!; + RenderBox get actions => childForSlot(.actions)!; + Offset _getOffset(RenderBox child) { return (child.parentData as BoxParentData).offset; } @@ -79,16 +83,16 @@ class RenderHeaderWidget extends RenderBox final maxWidth = constraints.maxWidth; _setOffset( - childForSlot(HeaderType.header)!..layout(constraints), + header..layout(constraints), Offset.zero, ); _setOffset( - childForSlot(HeaderType.avatar)!..layout(constraints), + avatar..layout(constraints), const Offset(_kAvatarLeftPadding, _kAvatarTopPadding), ); - final actions = childForSlot(HeaderType.actions)!; + final actions = this.actions; final childSize = (actions..layout( BoxConstraints( @@ -114,16 +118,18 @@ class RenderHeaderWidget extends RenderBox @override void paint(PaintingContext context, Offset offset) { - for (var slot in HeaderType.values) { - final child = childForSlot(slot)!; + void doPaint(RenderBox child) { context.paintChild(child, _getOffset(child) + offset); } + + doPaint(header); + doPaint(avatar); + doPaint(actions); } @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { - for (var slot in HeaderType.values.reversed) { - final child = childForSlot(slot)!; + for (final child in children) { final bool isHit = result.addWithPaintOffset( offset: _getOffset(child), position: position, diff --git a/lib/pages/member_coin_arc/view.dart b/lib/pages/member_coin_arc/view.dart index 43de1ecab..2aee1bdaa 100644 --- a/lib/pages/member_coin_arc/view.dart +++ b/lib/pages/member_coin_arc/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/video_card_v.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/member/coin_like_arc/item.dart'; import 'package:PiliPlus/pages/member_coin_arc/controller.dart'; @@ -42,8 +43,7 @@ class _MemberCoinArcPageState extends State { @override Widget build(BuildContext context) { final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text( '${widget.mid == mid ? '我' : '${widget.name}'}的最近投币', diff --git a/lib/pages/member_dynamics/view.dart b/lib/pages/member_dynamics/view.dart index 801abc637..40b234f11 100644 --- a/lib/pages/member_dynamics/view.dart +++ b/lib/pages/member_dynamics/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart'; @@ -45,8 +46,7 @@ class _MemberDynamicsPageState extends State super.build(context); final padding = MediaQuery.viewPaddingOf(context); return widget.mid == null - ? Scaffold( - resizeToAvoidBottomInset: false, + ? SimpleScaffold( appBar: AppBar(title: const Text('我的动态')), body: Padding( padding: EdgeInsets.only( diff --git a/lib/pages/member_guard/view.dart b/lib/pages/member_guard/view.dart index f3c49db11..3638d81cf 100644 --- a/lib/pages/member_guard/view.dart +++ b/lib/pages/member_guard/view.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/member_guard/guard_top_list.dart'; @@ -53,8 +54,7 @@ class _MemberGuardState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text('$_userName的舰队${_count == null ? '' : '($_count)'}'), ), diff --git a/lib/pages/member_like_arc/view.dart b/lib/pages/member_like_arc/view.dart index 48ae86c2c..5063b2e3a 100644 --- a/lib/pages/member_like_arc/view.dart +++ b/lib/pages/member_like_arc/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/video_card_v.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/member/coin_like_arc/item.dart'; import 'package:PiliPlus/pages/member_coin_arc/widgets/item.dart'; @@ -42,8 +43,7 @@ class _MemberLikeArcPageState extends State { @override Widget build(BuildContext context) { final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text( '${widget.mid == mid ? '我' : '${widget.name}'}的推荐', diff --git a/lib/pages/member_opus/view.dart b/lib/pages/member_opus/view.dart index a5f244e50..73f48e7b4 100644 --- a/lib/pages/member_opus/view.dart +++ b/lib/pages/member_opus/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/space_opus.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/space/space_opus/item.dart'; import 'package:PiliPlus/pages/common/fab_mixin.dart'; @@ -54,44 +55,42 @@ class _MemberOpusState extends State Widget build(BuildContext context) { super.build(context); final bottom = MediaQuery.viewPaddingOf(context).bottom; - return Stack( - clipBehavior: .none, - children: [ - refreshIndicator( - onRefresh: _controller.onRefresh, - child: fabAnimWrapper( - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - top: widget.isSingle ? 12 : 0, - left: Style.safeSpace, - right: Style.safeSpace, - bottom: bottom + 100, - ), - sliver: Obx(() => _buildBody(_controller.loadingState.value)), + return ScaffoldLayout( + body: refreshIndicator( + onRefresh: _controller.onRefresh, + child: fabAnimWrapper( + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: widget.isSingle ? 12 : 0, + left: Style.safeSpace, + right: Style.safeSpace, + bottom: bottom + 100, ), - ], - ), + sliver: Obx(() => _buildBody(_controller.loadingState.value)), + ), + ], ), ), - if (_controller.filter?.isNotEmpty == true) - Positioned( - right: kFloatingActionButtonMargin, - bottom: 0, - child: SlideTransition( + ), + fab: _controller.filter?.isNotEmpty == true + ? SlideTransition( position: fabAnimation, child: Padding( padding: .only( - bottom: bottom + kFloatingActionButtonMargin, + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin + bottom, ), child: FloatingActionButton.extended( onPressed: () => showDialog( context: context, builder: (context) => SimpleDialog( clipBehavior: Clip.hardEdge, - contentPadding: const EdgeInsets.symmetric(vertical: 12), + contentPadding: const EdgeInsets.symmetric( + vertical: 12, + ), children: _controller.filter! .map( (e) => ListTile( @@ -126,9 +125,8 @@ class _MemberOpusState extends State ), ), ), - ), - ), - ], + ) + : null, ); } diff --git a/lib/pages/member_profile/view.dart b/lib/pages/member_profile/view.dart index debf999a1..386f80c5d 100644 --- a/lib/pages/member_profile/view.dart +++ b/lib/pages/member_profile/view.dart @@ -3,6 +3,7 @@ import 'dart:io' show File; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -61,8 +62,7 @@ class _EditProfilePageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('账号资料')), body: _buildBody(theme, _loadingState), ); diff --git a/lib/pages/member_search/view.dart b/lib/pages/member_search/view.dart index b4c5c1c50..a67c1e0ea 100644 --- a/lib/pages/member_search/view.dart +++ b/lib/pages/member_search/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/pages/member_search/child/view.dart'; @@ -21,8 +22,7 @@ class _MemberSearchPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( actions: [ IconButton( diff --git a/lib/pages/member_season_series/view.dart b/lib/pages/member_season_series/view.dart index 9e3439fae..f58207a6f 100644 --- a/lib/pages/member_season_series/view.dart +++ b/lib/pages/member_season_series/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/member/contribute_type.dart'; @@ -78,8 +79,7 @@ class _SeasonSeriesPageState extends State ? item.meta!.seasonId : item.meta!.seriesId; Get.to( - Scaffold( - resizeToAvoidBottomInset: false, + SimpleScaffold( appBar: AppBar(title: Text(item.meta!.name!)), body: ViewSafeArea( child: MemberVideo( diff --git a/lib/pages/member_upower_rank/view.dart b/lib/pages/member_upower_rank/view.dart index 17d679961..09ce3b3fd 100644 --- a/lib/pages/member_upower_rank/view.dart +++ b/lib/pages/member_upower_rank/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -86,8 +87,7 @@ class _UpowerRankPageState extends State ), ); if (widget.privilegeType == null) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text('$_name的充电排行榜${_count == null ? '' : '($_count)'}'), actions: [ diff --git a/lib/pages/member_video/view.dart b/lib/pages/member_video/view.dart index 66e4bb82a..fa9d5f0ff 100644 --- a/lib/pages/member_video/view.dart +++ b/lib/pages/member_video/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show ReloadScrollPhysics; import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart'; @@ -117,50 +118,44 @@ class _MemberVideoState extends State ), ); if (_controller.isVideo && _controller.fromViewAid?.isNotEmpty == true) { - return Stack( - clipBehavior: Clip.none, - children: [ - fabAnimWrapper(child: child), - Obx( - () => !_controller.isLocating.value - ? Positioned( - right: kFloatingActionButtonMargin, - bottom: 0, - child: SlideTransition( - position: fabAnimation, - child: Padding( - padding: .only( - bottom: padding.bottom + kFloatingActionButtonMargin, - ), - child: FloatingActionButton.extended( - onPressed: () { - final fromViewAid = _controller.fromViewAid; - _controller.isLocating.value = true; - final locatedIndex = - _controller.loadingState.value.dataOrNull - ?.indexWhere( - (i) => i.param == fromViewAid, - ) ?? - -1; - if (locatedIndex == -1) { - _controller - ..lastAid = fromViewAid - ..reload = true - ..page = 0 - ..loadingState.value = LoadingState.loading() - ..queryData(); - } else { - _jumpToIndex(locatedIndex); - } - }, - label: const Text('定位至上次观看'), - ), - ), + return ScaffoldLayout( + body: fabAnimWrapper(child: child), + fab: Obx( + () => !_controller.isLocating.value + ? SlideTransition( + position: fabAnimation, + child: Padding( + padding: .only( + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin + padding.bottom, ), - ) - : const SizedBox.shrink(), - ), - ], + child: FloatingActionButton.extended( + onPressed: () { + final fromViewAid = _controller.fromViewAid; + _controller.isLocating.value = true; + final locatedIndex = + _controller.loadingState.value.dataOrNull + ?.indexWhere( + (i) => i.param == fromViewAid, + ) ?? + -1; + if (locatedIndex == -1) { + _controller + ..lastAid = fromViewAid + ..reload = true + ..page = 0 + ..loadingState.value = LoadingState.loading() + ..queryData(); + } else { + _jumpToIndex(locatedIndex); + } + }, + label: const Text('定位至上次观看'), + ), + ), + ) + : const SizedBox.shrink(), + ), ); } return child; diff --git a/lib/pages/member_video_web/base/view.dart b/lib/pages/member_video_web/base/view.dart index 2025aa56b..d36b0b97f 100644 --- a/lib/pages/member_video_web/base/view.dart +++ b/lib/pages/member_video_web/base/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show ReloadScrollPhysics; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; @@ -40,8 +41,7 @@ abstract class BaseVideoWebState< @override Widget build(BuildContext context) { final colorScheme = ColorScheme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text(name), actions: [ diff --git a/lib/pages/msg_feed_top/at_me/view.dart b/lib/pages/msg_feed_top/at_me/view.dart index 554888ee6..94da53f3e 100644 --- a/lib/pages/msg_feed_top/at_me/view.dart +++ b/lib/pages/msg_feed_top/at_me/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart' show IMSettingType; import 'package:PiliPlus/http/loading_state.dart'; @@ -30,8 +31,7 @@ class _AtMePageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('@我的'), actions: [ diff --git a/lib/pages/msg_feed_top/like_detail/view.dart b/lib/pages/msg_feed_top/like_detail/view.dart index c4f55fc2a..e793a96d7 100644 --- a/lib/pages/msg_feed_top/like_detail/view.dart +++ b/lib/pages/msg_feed_top/like_detail/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/msg/msg_like_detail/card.dart'; @@ -29,8 +30,7 @@ class _LikeDetailPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('点赞详情')), body: refreshIndicator( onRefresh: _controller.onRefresh, diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart index 92ec062f4..c4d7e9862 100644 --- a/lib/pages/msg_feed_top/like_me/view.dart +++ b/lib/pages/msg_feed_top/like_me/view.dart @@ -6,6 +6,7 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart' show IMSettingType; import 'package:PiliPlus/http/loading_state.dart'; @@ -32,8 +33,7 @@ class _LikeMePageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('收到的赞'), actions: [ diff --git a/lib/pages/msg_feed_top/reply_me/view.dart b/lib/pages/msg_feed_top/reply_me/view.dart index ed941d6db..7572407e3 100644 --- a/lib/pages/msg_feed_top/reply_me/view.dart +++ b/lib/pages/msg_feed_top/reply_me/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart' show IMSettingType; import 'package:PiliPlus/http/loading_state.dart'; @@ -30,8 +31,7 @@ class _ReplyMePageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('回复我的'), actions: [ diff --git a/lib/pages/msg_feed_top/sys_msg/view.dart b/lib/pages/msg_feed_top/sys_msg/view.dart index e33e34a42..d58d9fa04 100644 --- a/lib/pages/msg_feed_top/sys_msg/view.dart +++ b/lib/pages/msg_feed_top/sys_msg/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/msg/msg_sys/data.dart'; import 'package:PiliPlus/pages/msg_feed_top/sys_msg/controller.dart'; @@ -32,8 +33,7 @@ class _SysMsgPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('系统通知')), body: refreshIndicator( onRefresh: _sysMsgController.onRefresh, diff --git a/lib/pages/music/view.dart b/lib/pages/music/view.dart index b3a754f1f..145633091 100644 --- a/lib/pages/music/view.dart +++ b/lib/pages/music/view.dart @@ -7,6 +7,8 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; import 'package:PiliPlus/common/widgets/marquee.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_to_box_adapter.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -54,20 +56,20 @@ class _MusicDetailPageState extends CommonDynPageState { @override Widget build(BuildContext context) { - final child = Scaffold( - resizeToAvoidBottomInset: false, - appBar: _buildAppBar(), - body: Padding( - padding: EdgeInsets.only(left: padding.left, right: padding.right), - child: isPortrait - ? refreshIndicator( - onRefresh: controller.onRefresh, - child: _buildBody(), - ) - : _buildBody(), + return fabAnimWrapper( + child: SimpleScaffold( + appBar: _buildAppBar(), + body: Padding( + padding: EdgeInsets.only(left: padding.left, right: padding.right), + child: isPortrait + ? refreshIndicator( + onRefresh: controller.onRefresh, + child: _buildBody(), + ) + : _buildBody(), + ), ), ); - return fabAnimWrapper(child: child); } PreferredSizeWidget _buildAppBar() => AppBar( @@ -171,9 +173,7 @@ class _MusicDetailPageState extends CommonDynPageState { flex: flex1, child: Padding( padding: EdgeInsets.only(right: padding), - child: Scaffold( - backgroundColor: Colors.transparent, - resizeToAvoidBottomInset: false, + child: MiniScaffold( body: refreshIndicator( onRefresh: controller.onRefresh, child: CustomScrollView( @@ -192,12 +192,9 @@ class _MusicDetailPageState extends CommonDynPageState { ], ); } - return Stack( - clipBehavior: Clip.none, - children: [ - child, - _buildBottom(response), - ], + return ScaffoldLayout( + body: child, + fab: _buildBottom(response), ); default: return const SizedBox.shrink(); @@ -206,9 +203,8 @@ class _MusicDetailPageState extends CommonDynPageState { Widget _buildBottom(MusicDetail item) { if (!controller.showDynActionBar) { - return Positioned( - right: kFloatingActionButtonMargin, - bottom: 0, + return Padding( + padding: const .only(right: kFloatingActionButtonMargin), child: SlideTransition( position: fabAnimation, child: fabButton, @@ -248,110 +244,104 @@ class _MusicDetailPageState extends CommonDynPageState { ); } - return Positioned( - left: 0, - right: 0, - bottom: 0, - child: SlideTransition( - position: fabAnimation, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only( - right: kFloatingActionButtonMargin, - bottom: kFloatingActionButtonMargin, - ), - child: replyButton, + return SlideTransition( + position: fabAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only( + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin, ), - Container( - decoration: BoxDecoration( - color: theme.colorScheme.surface, - border: Border( - top: BorderSide( - color: theme.colorScheme.outline.withValues( - alpha: 0.08, - ), + child: replyButton, + ), + Container( + decoration: BoxDecoration( + color: theme.colorScheme.surface, + border: Border( + top: BorderSide( + color: theme.colorScheme.outline.withValues( + alpha: 0.08, ), ), ), - padding: EdgeInsets.only(bottom: padding.bottom), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - // TODO - // Expanded( - // child: textIconButton( - // icon: FontAwesomeIcons.shareFromSquare, - // text: '转发', - // count: item.musicShares, - // onPressed: () { - // final data = controller.infoState.value.dataOrNull; - // if (data != null) { - // showModalBottomSheet( - // context: context, - // isScrollControlled: true, - // useSafeArea: true, - // builder: (context) => RepostPanel( - // rid: controller.oid, - // dynType: null, - // pic: data.mvCover, - // title: data.musicTitle, - // ), - // ); - // } - // }, - // ), - // ), - Expanded( - child: textIconButton( - icon: CustomIcons.share_node, - text: '分享', - onPressed: () => - ShareUtils.shareText(controller.shareUrl), - ), - ), - Expanded( - child: Builder( - builder: (context) => textIconButton( - icon: FontAwesomeIcons.thumbsUp, - activatedIcon: FontAwesomeIcons.solidThumbsUp, - text: '点赞', - count: item.wishCount, - status: item.wishListen ?? false, - onPressed: () async { - if (!Accounts.main.isLogin) { - SmartDialog.showToast('请先登录'); - return; - } - final hasLike = item.wishListen ?? false; - final res = await MusicHttp.wishUpdate( - controller.musicId, - hasLike, - ); - if (res.isSuccess) { - if (hasLike) { - item.wishCount--; - } else { - item.wishCount++; - } - item.wishListen = !hasLike; - if (context.mounted) { - (context as Element).markNeedsBuild(); - } - } else { - res.toast(); - } - }, - ), - ), - ), - ], - ), ), - ], - ), + padding: EdgeInsets.only(bottom: padding.bottom), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + // TODO + // Expanded( + // child: textIconButton( + // icon: FontAwesomeIcons.shareFromSquare, + // text: '转发', + // count: item.musicShares, + // onPressed: () { + // final data = controller.infoState.value.dataOrNull; + // if (data != null) { + // showModalBottomSheet( + // context: context, + // isScrollControlled: true, + // useSafeArea: true, + // builder: (context) => RepostPanel( + // rid: controller.oid, + // dynType: null, + // pic: data.mvCover, + // title: data.musicTitle, + // ), + // ); + // } + // }, + // ), + // ), + Expanded( + child: textIconButton( + icon: CustomIcons.share_node, + text: '分享', + onPressed: () => ShareUtils.shareText(controller.shareUrl), + ), + ), + Expanded( + child: Builder( + builder: (context) => textIconButton( + icon: FontAwesomeIcons.thumbsUp, + activatedIcon: FontAwesomeIcons.solidThumbsUp, + text: '点赞', + count: item.wishCount, + status: item.wishListen ?? false, + onPressed: () async { + if (!Accounts.main.isLogin) { + SmartDialog.showToast('请先登录'); + return; + } + final hasLike = item.wishListen ?? false; + final res = await MusicHttp.wishUpdate( + controller.musicId, + hasLike, + ); + if (res.isSuccess) { + if (hasLike) { + item.wishCount--; + } else { + item.wishCount++; + } + item.wishListen = !hasLike; + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + } else { + res.toast(); + } + }, + ), + ), + ), + ], + ), + ), + ], ), ); } diff --git a/lib/pages/my_reply/view.dart b/lib/pages/my_reply/view.dart index 1136d8517..b569156a0 100644 --- a/lib/pages/my_reply/view.dart +++ b/lib/pages/my_reply/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/dialog/export_import.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; @@ -42,8 +43,7 @@ class _MyReplyState extends State with DynMixin { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('我的评论'), actions: [ diff --git a/lib/pages/pgc/view.dart b/lib/pages/pgc/view.dart index bbf19be81..dd3a2f669 100644 --- a/lib/pages/pgc/view.dart +++ b/lib/pages/pgc/view.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/button/more_btn.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -248,8 +249,7 @@ class _PgcPageState extends State with AutomaticKeepAliveClientMixin { ]; List types = const [102, 2, 5, 3, 7]; Get.to( - Scaffold( - resizeToAvoidBottomInset: false, + SimpleScaffold( appBar: AppBar(title: const Text('索引')), body: DefaultTabController( length: types.length, diff --git a/lib/pages/pgc_index/view.dart b/lib/pages/pgc_index/view.dart index 18c54435e..31a473066 100644 --- a/lib/pages/pgc_index/view.dart +++ b/lib/pages/pgc_index/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/pgc/pgc_index_condition/data.dart'; @@ -44,8 +45,7 @@ class _PgcIndexPageState extends State super.build(context); final theme = Theme.of(context); return widget.indexType == null - ? Scaffold( - resizeToAvoidBottomInset: false, + ? SimpleScaffold( appBar: AppBar(title: const Text('索引')), body: Obx(() => _buildBody(theme, _ctr.conditionState.value)), ) diff --git a/lib/pages/pgc_review/view.dart b/lib/pages/pgc_review/view.dart index 62fd58ef4..707664adf 100644 --- a/lib/pages/pgc_review/view.dart +++ b/lib/pages/pgc_review/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/models/common/pgc_review_type.dart'; import 'package:PiliPlus/pages/pgc_review/child/controller.dart'; import 'package:PiliPlus/pages/pgc_review/child/view.dart'; @@ -45,117 +46,114 @@ class _PgcReviewPageState extends State Widget build(BuildContext context) { super.build(context); final theme = Theme.of(context); - return Stack( - clipBehavior: Clip.none, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TabBar( + return SimpleScaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TabBar( + controller: _tabController, + isScrollable: true, + tabAlignment: TabAlignment.start, + dividerHeight: 0, + indicatorWeight: 0, + overlayColor: const WidgetStatePropertyAll(Colors.transparent), + splashFactory: NoSplash.splashFactory, + padding: const EdgeInsets.only(left: 6), + indicatorPadding: const EdgeInsets.symmetric( + horizontal: 3, + vertical: 8, + ), + indicator: BoxDecoration( + color: theme.colorScheme.secondaryContainer, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: theme.colorScheme.onSecondaryContainer, + unselectedLabelColor: theme.colorScheme.outline, + labelStyle: + TabBarTheme.of( + context, + ).labelStyle?.copyWith(fontSize: 13) ?? + const TextStyle(fontSize: 13), + dividerColor: Colors.transparent, + tabs: PgcReviewType.values.map((e) => Tab(text: e.label)).toList(), + onTap: (index) { + try { + if (!_tabController.indexIsChanging) { + final item = PgcReviewType.values[index]; + Get.find( + tag: '${widget.mediaId}${item.name}', + ).scrollController.animToTop(); + } + } catch (_) {} + }, + ), + Expanded( + child: TabBarView( controller: _tabController, - isScrollable: true, - tabAlignment: TabAlignment.start, - dividerHeight: 0, - indicatorWeight: 0, - overlayColor: const WidgetStatePropertyAll(Colors.transparent), - splashFactory: NoSplash.splashFactory, - padding: const EdgeInsets.only(left: 6), - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 3, - vertical: 8, - ), - indicator: BoxDecoration( - color: theme.colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: theme.colorScheme.onSecondaryContainer, - unselectedLabelColor: theme.colorScheme.outline, - labelStyle: - TabBarTheme.of( - context, - ).labelStyle?.copyWith(fontSize: 13) ?? - const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - tabs: PgcReviewType.values - .map((e) => Tab(text: e.label)) + physics: const NeverScrollableScrollPhysics(), + children: PgcReviewType.values + .map( + (e) => PgcReviewChildPage( + type: e, + name: widget.name, + mediaId: widget.mediaId, + ), + ) .toList(), - onTap: (index) { - try { - if (!_tabController.indexIsChanging) { - final item = PgcReviewType.values[index]; - Get.find( - tag: '${widget.mediaId}${item.name}', - ).scrollController.animToTop(); - } - } catch (_) {} - }, ), - Expanded( - child: TabBarView( - controller: _tabController, - physics: const NeverScrollableScrollPhysics(), - children: PgcReviewType.values - .map( - (e) => PgcReviewChildPage( - type: e, - name: widget.name, - mediaId: widget.mediaId, - ), - ) - .toList(), - ), - ), - ], - ), - Positioned( + ), + ], + ), + fab: Padding( + padding: .only( right: kFloatingActionButtonMargin, bottom: MediaQuery.viewPaddingOf(context).bottom + kFloatingActionButtonMargin, - child: FloatingActionButton( - onPressed: () => showDialog( - context: context, - builder: (context) => SimpleDialog( - clipBehavior: Clip.hardEdge, - contentPadding: const EdgeInsets.symmetric(vertical: 12), - children: [ - DialogOption( - child: const Text('写短评', style: TextStyle(fontSize: 14)), - onPressed: () { - Get.back(); - showModalBottomSheet( - context: context, - useSafeArea: true, - isScrollControlled: true, - builder: (context) { - return PgcReviewPostPanel( - name: widget.name, - mediaId: widget.mediaId, - ); - }, - ); - }, - ), - DialogOption( - child: const Text('写长评', style: TextStyle(fontSize: 14)), - onPressed: () => Get - ..back() - ..toNamed( - '/webview', - parameters: { - 'url': - 'https://member.bilibili.com/article-text/mobile?theme=${theme.isDark ? 1 : 0}&media_id=${widget.mediaId}', - }, - ), - ), - ], - ), - ), - child: const Icon(Icons.edit), - ), ), - ], + child: FloatingActionButton( + onPressed: () => showDialog( + context: context, + builder: (context) => SimpleDialog( + clipBehavior: Clip.hardEdge, + contentPadding: const EdgeInsets.symmetric(vertical: 12), + children: [ + DialogOption( + child: const Text('写短评', style: TextStyle(fontSize: 14)), + onPressed: () { + Get.back(); + showModalBottomSheet( + context: context, + useSafeArea: true, + isScrollControlled: true, + builder: (context) { + return PgcReviewPostPanel( + name: widget.name, + mediaId: widget.mediaId, + ); + }, + ); + }, + ), + DialogOption( + child: const Text('写长评', style: TextStyle(fontSize: 14)), + onPressed: () => Get + ..back() + ..toNamed( + '/webview', + parameters: { + 'url': + 'https://member.bilibili.com/article-text/mobile?theme=${theme.isDark ? 1 : 0}&media_id=${widget.mediaId}', + }, + ), + ), + ], + ), + ), + child: const Icon(Icons.edit), + ), + ), ); } diff --git a/lib/pages/popular_precious/view.dart b/lib/pages/popular_precious/view.dart index 9ae434881..94b4dc305 100644 --- a/lib/pages/popular_precious/view.dart +++ b/lib/pages/popular_precious/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -24,8 +25,7 @@ class _PopularPreciousPageState extends State @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('入站必刷')), body: refreshIndicator( onRefresh: _controller.onRefresh, diff --git a/lib/pages/popular_series/view.dart b/lib/pages/popular_series/view.dart index da16edfd1..69aea1b8c 100644 --- a/lib/pages/popular_series/view.dart +++ b/lib/pages/popular_series/view.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show ReloadScrollPhysics; import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart'; @@ -29,8 +30,7 @@ class _PopularSeriesPageState extends State with GridMixin { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Obx(() { final config = _controller.config.value; diff --git a/lib/pages/search_result/view.dart b/lib/pages/search_result/view.dart index 300bb4cdb..e2c42d6d5 100644 --- a/lib/pages/search_result/view.dart +++ b/lib/pages/search_result/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/models/common/search/search_type.dart'; @@ -65,8 +66,7 @@ class _SearchResultPageState extends State @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( shape: Border( bottom: BorderSide( diff --git a/lib/pages/search_trending/view.dart b/lib/pages/search_trending/view.dart index cc96759d8..8e8bf1167 100644 --- a/lib/pages/search_trending/view.dart +++ b/lib/pages/search_trending/view.dart @@ -41,79 +41,86 @@ class _SearchTrendingPageState extends State { final maxWidth = size.width - padding.horizontal; final width = size.isPortrait ? maxWidth : min(640.0, maxWidth * 0.6); final height = width * 528 / 1125; - _offset = height - kToolbarHeight - padding.top; - return Scaffold( - extendBody: true, - extendBodyBehindAppBar: true, - resizeToAvoidBottomInset: false, - appBar: PreferredSize( - preferredSize: const Size.fromHeight(kToolbarHeight), - child: Obx( - () { - final scrollRatio = _scrollRatio.value; - final flag = maxWidth > width || scrollRatio >= 0.5; - return AppBar( - title: Opacity( - opacity: scrollRatio, - child: Text( - 'bilibili热搜', - style: TextStyle(color: flag ? null : Colors.white), - ), - ), - backgroundColor: theme.colorScheme.surface.withValues( - alpha: scrollRatio, - ), - foregroundColor: flag ? null : Colors.white, - systemOverlayStyle: flag - ? null - : const SystemUiOverlayStyle( - statusBarBrightness: .dark, - statusBarIconBrightness: .light, - ), - shape: scrollRatio == 1 - ? Border( - bottom: BorderSide( - color: theme.colorScheme.outline.withValues(alpha: 0.1), + _offset = height - 56 - padding.top; + return Material( + child: Stack( + children: [ + Padding( + padding: .only(left: padding.left, right: padding.right), + child: Center( + child: SizedBox( + width: width, + child: refreshIndicator( + onRefresh: _controller.onRefresh, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + TrendingHeader( + offset: _offset, + onScrollRatioChanged: _scrollRatio.call, + child: Image.asset( + width: width, + height: height, + cacheWidth: width.cacheSize(context), + Assets.trendingBanner, + filterQuality: .low, + ), ), - ) - : null, - ); - }, - ), - ), - body: Padding( - padding: .only(left: padding.left, right: padding.right), - child: Center( - child: SizedBox( - width: width, - child: refreshIndicator( - onRefresh: _controller.onRefresh, - child: CustomScrollView( - controller: _controller.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - TrendingHeader( - offset: _offset, - onScrollRatioChanged: _scrollRatio.call, - child: Image.asset( - width: width, - height: height, - cacheWidth: width.cacheSize(context), - Assets.trendingBanner, - filterQuality: FilterQuality.low, - ), + SliverPadding( + padding: .only(bottom: padding.bottom + 100), + sliver: Obx( + () => + _buildBody(theme, _controller.loadingState.value), + ), + ), + ], ), - SliverPadding( - padding: EdgeInsets.only(bottom: padding.bottom + 100), - sliver: Obx( - () => _buildBody(theme, _controller.loadingState.value), - ), - ), - ], + ), ), ), ), - ), + Positioned( + left: 0, + top: 0, + right: 0, + child: Obx( + () { + final scrollRatio = _scrollRatio.value; + final flag = maxWidth > width || scrollRatio >= 0.5; + return AppBar( + title: Opacity( + opacity: scrollRatio, + child: Text( + 'bilibili热搜', + style: TextStyle( + color: flag ? null : Colors.white, + ), + ), + ), + backgroundColor: theme.colorScheme.surface.withValues( + alpha: scrollRatio, + ), + foregroundColor: flag ? null : Colors.white, + systemOverlayStyle: flag + ? null + : const SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + statusBarIconBrightness: Brightness.light, + ), + shape: scrollRatio == 1 + ? Border( + bottom: BorderSide( + color: theme.colorScheme.outline.withValues( + alpha: 0.1, + ), + ), + ) + : null, + ); + }, + ), + ), + ], ), ); } @@ -152,7 +159,7 @@ class _SearchTrendingPageState extends State { : Text( '${index + 1 - _controller.topCount}', style: TextStyle( - fontWeight: FontWeight.bold, + fontWeight: .bold, color: ColourUtils.index2Color( index - _controller.topCount, theme.colorScheme.outline, @@ -167,7 +174,7 @@ class _SearchTrendingPageState extends State { child: Text( item.keyword!, maxLines: 1, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, strutStyle: const StrutStyle(height: 1, leading: 0), style: const TextStyle(height: 1, fontSize: 15), ), diff --git a/lib/pages/setting/common_setting.dart b/lib/pages/setting/common_setting.dart index abb605284..8ff30b04c 100644 --- a/lib/pages/setting/common_setting.dart +++ b/lib/pages/setting/common_setting.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/models/common/setting_type.dart'; import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:flutter/material.dart'; @@ -47,8 +48,7 @@ class _CommonSettingState extends State { @override Widget build(BuildContext context) { final showAppBar = widget.showAppBar; - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: showAppBar ? AppBar(title: Text(widget.settingType.title)) : null, body: ListView.builder( key: ValueKey(widget.settingType), diff --git a/lib/pages/setting/pages/bar_set.dart b/lib/pages/setting/pages/bar_set.dart index 422fdfb4f..92647343c 100644 --- a/lib/pages/setting/pages/bar_set.dart +++ b/lib/pages/setting/pages/bar_set.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/common/widgets/reorder_mixin.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/models/common/enum_with_label.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; @@ -67,8 +68,7 @@ class _BarSetPageState extends State with ReorderMixin { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text('$title编辑'), actions: [ diff --git a/lib/pages/setting/pages/color_select.dart b/lib/pages/setting/pages/color_select.dart index d8e322dfc..7eea81e2d 100644 --- a/lib/pages/setting/pages/color_select.dart +++ b/lib/pages/setting/pages/color_select.dart @@ -2,6 +2,7 @@ import 'dart:io' show Platform; import 'package:PiliPlus/common/widgets/animated_height.dart'; import 'package:PiliPlus/common/widgets/color_palette.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/main.dart' show MyApp; import 'package:PiliPlus/models/common/nav_bar_config.dart'; import 'package:PiliPlus/models/common/theme/theme_color_type.dart'; @@ -67,8 +68,7 @@ class _ColorSelectPageState extends State { final padding = MediaQuery.viewPaddingOf( context, ).copyWith(top: 0, bottom: 0); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('选择应用主题')), body: ListView( children: [ diff --git a/lib/pages/setting/pages/display_mode.dart b/lib/pages/setting/pages/display_mode.dart index d5d3c0905..8d98947a3 100644 --- a/lib/pages/setting/pages/display_mode.dart +++ b/lib/pages/setting/pages/display_mode.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:collection/collection.dart'; @@ -59,8 +60,7 @@ class _SetDisplayModeState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('屏幕帧率设置')), body: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/pages/setting/pages/font_size_select.dart b/lib/pages/setting/pages/font_size_select.dart index f71d5260b..0734723f9 100644 --- a/lib/pages/setting/pages/font_size_select.dart +++ b/lib/pages/setting/pages/font_size_select.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; @@ -28,8 +29,7 @@ class _FontSizeSelectPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( actions: [ TextButton( diff --git a/lib/pages/setting/pages/fullscreen_sc_size.dart b/lib/pages/setting/pages/fullscreen_sc_size.dart index 220da117e..78c7e07ec 100644 --- a/lib/pages/setting/pages/fullscreen_sc_size.dart +++ b/lib/pages/setting/pages/fullscreen_sc_size.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/extra_hittest_stack.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart'; import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart'; @@ -69,8 +70,7 @@ class _FullScreenScSizeState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('全屏 SC 大小设置'), actions: [ diff --git a/lib/pages/setting/pages/logs.dart b/lib/pages/setting/pages/logs.dart index 1ee3a591e..c80182852 100644 --- a/lib/pages/setting/pages/logs.dart +++ b/lib/pages/setting/pages/logs.dart @@ -4,6 +4,7 @@ import 'dart:convert' show jsonDecode; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/services/logger.dart'; import 'package:PiliPlus/utils/date_utils.dart'; @@ -107,8 +108,7 @@ class _LogsPageState extends State { @override Widget build(BuildContext context) { final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('日志'), actions: [ diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index 16f300059..395d8ff23 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/pages/setting/widgets/switch_item.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; @@ -181,8 +182,7 @@ class _PlaySpeedPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('倍速设置'), actions: [ diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index 633dd7317..fa31140e2 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/login.dart'; import 'package:PiliPlus/models/common/setting_type.dart'; @@ -91,8 +92,7 @@ class _SettingPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: _isPortrait ? const Text('设置') : Text(_type.title), ), diff --git a/lib/pages/space_setting/view.dart b/lib/pages/space_setting/view.dart index 4cfb6555d..e3d77a693 100644 --- a/lib/pages/space_setting/view.dart +++ b/lib/pages/space_setting/view.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/space_setting/privacy.dart'; import 'package:PiliPlus/pages/space_setting/controller.dart'; @@ -20,8 +21,7 @@ class _SpaceSettingPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('空间设置')), body: Obx(() => _buildBody(theme, _controller.loadingState.value)), ); diff --git a/lib/pages/sponsor_block/view.dart b/lib/pages/sponsor_block/view.dart index b053bbb72..0fa3fdfbf 100644 --- a/lib/pages/sponsor_block/view.dart +++ b/lib/pages/sponsor_block/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/pair.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -479,8 +480,7 @@ class _SponsorBlockPageState extends State { ), ); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('空降助手')), body: CustomScrollView( slivers: [ diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 6d9ef4d63..39ac4c867 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/sub/sub/list.dart'; @@ -21,8 +22,7 @@ class _SubPageState extends State with GridMixin { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('我的订阅')), body: refreshIndicator( onRefresh: _subController.onRefresh, diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index dbaa832a1..fb59cccb3 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pbenum.dart' show PlaylistSource; import 'package:PiliPlus/grpc/dm.dart'; @@ -119,7 +120,7 @@ class VideoDetailController extends GetxController final RxBool _autoPlay = Pref.autoPlayEnable.obs; final videoPlayerKey = GlobalKey(); - final childKey = GlobalKey(); + final childKey = GlobalKey(); final plPlayerController = PlPlayerController.getInstance() ..brightness.value = -1; @@ -503,7 +504,6 @@ class VideoDetailController extends GetxController ); } else { childKey.currentState?.showBottomSheet( - backgroundColor: Colors.transparent, constraints: const BoxConstraints(), (context) => panel(), ); @@ -1008,7 +1008,6 @@ class VideoDetailController extends GetxController ); } else { childKey.currentState?.showBottomSheet( - backgroundColor: Colors.transparent, constraints: const BoxConstraints(), (context) => PostPanel( videoDetailController: this, @@ -1345,7 +1344,6 @@ class VideoDetailController extends GetxController ); } else { childKey.currentState?.showBottomSheet( - backgroundColor: Colors.transparent, constraints: const BoxConstraints(), (context) => NoteListPage( oid: aid, diff --git a/lib/pages/video/note/view.dart b/lib/pages/video/note/view.dart index e83874b4f..fd0b38178 100644 --- a/lib/pages/video/note/view.dart +++ b/lib/pages/video/note/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/video/video_note_list/list.dart'; @@ -55,8 +56,7 @@ class _NoteListPageState extends State @override Widget buildPage(ThemeData theme) { - return Scaffold( - resizeToAvoidBottomInset: false, + return MiniScaffold( body: Column( children: [ SizedBox( @@ -153,7 +153,7 @@ class _NoteListPageState extends State SmartDialog.showToast('账号未登录'); return; } - Scaffold.of(context).showBottomSheet( + MiniScaffold.of(context).showBottomSheet( constraints: const BoxConstraints(), (context) => WebviewPage( oid: widget.oid, diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index ae6e1e287..a19bc69a0 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/sponsor_block.dart'; import 'package:PiliPlus/models/common/sponsor_block/action_type.dart'; @@ -186,10 +187,11 @@ class _PostPanelState extends State double currentPos() => plPlayerController.positionInMilliseconds / 1000; + late double bottom; + @override Widget buildPage(ThemeData theme) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( primary: false, toolbarHeight: 45, @@ -230,6 +232,37 @@ class _PostPanelState extends State ], ), body: enableSlide ? slideList(theme) : buildList(theme), + fab: list.isEmpty + ? null + : Padding( + padding: .only( + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin + bottom, + ), + child: FloatingActionButton( + tooltip: '提交', + onPressed: () => showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('确定无误再提交'), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle(color: theme.colorScheme.outline), + ), + ), + TextButton( + onPressed: _onPost, + child: const Text('确定提交'), + ), + ], + ), + ), + child: const Icon(Icons.check), + ), + ), ); } @@ -240,6 +273,7 @@ class _PostPanelState extends State super.didChangeDependencies(); final controller = PrimaryScrollController.of(context); _key = ValueKey(controller.hashCode); + bottom = MediaQuery.viewPaddingOf(context).bottom; } @override @@ -247,47 +281,14 @@ class _PostPanelState extends State if (list.isEmpty) { return scrollableError; } - final bottom = MediaQuery.viewPaddingOf(context).bottom; - return Stack( - clipBehavior: Clip.none, - children: [ - ListView.builder( - key: _key, - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.only(bottom: 88 + bottom), - itemCount: list.length, - itemBuilder: (context, index) { - return _buildItem(theme, index, list[index]); - }, - ), - Positioned( - right: kFloatingActionButtonMargin, - bottom: kFloatingActionButtonMargin + bottom, - child: FloatingActionButton( - tooltip: '提交', - onPressed: () => showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('确定无误再提交'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle(color: theme.colorScheme.outline), - ), - ), - TextButton( - onPressed: _onPost, - child: const Text('确定提交'), - ), - ], - ), - ), - child: const Icon(Icons.check), - ), - ), - ], + return ListView.builder( + key: _key, + physics: const AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only(bottom: 88 + bottom), + itemCount: list.length, + itemBuilder: (context, index) { + return _buildItem(theme, index, list[index]); + }, ); } diff --git a/lib/pages/video/reply/view.dart b/lib/pages/video/reply/view.dart index 1885c7a83..e1888df13 100644 --- a/lib/pages/video/reply/view.dart +++ b/lib/pages/video/reply/view.dart @@ -2,6 +2,8 @@ import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; @@ -71,80 +73,73 @@ class _VideoReplyPanelState extends State child: refreshIndicator( onRefresh: _videoReplyController.onRefresh, isClampingScrollPhysics: widget.isNested, - child: Stack( - clipBehavior: .none, - children: [ - CustomScrollView( - controller: widget.isNested - ? null - : _videoReplyController.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - key: const PageStorageKey(_VideoReplyPanelState), - slivers: [ - SliverFloatingHeaderWidget( - backgroundColor: colorScheme.surface, - child: Padding( - padding: const .fromLTRB(12, 2.5, 6, 2.5), - child: Obx(() { - final sortType = _videoReplyController.sortType.value; - return Row( - mainAxisAlignment: .spaceBetween, - children: [ - Text( - sortType.title, - style: const TextStyle(fontSize: 13), + child: ScaffoldLayout( + body: CustomScrollView( + controller: widget.isNested + ? null + : _videoReplyController.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + key: const PageStorageKey(_VideoReplyPanelState), + slivers: [ + SliverFloatingHeaderWidget( + backgroundColor: colorScheme.surface, + child: Padding( + padding: const .fromLTRB(12, 2.5, 6, 2.5), + child: Obx(() { + final sortType = _videoReplyController.sortType.value; + return Row( + mainAxisAlignment: .spaceBetween, + children: [ + Text( + sortType.title, + style: const TextStyle(fontSize: 13), + ), + TextButton.icon( + style: Style.buttonStyle, + onPressed: _videoReplyController.queryBySort, + icon: Icon( + Icons.sort, + size: 16, + color: colorScheme.secondary, ), - TextButton.icon( - style: Style.buttonStyle, - onPressed: _videoReplyController.queryBySort, - icon: Icon( - Icons.sort, - size: 16, + label: Text( + sortType.label, + style: TextStyle( + fontSize: 13, color: colorScheme.secondary, ), - label: Text( - sortType.label, - style: TextStyle( - fontSize: 13, - color: colorScheme.secondary, - ), - ), ), - ], - ); - }), - ), - ), - Obx(() => _buildBody(_videoReplyController.loadingState.value)), - ], - ), - Positioned( - right: 0, - bottom: 0, - child: SlideTransition( - position: fabAnimation, - child: Padding( - padding: .only( - right: kFloatingActionButtonMargin, - bottom: kFloatingActionButtonMargin + bottom, - ), - child: FloatingActionButton( - heroTag: null, - onPressed: () { - feedBack(); - _videoReplyController.onReply( - null, - oid: _videoReplyController.aid, - replyType: _videoReplyController.videoType.replyType, - ); - }, - tooltip: '发表评论', - child: const Icon(Icons.reply), - ), + ), + ], + ); + }), ), ), + Obx(() => _buildBody(_videoReplyController.loadingState.value)), + ], + ), + fab: SlideTransition( + position: fabAnimation, + child: Padding( + padding: .only( + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin + bottom, + ), + child: FloatingActionButton( + heroTag: null, + onPressed: () { + feedBack(); + _videoReplyController.onReply( + null, + oid: _videoReplyController.aid, + replyType: _videoReplyController.videoType.replyType, + ); + }, + tooltip: '发表评论', + child: const Icon(Icons.reply), + ), ), - ], + ), ), ), ); @@ -239,8 +234,7 @@ class _VideoReplyPanelState extends State EasyThrottle.throttle('replyReply', const Duration(milliseconds: 500), () { int oid = replyItem.oid.toInt(); int rpid = replyItem.id.toInt(); - Scaffold.of(context).showBottomSheet( - backgroundColor: Colors.transparent, + MiniScaffold.of(context).showBottomSheet( constraints: const BoxConstraints(), (context) => VideoReplyReplyPanel( id: id, diff --git a/lib/pages/video/reply_reply/view.dart b/lib/pages/video/reply_reply/view.dart index a40a83faa..50214b8c5 100644 --- a/lib/pages/video/reply_reply/view.dart +++ b/lib/pages/video/reply_reply/view.dart @@ -3,6 +3,9 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/colored_box_transition.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; +import 'package:PiliPlus/common/widgets/simple_colored_box.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' @@ -65,8 +68,7 @@ class VideoReplyReplyPanel extends CommonSlidePage { 'type': type, 'enterUri': ?uri?.toString(), // save panel }, - () => Scaffold( - resizeToAvoidBottomInset: false, + () => SimpleScaffold( appBar: AppBar( title: const Text('评论详情'), actions: [ @@ -140,38 +142,40 @@ class _VideoReplyReplyPanelState extends State @override Widget buildPage(ThemeData theme) { Widget child() => enableSlide ? slideList(theme) : buildList(theme); - return Scaffold( - resizeToAvoidBottomInset: false, - body: widget.isVideoDetail - ? Column( - children: [ - Container( - height: 45, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 1, - color: theme.dividerColor.withValues(alpha: 0.1), + return SimpleColoredBox( + color: theme.canvasColor, + child: MiniScaffold( + body: widget.isVideoDetail + ? Column( + children: [ + Container( + height: 45, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1, + color: theme.dividerColor.withValues(alpha: 0.1), + ), ), ), + padding: const EdgeInsets.only(left: 12, right: 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(isDialogue ? '对话列表' : '评论详情'), + IconButton( + tooltip: '关闭', + icon: const Icon(Icons.close, size: 20), + onPressed: Get.back, + ), + ], + ), ), - padding: const EdgeInsets.only(left: 12, right: 2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(isDialogue ? '对话列表' : '评论详情'), - IconButton( - tooltip: '关闭', - icon: const Icon(Icons.close, size: 20), - onPressed: Get.back, - ), - ], - ), - ), - Expanded(child: child()), - ], - ) - : child(), + Expanded(child: child()), + ], + ) + : child(), + ), ); } @@ -335,8 +339,7 @@ class _VideoReplyReplyPanelState extends State onReply: (replyItem) => _controller.onReply(replyItem, index: index), onDelete: (item, subIndex) => _controller.onRemove(index, item, null), upMid: _controller.upMid, - showDialogue: () => Scaffold.of(context).showBottomSheet( - backgroundColor: Colors.transparent, + showDialogue: () => MiniScaffold.of(context).showBottomSheet( constraints: const BoxConstraints(), (context) => VideoReplyReplyPanel( oid: replyItem.oid.toInt(), diff --git a/lib/pages/video/reply_search_item/view.dart b/lib/pages/video/reply_search_item/view.dart index 20c0f2b0a..aa089e143 100644 --- a/lib/pages/video/reply_search_item/view.dart +++ b/lib/pages/video/reply_search_item/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/models/common/reply/reply_search_type.dart'; @@ -35,8 +36,7 @@ class _ReplySearchPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( actions: [ IconButton( diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 9f5f60407..73af626f4 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -8,6 +8,8 @@ import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; +import 'package:PiliPlus/common/widgets/scaffold/mini_scaffold.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_behavior.dart' show NoOverscrollIndicator; import 'package:PiliPlus/common/widgets/scroll_physics.dart' @@ -486,38 +488,32 @@ class _VideoDetailPageVState extends State return Obx( () { final isFullScreen = this.isFullScreen; - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: removeAppBar(isFullScreen) ? null - : PreferredSize( - preferredSize: const .fromHeight(0), - child: Obx( - () { - final scrollRatio = - videoDetailController.scrollRatio.value; - Brightness? statusBarIconBrightness; - if (Platform.isAndroid) { - statusBarIconBrightness = - isPortrait && scrollRatio >= 0.5 - ? colorScheme.brightness.reverse - : .light; - } - final backgroundColor = isPortrait && scrollRatio > 0 - ? Color.lerp( - Colors.black, - colorScheme.surface, - scrollRatio, - )! - : Colors.black; - return SimpleAppBar( - height: padding.top, - backgroundColor: backgroundColor, - brightness: colorScheme.brightness, - statusBarIconBrightness: statusBarIconBrightness, - ); - }, - ), + : Obx( + () { + final scrollRatio = videoDetailController.scrollRatio.value; + Brightness? statusBarIconBrightness; + if (Platform.isAndroid) { + statusBarIconBrightness = isPortrait && scrollRatio >= 0.5 + ? colorScheme.brightness.reverse + : .light; + } + final backgroundColor = isPortrait && scrollRatio > 0 + ? Color.lerp( + Colors.black, + colorScheme.surface, + scrollRatio, + )! + : Colors.black; + return SimpleAppBar( + height: padding.top, + backgroundColor: backgroundColor, + brightness: colorScheme.brightness, + statusBarIconBrightness: statusBarIconBrightness, + ); + }, ), body: ExtendedNestedScrollView( onlyOneScrollInBody: true, @@ -578,10 +574,8 @@ class _VideoDetailPageVState extends State ), ]; }, - body: Scaffold( + body: MiniScaffold( key: videoDetailController.childKey, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, body: Column( children: [ buildTabBar(onTap: videoDetailController.animToTop), @@ -752,16 +746,12 @@ class _VideoDetailPageVState extends State Widget get childWhenDisabledLandscape => Obx( () { final isFullScreen = this.isFullScreen; - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: removeAppBar(isFullScreen) ? null - : PreferredSize( - preferredSize: const .fromHeight(0), - child: SimpleAppBar( - height: padding.top, - brightness: colorScheme.brightness, - ), + : SimpleAppBar( + height: padding.top, + brightness: colorScheme.brightness, ), body: Padding( padding: isFullScreen @@ -794,10 +784,8 @@ class _VideoDetailPageVState extends State child: SizedBox( width: introWidth, height: maxHeight - padding.top, - child: Scaffold( + child: MiniScaffold( key: videoDetailController.childKey, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -860,10 +848,8 @@ class _VideoDetailPageVState extends State child: SizedBox( width: introWidth, height: introHeight, - child: Scaffold( + child: MiniScaffold( key: videoDetailController.childKey, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, body: Column( children: [ buildTabBar(showIntro: false), @@ -944,10 +930,8 @@ class _VideoDetailPageVState extends State child: SizedBox( width: maxWidth - width - padding.horizontal, height: maxHeight - padding.top, - child: Scaffold( + child: MiniScaffold( key: videoDetailController.childKey, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -993,16 +977,12 @@ class _VideoDetailPageVState extends State Widget get childWhenDisabledAlmostSquare => Obx(() { final isFullScreen = this.isFullScreen; - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: removeAppBar(isFullScreen) ? null - : PreferredSize( - preferredSize: const .fromHeight(0), - child: SimpleAppBar( - height: padding.top, - brightness: colorScheme.brightness, - ), + : SimpleAppBar( + height: padding.top, + brightness: colorScheme.brightness, ), body: Padding( padding: isFullScreen @@ -1052,10 +1032,8 @@ class _VideoDetailPageVState extends State child: SizedBox( width: maxWidth - padding.horizontal, height: bottomHeight, - child: Scaffold( + child: MiniScaffold( key: videoDetailController.childKey, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1090,10 +1068,9 @@ class _VideoDetailPageVState extends State ); } - Widget get manualPlayerWidget => Obx(() { + Widget manualPlayerWidget(double height) => Obx(() { if (!videoDetailController.autoPlay) { return Stack( - clipBehavior: Clip.none, children: [ Positioned( top: 0, @@ -1156,12 +1133,8 @@ class _VideoDetailPageVState extends State ), Positioned( right: 12, - bottom: 10, - child: IconButton( - tooltip: '播放', - onPressed: handlePlay, - icon: const PlayIcon(), - ), + top: height - 70, + child: const PlayIcon(), ), ], ); @@ -1498,7 +1471,7 @@ class _VideoDetailPageVState extends State } return const SizedBox.shrink(); }), - manualPlayerWidget, + manualPlayerWidget(height), if (videoDetailController.plPlayerController.enableBlock || videoDetailController.continuePlayingPart) @@ -1863,7 +1836,6 @@ class _VideoDetailPageVState extends State // ai总结 void showAiBottomSheet() { videoDetailController.childKey.currentState?.showBottomSheet( - backgroundColor: Colors.transparent, constraints: const BoxConstraints(), (context) => AiConclusionPanel(item: ugcIntroController.aiConclusionResult!), @@ -1875,7 +1847,6 @@ class _VideoDetailPageVState extends State List? videoTags, ) { videoDetailController.childKey.currentState?.showBottomSheet( - backgroundColor: Colors.transparent, constraints: const BoxConstraints(), (context) => PgcIntroPanel( item: videoDetail, @@ -1946,7 +1917,6 @@ class _VideoDetailPageVState extends State ); } else { videoDetailController.childKey.currentState?.showBottomSheet( - backgroundColor: Colors.transparent, constraints: const BoxConstraints(), (context) => listSheetContent(), ); @@ -2027,7 +1997,6 @@ class _VideoDetailPageVState extends State ); } else { videoDetailController.childKey.currentState?.showBottomSheet( - backgroundColor: Colors.transparent, constraints: const BoxConstraints(), (context) => ViewPointsPage( videoDetailController: videoDetailController, @@ -2039,7 +2008,6 @@ class _VideoDetailPageVState extends State void onShowMemberPage(int? mid) { videoDetailController.childKey.currentState?.showBottomSheet( - shape: const RoundedRectangleBorder(), constraints: const BoxConstraints(), (context) { return HorizontalMemberPage( diff --git a/lib/pages/video/view_point/view.dart b/lib/pages/video/view_point/view.dart index 072e8e3db..5f398209f 100644 --- a/lib/pages/video/view_point/view.dart +++ b/lib/pages/video/view_point/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/pages/common/slide/common_slide_page.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; @@ -34,8 +35,7 @@ class _ViewPointsPageState extends State @override Widget buildPage(ThemeData theme) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( primary: false, automaticallyImplyLeading: false, diff --git a/lib/pages/webdav/view.dart b/lib/pages/webdav/view.dart index f71cf6c37..60df06b71 100644 --- a/lib/pages/webdav/view.dart +++ b/lib/pages/webdav/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/style.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/pages/webdav/webdav.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; @@ -38,121 +39,117 @@ class _WebDavSettingPageState extends State { Widget build(BuildContext context) { final showAppBar = widget.showAppBar; final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( + final viewInsets = MediaQuery.viewInsetsOf(context).bottom; + return SimpleScaffold( appBar: showAppBar ? AppBar(title: const Text('WebDAV 设置')) : null, - body: Stack( - clipBehavior: Clip.none, + body: ListView( + padding: padding.copyWith( + top: 20, + left: 20 + (showAppBar ? padding.left : 0), + right: 20 + (showAppBar ? padding.right : 0), + bottom: padding.bottom + viewInsets + 100, + ), children: [ - ListView( - padding: padding.copyWith( - top: 20, - left: 20 + (showAppBar ? padding.left : 0), - right: 20 + (showAppBar ? padding.right : 0), - bottom: padding.bottom + 100, + TextField( + controller: _uriCtr, + decoration: const InputDecoration( + labelText: '地址', + border: OutlineInputBorder(), ), + ), + const SizedBox(height: 20), + TextField( + controller: _usernameCtr, + decoration: const InputDecoration( + labelText: '用户', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _passwordCtr, + autofillHints: const [AutofillHints.password], + decoration: InputDecoration( + labelText: '密码', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + onPressed: () => setState(() => _obscureText = !_obscureText), + icon: _obscureText + ? const Icon(Icons.visibility) + : const Icon(Icons.visibility_off), + ), + ), + obscureText: _obscureText, + ), + const SizedBox(height: 20), + TextField( + controller: _directoryCtr, + decoration: const InputDecoration( + labelText: '路径', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + Row( children: [ - TextField( - controller: _uriCtr, - decoration: const InputDecoration( - labelText: '地址', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - TextField( - controller: _usernameCtr, - decoration: const InputDecoration( - labelText: '用户', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - TextField( - controller: _passwordCtr, - autofillHints: const [AutofillHints.password], - decoration: InputDecoration( - labelText: '密码', - border: const OutlineInputBorder(), - suffixIcon: IconButton( - onPressed: () => - setState(() => _obscureText = !_obscureText), - icon: _obscureText - ? const Icon(Icons.visibility) - : const Icon(Icons.visibility_off), - ), - ), - obscureText: _obscureText, - ), - const SizedBox(height: 20), - TextField( - controller: _directoryCtr, - decoration: const InputDecoration( - labelText: '路径', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: FilledButton.tonal( - style: FilledButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: Style.mdRadius, - ), - ), - onPressed: WebDav().backup, - child: const Text('备份设置'), + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: Style.mdRadius, ), ), - const SizedBox(width: 20), - Expanded( - child: FilledButton.tonal( - style: FilledButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: Style.mdRadius, - ), - ), - onPressed: WebDav().restore, - child: const Text('恢复设置'), + onPressed: WebDav().backup, + child: const Text('备份设置'), + ), + ), + const SizedBox(width: 20), + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: Style.mdRadius, ), ), - ], + onPressed: WebDav().restore, + child: const Text('恢复设置'), + ), ), ], ), - Positioned( - right: - kFloatingActionButtonMargin + (showAppBar ? padding.right : 0), - bottom: kFloatingActionButtonMargin + padding.bottom, - child: FloatingActionButton( - child: const Icon(Icons.save), - onPressed: () async { - await GStorage.setting.putAll({ - SettingBoxKey.webdavUri: _uriCtr.text, - SettingBoxKey.webdavUsername: _usernameCtr.text, - SettingBoxKey.webdavPassword: _passwordCtr.text, - SettingBoxKey.webdavDirectory: _directoryCtr.text, - }); - if (_uriCtr.text.isEmpty) { - return; - } - try { - final res = await WebDav().init(); - if (res.first) { - SmartDialog.showToast('配置成功'); - } else { - SmartDialog.showToast('配置失败: ${res.second}'); - } - } catch (e) { - SmartDialog.showToast('配置失败: ${e.toString()}'); - return; - } - }, - ), - ), ], ), + fab: Padding( + padding: .only( + right: kFloatingActionButtonMargin + (showAppBar ? padding.right : 0), + bottom: kFloatingActionButtonMargin + padding.bottom + viewInsets, + ), + child: FloatingActionButton( + child: const Icon(Icons.save), + onPressed: () async { + await GStorage.setting.putAll({ + SettingBoxKey.webdavUri: _uriCtr.text, + SettingBoxKey.webdavUsername: _usernameCtr.text, + SettingBoxKey.webdavPassword: _passwordCtr.text, + SettingBoxKey.webdavDirectory: _directoryCtr.text, + }); + if (_uriCtr.text.isEmpty) { + return; + } + try { + final res = await WebDav().init(); + if (res.first) { + SmartDialog.showToast('配置成功'); + } else { + SmartDialog.showToast('配置失败: ${res.second}'); + } + } catch (e) { + SmartDialog.showToast('配置失败: ${e.toString()}'); + return; + } + }, + ), + ), ); } } diff --git a/lib/pages/webview/view.dart b/lib/pages/webview/view.dart index 53353f046..ffe2f2f5e 100644 --- a/lib/pages/webview/view.dart +++ b/lib/pages/webview/view.dart @@ -1,5 +1,6 @@ import 'dart:io' show Platform; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/http/browser_ua.dart'; import 'package:PiliPlus/main.dart'; @@ -75,9 +76,8 @@ class _WebviewPageState extends State { @override Widget build(BuildContext context) { if (Platform.isLinux) { - return Scaffold( + return SimpleScaffold( appBar: AppBar(), - resizeToAvoidBottomInset: false, body: Center( child: TextButton( onPressed: () => PageUtils.launchURL(_url), diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 5a1ea50df..5ead8064b 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/skeleton/whisper_item.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/whisper/controller.dart'; @@ -26,8 +27,7 @@ class _WhisperPageState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); final padding = MediaQuery.viewPaddingOf(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: const Text('消息'), actions: [ diff --git a/lib/pages/whisper_block/view.dart b/lib/pages/whisper_block/view.dart index 2f060bc5e..27e7a2cf3 100644 --- a/lib/pages/whisper_block/view.dart +++ b/lib/pages/whisper_block/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' show KeywordBlockingItem; import 'package:PiliPlus/http/loading_state.dart'; @@ -26,8 +27,7 @@ class _WhisperBlockPageState extends State { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('消息屏蔽词')), body: Obx(() => _buildBody(theme, _controller.loadingState.value)), ); diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index de0ce22b4..d032689a7 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/common/widgets/flutter/chat_list_view.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show platformAlwaysClampingPhysics; import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart' show Msg; @@ -58,8 +59,7 @@ class _WhisperDetailPageState theme.hoverColor, 1, ); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: GestureDetector( behavior: HitTestBehavior.opaque, diff --git a/lib/pages/whisper_link_setting/view.dart b/lib/pages/whisper_link_setting/view.dart index a85f2a550..b3c902372 100644 --- a/lib/pages/whisper_link_setting/view.dart +++ b/lib/pages/whisper_link_setting/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/msg/im_user_infos/datum.dart'; import 'package:PiliPlus/models_new/msg/msg_dnd/uid_setting.dart'; @@ -46,8 +47,7 @@ class _WhisperLinkSettingPageState extends State { indent: 16, color: theme.colorScheme.outline.withValues(alpha: 0.1), ); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar(title: const Text('聊天设置')), body: ListView( padding: EdgeInsets.only( diff --git a/lib/pages/whisper_secondary/view.dart b/lib/pages/whisper_secondary/view.dart index cabab1358..33457c0bc 100644 --- a/lib/pages/whisper_secondary/view.dart +++ b/lib/pages/whisper_secondary/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/skeleton/whisper_item.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/whisper/widgets/item.dart'; @@ -37,8 +38,7 @@ class _WhisperSecPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Text(widget.name), actions: [ diff --git a/lib/pages/whisper_settings/view.dart b/lib/pages/whisper_settings/view.dart index ae1a77703..79d1fedd5 100644 --- a/lib/pages/whisper_settings/view.dart +++ b/lib/pages/whisper_settings/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' show IMSettingType, Setting; import 'package:PiliPlus/http/loading_state.dart'; @@ -39,8 +40,7 @@ class _WhisperSettingsPageState extends State { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - return Scaffold( - resizeToAvoidBottomInset: false, + return SimpleScaffold( appBar: AppBar( title: Obx(() => Text(_controller.title.value)), ), diff --git a/lib/scripts/patch.ps1 b/lib/scripts/patch.ps1 index 401c48f8b..15c19bf70 100644 --- a/lib/scripts/patch.ps1 +++ b/lib/scripts/patch.ps1 @@ -67,6 +67,8 @@ $ScrollPositionPatch = "lib/scripts/scroll_position.patch" # expose `_shouldIgnorePointer` $ScrollablePatch = "lib/scripts/scrollable.patch" +$ScaffoldPatch = "lib/scripts/scaffold.patch" + $ScrollableGesturePatch = "lib/scripts/scrollable_gesture.patch" $DraggableScrollableSheetPatch = "lib/scripts/draggable_scrollable_sheet.patch" @@ -106,7 +108,7 @@ $patches = @($ModalBarrierPatch, $TextSelectionPatch, $MouseCursorPatch, $PopupMenuPatch, $FABPatch, $NullSafetySelectableRegionPatch, $SelectableRegionPatch, $EditableTextPatch, $TextFieldPatch, $ScrollPositionPatch, $ScrollablePatch, $ScrollableGesturePatch, - $DraggableScrollableSheetPatch) + $DraggableScrollableSheetPatch, $ScaffoldPatch) switch ($platform.ToLower()) { "android" { diff --git a/lib/scripts/scaffold.patch b/lib/scripts/scaffold.patch new file mode 100644 index 000000000..d014f34f7 --- /dev/null +++ b/lib/scripts/scaffold.patch @@ -0,0 +1,145 @@ +diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart +index 0fba5686f28..f6243932c9e 100644 +--- a/packages/flutter/lib/src/material/bottom_sheet.dart ++++ b/packages/flutter/lib/src/material/bottom_sheet.dart +@@ -254,8 +254,11 @@ class BottomSheet extends StatefulWidget { + } + } + ++typedef BottomSheetState = _BottomSheetState; ++ + class _BottomSheetState extends State { + final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child'); ++ GlobalKey get childKey => _childKey; + + double get _childHeight { + final renderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; +@@ -266,14 +269,14 @@ class _BottomSheetState extends State { + + Set dragHandleStates = {}; + +- void _handleDragStart(DragStartDetails details) { ++ void handleDragStart(DragStartDetails details) { + setState(() { + dragHandleStates.add(WidgetState.dragged); + }); + widget.onDragStart?.call(details); + } + +- void _handleDragUpdate(DragUpdateDetails details) { ++ void handleDragUpdate(DragUpdateDetails details) { + assert( + (widget.enableDrag || (widget.showDragHandle ?? false)) && widget.animationController != null, + "'BottomSheet.animationController' cannot be null when 'BottomSheet.enableDrag' or 'BottomSheet.showDragHandle' is true. " +@@ -285,7 +288,7 @@ class _BottomSheetState extends State { + widget.animationController!.value -= details.primaryDelta! / _childHeight; + } + +- void _handleDragEnd(DragEndDetails details) { ++ void handleDragEnd(DragEndDetails details) { + assert( + (widget.enableDrag || (widget.showDragHandle ?? false)) && widget.animationController != null, + "'BottomSheet.animationController' cannot be null when 'BottomSheet.enableDrag' or 'BottomSheet.showDragHandle' is true. " +@@ -376,9 +379,9 @@ class _BottomSheetState extends State { + // no need to add it. + if (!widget.enableDrag) { + dragHandle = _BottomSheetGestureDetector( +- onVerticalDragStart: _handleDragStart, +- onVerticalDragUpdate: _handleDragUpdate, +- onVerticalDragEnd: _handleDragEnd, ++ onVerticalDragStart: handleDragStart, ++ onVerticalDragUpdate: handleDragUpdate, ++ onVerticalDragEnd: handleDragEnd, + child: dragHandle, + ); + } +@@ -420,9 +423,9 @@ class _BottomSheetState extends State { + return !widget.enableDrag + ? bottomSheet + : _BottomSheetGestureDetector( +- onVerticalDragStart: _handleDragStart, +- onVerticalDragUpdate: _handleDragUpdate, +- onVerticalDragEnd: _handleDragEnd, ++ onVerticalDragStart: handleDragStart, ++ onVerticalDragUpdate: handleDragUpdate, ++ onVerticalDragEnd: handleDragEnd, + child: bottomSheet, + ); + } +@@ -1472,6 +1475,8 @@ class _BottomSheetGestureDetector extends StatelessWidget { + } + } + ++typedef BottomSheetDefaultsM3 = _BottomSheetDefaultsM3; ++ + // BEGIN GENERATED TOKEN PROPERTIES - BottomSheet + + // Do not edit by hand. The code between the "BEGIN GENERATED" and +diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart +index 6f4fc67ff84..9b306c84094 100644 +--- a/packages/flutter/lib/src/material/scaffold.dart ++++ b/packages/flutter/lib/src/material/scaffold.dart +@@ -3295,7 +3295,9 @@ class _DismissDrawerAction extends DismissAction { + class ScaffoldFeatureController { + const ScaffoldFeatureController._(this._widget, this._completer, this.close, this.setState); + final T _widget; ++ T get widget => _widget; + final Completer _completer; ++ Completer get completer => _completer; + + /// Completes when the feature controlled by this object is no longer visible. + Future get closed => _completer.future; +@@ -3307,6 +3309,9 @@ class ScaffoldFeatureController { + final StateSetter? setState; + } + ++typedef StandardBottomSheet = _StandardBottomSheet; ++typedef StandardBottomSheetState = _StandardBottomSheetState; ++ + class _StandardBottomSheet extends StatefulWidget { + const _StandardBottomSheet({ + super.key, +@@ -3372,12 +3377,12 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> { + widget.onClosing?.call(); + } + +- void _handleDragStart(DragStartDetails details) { ++ void handleDragStart(DragStartDetails details) { + // Allow the bottom sheet to track the user's finger accurately. + animationCurve = Curves.linear; + } + +- void _handleDragEnd(DragEndDetails details, {bool? isClosing}) { ++ void handleDragEnd(DragEndDetails details, {bool? isClosing}) { + // Allow the bottom sheet to animate smoothly from its current position. + animationCurve = Split(widget.animationController.value, endCurve: _standardBottomSheetCurve); + } +@@ -3430,8 +3435,8 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> { + animationController: widget.animationController, + enableDrag: widget.enableDrag, + showDragHandle: widget.showDragHandle, +- onDragStart: _handleDragStart, +- onDragEnd: _handleDragEnd, ++ onDragStart: handleDragStart, ++ onDragEnd: handleDragEnd, + onClosing: widget.onClosing!, + builder: widget.builder, + backgroundColor: widget.backgroundColor, +@@ -3463,7 +3468,17 @@ class PersistentBottomSheetController + this._isLocalHistoryEntry, + ) : super._(); + ++ const PersistentBottomSheetController( ++ super.widget, ++ super.completer, ++ super.close, ++ StateSetter super.setState, ++ this._isLocalHistoryEntry, ++ ) : super._(); ++ + final bool _isLocalHistoryEntry; ++ ++ bool get isLocalHistoryEntry => _isLocalHistoryEntry; + } + + class _ScaffoldScope extends InheritedWidget { diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index c53cedeed..ce98bd719 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -2,6 +2,7 @@ import 'dart:async' show StreamSubscription; +import 'package:PiliPlus/common/widgets/scaffold/simple_scaffold.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pbenum.dart' show PlaylistSource; @@ -366,8 +367,7 @@ abstract final class PiliScheme { return false; case 'livearea': Get.to( - Scaffold( - resizeToAvoidBottomInset: false, + SimpleScaffold( appBar: AppBar(title: const Text('直播')), body: const ViewSafeArea(child: LivePage()), ), @@ -375,8 +375,7 @@ abstract final class PiliScheme { return true; case 'rank': Get.to( - Scaffold( - resizeToAvoidBottomInset: false, + SimpleScaffold( appBar: AppBar(title: const Text('排行榜')), body: const ViewSafeArea(child: RankPage()), ), diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index 817d4afff..604053eb5 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -413,25 +413,6 @@ abstract final class PageUtils { } } - static void onHorizontalPreviewState( - ScaffoldState state, - List imgList, - int index, - ) { - state.showBottomSheet( - constraints: const BoxConstraints(), - (context) => GalleryViewer( - sources: imgList, - initIndex: index, - quality: GlobalData().imgQuality, - ), - enableDrag: false, - elevation: 0.0, - backgroundColor: Colors.transparent, - sheetAnimationStyle: AnimationStyle.noAnimation, - ); - } - static void inAppWebview( String url, { bool off = false,