This commit is contained in:
2025-02-27 18:08:08 +02:00
commit 499020323e
48 changed files with 3036 additions and 0 deletions

View File

@@ -0,0 +1,219 @@
//s1 Imports
//s2 Core Package Imports
import 'package:flutter/material.dart';
//s2 1st-party Package Imports
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
import '../../dependencies/vsync-provider/vsync_provider.dart';
//s3 Models
import '../form/form_helper.astromic.dart';
import '../sheet/src/models/models.exports.dart';
import '../sheet/src/enums/enums.exports.dart';
import '../sheet/src/widgets/widgets.exports.dart';
//s1 Exports
export '../sheet/src/models/models.exports.dart';
export '../form/form_helper.astromic.dart' show AstromicFormController;
class AstromicSheetHelper {
static VsyncProviderState? _vsyncState;
/// A wrapper to initialize the vSync plugin for the sheets' animations.
static Widget vSyncBuilder(Widget child) {
return VsyncProvider(child: child);
}
/// An initializer method to initialize the vSync state.
static _initializevSync(BuildContext context) {
_vsyncState = VsyncProvider.of(context);
}
/// Show FLEX sheet, a flexible sheet that hugs the height of it's content.
static Future<T?> flex<T extends Object?>(
BuildContext context, {
//
Widget? headSection,
required Widget contentSection,
Widget? footerSection,
//
AstromicSheetConfiguration? configuration,
AstromicSheetStyle? style,
//
}) async {
if (_vsyncState == null) {
_initializevSync(context);
}
AstromicSheetConfiguration sheetConfigs = configuration ?? const AstromicSheetConfiguration();
AstromicSheetStyle sheetStyle = style ?? const AstromicSheetStyle();
return await BasicSheet.show<T>(
context: context,
vsyncState: _vsyncState!,
//
useRootNavigator: sheetConfigs.useRootNavigator,
enableOutsideInteraction: sheetConfigs.enableOutsideInteraction,
enableSheetInteraction: sheetConfigs.enableSheetInteraction,
//
forwardAnimationDuration: sheetConfigs.forwardAnimationDuration ?? const Duration(milliseconds: 250),
reverseAnimationDuration: sheetConfigs.reverseAnimationDuration ?? const Duration(milliseconds: 250),
animationCurve: sheetConfigs.animationCurve ?? Curves.easeOut,
//
barrierColor: sheetStyle.maskColor ,
radius: sheetStyle.radius,
//
child: BaseSheetWidget<T?>(
sheetConfiguration: sheetConfigs,
sheetStyle: sheetStyle,
//
headSection: headSection,
contentSection: contentSection,
footerSection: footerSection,
),
);
}
/// Show FLEX sheet using a pre-set FlexSheetTemplate.
static Future<T?> flexTemplate<T extends Object?>(BuildContext context, AstromicFlexSheetTemplate<T> template) async => await flex<T>(
context,
headSection: template.headSection,
contentSection: template.contentSection,
footerSection: template.footerSection,
//
configuration: template.configuration,
style: template.style,
);
/// Show Form sheet, a FLEX sheet but with an integrated form controller.
static Future<T?> form<T extends Object?>(
BuildContext context, {
//
Widget Function(AstromicFormController)? headSectionBuilder,
required Widget Function(AstromicFormController) contentSectionBuilder,
Widget Function(AstromicFormController)? footerSectionBuilder,
//
AstromicSheetConfiguration? configuration,
AstromicSheetStyle? style,
}) async {
if (_vsyncState == null) {
_initializevSync(context);
}
AstromicSheetConfiguration sheetConfigs = configuration ?? const AstromicSheetConfiguration();
AstromicSheetStyle sheetStyle = style ?? const AstromicSheetStyle();
return await BasicSheet.show<T>(
context: context,
vsyncState: _vsyncState!,
//
useRootNavigator: sheetConfigs.useRootNavigator,
enableOutsideInteraction: sheetConfigs.enableOutsideInteraction,
enableSheetInteraction: sheetConfigs.enableSheetInteraction,
//
forwardAnimationDuration: sheetConfigs.forwardAnimationDuration ?? const Duration(milliseconds: 250),
reverseAnimationDuration: sheetConfigs.reverseAnimationDuration ?? const Duration(milliseconds: 250),
animationCurve: sheetConfigs.animationCurve ?? Curves.easeOut,
//
barrierColor: sheetStyle.maskColor,
radius: sheetStyle.radius,
//
child: BaseSheetWidget<T?>(
sheetType: SheetType.form,
//
sheetConfiguration: sheetConfigs,
sheetStyle: sheetStyle,
//
headSectionFormBuilder: headSectionBuilder,
contentSectionFormBuilder: contentSectionBuilder,
footerSectionFormBuilder: footerSectionBuilder,
),
);
}
/// Show Form sheet using a pre-set FormSheetTemplate.
static Future<T?> formTemplate<T extends Object?>(BuildContext c, AstromicFormSheetTemplate<T> template) async => await form<T>(
c,
headSectionBuilder: template.headSectionBuilder,
contentSectionBuilder: template.contentSectionBuilder,
footerSectionBuilder: template.footerSectionBuilder,
//
configuration: template.configuration,
style: template.style,
//
);
/// Show Scroller sheet, a sheet that contains a vertically scrollable elements with some tweeks for animation.
static Future<T?> scroller<T extends Object?>(
BuildContext context, {
//
Widget Function(ScrollController)? headSectionBuilder,
required Widget Function(ScrollController, ScrollPhysics) contentSectionBuilder,
Widget Function(ScrollController)? footerSectionBuilder,
//
AstromicSheetConfiguration? configuration,
AstromicSheetStyle? style,
}) async {
if (_vsyncState == null) {
_initializevSync(context);
}
AstromicSheetConfiguration sheetConfigs = configuration ?? const AstromicSheetConfiguration();
AstromicSheetStyle sheetStyle = style ?? const AstromicSheetStyle();
return await ScrollerSheet.show<T>(
context: context,
vsyncState: _vsyncState!,
//
useRootNavigator: sheetConfigs.useRootNavigator,
enableOutsideInteraction: sheetConfigs.enableOutsideInteraction,
enableSheetInteraction: sheetConfigs.enableSheetInteraction,
respectKeyboardInset: sheetConfigs.respectKeyboardInset,
//
forwardAnimationDuration: sheetConfigs.forwardAnimationDuration ?? const Duration(milliseconds: 250),
reverseAnimationDuration: sheetConfigs.reverseAnimationDuration ?? const Duration(milliseconds: 250),
animationCurve: sheetConfigs.animationCurve ?? Curves.easeOut,
dragThreshold: sheetConfigs.dragThreshold ?? 25,
//
physics: sheetConfigs.physics,
stops: sheetConfigs.expandedHeightFactor != null && (sheetConfigs.expandedHeightFactor! > sheetConfigs.initialHeightFactor)
? <double?>[sheetConfigs.initialHeightFactor, sheetConfigs.expandedHeightFactor].map((double? s) {
double r = sheetConfigs.safeAreaAware ? ((s! * MediaQuery.of(context).size.height) - MediaQuery.of(context).viewPadding.top) : (s! * MediaQuery.of(context).size.height);
return r;
}).toList()
: <double>[
sheetConfigs.initialHeightFactor,
].map((double s) {
double r = sheetConfigs.safeAreaAware ? ((s * MediaQuery.of(context).size.height) - MediaQuery.of(context).viewPadding.top) : (s * MediaQuery.of(context).size.height);
return r;
}).toList(),
// s2 -- Styling
barrierColor: sheetStyle.maskColor,
topInset: sheetStyle.topInset,
// s2 -- Child
builder: (BuildContext context, ScrollController scrollController, ScrollPhysics scrollPhysics, int stop) {
return BaseSheetWidget<T?>(
sheetType: SheetType.scroller,
sheetConfiguration: sheetConfigs,
sheetStyle: sheetStyle,
headSectionScrollerBuilder: headSectionBuilder,
contentSectionScrollBuilder: contentSectionBuilder,
footerSectionScrollerBuilder: footerSectionBuilder,
scrollController: scrollController,
scrollPhysics: scrollPhysics,
);
},
);
}
/// Show Scroller sheet using a pre-set ScrollerSheetTemplate.
static Future<T?> scrollerTemplate<T extends Object?>(BuildContext context, AstromicScrollerSheetTemplate<T> template) async => await scroller<T>(
context,
headSectionBuilder: template.headSectionBuilder,
contentSectionBuilder: template.contentSectionBuilder,
footerSectionBuilder: template.footerSectionBuilder,
//
configuration: template.configuration,
style: template.style,
);
}

View File

@@ -0,0 +1 @@
export 'type.enum.dart';

View File

@@ -0,0 +1,5 @@
enum SheetType {
flex,
form,
scroller,
}

View File

@@ -0,0 +1,45 @@
//s1 Imports
//s2 Core Package Imports
import 'package:flutter/widgets.dart';
//s2 1st-party Package Imports
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
//s3 Models & Widgets
import 'template_base.dart';
import '../models.exports.dart';
//s1 Exports
class AstromicFlexSheetTemplate<T> extends AstromicSheetTemplate<T> {
final Widget? headSection;
final Widget contentSection;
final Widget? footerSection;
//
final AstromicSheetConfiguration? configuration;
final AstromicSheetStyle? style;
AstromicFlexSheetTemplate({
this.headSection,
required this.contentSection,
this.footerSection,
this.configuration,
this.style,
});
AstromicFlexSheetTemplate<T> copyWith({
Widget? headSection,
Widget? contentSection,
Widget? footerSection,
AstromicSheetConfiguration? configuration,
AstromicSheetStyle? style,
}) {
return AstromicFlexSheetTemplate<T>(
headSection: headSection ?? this.headSection,
contentSection: contentSection ?? this.contentSection,
footerSection: footerSection ?? this.footerSection,
configuration: configuration ?? this.configuration,
style: style ?? this.style,
);
}
}

View File

@@ -0,0 +1,46 @@
//s1 Imports
//s2 Core Package Imports
import 'package:flutter/widgets.dart';
//s2 1st-party Package Imports
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
//s3 Models
import 'template_base.dart';
import '../models.exports.dart';
import '../../../../form/form_helper.astromic.dart';
//s1 Exports
class AstromicFormSheetTemplate<T> extends AstromicSheetTemplate<T> {
final Widget Function(AstromicFormController)? headSectionBuilder;
final Widget Function(AstromicFormController) contentSectionBuilder;
final Widget Function(AstromicFormController)? footerSectionBuilder;
//
final AstromicSheetConfiguration? configuration;
final AstromicSheetStyle? style;
AstromicFormSheetTemplate({
this.headSectionBuilder,
required this.contentSectionBuilder,
this.footerSectionBuilder,
this.configuration,
this.style,
});
AstromicFormSheetTemplate<T> copyWith({
Widget Function(AstromicFormController)? headSectionBuilder,
Widget Function(AstromicFormController)? contentSectionBuilder,
Widget Function(AstromicFormController)? footerSectionBuilder,
AstromicSheetConfiguration? configuration,
AstromicSheetStyle? style,
}) {
return AstromicFormSheetTemplate<T>(
headSectionBuilder: headSectionBuilder ?? this.headSectionBuilder,
contentSectionBuilder: contentSectionBuilder ?? this.contentSectionBuilder,
footerSectionBuilder: footerSectionBuilder ?? this.footerSectionBuilder,
configuration: configuration ?? this.configuration,
style: style ?? this.style,
);
}
}

View File

@@ -0,0 +1,45 @@
//s1 Imports
//s2 Core Package Imports
import 'package:flutter/widgets.dart';
//s2 1st-party Package Imports
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
//s3 Models
import 'template_base.dart';
import '../models.exports.dart';
//s1 Exports
class AstromicScrollerSheetTemplate<T> extends AstromicSheetTemplate<T> {
final Widget Function(ScrollController)? headSectionBuilder;
final Widget Function(ScrollController, ScrollPhysics) contentSectionBuilder;
final Widget Function(ScrollController)? footerSectionBuilder;
//
final AstromicSheetConfiguration? configuration;
final AstromicSheetStyle? style;
AstromicScrollerSheetTemplate({
this.headSectionBuilder,
required this.contentSectionBuilder,
this.footerSectionBuilder,
this.configuration,
this.style,
});
AstromicScrollerSheetTemplate<T> copyWith({
Widget Function(ScrollController)? headSection,
Widget Function(ScrollController, ScrollPhysics)? contentSectionBuilder,
Widget Function(ScrollController)? footerSection,
AstromicSheetConfiguration? configuration,
AstromicSheetStyle? style,
}) {
return AstromicScrollerSheetTemplate<T>(
headSectionBuilder: headSection ?? this.headSectionBuilder,
contentSectionBuilder: contentSectionBuilder ?? this.contentSectionBuilder,
footerSectionBuilder: footerSection ?? this.footerSectionBuilder,
configuration: configuration ?? this.configuration,
style: style ?? this.style,
);
}
}

View File

@@ -0,0 +1 @@
abstract class AstromicSheetTemplate<T> {}

View File

@@ -0,0 +1,3 @@
export 'flex_sheet.template.dart';
export 'form_sheet.template.dart';
export 'scroller_sheet.template.dart';

View File

@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
class AstromicSheetConfiguration {
//s1 -- Shared
final bool useRootNavigator;
//
final Duration? closeDelay;
final Duration? forwardAnimationDuration;
final Duration? reverseAnimationDuration;
final Curve? animationCurve;
//
final bool enableOutsideInteraction;
final bool enableSheetInteraction;
final bool dismissOnBack;
//s1 -- Scroller Specific
final double initialHeightFactor;
final double? expandedHeightFactor;
final bool respectKeyboardInset;
final bool safeAreaAware;
final ScrollPhysics? physics;
final double? dragThreshold;
const AstromicSheetConfiguration({
this.useRootNavigator = true,
this.closeDelay,
this.forwardAnimationDuration = const Duration(milliseconds: 300),
this.reverseAnimationDuration = const Duration(milliseconds: 300),
this.animationCurve = Curves.ease,
this.enableOutsideInteraction = true,
this.enableSheetInteraction = true,
this.dismissOnBack = false,
this.respectKeyboardInset = true,
this.safeAreaAware = false,
this.expandedHeightFactor,
this.initialHeightFactor = 0.5,
this.physics,
this.dragThreshold,
});
AstromicSheetConfiguration copyWith({
bool? useRootNavigator,
Duration? closeDelay,
Duration? forwardAnimationDuration,
Duration? reverseAnimationDuration,
Curve? animationCurve,
bool? enableOutsideInteraction,
bool? enableSheetInteraction,
bool? dismissOnBack,
double? initialHeightFactor,
double? expandedHeightFactor,
bool? respectKeyboardInset,
bool? safeAreaAware,
ScrollPhysics? physics,
double? dragThreshold,
}) {
return AstromicSheetConfiguration(
useRootNavigator: useRootNavigator ?? this.useRootNavigator,
closeDelay: closeDelay ?? this.closeDelay,
forwardAnimationDuration: forwardAnimationDuration ?? this.forwardAnimationDuration,
reverseAnimationDuration: reverseAnimationDuration ?? this.reverseAnimationDuration,
animationCurve: animationCurve ?? this.animationCurve,
enableOutsideInteraction: enableOutsideInteraction ?? this.enableOutsideInteraction,
enableSheetInteraction: enableSheetInteraction ?? this.enableSheetInteraction,
dismissOnBack: dismissOnBack ?? this.dismissOnBack,
initialHeightFactor: initialHeightFactor ?? this.initialHeightFactor,
expandedHeightFactor: expandedHeightFactor ?? this.expandedHeightFactor,
respectKeyboardInset: respectKeyboardInset ?? this.respectKeyboardInset,
safeAreaAware: safeAreaAware ?? this.safeAreaAware,
physics: physics ?? this.physics,
dragThreshold: dragThreshold ?? this.dragThreshold,
);
}
}

View File

@@ -0,0 +1,3 @@
export 'configuration.model.dart';
export 'style.model.dart';
export 'templates/templates.exports.dart';

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
class AstromicSheetStyle {
final Color maskColor;
final Color headSectionColor;
final Color contentSectionColor;
final Color footerSectionColor;
//
final EdgeInsets headSectionPadding;
final EdgeInsets contentSectionPadding;
final EdgeInsets footerSectionPadding;
//
final double radius;
final double topInset;
//
const AstromicSheetStyle({
this.maskColor = Colors.black38,
this.headSectionColor = Colors.white,
this.contentSectionColor = Colors.white,
this.footerSectionColor = Colors.white,
this.headSectionPadding = const EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0),
this.contentSectionPadding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 18.0),
this.footerSectionPadding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 18.0),
this.radius = 24,
this.topInset = 0,
});
AstromicSheetStyle copyWith({
Color? maskColor,
Color? headSectionColor,
Color? contentSectionColor,
Color? footerSectionColor,
EdgeInsets? headSectionPadding,
EdgeInsets? contentSectionPadding,
EdgeInsets? footerSectionPadding,
double? radius,
double? topInset,
}) {
return AstromicSheetStyle(
maskColor: maskColor ?? this.maskColor,
headSectionColor: headSectionColor ?? this.headSectionColor,
contentSectionColor: contentSectionColor ?? this.contentSectionColor,
footerSectionColor: footerSectionColor ?? this.footerSectionColor,
headSectionPadding: headSectionPadding ?? this.headSectionPadding,
contentSectionPadding: contentSectionPadding ?? this.contentSectionPadding,
footerSectionPadding: footerSectionPadding ?? this.footerSectionPadding,
radius: radius ?? this.radius,
topInset: topInset ?? this.topInset,
);
}
}

View File

@@ -0,0 +1,263 @@
//s1 Imports
//s2 Core Package Imports
import 'package:flutter/material.dart';
//s2 1st-party Package Imports
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
//s3 Models & Widgets
import '../../../form/form_helper.astromic.dart';
import '../enums/enums.exports.dart';
import '../models/models.exports.dart';
//s1 Exports
class BaseSheetWidget<T> extends StatefulWidget {
//SECTION - Widget Arguments
final SheetType sheetType;
//
final AstromicSheetConfiguration? sheetConfiguration;
final AstromicSheetStyle? sheetStyle;
//
final Widget? headSection;
final Widget? contentSection;
final Widget? footerSection;
//
final Widget Function(AstromicFormController)? headSectionFormBuilder;
final Widget Function(AstromicFormController)? contentSectionFormBuilder;
final Widget Function(AstromicFormController)? footerSectionFormBuilder;
//
final ScrollController? scrollController;
final ScrollPhysics? scrollPhysics;
final Widget Function(ScrollController)? headSectionScrollerBuilder;
final Widget Function(ScrollController, ScrollPhysics)? contentSectionScrollBuilder;
final Widget Function(ScrollController)? footerSectionScrollerBuilder;
//!SECTION
const BaseSheetWidget({
super.key,
this.sheetType = SheetType.flex,
this.sheetConfiguration,
this.sheetStyle,
this.headSection,
this.contentSection,
this.footerSection,
this.contentSectionFormBuilder,
this.footerSectionFormBuilder,
this.headSectionFormBuilder,
this.scrollController,
this.scrollPhysics,
this.headSectionScrollerBuilder,
this.contentSectionScrollBuilder,
this.footerSectionScrollerBuilder,
//
}) : assert(sheetType != SheetType.flex || (sheetType == SheetType.flex && contentSection != null)),
assert(sheetType != SheetType.form || (sheetType == SheetType.form && contentSectionFormBuilder != null)),
assert(sheetType != SheetType.scroller || (sheetType == SheetType.scroller && contentSectionScrollBuilder != null && scrollController != null && scrollPhysics != null));
@override
State<BaseSheetWidget<T>> createState() => _BaseSheetWidgetState<T>();
}
class _BaseSheetWidgetState<T> extends State<BaseSheetWidget<T>> {
//
//SECTION - State Variables
//s1 --Controllers
late AstromicFormController _formController;
late AstromicSheetConfiguration _config;
late AstromicSheetStyle _style;
//s1 --Controllers
//
//s1 --State
//s1 --State
//
//s1 --Constants
//s1 --Constants
//!SECTION
@override
void initState() {
super.initState();
//
//SECTION - State Variables initializations & Listeners
//s1 --Controllers & Listeners
_formController = AstromicFormController();
//s1 --Controllers & Listeners
//
//s1 --State
//s1 --State
//!SECTION
}
//SECTION - Stateless functions
//!SECTION
//SECTION - Action Callbacks
//!SECTION
@override
Widget build(BuildContext context) {
//SECTION - Build Setup
//s1 -Values
_config = widget.sheetConfiguration ?? const AstromicSheetConfiguration();
_style = widget.sheetStyle ?? const AstromicSheetStyle();
//s1 -Values
//s1 -Widgets
List<Widget> flexChildren = <Widget>[
widget.headSection != null
? ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(_style.radius),
),
child: Container(
padding: _style.headSectionPadding,
child: widget.headSection,
),
)
: Container(),
Container(
color: _style.contentSectionColor,
padding: _style.contentSectionPadding,
child: widget.contentSection != null ? widget.contentSection! : Container(),
),
widget.footerSection != null
? Container(
color: _style.footerSectionColor,
padding: _style.footerSectionPadding,
child: widget.footerSection,
)
: Container()
];
List<Widget> formChildren = <Widget>[
widget.headSectionFormBuilder != null
? ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(_style.radius),
),
child: Container(
padding: _style.headSectionPadding,
child: widget.headSectionFormBuilder!(_formController),
),
)
: Container(),
Container(
color: _style.contentSectionColor,
padding: _style.contentSectionPadding,
child: widget.contentSectionFormBuilder != null ? widget.contentSectionFormBuilder!(_formController) : Container(),
),
widget.footerSectionFormBuilder != null
? Container(
color: _style.footerSectionColor,
padding: _style.footerSectionPadding,
child: widget.footerSectionFormBuilder != null ? widget.footerSectionFormBuilder!(_formController) : Container(),
)
: Container()
];
List<Widget> scrollerChildren = <Widget>[
widget.headSectionScrollerBuilder != null
? ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(_style.radius),
),
child: Container(
decoration: BoxDecoration(
color: _style.headSectionColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(_style.radius),
),
),
padding: _style.headSectionPadding,
width: double.infinity,
child: widget.headSectionScrollerBuilder!(widget.scrollController!),
),
)
: Container(),
Expanded(
child: Container(
padding: _style.contentSectionPadding,
width: double.infinity,
color: _style.contentSectionColor,
child: widget.contentSectionScrollBuilder != null ? widget.contentSectionScrollBuilder!(widget.scrollController!, widget.scrollPhysics!) : Container(),
),
),
widget.footerSectionScrollerBuilder != null
? Container(
color: _style.footerSectionColor,
padding: _style.footerSectionPadding,
child: widget.footerSectionScrollerBuilder!(widget.scrollController!),
)
: Container()
];
//s1 -Widgets
//!SECTION
//SECTION - Build Return
return PopScope(
canPop: _config.dismissOnBack || _config.enableOutsideInteraction || _config.enableSheetInteraction,
child: Container(
child:
//FLEX
widget.sheetType == SheetType.flex
? Container(
padding: EdgeInsets.only(bottom: _config.respectKeyboardInset ? MediaQuery.of(context).viewInsets.bottom : 0),
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: _style.headSectionColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(_style.radius),
),
),
child: Column(
children: flexChildren,
),
),
)
:
//FORM
widget.sheetType == SheetType.form
? Container(
padding: EdgeInsets.only(bottom: _config.respectKeyboardInset ? MediaQuery.of(context).viewInsets.bottom : 0),
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: _style.headSectionColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(_style.radius),
),
),
child: Form(
key: _formController.key,
child: Column(
children: formChildren,
),
),
))
:
//Scroller
widget.sheetType == SheetType.scroller
? ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(_style.radius),
),
child: Column(
children: scrollerChildren,
),
)
: Container(),
),
);
//!SECTION
}
@override
void dispose() {
//SECTION - Disposable variables
_formController.dispose();
//!SECTION
super.dispose();
}
}

View File

@@ -0,0 +1,63 @@
//s1 Imports
//s2 Core Package Imports
import 'package:flutter/material.dart';
//s2 1st-party Package Imports
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
import '../../../../dependencies/vsync-provider/vsync_provider.dart';
//s3 Models
//s1 Exports
@immutable
class BasicSheet {
//
static Future<T?> show<T extends Object?>({
required BuildContext context,
required VsyncProviderState vsyncState,
required Widget child,
//
bool useRootNavigator = false,
bool enableSheetInteraction = true,
bool enableOutsideInteraction = true,
//
Color barrierColor = const Color(0xb3212121),
double radius = 24,
//
Duration forwardAnimationDuration = const Duration(milliseconds: 250),
Duration reverseAnimationDuration = const Duration(milliseconds: 250),
Curve? animationCurve,
//
}) async {
AnimationController controller = AnimationController(
vsync: vsyncState,
);
// Animation duration for displaying the BottomSheet
controller.duration = forwardAnimationDuration;
// Animation duration for retracting the BottomSheet
controller.reverseDuration = reverseAnimationDuration;
// Set animation curve duration for the BottomSheet
controller.drive(CurveTween(curve: animationCurve ?? Curves.easeOut));
return await showModalBottomSheet<T?>(
backgroundColor: Colors.transparent,
barrierColor: barrierColor,
//
enableDrag: enableSheetInteraction,
isDismissible: enableOutsideInteraction,
useRootNavigator: useRootNavigator,
//
isScrollControlled: true,
transitionAnimationController: controller,
//
context: context,
builder: (BuildContext c) {
return Wrap(children: <Widget>[ClipRRect(borderRadius: BorderRadius.vertical(top: Radius.circular(radius)), child: child)]);
},
);
}
}

View File

@@ -0,0 +1,286 @@
//s1 Imports
//s2 Core Package Imports
import 'dart:math';
import 'package:flutter/material.dart';
//s2 1st-party Package Imports
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
import '../../../../dependencies/vsync-provider/vsync_provider.dart';
//s3 Models
//s1 Exports
typedef StopperBuilder = Widget Function(
BuildContext context,
ScrollController controller,
ScrollPhysics physics,
int stop,
);
class ScrollerSheet<T extends Object?> extends StatefulWidget {
//
final List<double>? stops;
final StopperBuilder? builder;
final int initialStop;
final double dragThreshold;
final Color? barrierColor;
final double? topInset;
//
final ScrollPhysics? physics;
final bool? enableSheetInteraction;
//
ScrollerSheet({
super.key,
required this.builder,
required this.stops,
this.initialStop = 0,
this.barrierColor = Colors.black38,
this.dragThreshold = 25,
this.topInset,
this.physics,
this.enableSheetInteraction = true,
//
}) : assert(initialStop < stops!.length);
@override
ScrollerSheetState<T> createState() => ScrollerSheetState<T>();
static Future<T?> show<T extends Object?>({
Key? key,
required BuildContext context,
required VsyncProviderState vsyncState,
required StopperBuilder? builder,
//
bool useRootNavigator = true,
bool enableSheetInteraction = true,
bool enableOutsideInteraction = true,
bool respectKeyboardInset = true,
ScrollPhysics? physics = const BouncingScrollPhysics(),
//
required List<double>? stops,
int initialStop = 0,
double dragThreshold = 25,
//
Duration? forwardAnimationDuration = const Duration(milliseconds: 250),
Duration? reverseAnimationDuration = const Duration(milliseconds: 250),
Curve? animationCurve,
//
Color barrierColor = const Color(0xb3212121),
double? topInset,
}) async {
Future<T?>? ret;
AnimationController controller = AnimationController(
vsync: vsyncState,
);
controller.duration = forwardAnimationDuration;
controller.reverseDuration = reverseAnimationDuration;
controller.drive(CurveTween(curve: animationCurve ?? Curves.easeOut));
ret = showModalBottomSheet<T?>(
backgroundColor: Colors.transparent,
barrierColor: barrierColor,
//
enableDrag: enableSheetInteraction,
isDismissible: enableOutsideInteraction,
//
isScrollControlled: true,
useRootNavigator: useRootNavigator,
transitionAnimationController: controller,
//
context: context,
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.only(
bottom: respectKeyboardInset ? MediaQuery.of(context).viewInsets.bottom : 0,
),
child: ScrollerSheet<T>(
key: key,
builder: builder,
stops: stops,
initialStop: initialStop,
dragThreshold: dragThreshold,
physics: physics,
topInset: topInset,
enableSheetInteraction: enableSheetInteraction,
),
);
},
);
return ret;
}
}
class ScrollerSheetState<T> extends State<ScrollerSheet<T>> with SingleTickerProviderStateMixin {
List<double>? _stops;
int? _currentStop;
int? _targetStop;
bool _dragging = false;
bool _closing = false;
double? _dragOffset;
double? _closingHeight;
ScrollController? _scrollController;
ScrollPhysics? _scrollPhysics;
Animation<double>? _animation;
AnimationController? _animationController;
Tween<double>? _tween;
ScrollPhysics _getScrollPhysicsForStop(dynamic s) {
if (s == _stops!.length - 1) {
return widget.physics ?? const BouncingScrollPhysics();
} else {
return const NeverScrollableScrollPhysics();
}
}
@override
void initState() {
super.initState();
_stops = widget.stops;
_currentStop = widget.initialStop;
_targetStop = _currentStop;
_scrollController = ScrollController();
_scrollPhysics = _getScrollPhysicsForStop(_currentStop);
_animationController = AnimationController(vsync: this, duration: const Duration(seconds: 2));
final CurvedAnimation curveAnimation = CurvedAnimation(parent: _animationController!, curve: Curves.linear);
_tween = Tween<double>(begin: _stops?[_currentStop!], end: _stops?[_targetStop!]);
_animation = _tween?.animate(curveAnimation);
_scrollController?.addListener(() {
if (_scrollController!.offset < -widget.dragThreshold) {
if (_currentStop != _targetStop || _dragging) return;
if (_currentStop! > 0) {
double h0 = height;
_targetStop = _currentStop! - 1;
_animate(h0, _stops![_targetStop!]);
} else if (!_closing) {
close();
}
}
});
}
@override
void didUpdateWidget(ScrollerSheet<T> oldWidget) {
super.didUpdateWidget(oldWidget);
_stops = widget.stops;
_currentStop = min(_currentStop!, _stops!.length - 1);
_targetStop = min(_currentStop!, _stops!.length - 1);
}
@override
void dispose() {
super.dispose();
_animationController!.dispose();
_scrollController!.dispose();
}
get stop => _currentStop;
set stop(dynamic nextStop) {
_targetStop = max(0, min(_stops!.length - 1, nextStop));
_animate(height, nextStop);
}
bool get canClose {
return widget.enableSheetInteraction!;
}
void close({T? result}) {
if (!_closing && canClose) {
_closingHeight = height;
_animationController!.stop();
_dragging = false;
_closing = true;
Navigator.pop(context);
}
}
void _animate(double from, double to) {
_tween!.begin = from;
_tween!.end = to;
_animationController!.value = 0;
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
if (_scrollController != null && _scrollController!.hasClients) {
if (_scrollController!.offset < 0) {
_scrollController!.animateTo(0, duration: const Duration(seconds: 200), curve: Curves.linear);
}
}
});
_animationController!.fling().then((_) {
_currentStop = _targetStop;
setState(() {
_scrollPhysics = _getScrollPhysicsForStop(_currentStop);
});
});
}
get height {
if (_closing) {
return _closingHeight;
} else if (_dragging) {
return _stops![_currentStop!] + _dragOffset!;
} else if (_animationController!.isAnimating) {
return _animation!.value;
} else {
return _stops![_currentStop!];
}
}
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
top: widget.topInset ?? 0,
),
child: AnimatedBuilder(
animation: _animation!,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails details) {
if (_currentStop != _targetStop) return;
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
if (_scrollController != null && _scrollController!.hasClients) {
_scrollController!.jumpTo(0);
}
});
_dragging = true;
_dragOffset = 0;
setState(() {});
},
onVerticalDragUpdate: (DragUpdateDetails details) {
if (_dragging) {
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
if (_scrollController != null && _scrollController!.hasClients) {
_scrollController!.jumpTo(0);
}
});
_dragOffset = _dragOffset! - details.delta.dy;
setState(() {});
}
},
onVerticalDragEnd: (DragEndDetails details) {
if (!_dragging || _closing) return;
if (_dragOffset! > widget.dragThreshold) {
_targetStop = min(_currentStop! + 1, _stops!.length - 1);
} else if (_dragOffset! < -widget.dragThreshold) {
_targetStop = max(canClose ? -1 : 0, _currentStop! - 1);
}
if (_targetStop! < 0) {
close();
} else {
_dragging = false;
_animate(_stops![_currentStop!] + _dragOffset!, _stops![_targetStop!]);
}
},
child: widget.builder!(context, _scrollController!, _scrollPhysics!, _currentStop!),
),
builder: (BuildContext context, Widget? child) {
return SizedBox(
height: min(_stops![_stops!.length - 1], max(0, height)),
child: ClipRRect(child: Container(color: Colors.transparent, child: child)),
);
}),
);
}
}

View File

@@ -0,0 +1,3 @@
export 'base.dart';
export 'basic.sheet.dart';
export 'scroller.sheet.dart';