adding packages
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
library storyboard;
|
||||
|
||||
export 'src/storyboard.dart';
|
||||
export 'src/nested_app.dart';
|
||||
export 'package:device_frame/device_frame.dart';
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomRect extends CustomClipper<Rect> {
|
||||
final Offset offset;
|
||||
|
||||
CustomRect(this.offset);
|
||||
@override
|
||||
Rect getClip(Size size) {
|
||||
return Rect.fromLTWH(
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
size.width,
|
||||
size.height,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomRect oldClipper) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MediaQueryObserver extends StatefulWidget {
|
||||
final Widget child;
|
||||
final MediaQueryData? data;
|
||||
|
||||
const MediaQueryObserver({
|
||||
required this.child,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
@override
|
||||
_MediaQueryObserverState createState() => _MediaQueryObserverState();
|
||||
}
|
||||
|
||||
class _MediaQueryObserverState extends State<MediaQueryObserver>
|
||||
with WidgetsBindingObserver {
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
setState(() {});
|
||||
super.didChangeMetrics();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
WidgetsBinding.instance?.addObserver(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance?.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MediaQuery(
|
||||
data: widget.data ??
|
||||
MediaQueryData.fromWindow(WidgetsBinding.instance!.window),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'media_query_observer.dart';
|
||||
|
||||
class NestedApp extends StatefulWidget {
|
||||
const NestedApp({
|
||||
Key? key,
|
||||
required this.route,
|
||||
required this.size,
|
||||
required this.customWidget,
|
||||
required this.materialApp,
|
||||
required this.cupertinoApp,
|
||||
required this.widgetsApp,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
final bool customWidget;
|
||||
final CupertinoApp? cupertinoApp;
|
||||
final MaterialApp? materialApp;
|
||||
final WidgetsApp? widgetsApp;
|
||||
final RouteSettings? route;
|
||||
final Size? size;
|
||||
|
||||
@override
|
||||
_NestedAppState createState() => _NestedAppState();
|
||||
}
|
||||
|
||||
class _NestedAppState extends State<NestedApp> {
|
||||
late GlobalObjectKey<NavigatorState> _navKey;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
setup();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
_navKey = GlobalObjectKey<NavigatorState>(this);
|
||||
WidgetsBinding.instance?.addPostFrameCallback((_) {
|
||||
if (widget.route != null) {
|
||||
_navKey.currentState?.pushReplacementNamed(
|
||||
widget.route!.name!,
|
||||
arguments: widget.route!.arguments,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(NestedApp oldWidget) {
|
||||
if (oldWidget.materialApp != widget.materialApp) {
|
||||
if (mounted) setState(setup);
|
||||
}
|
||||
if (oldWidget.cupertinoApp != widget.cupertinoApp) {
|
||||
if (mounted) setState(setup);
|
||||
}
|
||||
if (oldWidget.widgetsApp != widget.widgetsApp) {
|
||||
if (mounted) setState(setup);
|
||||
}
|
||||
// if (oldWidget?.route != widget?.route) {
|
||||
// if (mounted) setState(() => setup());
|
||||
// }
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
Map<String, WidgetBuilder> get routes {
|
||||
if (widget.materialApp != null) return widget.materialApp!.routes!;
|
||||
if (widget.widgetsApp != null) return widget.widgetsApp!.routes!;
|
||||
if (widget.cupertinoApp != null) return widget.cupertinoApp!.routes!;
|
||||
return const <String, WidgetBuilder>{};
|
||||
}
|
||||
|
||||
Locale? get locale {
|
||||
if (widget.materialApp != null) return widget.materialApp?.locale;
|
||||
if (widget.widgetsApp != null) return widget.widgetsApp?.locale;
|
||||
if (widget.cupertinoApp != null) return widget.cupertinoApp?.locale;
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterable<LocalizationsDelegate<dynamic>>? get localizationsDelegates {
|
||||
if (widget.materialApp != null) {
|
||||
return widget.materialApp?.localizationsDelegates;
|
||||
}
|
||||
if (widget.widgetsApp != null) {
|
||||
return widget.widgetsApp?.localizationsDelegates;
|
||||
}
|
||||
if (widget.cupertinoApp != null) {
|
||||
return widget.cupertinoApp?.localizationsDelegates;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterable<Locale> get supportedLocales {
|
||||
if (widget.materialApp != null) return widget.materialApp!.supportedLocales;
|
||||
if (widget.widgetsApp != null) return widget.widgetsApp!.supportedLocales;
|
||||
if (widget.cupertinoApp != null) {
|
||||
return widget.cupertinoApp!.supportedLocales;
|
||||
}
|
||||
return const <Locale>[Locale('en', 'US')];
|
||||
}
|
||||
|
||||
LocaleResolutionCallback? get localeResolutionCallback {
|
||||
if (widget.materialApp != null) {
|
||||
return widget.materialApp?.localeResolutionCallback;
|
||||
}
|
||||
if (widget.widgetsApp != null) {
|
||||
return widget.widgetsApp?.localeResolutionCallback;
|
||||
}
|
||||
if (widget.cupertinoApp != null) {
|
||||
return widget.cupertinoApp?.localeResolutionCallback;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
LocaleListResolutionCallback? get localeListResolutionCallback {
|
||||
if (widget.materialApp != null) {
|
||||
return widget.materialApp?.localeListResolutionCallback;
|
||||
}
|
||||
if (widget.widgetsApp != null) {
|
||||
return widget.widgetsApp?.localeListResolutionCallback;
|
||||
}
|
||||
if (widget.cupertinoApp != null) {
|
||||
return widget.cupertinoApp?.localeListResolutionCallback;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<Type, Action<Intent>>? get actions {
|
||||
if (widget.materialApp != null) return widget.materialApp?.actions;
|
||||
if (widget.widgetsApp != null) return widget.widgetsApp?.actions;
|
||||
if (widget.cupertinoApp != null) return widget.cupertinoApp?.actions;
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<LogicalKeySet, Intent>? get shortcuts {
|
||||
if (widget.materialApp != null) return widget.materialApp?.shortcuts;
|
||||
if (widget.widgetsApp != null) return widget.widgetsApp?.shortcuts;
|
||||
if (widget.cupertinoApp != null) return widget.cupertinoApp?.shortcuts;
|
||||
return null;
|
||||
}
|
||||
|
||||
String get title {
|
||||
if (widget.materialApp != null) return widget.materialApp!.title;
|
||||
if (widget.widgetsApp != null) return widget.widgetsApp!.title;
|
||||
if (widget.cupertinoApp != null) return widget.cupertinoApp!.title;
|
||||
return '';
|
||||
}
|
||||
|
||||
String Function(BuildContext)? get onGenerateTitle {
|
||||
if (widget.materialApp != null) return widget.materialApp?.onGenerateTitle;
|
||||
if (widget.widgetsApp != null) return widget.widgetsApp?.onGenerateTitle;
|
||||
if (widget.cupertinoApp != null) {
|
||||
return widget.cupertinoApp?.onGenerateTitle;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget builder(BuildContext context, Widget child) {
|
||||
if (widget.materialApp?.builder != null) {
|
||||
return widget.materialApp!.builder!(context, child);
|
||||
}
|
||||
if (widget.widgetsApp?.builder != null) {
|
||||
return widget.widgetsApp!.builder!(context, child);
|
||||
}
|
||||
if (widget.cupertinoApp?.builder != null) {
|
||||
return widget.cupertinoApp!.builder!(context, child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.customWidget) {
|
||||
return MaterialApp(
|
||||
theme: widget.materialApp?.theme ?? ThemeData.light(),
|
||||
darkTheme: widget.materialApp?.darkTheme ?? ThemeData.dark(),
|
||||
themeMode: widget.materialApp?.themeMode ?? ThemeMode.system,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: widget.child,
|
||||
builder: (context, val) => MediaQueryObserver(
|
||||
data: MediaQuery.of(context).copyWith(size: widget.size),
|
||||
child: builder(context, val ?? Container()),
|
||||
),
|
||||
color: widget.materialApp?.color,
|
||||
locale: locale,
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
supportedLocales: supportedLocales,
|
||||
localeResolutionCallback: localeResolutionCallback,
|
||||
localeListResolutionCallback: localeListResolutionCallback,
|
||||
actions: actions,
|
||||
shortcuts: shortcuts,
|
||||
title: title,
|
||||
onGenerateTitle: onGenerateTitle,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.materialApp != null) {
|
||||
return MaterialApp(
|
||||
navigatorKey: _navKey,
|
||||
theme: widget.materialApp?.theme ?? ThemeData.light(),
|
||||
darkTheme: widget.materialApp?.darkTheme ?? ThemeData.dark(),
|
||||
themeMode: widget.materialApp?.themeMode ?? ThemeMode.system,
|
||||
debugShowCheckedModeBanner: false,
|
||||
routes: routes,
|
||||
onGenerateRoute: widget.materialApp?.onGenerateRoute,
|
||||
onUnknownRoute: widget.materialApp?.onUnknownRoute,
|
||||
onGenerateInitialRoutes: widget.materialApp?.onGenerateInitialRoutes,
|
||||
home: widget.child,
|
||||
builder: (context, val) => MediaQueryObserver(
|
||||
data: MediaQuery.of(context).copyWith(size: widget.size),
|
||||
child: builder(context, val ?? Container()),
|
||||
),
|
||||
// initialRoute: route?.name,
|
||||
color: widget.materialApp?.color,
|
||||
locale: locale,
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
supportedLocales: supportedLocales,
|
||||
localeResolutionCallback: localeResolutionCallback,
|
||||
localeListResolutionCallback: localeListResolutionCallback,
|
||||
actions: actions,
|
||||
shortcuts: shortcuts,
|
||||
title: title,
|
||||
onGenerateTitle: onGenerateTitle,
|
||||
);
|
||||
}
|
||||
if (widget.cupertinoApp != null) {
|
||||
return CupertinoApp(
|
||||
navigatorKey: _navKey,
|
||||
theme: widget.cupertinoApp?.theme ?? const CupertinoThemeData(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
routes: routes,
|
||||
onGenerateRoute: widget.cupertinoApp?.onGenerateRoute,
|
||||
onUnknownRoute: widget.cupertinoApp?.onUnknownRoute,
|
||||
onGenerateInitialRoutes: widget.cupertinoApp?.onGenerateInitialRoutes,
|
||||
home: widget.child,
|
||||
builder: (context, val) => MediaQueryObserver(
|
||||
data: MediaQuery.of(context).copyWith(size: widget.size),
|
||||
child: builder(context, val ?? Container()),
|
||||
),
|
||||
// initialRoute: route?.name,
|
||||
color: widget.cupertinoApp?.color,
|
||||
locale: locale,
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
supportedLocales: supportedLocales,
|
||||
localeResolutionCallback: localeResolutionCallback,
|
||||
localeListResolutionCallback: localeListResolutionCallback,
|
||||
actions: actions,
|
||||
shortcuts: shortcuts,
|
||||
title: title,
|
||||
onGenerateTitle: onGenerateTitle,
|
||||
);
|
||||
}
|
||||
if (widget.widgetsApp != null) {
|
||||
return WidgetsApp(
|
||||
navigatorKey: _navKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
routes: routes,
|
||||
onGenerateRoute: widget.widgetsApp?.onGenerateRoute,
|
||||
onUnknownRoute: widget.widgetsApp?.onUnknownRoute,
|
||||
onGenerateInitialRoutes: widget.widgetsApp?.onGenerateInitialRoutes,
|
||||
home: widget.child,
|
||||
builder: (context, val) => MediaQueryObserver(
|
||||
data: MediaQuery.of(context).copyWith(size: widget.size),
|
||||
child: builder(context, val ?? Container()),
|
||||
),
|
||||
// initialRoute: route?.name,
|
||||
color: widget.widgetsApp?.color ?? Colors.transparent,
|
||||
locale: locale,
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
supportedLocales: supportedLocales,
|
||||
localeResolutionCallback: localeResolutionCallback,
|
||||
localeListResolutionCallback: localeListResolutionCallback,
|
||||
actions: actions,
|
||||
shortcuts: shortcuts,
|
||||
title: title,
|
||||
onGenerateTitle: onGenerateTitle,
|
||||
);
|
||||
}
|
||||
return widget.child ?? Container();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
part of storyboard;
|
||||
|
||||
class CustomLane {
|
||||
final String title;
|
||||
final List<CustomScreen> children;
|
||||
|
||||
CustomLane({
|
||||
required this.title,
|
||||
required this.children,
|
||||
});
|
||||
}
|
||||
|
||||
class CustomScreen {
|
||||
final String? label;
|
||||
final Size? size;
|
||||
final Widget child;
|
||||
final DeviceInfo? cupertinoDevice;
|
||||
final DeviceInfo? androidDevice;
|
||||
final Orientation? orientation;
|
||||
|
||||
CustomScreen({
|
||||
required this.size,
|
||||
required this.child,
|
||||
this.label,
|
||||
this.androidDevice,
|
||||
this.cupertinoDevice,
|
||||
this.orientation = Orientation.portrait,
|
||||
});
|
||||
|
||||
CustomScreen copyWith({
|
||||
String? label,
|
||||
Size? size,
|
||||
Widget? child,
|
||||
DeviceInfo? cupertinoDevice,
|
||||
DeviceInfo? androidDevice,
|
||||
Orientation? orientation,
|
||||
}) {
|
||||
return CustomScreen(
|
||||
label: label ?? this.label,
|
||||
size: size ?? this.size,
|
||||
child: child ?? this.child,
|
||||
cupertinoDevice: cupertinoDevice ?? this.cupertinoDevice,
|
||||
androidDevice: androidDevice ?? this.androidDevice,
|
||||
orientation: orientation ?? this.orientation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef LaneBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
String? title,
|
||||
Widget child,
|
||||
);
|
||||
|
||||
class _Lane extends StatelessWidget {
|
||||
const _Lane({
|
||||
Key? key,
|
||||
required this.children,
|
||||
required this.crossAxisCount,
|
||||
required this.scale,
|
||||
required this.androidDevice,
|
||||
required this.cupertinoDevice,
|
||||
this.title,
|
||||
this.itemBuilder,
|
||||
this.laneBuilder,
|
||||
this.size,
|
||||
this.shadow,
|
||||
this.orientation = Orientation.portrait,
|
||||
}) : super(key: key);
|
||||
|
||||
final List<CustomScreen> children;
|
||||
final int? crossAxisCount;
|
||||
final double scale;
|
||||
final DeviceInfo? cupertinoDevice;
|
||||
final DeviceInfo? androidDevice;
|
||||
final Orientation? orientation;
|
||||
final Widget Function(BuildContext, Widget)? itemBuilder;
|
||||
final LaneBuilder? laneBuilder;
|
||||
final String? title;
|
||||
final Size? size;
|
||||
final BoxShadow? shadow;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<CustomScreen> _children = [];
|
||||
for (final child in children) {
|
||||
if (itemBuilder != null) {
|
||||
_children
|
||||
.add(child.copyWith(child: itemBuilder!(context, child.child)));
|
||||
} else {
|
||||
_children.add(child);
|
||||
}
|
||||
}
|
||||
int _itemsPerRow;
|
||||
int _rows;
|
||||
if (crossAxisCount != null) {
|
||||
_rows = (_children.length / crossAxisCount!).ceil();
|
||||
_itemsPerRow = crossAxisCount!;
|
||||
} else {
|
||||
_rows = 1;
|
||||
_itemsPerRow = _children.length;
|
||||
}
|
||||
Widget _child;
|
||||
_child = buildWrapLane(_rows, _children, _itemsPerRow, context);
|
||||
if (laneBuilder != null) {
|
||||
return laneBuilder!(context, title, _child);
|
||||
}
|
||||
return _child;
|
||||
}
|
||||
|
||||
Widget buildWrapLane(int _rows, List<CustomScreen> _children,
|
||||
int _itemsPerRow, BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(_kSpacing / 2),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (var i = 0; i < _rows; i++)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _children
|
||||
.skip(i * _itemsPerRow)
|
||||
.take(_itemsPerRow)
|
||||
.toList()
|
||||
.map((e) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(_kSpacing / 2),
|
||||
child: _buildChild(context, e),
|
||||
),
|
||||
if (e.label != null)
|
||||
SizedBox(
|
||||
height: _kLabelHeight,
|
||||
child: Center(child: RoundedLabel(e.label!)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChild(BuildContext context, CustomScreen e) {
|
||||
return Container(
|
||||
decoration: shadow == null ? null : BoxDecoration(boxShadow: [shadow!]),
|
||||
child: MediaQueryObserver(
|
||||
data: MediaQuery.of(context).copyWith(size: size),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
Widget _child = e.child;
|
||||
if (itemBuilder != null) {
|
||||
_child = itemBuilder!(context, _child);
|
||||
}
|
||||
DeviceInfo? _ios = e.cupertinoDevice ?? cupertinoDevice;
|
||||
DeviceInfo? _android = e.androidDevice ?? androidDevice;
|
||||
Orientation _orientation =
|
||||
e.orientation ?? orientation ?? Orientation.portrait;
|
||||
if (_ios != null) {
|
||||
return DeviceFrame(
|
||||
orientation: _orientation,
|
||||
device: _ios,
|
||||
screen: VirtualKeyboard(
|
||||
isEnabled: true,
|
||||
child: _child,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (_android != null) {
|
||||
return DeviceFrame(
|
||||
orientation: _orientation,
|
||||
device: _android,
|
||||
screen: VirtualKeyboard(
|
||||
isEnabled: true,
|
||||
child: _child,
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox.fromSize(
|
||||
size: e.size,
|
||||
child: _child,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RoundedLabel extends StatelessWidget {
|
||||
final String? label;
|
||||
|
||||
const RoundedLabel(this.label);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsetsDirectional.only(top: 10),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
label ?? '',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 20),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
library storyboard;
|
||||
|
||||
import 'package:device_frame/device_frame.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'custom_rect_clipper.dart';
|
||||
import 'media_query_observer.dart';
|
||||
import 'nested_app.dart';
|
||||
import 'rounded_label.dart';
|
||||
|
||||
part 'render_lane.dart';
|
||||
|
||||
const _kSpacing = 40.0;
|
||||
const _kLabelHeight = 65.0;
|
||||
const _kCompactBreakpoint = 400.0;
|
||||
const double _MAXSIZE = 100000;
|
||||
const double _MINSCALE = 0.0001;
|
||||
const double _STARTSCALE = 0.35;
|
||||
const _kTitle = 'Storyboard';
|
||||
|
||||
typedef ScreenBuilder = Widget Function(
|
||||
BuildContext context, RouteSettings route, Widget child);
|
||||
|
||||
class StoryBoard extends StatefulWidget {
|
||||
const StoryBoard({
|
||||
Key? key,
|
||||
this.children,
|
||||
this.customLanes,
|
||||
this.sizedChildren,
|
||||
bool showAppBar = false,
|
||||
Size? childSize,
|
||||
this.initialOffset = Offset.zero,
|
||||
this.initialScale = _STARTSCALE,
|
||||
this.customAppBar,
|
||||
this.crossAxisCount,
|
||||
this.title = _kTitle,
|
||||
this.laneBuilder,
|
||||
this.childrenLabel = 'Children',
|
||||
this.sizedChildrenLabel = 'Sized Children',
|
||||
this.orientation,
|
||||
this.androidDevice,
|
||||
this.cupertinoDevice,
|
||||
this.deviceFrameStyle,
|
||||
}) : _materialApp = null,
|
||||
_widgetsApp = null,
|
||||
_cupertinoApp = null,
|
||||
_widgets = true,
|
||||
screenSize = childSize,
|
||||
customRoutes = null,
|
||||
hideAppBar = !showAppBar,
|
||||
enabled = true,
|
||||
assert(
|
||||
children != null || customLanes != null || sizedChildren != null),
|
||||
super(key: key);
|
||||
|
||||
const StoryBoard.app({
|
||||
Key? key,
|
||||
MaterialApp? materialApp,
|
||||
WidgetsApp? widgetsApp,
|
||||
CupertinoApp? cupertinoApp,
|
||||
List<Widget>? customScreens,
|
||||
this.enabled = true,
|
||||
this.screenSize = const Size(400, 700),
|
||||
this.initialOffset = Offset.zero,
|
||||
this.initialScale = _STARTSCALE,
|
||||
this.customLanes,
|
||||
this.customAppBar,
|
||||
this.customRoutes,
|
||||
this.crossAxisCount,
|
||||
this.title = _kTitle,
|
||||
this.laneBuilder,
|
||||
this.childrenLabel = 'Children',
|
||||
this.sizedChildrenLabel = 'Sized Children',
|
||||
this.sizedChildren,
|
||||
this.orientation,
|
||||
this.androidDevice,
|
||||
this.cupertinoDevice,
|
||||
this.deviceFrameStyle,
|
||||
}) : _materialApp = materialApp,
|
||||
_widgetsApp = widgetsApp,
|
||||
_cupertinoApp = cupertinoApp,
|
||||
children = customScreens,
|
||||
_widgets = false,
|
||||
hideAppBar = false,
|
||||
assert(
|
||||
materialApp != null || widgetsApp != null || cupertinoApp != null),
|
||||
super(key: key);
|
||||
|
||||
const StoryBoard.cupertino({
|
||||
Key? key,
|
||||
required CupertinoApp child,
|
||||
List<Widget>? customScreens,
|
||||
this.enabled = true,
|
||||
this.screenSize = const Size(400, 700),
|
||||
this.initialOffset = Offset.zero,
|
||||
this.initialScale = _STARTSCALE,
|
||||
this.customLanes,
|
||||
this.customAppBar,
|
||||
this.customRoutes,
|
||||
this.crossAxisCount,
|
||||
this.title = _kTitle,
|
||||
this.laneBuilder,
|
||||
this.sizedChildren,
|
||||
this.childrenLabel = 'Children',
|
||||
this.sizedChildrenLabel = 'Sized Children',
|
||||
this.orientation,
|
||||
this.androidDevice,
|
||||
this.cupertinoDevice,
|
||||
this.deviceFrameStyle,
|
||||
}) : _materialApp = null,
|
||||
_widgetsApp = null,
|
||||
_cupertinoApp = child,
|
||||
children = customScreens,
|
||||
_widgets = false,
|
||||
hideAppBar = false,
|
||||
super(key: key);
|
||||
|
||||
const StoryBoard.widgets({
|
||||
Key? key,
|
||||
required WidgetsApp child,
|
||||
List<Widget>? customScreens,
|
||||
this.enabled = true,
|
||||
this.screenSize = const Size(400, 700),
|
||||
this.initialOffset = Offset.zero,
|
||||
this.initialScale = _STARTSCALE,
|
||||
this.customLanes,
|
||||
this.customAppBar,
|
||||
this.customRoutes,
|
||||
this.crossAxisCount,
|
||||
this.title = _kTitle,
|
||||
this.laneBuilder,
|
||||
this.sizedChildren,
|
||||
this.childrenLabel = 'Children',
|
||||
this.sizedChildrenLabel = 'Sized Children',
|
||||
this.orientation,
|
||||
this.androidDevice,
|
||||
this.cupertinoDevice,
|
||||
this.deviceFrameStyle,
|
||||
}) : _materialApp = null,
|
||||
_widgetsApp = child,
|
||||
_cupertinoApp = null,
|
||||
children = customScreens,
|
||||
_widgets = false,
|
||||
hideAppBar = false,
|
||||
super(key: key);
|
||||
|
||||
const StoryBoard.material({
|
||||
Key? key,
|
||||
required MaterialApp child,
|
||||
List<Widget>? customScreens,
|
||||
this.enabled = true,
|
||||
this.screenSize = const Size(400, 700),
|
||||
this.initialOffset = Offset.zero,
|
||||
this.initialScale = _STARTSCALE,
|
||||
this.customLanes,
|
||||
this.customAppBar,
|
||||
this.customRoutes,
|
||||
this.crossAxisCount,
|
||||
this.title = _kTitle,
|
||||
this.laneBuilder,
|
||||
this.sizedChildren,
|
||||
this.childrenLabel = 'Children',
|
||||
this.sizedChildrenLabel = 'Sized Children',
|
||||
this.orientation,
|
||||
this.androidDevice,
|
||||
this.cupertinoDevice,
|
||||
this.deviceFrameStyle,
|
||||
}) : _materialApp = child,
|
||||
_widgetsApp = null,
|
||||
_cupertinoApp = null,
|
||||
children = customScreens,
|
||||
_widgets = false,
|
||||
hideAppBar = false,
|
||||
super(key: key);
|
||||
|
||||
/// Override the default app bar of the storyboard
|
||||
final PreferredSizeWidget? customAppBar;
|
||||
|
||||
/// Custom routes you want to show with test data
|
||||
final List<RouteSettings>? customRoutes;
|
||||
|
||||
/// Custom screens you want to show
|
||||
final List<Widget>? children;
|
||||
|
||||
/// Children that can have custom sizes
|
||||
final List<CustomScreen>? sizedChildren;
|
||||
|
||||
/// List of custom lanes
|
||||
final List<CustomLane>? customLanes;
|
||||
|
||||
/// You can disable this widget at any time and just return the child
|
||||
final bool enabled;
|
||||
|
||||
/// Initial Offset of the canvas
|
||||
final Offset initialOffset;
|
||||
|
||||
/// Initial Scale of the canvas
|
||||
final double initialScale;
|
||||
|
||||
/// Size for each screen
|
||||
final Size? screenSize;
|
||||
|
||||
/// Wrap your Cupertino App with this widget
|
||||
final CupertinoApp? _cupertinoApp;
|
||||
|
||||
/// Wrap your Material App with this widget
|
||||
final MaterialApp? _materialApp;
|
||||
|
||||
/// Wrap your Widgets App with this widget
|
||||
final WidgetsApp? _widgetsApp;
|
||||
|
||||
/// Number of widgets to show in a row instead of maxLaneWidth
|
||||
final int? crossAxisCount;
|
||||
|
||||
/// Optionally hide the AppBar if used as a widget
|
||||
final bool hideAppBar;
|
||||
|
||||
/// Title for AppBar
|
||||
final String title;
|
||||
|
||||
/// Optionally wrap the lane building process to customize the look
|
||||
final LaneBuilder? laneBuilder;
|
||||
|
||||
/// Label for Custom Screens or Children
|
||||
final String? childrenLabel, sizedChildrenLabel;
|
||||
|
||||
/// Use a cupertino device for every screen and ignore the size
|
||||
final DeviceInfo? cupertinoDevice;
|
||||
|
||||
/// Use a android device for every screen and ignore the size
|
||||
final DeviceInfo? androidDevice;
|
||||
|
||||
/// Device orientation for every screen and ignore the size
|
||||
final Orientation? orientation;
|
||||
|
||||
/// Device Frame style
|
||||
final DeviceFrameStyle? deviceFrameStyle;
|
||||
|
||||
final bool _widgets;
|
||||
|
||||
@override
|
||||
StoryboardController createState() => StoryboardController();
|
||||
}
|
||||
|
||||
class StoryboardController extends State<StoryBoard> {
|
||||
final TransformationController _controller = TransformationController();
|
||||
late FocusNode _focusNode;
|
||||
Offset _offset = Offset.zero;
|
||||
double _scale = 1;
|
||||
UniqueKey _key = UniqueKey();
|
||||
|
||||
@override
|
||||
void didUpdateWidget(StoryBoard oldWidget) {
|
||||
if (oldWidget._widgetsApp != widget._widgetsApp) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
if (oldWidget._materialApp != widget._materialApp) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
if (oldWidget._cupertinoApp != widget._cupertinoApp) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
if (oldWidget.cupertinoDevice != widget.cupertinoDevice) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
if (oldWidget.androidDevice != widget.androidDevice) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
if (oldWidget.orientation != widget.orientation) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_focusNode = FocusNode();
|
||||
_controller.value.scale(widget.initialScale);
|
||||
final _offset = widget.initialOffset;
|
||||
_controller.value.translate(_offset.dx, _offset.dy);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void restart() {
|
||||
_key = UniqueKey();
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Widget get app {
|
||||
if (materialApp != null) return materialApp!;
|
||||
if (widgetsApp != null) return widgetsApp!;
|
||||
if (cupertinoApp != null) return cupertinoApp!;
|
||||
return Container();
|
||||
}
|
||||
|
||||
MaterialApp? get materialApp => widget._materialApp;
|
||||
WidgetsApp? get widgetsApp => widget._widgetsApp;
|
||||
CupertinoApp? get cupertinoApp => widget._cupertinoApp;
|
||||
|
||||
Widget? get home {
|
||||
if (materialApp != null) return materialApp?.home;
|
||||
if (widgetsApp != null) return widgetsApp?.home;
|
||||
if (cupertinoApp != null) return cupertinoApp?.home;
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, WidgetBuilder>? get routes {
|
||||
if (materialApp != null) return materialApp?.routes;
|
||||
if (widgetsApp != null) return widgetsApp?.routes;
|
||||
if (cupertinoApp != null) return cupertinoApp?.routes;
|
||||
return null;
|
||||
}
|
||||
|
||||
String? get initialRoute {
|
||||
if (materialApp != null) return materialApp?.initialRoute;
|
||||
if (widgetsApp != null) return widgetsApp?.initialRoute;
|
||||
if (cupertinoApp != null) return cupertinoApp?.initialRoute;
|
||||
return null;
|
||||
}
|
||||
|
||||
Size? get size {
|
||||
if (widget.screenSize != null) {
|
||||
return Size(
|
||||
widget.screenSize!.width,
|
||||
widget.screenSize!.height + _kLabelHeight,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
CustomScreen _addChild(
|
||||
RouteSettings? route, {
|
||||
Widget? child,
|
||||
String? label,
|
||||
bool customWidget = false,
|
||||
Size? customSize,
|
||||
}) {
|
||||
final Size? _size = customSize ?? widget.screenSize ?? size;
|
||||
return CustomScreen(
|
||||
size: size,
|
||||
child: _buildApp(_size, route, child, customWidget),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildApp(
|
||||
Size? _size,
|
||||
RouteSettings? route,
|
||||
Widget? child,
|
||||
bool customWidget,
|
||||
) {
|
||||
return Material(
|
||||
elevation: 2,
|
||||
child: SizedBox.fromSize(
|
||||
size: _size,
|
||||
child: ClipRect(
|
||||
clipper: CustomRect(const Offset(0, 0)),
|
||||
child: NestedApp(
|
||||
route: route,
|
||||
size: _size,
|
||||
customWidget: customWidget,
|
||||
materialApp: materialApp,
|
||||
cupertinoApp: cupertinoApp,
|
||||
widgetsApp: widgetsApp,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.enabled) return app;
|
||||
// ignore: unnecessary_nullable_for_final_variable_declarations
|
||||
final ThemeData? _theme = Theme.of(context);
|
||||
return MaterialApp(
|
||||
key: _key,
|
||||
debugShowCheckedModeBanner: false,
|
||||
themeMode: materialApp?.themeMode ?? ThemeMode.system,
|
||||
theme: materialApp?.theme ?? _theme ?? ThemeData.light(),
|
||||
darkTheme: materialApp?.darkTheme ?? _theme ?? ThemeData.dark(),
|
||||
// builder: (context, child) => MediaQueryObserver(
|
||||
// data: MediaQuery.of(context).copyWith(size: size),
|
||||
// child: child,
|
||||
// ),
|
||||
home: Builder(
|
||||
builder: (context) => DeviceFrameTheme(
|
||||
// style: Theme.of(context).brightness == Brightness.dark
|
||||
// ? DeviceFrameStyle.dark()
|
||||
// : DeviceFrameStyle.light(),
|
||||
style: widget.deviceFrameStyle ?? DeviceFrameStyle.dark(),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, dimens) => Scaffold(
|
||||
appBar: _buildAppBar(dimens.maxWidth <= _kCompactBreakpoint),
|
||||
body: InteractiveViewer(
|
||||
transformationController: _controller,
|
||||
constrained: false,
|
||||
minScale: _MINSCALE,
|
||||
child: SizedBox(
|
||||
width: _MAXSIZE,
|
||||
height: _MAXSIZE,
|
||||
child: renderView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget renderView() {
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
// overflow: Overflow.visible,
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
if (widget._widgets)
|
||||
Positioned(
|
||||
top: _offset.dy,
|
||||
left: _offset.dx,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.children != null)
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
laneBuilder: widget.laneBuilder,
|
||||
title: widget.childrenLabel ?? '',
|
||||
scale: _scale,
|
||||
size: size,
|
||||
children: widget.children!
|
||||
.map(
|
||||
(e) => CustomScreen(
|
||||
size: size,
|
||||
child: e,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
shadow: const BoxShadow(
|
||||
color: Colors.black45,
|
||||
offset: Offset(2, 2),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 2,
|
||||
),
|
||||
),
|
||||
if (widget.sizedChildren != null)
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
laneBuilder: widget.laneBuilder,
|
||||
title: widget.sizedChildrenLabel ?? '',
|
||||
scale: _scale,
|
||||
size: size,
|
||||
children: widget.sizedChildren!,
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
shadow: const BoxShadow(
|
||||
color: Colors.black45,
|
||||
offset: Offset(2, 2),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 2,
|
||||
),
|
||||
),
|
||||
if (widget.customLanes != null)
|
||||
for (final lane in widget.customLanes!)
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
laneBuilder: widget.laneBuilder,
|
||||
title: lane.title,
|
||||
scale: _scale,
|
||||
size: size,
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
children: lane.children,
|
||||
shadow: const BoxShadow(
|
||||
color: Colors.black45,
|
||||
offset: Offset(2, 2),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!widget._widgets) ...[
|
||||
Positioned(
|
||||
top: _offset.dy,
|
||||
left: _offset.dx,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
scale: _scale,
|
||||
title: 'Main',
|
||||
laneBuilder: widget.laneBuilder,
|
||||
size: size,
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
children: [
|
||||
if (initialRoute != null)
|
||||
_addChild(
|
||||
RouteSettings(name: initialRoute),
|
||||
label: 'Initial Route',
|
||||
),
|
||||
if (home != null)
|
||||
_addChild(
|
||||
null,
|
||||
label: 'Home',
|
||||
child: home,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (routes != null)
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
title: 'Routes',
|
||||
scale: _scale,
|
||||
laneBuilder: widget.laneBuilder,
|
||||
size: size,
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
children: [
|
||||
for (var i = 0; i < routes!.keys.length; i++)
|
||||
_addChild(
|
||||
RouteSettings(name: routes!.keys.toList()[i]),
|
||||
label: routes!.keys.toList()[i],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.children != null)
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
laneBuilder: widget.laneBuilder,
|
||||
title: widget.childrenLabel ?? '',
|
||||
scale: _scale,
|
||||
size: size,
|
||||
children: widget.children!
|
||||
.map((e) => _addChild(
|
||||
null,
|
||||
label: null,
|
||||
child: e,
|
||||
customWidget: true,
|
||||
))
|
||||
.toList(),
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
),
|
||||
if (widget.customRoutes != null)
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
laneBuilder: widget.laneBuilder,
|
||||
title: 'Custom Routes',
|
||||
scale: _scale,
|
||||
size: size,
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
children: [
|
||||
for (var i = 0; i < widget.customRoutes!.length; i++)
|
||||
_addChild(
|
||||
widget.customRoutes![i],
|
||||
label: widget.customRoutes![i].name,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.customLanes != null)
|
||||
for (final lane in widget.customLanes!)
|
||||
_Lane(
|
||||
cupertinoDevice: widget.cupertinoDevice,
|
||||
androidDevice: widget.androidDevice,
|
||||
orientation: widget.orientation,
|
||||
laneBuilder: widget.laneBuilder,
|
||||
title: lane.title,
|
||||
scale: _scale,
|
||||
size: size,
|
||||
crossAxisCount: widget.crossAxisCount,
|
||||
children: lane.children
|
||||
.map((e) => _addChild(
|
||||
null,
|
||||
label: e.label,
|
||||
child: e.child,
|
||||
customSize: e.size,
|
||||
customWidget: true,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
PreferredSizeWidget? _buildAppBar([bool compact = false]) {
|
||||
const kRestartKey = 0;
|
||||
const kResetKey = 1;
|
||||
const kZoomInKey = 2;
|
||||
const kZoomOutKey = 4;
|
||||
if (widget.hideAppBar) {
|
||||
return null;
|
||||
} else {
|
||||
return widget.customAppBar ??
|
||||
AppBar(
|
||||
title: const Text(_kTitle),
|
||||
actions: compact
|
||||
? [
|
||||
PopupMenuButton<int>(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) => [
|
||||
const PopupMenuItem(
|
||||
value: kRestartKey,
|
||||
child: Text('Restart App'),
|
||||
),
|
||||
],
|
||||
onSelected: (val) {
|
||||
if (val == kRestartKey) {
|
||||
restart();
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
: [
|
||||
IconButton(
|
||||
tooltip: 'Restart App',
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: restart,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user