249 lines
7.3 KiB
Dart
249 lines
7.3 KiB
Dart
import 'package:fl_clash/common/common.dart';
|
|
import 'package:fl_clash/controller.dart';
|
|
import 'package:fl_clash/models/common.dart';
|
|
import 'package:fl_clash/widgets/inherited.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'scaffold.dart';
|
|
import 'side_sheet.dart';
|
|
|
|
@immutable
|
|
class SheetProps {
|
|
final double? maxWidth;
|
|
final double? maxHeight;
|
|
final bool isScrollControlled;
|
|
final bool useSafeArea;
|
|
final Color? backgroundColor;
|
|
final bool blur;
|
|
|
|
const SheetProps({
|
|
this.maxWidth,
|
|
this.maxHeight,
|
|
this.backgroundColor,
|
|
this.useSafeArea = true,
|
|
this.isScrollControlled = false,
|
|
this.blur = true,
|
|
});
|
|
}
|
|
|
|
@immutable
|
|
class ExtendProps {
|
|
final double? maxWidth;
|
|
final bool useSafeArea;
|
|
final bool blur;
|
|
final bool forceFull;
|
|
|
|
const ExtendProps({
|
|
this.maxWidth,
|
|
this.useSafeArea = true,
|
|
this.blur = true,
|
|
this.forceFull = false,
|
|
});
|
|
}
|
|
|
|
enum SheetType { page, bottomSheet, sideSheet }
|
|
|
|
Future<T?> showSheet<T>({
|
|
required BuildContext context,
|
|
required WidgetBuilder builder,
|
|
SheetProps props = const SheetProps(),
|
|
}) {
|
|
final isMobile = appController.isMobile;
|
|
return switch (isMobile) {
|
|
true => showModalBottomSheet<T>(
|
|
context: context,
|
|
isScrollControlled: props.isScrollControlled,
|
|
builder: (_) {
|
|
return SheetProvider(
|
|
type: SheetType.bottomSheet,
|
|
child: builder(context),
|
|
);
|
|
},
|
|
backgroundColor: props.backgroundColor,
|
|
showDragHandle: false,
|
|
useSafeArea: props.useSafeArea,
|
|
),
|
|
false => showModalSideSheet<T>(
|
|
useSafeArea: props.useSafeArea,
|
|
isScrollControlled: props.isScrollControlled,
|
|
context: context,
|
|
backgroundColor: props.backgroundColor,
|
|
constraints: BoxConstraints(maxWidth: props.maxWidth ?? 360),
|
|
filter: props.blur ? commonFilter : null,
|
|
builder: (_) {
|
|
return SheetProvider(
|
|
type: SheetType.sideSheet,
|
|
child: builder(context),
|
|
);
|
|
},
|
|
),
|
|
};
|
|
}
|
|
|
|
Future<T?> showExtend<T>(
|
|
BuildContext context, {
|
|
required WidgetBuilder builder,
|
|
ExtendProps props = const ExtendProps(),
|
|
}) {
|
|
final isMobile = appController.isMobile;
|
|
return switch (isMobile || props.forceFull) {
|
|
true => BaseNavigator.push(
|
|
context,
|
|
SheetProvider(type: SheetType.page, child: builder(context)),
|
|
),
|
|
false => showModalSideSheet<T>(
|
|
useSafeArea: props.useSafeArea,
|
|
context: context,
|
|
constraints: BoxConstraints(maxWidth: props.maxWidth ?? 360),
|
|
filter: props.blur ? commonFilter : null,
|
|
builder: (context) {
|
|
return SheetProvider(
|
|
type: SheetType.sideSheet,
|
|
child: builder(context),
|
|
);
|
|
},
|
|
),
|
|
};
|
|
}
|
|
|
|
class AdaptiveSheetScaffold extends StatefulWidget {
|
|
final Widget body;
|
|
final String title;
|
|
final List<IconButtonData> actions;
|
|
|
|
const AdaptiveSheetScaffold({
|
|
super.key,
|
|
required this.body,
|
|
required this.title,
|
|
this.actions = const [],
|
|
});
|
|
|
|
@override
|
|
State<AdaptiveSheetScaffold> createState() => _AdaptiveSheetScaffoldState();
|
|
}
|
|
|
|
class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final backgroundColor = context.colorScheme.surface;
|
|
final sheetProvider = SheetProvider.of(context);
|
|
final nestedNavigatorPopCallback =
|
|
sheetProvider?.nestedNavigatorPopCallback;
|
|
final ModalRoute<dynamic>? route = ModalRoute.of(context);
|
|
final type = sheetProvider?.type ?? SheetType.page;
|
|
final useCloseIcon =
|
|
type != SheetType.page &&
|
|
(nestedNavigatorPopCallback != null &&
|
|
route?.impliesAppBarDismissal == false ||
|
|
nestedNavigatorPopCallback == null);
|
|
Widget buildIconButton(IconButtonData data) {
|
|
if (type == SheetType.bottomSheet) {
|
|
return IconButton.filledTonal(
|
|
onPressed: data.onPressed,
|
|
style: IconButton.styleFrom(
|
|
visualDensity: VisualDensity.standard,
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
icon: Icon(data.icon),
|
|
);
|
|
}
|
|
return IconButton(
|
|
onPressed: data.onPressed,
|
|
style: IconButton.styleFrom(
|
|
visualDensity: VisualDensity.standard,
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
icon: Icon(data.icon),
|
|
);
|
|
}
|
|
|
|
final actions = widget.actions.map(buildIconButton).toList();
|
|
|
|
IconData getBackIconData() {
|
|
if (kIsWeb) {
|
|
return Icons.arrow_back;
|
|
}
|
|
switch (Theme.of(context).platform) {
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.fuchsia:
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.windows:
|
|
return Icons.arrow_back;
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.macOS:
|
|
return Icons.arrow_back_ios_new_rounded;
|
|
}
|
|
}
|
|
|
|
final popButton = type != SheetType.page
|
|
? (useCloseIcon
|
|
? buildIconButton(
|
|
IconButtonData(
|
|
icon: Icons.close,
|
|
onPressed: () {
|
|
if (nestedNavigatorPopCallback != null) {
|
|
nestedNavigatorPopCallback();
|
|
} else {
|
|
Navigator.of(context).pop();
|
|
}
|
|
},
|
|
),
|
|
)
|
|
: buildIconButton(
|
|
IconButtonData(
|
|
icon: getBackIconData(),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
))
|
|
: null;
|
|
|
|
final suffixPop = type != SheetType.page && actions.isEmpty && useCloseIcon;
|
|
final appBar = AppBar(
|
|
forceMaterialTransparency: type == SheetType.bottomSheet ? true : false,
|
|
leading: suffixPop ? null : popButton,
|
|
automaticallyImplyLeading: type == SheetType.page ? true : false,
|
|
centerTitle: true,
|
|
backgroundColor: backgroundColor,
|
|
toolbarHeight: type == SheetType.bottomSheet ? 48 : null,
|
|
title: Text(widget.title),
|
|
titleTextStyle: type == SheetType.bottomSheet
|
|
? context.textTheme.titleLarge?.adjustSize(-4)
|
|
: null,
|
|
actions: !suffixPop ? genActions(actions) : genActions([?popButton]),
|
|
);
|
|
if (type == SheetType.bottomSheet) {
|
|
final handleSize = Size(28, 4);
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(top: 4),
|
|
child: Container(
|
|
alignment: Alignment.center,
|
|
height: handleSize.height,
|
|
width: handleSize.width,
|
|
decoration: ShapeDecoration(
|
|
color: context.colorScheme.onSurfaceVariant,
|
|
shape: RoundedSuperellipseBorder(
|
|
borderRadius: BorderRadius.circular(handleSize.height / 2),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Padding(padding: EdgeInsets.symmetric(horizontal: 4), child: appBar),
|
|
Flexible(flex: 1, child: widget.body),
|
|
SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
|
|
],
|
|
);
|
|
}
|
|
return CommonScaffold(
|
|
appBar: appBar,
|
|
backgroundColor: backgroundColor,
|
|
body: widget.body,
|
|
);
|
|
}
|
|
}
|