mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-03 01:00:14 +08:00
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<MainType, RenderBox> {
|
||||
@@ -37,6 +37,10 @@ class MainLayout
|
||||
|
||||
class _RenderMainLayout extends RenderBox
|
||||
with SlottedContainerRenderObjectMixin<MainType, RenderBox> {
|
||||
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(
|
||||
|
||||
80
lib/common/widgets/scaffold/bottom_sheet.dart
Normal file
80
lib/common/widgets/scaffold/bottom_sheet.dart
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
308
lib/common/widgets/scaffold/mini_scaffold.dart
Normal file
308
lib/common/widgets/scaffold/mini_scaffold.dart
Normal file
@@ -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<MiniScaffoldState>()!;
|
||||
}
|
||||
|
||||
static MiniScaffoldState? maybeOf(BuildContext context) {
|
||||
return context.findAncestorStateOfType<MiniScaffoldState>();
|
||||
}
|
||||
|
||||
@override
|
||||
State<MiniScaffold> createState() => MiniScaffoldState();
|
||||
}
|
||||
|
||||
class MiniScaffoldState extends State<MiniScaffold>
|
||||
with TickerProviderStateMixin {
|
||||
final _dismissedBottomSheets = <StandardBottomSheet>[];
|
||||
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<void>();
|
||||
final bottomSheetKey = GlobalKey<StandardBottomSheetState>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
152
lib/common/widgets/scaffold/simple_scaffold.dart
Normal file
152
lib/common/widgets/scaffold/simple_scaffold.dart
Normal file
@@ -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<ScaffoldType, RenderBox> {
|
||||
const ScaffoldLayout({
|
||||
super.key,
|
||||
this.fab,
|
||||
this.appBar,
|
||||
required this.body,
|
||||
});
|
||||
|
||||
final Widget? fab;
|
||||
final Widget? appBar;
|
||||
final Widget body;
|
||||
|
||||
@override
|
||||
Iterable<ScaffoldType> get slots => ScaffoldType.values;
|
||||
|
||||
@override
|
||||
Widget? childForSlot(slot) => switch (slot) {
|
||||
.fab => fab,
|
||||
.appBar => appBar,
|
||||
.body => body,
|
||||
};
|
||||
|
||||
@override
|
||||
SlottedContainerRenderObjectMixin<ScaffoldType, RenderBox> createRenderObject(
|
||||
BuildContext context,
|
||||
) {
|
||||
return _RenderScaffoldLayout();
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderScaffoldLayout extends RenderBox
|
||||
with SlottedContainerRenderObjectMixin<ScaffoldType, RenderBox> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
68
lib/common/widgets/simple_colored_box.dart
Normal file
68
lib/common/widgets/simple_colored_box.dart
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user