From 9e19286d1a96a69b6860436dcc65504f0d558038 Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Thu, 8 Aug 2024 18:16:53 -0400 Subject: [PATCH] wip --- packages/superdeck/build.yaml | 8 +- packages/superdeck/example/ios/Podfile.lock | 10 +- .../example/ios/Runner/AppDelegate.swift | 2 +- packages/superdeck/example/lib/main.dart | 2 - packages/superdeck/example/pubspec.yaml | 1 - .../superdeck/example/pubspec_overrides.yaml | 2 +- .../lib/components/atoms/slide_thumbnail.dart | 135 +++++++----------- .../components/molecules/navigation_rail.dart | 68 +++++++++ .../components/molecules/slide_preview.dart | 2 +- .../molecules/slide_thumbnail_list.dart | 61 +++----- .../lib/components/molecules/split_view.dart | 65 +++++---- .../lib/components/organisms/app_shell.dart | 61 +------- .../lib/components/remix/button.dart | 52 +++++++ .../lib/components/remix/button_style.dart | 0 .../lib/components/superdeck_app.dart | 133 +++++------------ .../superdeck/lib/helpers/dialog_page.dart | 42 ++++++ packages/superdeck/lib/helpers/hooks.dart | 19 +++ packages/superdeck/lib/helpers/routes.dart | 68 +++++++++ packages/superdeck/lib/helpers/theme.dart | 6 +- .../superdeck/lib/providers/controller.dart | 19 ++- .../superdeck/lib/screens/export_screen.dart | 49 ++++--- ...e_screen.dart => presentation_screen.dart} | 21 ++- packages/superdeck/pubspec.yaml | 6 + packages/superdeck/pubspec_overrides.yaml | 11 ++ 24 files changed, 483 insertions(+), 360 deletions(-) create mode 100644 packages/superdeck/lib/components/molecules/navigation_rail.dart create mode 100644 packages/superdeck/lib/components/remix/button.dart create mode 100644 packages/superdeck/lib/components/remix/button_style.dart create mode 100644 packages/superdeck/lib/helpers/dialog_page.dart create mode 100644 packages/superdeck/lib/helpers/routes.dart rename packages/superdeck/lib/screens/{home_screen.dart => presentation_screen.dart} (64%) create mode 100644 packages/superdeck/pubspec_overrides.yaml diff --git a/packages/superdeck/build.yaml b/packages/superdeck/build.yaml index 00c5e916..a77b33df 100644 --- a/packages/superdeck/build.yaml +++ b/packages/superdeck/build.yaml @@ -6,16 +6,16 @@ targets: - lib/**/*.dart mix_generator|spec: generate_for: - - lib/**/*_spec.dart + - lib/**/*.dart mix_generator|dto: generate_for: - - lib/**/*_spec.dart + - lib/**/*.dart mix_generator|enum_utility: generate_for: - - lib/**/*_spec.dart + - lib/**/*.dart mix_generator|class_utility: generate_for: - - lib/**/*_spec.dart + - lib/**/*.dart global_options: dart_mappable_builder: diff --git a/packages/superdeck/example/ios/Podfile.lock b/packages/superdeck/example/ios/Podfile.lock index a624b610..84eed3eb 100644 --- a/packages/superdeck/example/ios/Podfile.lock +++ b/packages/superdeck/example/ios/Podfile.lock @@ -33,6 +33,8 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter + - file_saver (0.0.1): + - Flutter - Flutter (1.0.0) - path_provider_foundation (0.0.1): - Flutter @@ -49,6 +51,7 @@ PODS: DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) + - file_saver (from `.symlinks/plugins/file_saver/ios`) - Flutter (from `Flutter`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - sqflite (from `.symlinks/plugins/sqflite/darwin`) @@ -64,6 +67,8 @@ SPEC REPOS: EXTERNAL SOURCES: file_picker: :path: ".symlinks/plugins/file_picker/ios" + file_saver: + :path: ".symlinks/plugins/file_saver/ios" Flutter: :path: Flutter path_provider_foundation: @@ -77,12 +82,13 @@ SPEC CHECKSUMS: DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 + file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 SDWebImage: 981fd7e860af070920f249fd092420006014c3eb sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/packages/superdeck/example/ios/Runner/AppDelegate.swift b/packages/superdeck/example/ios/Runner/AppDelegate.swift index 9074fee9..62666446 100644 --- a/packages/superdeck/example/ios/Runner/AppDelegate.swift +++ b/packages/superdeck/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/superdeck/example/lib/main.dart b/packages/superdeck/example/lib/main.dart index eb5a4faf..d1c5004c 100644 --- a/packages/superdeck/example/lib/main.dart +++ b/packages/superdeck/example/lib/main.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:superdeck/superdeck.dart'; import 'src/style.dart'; import 'src/widget/mix_demo.dart'; void main() async { - debugRepaintRainbowEnabled = true; await SuperDeckApp.initialize(); runApp( Builder(builder: (context) { diff --git a/packages/superdeck/example/pubspec.yaml b/packages/superdeck/example/pubspec.yaml index 80be19db..0bdfffe6 100644 --- a/packages/superdeck/example/pubspec.yaml +++ b/packages/superdeck/example/pubspec.yaml @@ -11,7 +11,6 @@ environment: dependencies: flutter: sdk: flutter - google_fonts: ^6.2.0 superdeck: path: ../ diff --git a/packages/superdeck/example/pubspec_overrides.yaml b/packages/superdeck/example/pubspec_overrides.yaml index 32c9384f..081307e9 100644 --- a/packages/superdeck/example/pubspec_overrides.yaml +++ b/packages/superdeck/example/pubspec_overrides.yaml @@ -5,6 +5,6 @@ dependency_overrides: # mix: # path: ../../mix/packages/mix superdeck: - path: .. + path: ../ superdeck_cli: path: ../../superdeck_cli diff --git a/packages/superdeck/lib/components/atoms/slide_thumbnail.dart b/packages/superdeck/lib/components/atoms/slide_thumbnail.dart index 258939e9..3456857d 100644 --- a/packages/superdeck/lib/components/atoms/slide_thumbnail.dart +++ b/packages/superdeck/lib/components/atoms/slide_thumbnail.dart @@ -3,40 +3,36 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:remix/remix.dart'; import '../../helpers/constants.dart'; import '../../helpers/extensions.dart'; import '../../services/reference_service.dart'; import '../../services/snapshot_service.dart'; import '../../superdeck.dart'; -import '../molecules/scaled_app.dart'; import 'cache_image_widget.dart'; import 'loading_indicator.dart'; -import 'slide_view.dart'; class SlideThumbnail extends HookWidget { final VoidCallback onTap; - final int index; + final bool selected; final Slide slide; + final int page; const SlideThumbnail({ super.key, - required this.index, + required this.selected, required this.onTap, required this.slide, + required this.page, }); @override Widget build(BuildContext context) { - final navigation = useNavigation(); - final processThumbnail = useFuture( useMemoized(() => _generateThumbnail(slide), [slide]), ); return LayoutBuilder(builder: (context, constraints) { - final selectedColor = - index == navigation.page ? Colors.blue : Colors.transparent; - final child = LoadingOverlay( isLoading: processThumbnail.isLoading, child: processThumbnail.when( @@ -62,44 +58,42 @@ class SlideThumbnail extends HookWidget { return GestureDetector( onTap: onTap, child: _PreviewContainer( - selectedColor: selectedColor, - child: AbsorbPointer( - child: AspectRatio( - aspectRatio: kAspectRatio, - child: Stack( - children: [ - child, - Positioned( - top: 0, - right: 0, - left: 0, - child: SizedBox( - child: processThumbnail.isRefreshing - ? const LinearProgressIndicator( - minHeight: 3, - backgroundColor: Colors.transparent, - ) - : null, - ), + selected: selected, + child: AspectRatio( + aspectRatio: kAspectRatio, + child: Stack( + children: [ + child, + Positioned( + top: 0, + right: 0, + left: 0, + child: SizedBox( + child: processThumbnail.isRefreshing + ? const LinearProgressIndicator( + minHeight: 3, + backgroundColor: Colors.transparent, + ) + : null, ), - Positioned( - right: 0, - bottom: 0, - child: Container( - padding: const EdgeInsets.fromLTRB(12, 4, 12, 4), - margin: const EdgeInsets.all(1), - color: Colors.black.withOpacity(0.5), - child: Text( - '${index + 1}', - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), + ), + Positioned( + right: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.fromLTRB(12, 4, 12, 4), + margin: const EdgeInsets.all(1), + color: Colors.black.withOpacity(0.5), + child: Text( + '$page', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, ), ), ), - ], - ), + ), + ], ), ), ), @@ -108,67 +102,36 @@ class SlideThumbnail extends HookWidget { } } -class SlideThumbnailDynamic extends StatelessWidget { - final bool selected; - final VoidCallback onTap; - final int index; - final T slide; - - const SlideThumbnailDynamic({ - super.key, - required this.selected, - required this.index, - required this.onTap, - required this.slide, - }); - - @override - Widget build(BuildContext context) { - final selectedColor = selected ? Colors.blue : Colors.transparent; - - return GestureDetector( - onTap: onTap, - child: _PreviewContainer( - selectedColor: selectedColor, - child: AbsorbPointer( - child: AspectRatio( - aspectRatio: kAspectRatio, - child: ScaledWidget( - child: SlideView(slide), - ), - ), - ), - ), - ); - } -} - class _PreviewContainer extends StatelessWidget { - final Color selectedColor; final Widget child; + final bool selected; const _PreviewContainer({ - required this.selectedColor, + required this.selected, required this.child, }); @override Widget build(BuildContext context) { final style = Style( - $box.color.grey.shade900(), + $box.color.$neutral(2), $box.margin.all(8), $box.border.width(2), $box.shadow( - color: Colors.black.withOpacity(0.5), blurRadius: 4, spreadRadius: 1, ), - ); + + selected ? $box.wrap.scale(1.05) : $box.wrap.scale(1), + selected ? $box.wrap.opacity(1) : $box.wrap.opacity(0.5), + selected ? $box.border.color.$accent() : $box.border.color.transparent(), + // $on.hover( + // $box.wrap.opacity(1), + // ), + ).animate(); return Box( - style: style.add( - $box.border.color(selectedColor), - ), + style: style, child: child, ); } diff --git a/packages/superdeck/lib/components/molecules/navigation_rail.dart b/packages/superdeck/lib/components/molecules/navigation_rail.dart new file mode 100644 index 00000000..fc65f8dd --- /dev/null +++ b/packages/superdeck/lib/components/molecules/navigation_rail.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:mix/mix.dart'; + +import '../remix/button.dart'; + +class CustomNavigationRail extends HookWidget { + final int selectedIndex; + final ValueChanged? onDestinationSelected; + final List destinations; + final bool displayLabel; + final double? leading; + final double? trailing; + + CustomNavigationRail({ + required this.selectedIndex, + this.onDestinationSelected, + required this.destinations, + this.displayLabel = false, + this.leading, + this.trailing, + }); + + @override + Widget build(BuildContext context) { + final _buildDestination = useCallback((int index) { + final destination = destinations[index]; + final isSelected = selectedIndex == index; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: SDIconButton( + icon: destination.icon, + onPressed: () => onDestinationSelected?.call(index), + selected: isSelected, + ), + ); + }, [selectedIndex, destinations]); + + return VBox( + style: _containerStyle, + children: [ + if (leading != null) SizedBox(height: leading), + for (int i = 0; i < destinations.length; i++) _buildDestination(i), + if (trailing != null) SizedBox(height: trailing), + ], + ); + } +} + +get _containerStyle => Style( + $box.color.black(), + $box.padding(16), + $box.border.right( + color: Colors.white10, + width: 1, + ), + ); + +class CustomNavigationRailDestination { + final IconData icon; + final String label; + + CustomNavigationRailDestination({ + required this.icon, + required this.label, + }); +} diff --git a/packages/superdeck/lib/components/molecules/slide_preview.dart b/packages/superdeck/lib/components/molecules/slide_preview.dart index ac992e3a..f733f2e5 100644 --- a/packages/superdeck/lib/components/molecules/slide_preview.dart +++ b/packages/superdeck/lib/components/molecules/slide_preview.dart @@ -17,7 +17,7 @@ class SlidePreview extends StatelessWidget { return Center( child: Container( decoration: BoxDecoration( - color: const Color.fromARGB(144, 0, 0, 0), + color: Colors.black, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), diff --git a/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart b/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart index 13f9a3e3..d10bd561 100644 --- a/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart +++ b/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart @@ -57,49 +57,28 @@ class SlideThumbnailList extends HookWidget { return; }, [currentPage, slides]); - return Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(0), - child: Container( - height: 30, - color: Colors.black, - ), - ), - bottomNavigationBar: BottomNavigationBar( - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.arrow_back), - label: 'Previous', - ), - BottomNavigationBarItem( - icon: Icon(Icons.arrow_forward), - label: 'Next', - ), - ], - onTap: (index) { - if (index == 0) { - navigation.goToPage(currentPage - 1); - } else { - navigation.goToPage(currentPage + 1); - } - }, - ), - body: Container( - color: const Color.fromARGB(108, 0, 0, 0), - child: ScrollablePositionedList.builder( - scrollDirection: context.isSmall ? Axis.horizontal : Axis.vertical, - itemCount: slides.length, - itemPositionsListener: controller.itemPositionsListener, - itemScrollController: controller.itemScrollController, - padding: const EdgeInsets.all(20), - itemBuilder: (context, index) { - return SlideThumbnail( - index: index, + return Container( + color: Colors.black, + child: ScrollablePositionedList.builder( + scrollDirection: context.isSmall ? Axis.horizontal : Axis.vertical, + itemCount: slides.length, + itemPositionsListener: controller.itemPositionsListener, + itemScrollController: controller.itemScrollController, + padding: const EdgeInsets.all(20), + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 10, + ), + child: SlideThumbnail( + page: index + 1, + selected: currentPage == index, onTap: () => navigation.goToPage(index), slide: slides[index], - ); - }), - ), + ), + ); + }), ); } } diff --git a/packages/superdeck/lib/components/molecules/split_view.dart b/packages/superdeck/lib/components/molecules/split_view.dart index d56f2d2b..b4307ad9 100644 --- a/packages/superdeck/lib/components/molecules/split_view.dart +++ b/packages/superdeck/lib/components/molecules/split_view.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import '../../helpers/hooks.dart'; import '../../helpers/utils.dart'; import '../../superdeck.dart'; import '../atoms/sized_transition.dart'; +import 'navigation_rail.dart'; import 'slide_thumbnail_list.dart'; -final _valueKey = GlobalKey(); - class SplitView extends HookWidget { final Widget child; @@ -17,14 +17,20 @@ class SplitView extends HookWidget { required this.child, }); - final _maxWidth = 450.0; - final _minWidth = 300.0; + final _maxWidth = 400.0; @override Widget build(BuildContext context) { final navigation = useNavigation(); final sideSize = useState(context.isMobileLandscape ? 200.0 : _maxWidth); - final isDragging = useState(false); + final location = useRouteLocation(); + + final locationIndex = switch (location) { + '/' => 0, + '/export' => 1, + '/settings' => 2, + _ => 0, + }; final animationController = useAnimationController( duration: Durations.medium1, @@ -43,11 +49,6 @@ class SplitView extends HookWidget { } }, [navigation.sideIsOpen]); - final handleUpdateSize = useCallback((DragUpdateDetails details) { - sideSize.value = - (sideSize.value + details.delta.dx).clamp(_minWidth, _maxWidth); - }, [sideSize.value]); - const sideHeight = 200.0; final isSmall = context.isSmall || context.isMobileLandscape; @@ -64,23 +65,6 @@ class SplitView extends HookWidget { padding = EdgeInsets.only(left: animatedWidth); } - final divider = MouseRegion( - cursor: SystemMouseCursors.resizeColumn, - child: GestureDetector( - onHorizontalDragUpdate: handleUpdateSize, - onHorizontalDragStart: (_) { - isDragging.value = true; - }, - onHorizontalDragEnd: (_) { - isDragging.value = false; - }, - child: Container( - color: Colors.black, - width: 8, - ), - ), - ); - final drawer = Align( alignment: isSmall ? Alignment.bottomCenter : Alignment.centerLeft, child: SizedTransition( @@ -90,10 +74,35 @@ class SplitView extends HookWidget { height: isSmall ? sideHeight : null, child: Row( children: [ + CustomNavigationRail( + selectedIndex: locationIndex, + onDestinationSelected: (value) { + return switch (value) { + 0 => context.go('/'), + 1 => context.push('/export'), + 2 => context.go('/settings'), + _ => null, + }; + }, + leading: 20, + destinations: [ + CustomNavigationRailDestination( + icon: Icons.view_carousel, + label: 'Home', + ), + CustomNavigationRailDestination( + icon: Icons.save_alt, + label: 'Export', + ), + CustomNavigationRailDestination( + icon: Icons.settings, + label: 'Settings', + ), + ], + ), const Expanded( child: SlideThumbnailList(), ), - divider, ], ), ), diff --git a/packages/superdeck/lib/components/organisms/app_shell.dart b/packages/superdeck/lib/components/organisms/app_shell.dart index 95315fc6..3ca4e999 100644 --- a/packages/superdeck/lib/components/organisms/app_shell.dart +++ b/packages/superdeck/lib/components/organisms/app_shell.dart @@ -3,17 +3,16 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; -import '../../helpers/hooks.dart'; import '../../helpers/utils.dart'; import '../../superdeck.dart'; +import '../molecules/split_view.dart'; final scaffoldKey = GlobalKey(); /// Builds the "shell" for the app by building a Scaffold with a /// BottomNavigationBar, where [child] is placed in the body of the Scaffold. -class ScaffoldWithNavBar extends HookWidget { - /// Constructs an [ScaffoldWithNavBar]. - const ScaffoldWithNavBar({ +class AppShell extends HookWidget { + const AppShell({ required this.navigationShell, super.key = const ValueKey('ScaffoldWithNavBar'), }); @@ -21,34 +20,12 @@ class ScaffoldWithNavBar extends HookWidget { /// The navigation shell and container for the branch Navigators. final StatefulNavigationShell navigationShell; - void _onTap(BuildContext context, int index) { - // When navigating to a new branch, it's recommended to use the goBranch - // method, as doing so makes sure the last navigation state of the - // Navigator for the branch is restored. - navigationShell.goBranch( - index, - // A common pattern when using bottom navigation bars is to support - // navigating to the initial location when tapping the item that is - // already active. This example demonstrates how to support this behavior, - // using the initialLocation parameter of goBranch. - // initialLocation: index == navigationShell.currentIndex, - ); - } - @override Widget build(BuildContext context) { final isSmall = context.isSmall; final navigation = useNavigation(); final slides = useSlides(); final invalidSlides = slides.whereType().toList(); - final animationController = useAnimationController( - duration: Durations.short3, - ); - - final animation = useAnimation(CurvedAnimation( - parent: animationController, - curve: Curves.ease, - )); final handlePrevious = useCallback(() { if (navigation.page == 0) return; @@ -68,14 +45,6 @@ class ScaffoldWithNavBar extends HookWidget { } }, [navigation.sideIsOpen]); - usePostFrameEffect(() { - if (navigation.sideIsOpen) { - animationController.forward(); - } else { - animationController.reverse(); - } - }, [navigation.sideIsOpen]); - final bindings = { const SingleActivator( LogicalKeyboardKey.arrowRight, @@ -97,30 +66,10 @@ class ScaffoldWithNavBar extends HookWidget { return CallbackShortcuts( bindings: bindings, child: Scaffold( + backgroundColor: const Color.fromARGB(255, 9, 9, 9), bottomNavigationBar: null, extendBodyBehindAppBar: true, extendBody: true, - appBar: AppBar( - backgroundColor: Colors.transparent, - surfaceTintColor: Colors.transparent, - toolbarOpacity: animation, - actions: [ - IconButton( - onPressed: () => context.go('/export'), - icon: const Icon( - Icons.picture_as_pdf, - ), - ), - IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: handlePrevious, - ), - IconButton( - icon: const Icon(Icons.arrow_forward), - onPressed: handleNext, - ), - ], - ), key: scaffoldKey, floatingActionButtonLocation: isSmall ? FloatingActionButtonLocation.miniEndFloat @@ -133,7 +82,7 @@ class ScaffoldWithNavBar extends HookWidget { child: const Icon(Icons.menu), ), ), - body: navigationShell, + body: SplitView(child: navigationShell), ), ); } diff --git a/packages/superdeck/lib/components/remix/button.dart b/packages/superdeck/lib/components/remix/button.dart new file mode 100644 index 00000000..a5c961bc --- /dev/null +++ b/packages/superdeck/lib/components/remix/button.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:remix/remix.dart'; + +class SDButton extends StatelessWidget { + const SDButton({ + required this.onPressed, + super.key, + required this.label, + this.icon, + }); + + final VoidCallback onPressed; + final String label; + + final IconData? icon; + + @override + Widget build(BuildContext context) { + return RxButton( + onPressed: onPressed, + type: ButtonVariant.surface, + iconLeft: icon, + label: '', + ); + } +} + +class SDIconButton extends StatelessWidget { + const SDIconButton({ + required this.onPressed, + super.key, + required this.icon, + this.selected = false, + }); + + final VoidCallback onPressed; + final bool selected; + + final IconData icon; + + @override + Widget build(BuildContext context) { + return RxButton( + onPressed: onPressed, + type: selected ? ButtonVariant.surface : ButtonVariant.ghost, + iconLeft: icon, + size: ButtonSize.large, + label: '', + ); + } +} diff --git a/packages/superdeck/lib/components/remix/button_style.dart b/packages/superdeck/lib/components/remix/button_style.dart new file mode 100644 index 00000000..e69de29b diff --git a/packages/superdeck/lib/components/superdeck_app.dart b/packages/superdeck/lib/components/superdeck_app.dart index 6170fe43..4445e382 100644 --- a/packages/superdeck/lib/components/superdeck_app.dart +++ b/packages/superdeck/lib/components/superdeck_app.dart @@ -3,19 +3,17 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; -import 'package:localstorage/localstorage.dart'; +import 'package:remix/remix.dart'; import 'package:window_manager/window_manager.dart'; import '../../helpers/syntax_highlighter.dart'; import '../../superdeck.dart'; import '../helpers/constants.dart'; +import '../helpers/routes.dart'; import '../helpers/theme.dart'; import '../providers/examples_provider.dart'; import '../providers/snapshot_provider.dart'; import '../providers/style_provider.dart'; -import '../screens/export_screen.dart'; -import '../screens/home_screen.dart'; import 'atoms/loading_indicator.dart'; final _uniqueKey = UniqueKey(); @@ -40,7 +38,8 @@ class SuperDeckApp extends HookWidget { WidgetsFlutterBinding.ensureInitialized(); await Future.wait([ - initLocalStorage(), + SuperDeckController.initialize(), + NavigationController.initialize(), SyntaxHighlight.initialize(), _initializeWindowManager(), ]); @@ -50,47 +49,41 @@ class SuperDeckApp extends HookWidget { @override Widget build(BuildContext context) { - return Theme( - data: theme, - child: FutureBuilder( - future: SuperDeckApp.initialize(), - builder: (context, snapshot) { - return StyleProvider( - baseStyle: baseStyle, - styles: styles, - child: ExamplesProvider( - examples: examples, - child: ListenableBuilder( - listenable: $superdeck, - builder: (context, snapshot) { - return MixTheme( - data: MixThemeData.withMaterial(), - child: MaterialApp.router( - debugShowCheckedModeBanner: false, - title: 'Superdeck', - routerConfig: _router, - theme: Theme.of(context), - builder: (context, child) { - return SnapshotProvider( - child: LoadingOverlay( - isLoading: $superdeck.loading, - key: _uniqueKey, - child: Builder( - builder: (context) { - return $superdeck.completed - ? child! - : const SizedBox(); - }, - ), - ), - ); - }, - ), - ); - }), - ), - ); - }), + return FutureBuilder( + future: SuperDeckApp.initialize(), + builder: (context, snapshot) { + return StyleProvider( + baseStyle: baseStyle, + styles: styles, + child: ExamplesProvider( + examples: examples, + child: ListenableBuilder( + listenable: $superdeck, + builder: (context, snapshot) { + return RemixTokens( + data: RemixTokens.dark, + child: MaterialApp.router( + debugShowCheckedModeBanner: true, + title: 'Superdeck', + routerConfig: goRouterConfig, + theme: theme, + builder: (context, child) { + return SnapshotProvider( + child: LoadingOverlay( + isLoading: $superdeck.loading, + key: _uniqueKey, + child: $superdeck.completed + ? child! + : const SizedBox(), + ), + ); + }, + ), + ); + }), + ), + ); + }, ); } } @@ -116,51 +109,3 @@ Future _initializeWindowManager() async { await windowManager.setAspectRatio(kAspectRatio); } - -final _rootNavigatorKey = GlobalKey(debugLabel: 'root'); -final _sectionANavigatorKey = - GlobalKey(debugLabel: 'sectionANav'); - -final _router = GoRouter( - navigatorKey: _rootNavigatorKey, - initialLocation: '/', - routes: [ - StatefulShellRoute.indexedStack( - builder: ( - BuildContext context, - GoRouterState state, - StatefulNavigationShell navigationShell, - ) { - // Return the widget that implements the custom shell (in this case - // using a BottomNavigationBar). The StatefulNavigationShell is passed - // to be able access the state of the shell and to navigate to other - // branches in a stateful way. - return ScaffoldWithNavBar(navigationShell: navigationShell); - }, - branches: [ - // The route branch for the first tab of the bottom navigation bar. - StatefulShellBranch( - navigatorKey: _sectionANavigatorKey, - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) { - return const HomeScreen(); - }, - ), - ], - ), - StatefulShellBranch( - routes: [ - GoRoute( - path: '/export', - builder: (BuildContext context, GoRouterState state) { - return const ExportScreen(); - }, - ), - ], - ), - ], - ), - ], -); diff --git a/packages/superdeck/lib/helpers/dialog_page.dart b/packages/superdeck/lib/helpers/dialog_page.dart new file mode 100644 index 00000000..bc23bedd --- /dev/null +++ b/packages/superdeck/lib/helpers/dialog_page.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +/// A dialog page with Material entrance and exit animations, modal barrier color, +/// and modal barrier behavior (dialog is dismissible with a tap on the barrier). +class DialogPage extends Page { + final Offset? anchorPoint; + final Color? barrierColor; + final bool barrierDismissible; + final String? barrierLabel; + final bool useSafeArea; + final CapturedThemes? themes; + final WidgetBuilder builder; + + const DialogPage({ + required this.builder, + this.anchorPoint, + this.barrierColor = Colors.black54, + this.barrierDismissible = true, + this.barrierLabel, + this.useSafeArea = true, + this.themes, + super.key, + super.name, + super.arguments, + super.restorationId, + }); + + @override + Route createRoute(BuildContext context) { + return DialogRoute( + context: context, + settings: this, + builder: builder, + anchorPoint: anchorPoint, + barrierColor: barrierColor, + barrierDismissible: barrierDismissible, + barrierLabel: barrierLabel, + useSafeArea: useSafeArea, + themes: themes, + ); + } +} diff --git a/packages/superdeck/lib/helpers/hooks.dart b/packages/superdeck/lib/helpers/hooks.dart index a49d84e5..38ced986 100644 --- a/packages/superdeck/lib/helpers/hooks.dart +++ b/packages/superdeck/lib/helpers/hooks.dart @@ -1,5 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; void useMount(VoidCallback fn) { @@ -113,3 +114,21 @@ T? useDistinct(T value, [Predicate? compare]) { } typedef Predicate = bool Function(T prev, T next); + +GoRouter useGoRouter() { + final context = useContext(); + return GoRouter.of(context); +} + +String useRouteLocation() { + final router = useGoRouter(); + final uri = useState(router.routeInformationProvider.value.uri); + + useEffect(() { + router.routerDelegate.addListener(() { + uri.value = router.routeInformationProvider.value.uri; + }); + }, [router.routerDelegate]); + + return uri.value.toString(); +} diff --git a/packages/superdeck/lib/helpers/routes.dart b/packages/superdeck/lib/helpers/routes.dart new file mode 100644 index 00000000..70942835 --- /dev/null +++ b/packages/superdeck/lib/helpers/routes.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:go_router_paths/go_router_paths.dart'; + +import '../../superdeck.dart'; +import '../helpers/dialog_page.dart'; +import '../screens/export_screen.dart'; +import '../screens/presentation_screen.dart'; + +class SDPaths { + static Path get presentation => Path('/'); + static ExportPath get export => ExportPath(); + + static Param get slides => Param('slides', 'page'); +} + +class ExportPath extends Path { + ExportPath() : super('export'); + + Path get low => Path('low', parent: this); + Path get good => Path('good', parent: this); + Path get better => Path('better', parent: this); + Path get best => Path('best', parent: this); +} + +final _rootNavigatorKey = GlobalKey(debugLabel: 'root'); +final _sectionANavigatorKey = + GlobalKey(debugLabel: 'sectionANav'); + +final goRouterConfig = GoRouter( + navigatorKey: _rootNavigatorKey, + initialLocation: '/', + routes: [ + StatefulShellRoute.indexedStack( + builder: (context, state, navigationShell) { + return AppShell(navigationShell: navigationShell); + }, + branches: [ + // The route branch for the first tab of the bottom navigation bar. + StatefulShellBranch( + navigatorKey: _sectionANavigatorKey, + routes: [ + GoRoute( + path: SDPaths.presentation.goRoute, + builder: (context, state) => const PresentationScreen(), + ), + ], + ), + StatefulShellBranch( + routes: [ + GoRoute( + path: SDPaths.export.goRoute, + pageBuilder: (context, state) { + return DialogPage( + builder: (_) => Dialog( + child: ExportScreen(), + ), + barrierColor: Colors.black54, // Optional, as it's the default + barrierDismissible: true, // Optional, as it's the default + ); + }, + ), + ], + ), + ], + ), + ], +); diff --git a/packages/superdeck/lib/helpers/theme.dart b/packages/superdeck/lib/helpers/theme.dart index 5363a1f0..e94f1129 100644 --- a/packages/superdeck/lib/helpers/theme.dart +++ b/packages/superdeck/lib/helpers/theme.dart @@ -2,9 +2,11 @@ import 'package:flutter/material.dart'; ThemeData get theme => ThemeData( colorScheme: ColorScheme.fromSeed( - seedColor: Colors.cyan, + seedColor: Colors.black, + dynamicSchemeVariant: DynamicSchemeVariant.fidelity, + contrastLevel: 1, error: Colors.red, - onTertiary: Colors.orange, + onTertiary: Colors.cyan, brightness: Brightness.dark, ), ); diff --git a/packages/superdeck/lib/providers/controller.dart b/packages/superdeck/lib/providers/controller.dart index 06b6f5a5..645d1e16 100644 --- a/packages/superdeck/lib/providers/controller.dart +++ b/packages/superdeck/lib/providers/controller.dart @@ -8,13 +8,22 @@ import '../models/asset_model.dart'; import '../models/slide_model.dart'; import '../services/reference_service.dart'; -final $superdeck = SuperDeckController(); +final $superdeck = SuperDeckController.instance; class SuperDeckController extends ChangeNotifier { - SuperDeckController() { - _loadData(); - ReferenceService.instance.listen(_loadData); + SuperDeckController._(); + + static final instance = SuperDeckController._(); + + bool _initialized = false; + + static Future initialize() async { + if (instance._initialized) return; + instance._initialized = true; + await instance._loadData(); + ReferenceService.instance.listen(instance._loadData); } + bool _loading = false; Object? _error; List _slides = []; @@ -96,6 +105,8 @@ class NavigationController extends ChangeNotifier { static final instance = NavigationController._(); + static Future initialize() => initLocalStorage(); + int _page = 0; bool _sideIsOpen = false; int _screen = 0; diff --git a/packages/superdeck/lib/screens/export_screen.dart b/packages/superdeck/lib/screens/export_screen.dart index a87c286c..e004ce19 100644 --- a/packages/superdeck/lib/screens/export_screen.dart +++ b/packages/superdeck/lib/screens/export_screen.dart @@ -5,6 +5,7 @@ import 'package:file_saver/file_saver.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; @@ -78,32 +79,30 @@ class ExportScreen extends HookWidget { }).toList(); } - return Scaffold( - appBar: AppBar( - title: const Text('Export'), - ), - body: Center( - child: SizedBox( - width: 300, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Select Quality:', - style: TextStyle( - fontSize: 16.0, - ), - ), - ...buildRadioList(), - const SizedBox(height: 24.0), - ElevatedButton( - onPressed: convertToPdf, - child: const Text('Save'), - ), - ], + return SizedBox( + width: 300, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Select Quality:', + style: TextStyle( + fontSize: 16.0, + ), ), - ), + ...buildRadioList(), + const SizedBox(height: 24.0), + ElevatedButton( + onPressed: convertToPdf, + child: const Text('Save'), + ), + ElevatedButton( + onPressed: () { + context.pop(); + }, + child: Text('Close')) + ], ), ); } diff --git a/packages/superdeck/lib/screens/home_screen.dart b/packages/superdeck/lib/screens/presentation_screen.dart similarity index 64% rename from packages/superdeck/lib/screens/home_screen.dart rename to packages/superdeck/lib/screens/presentation_screen.dart index 3540c905..1d9cbdb0 100644 --- a/packages/superdeck/lib/screens/home_screen.dart +++ b/packages/superdeck/lib/screens/presentation_screen.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import '../components/molecules/slide_preview.dart'; -import '../components/molecules/split_view.dart'; import '../helpers/hooks.dart'; import '../superdeck.dart'; -class HomeScreen extends HookWidget { - const HomeScreen({super.key}); +class PresentationScreen extends HookWidget { + const PresentationScreen({super.key}); final _duration = const Duration(milliseconds: 300); final _curve = Curves.easeInOutCubic; @@ -27,15 +26,13 @@ class HomeScreen extends HookWidget { ); }, [page]); - return SplitView( - child: Center( - child: PageView.builder( - controller: pageController, - itemCount: slides.length, - itemBuilder: (_, index) { - return SlidePreview(slides[index]); - }, - ), + return Center( + child: PageView.builder( + controller: pageController, + itemCount: slides.length, + itemBuilder: (_, index) { + return SlidePreview(slides[index]); + }, ), ); } diff --git a/packages/superdeck/pubspec.yaml b/packages/superdeck/pubspec.yaml index 817ba091..97da8d9a 100644 --- a/packages/superdeck/pubspec.yaml +++ b/packages/superdeck/pubspec.yaml @@ -3,6 +3,8 @@ description: Presentation slides for Flutter with Flutter version: 0.0.4 homepage: https://github.com/leoafarias/superdeck +publish_to: none + environment: sdk: ">=3.3.0 <4.0.0" @@ -34,6 +36,10 @@ dependencies: web: ^0.5.1 file_saver: ^0.2.13 render: ^0.0.1 + remix: + path: ../../../mix/packages/remix + go_router_paths: ^0.2.2 + dev_dependencies: flutter_test: diff --git a/packages/superdeck/pubspec_overrides.yaml b/packages/superdeck/pubspec_overrides.yaml new file mode 100644 index 00000000..c11e4d1a --- /dev/null +++ b/packages/superdeck/pubspec_overrides.yaml @@ -0,0 +1,11 @@ +dependency_overrides: + mix: + path: ../../../mix/packages/mix + mix_generator: + path: ../../../mix/packages/mix_generator + mix_lint: + path: ../../../mix/packages/mix_lint + mix_annotations: + path: ../../../mix/packages/mix_annotations + remix: + path: ../../../mix/packages/remix