diff --git a/assets/images/_common/location-pin.png b/assets/images/_common/location-pin.png index 4b4cc87e..dd9a5e79 100644 Binary files a/assets/images/_common/location-pin.png and b/assets/images/_common/location-pin.png differ diff --git a/assets/images/pyramids/2.0x/foreground-back.png b/assets/images/pyramids/2.0x/foreground-back.png index b6003ba3..41514b7a 100644 Binary files a/assets/images/pyramids/2.0x/foreground-back.png and b/assets/images/pyramids/2.0x/foreground-back.png differ diff --git a/assets/images/pyramids/3.0x/foreground-back.png b/assets/images/pyramids/3.0x/foreground-back.png index 6472dda6..79ee34c5 100644 Binary files a/assets/images/pyramids/3.0x/foreground-back.png and b/assets/images/pyramids/3.0x/foreground-back.png differ diff --git a/assets/images/pyramids/4.0x/foreground-back.png b/assets/images/pyramids/4.0x/foreground-back.png index 6b0c0500..66cea2bc 100644 Binary files a/assets/images/pyramids/4.0x/foreground-back.png and b/assets/images/pyramids/4.0x/foreground-back.png differ diff --git a/assets/images/pyramids/foreground-back.png b/assets/images/pyramids/foreground-back.png index 93a138be..a27687b4 100644 Binary files a/assets/images/pyramids/foreground-back.png and b/assets/images/pyramids/foreground-back.png differ diff --git a/lib/_tools/artifact_download_helper.dart b/lib/_tools/artifact_download_helper.dart index 8a9f7e3d..f4d11521 100644 --- a/lib/_tools/artifact_download_helper.dart +++ b/lib/_tools/artifact_download_helper.dart @@ -13,9 +13,9 @@ import 'package:wonders/logic/data/wonders_data/machu_picchu_data.dart'; import 'package:wonders/logic/data/wonders_data/petra_data.dart'; import 'package:wonders/logic/data/wonders_data/pyramids_giza_data.dart'; import 'package:wonders/logic/data/wonders_data/taj_mahal_data.dart'; +import 'package:wonders/logic/data/wonders_data/colosseum_data.dart'; import 'package:wonders/common_libs.dart'; -import 'package:wonders/logic/data/wonders_data/colosseum_data.dart'; class ArtifactDownloadHelper extends StatefulWidget { const ArtifactDownloadHelper({super.key}); @@ -82,16 +82,16 @@ class _ArtifactDownloadHelperState extends State { PyramidsGizaData().searchData + TajMahalData().searchData; - // for (var a in searchData) { - // final id = a.id.toString(); - // if (await downloadImageAndJson(id) == false) { - // missingIds.add(id); - // } - // final index = searchData.indexOf(a) + 1; - // if (index % 100 == 0) { - // debugPrint('$index/${searchData.length}'); - // } - // } + for (var a in searchData) { + final id = a.id.toString(); + if (await downloadImageAndJson(id) == false) { + missingIds.add(id); + } + final index = searchData.indexOf(a) + 1; + if (index % 100 == 0) { + debugPrint('$index/${searchData.length}'); + } + } debugPrint('Download complete :) Missing IDs: $missingIds'); } diff --git a/lib/logic/app_logic.dart b/lib/logic/app_logic.dart index c696caed..5b0878cd 100644 --- a/lib/logic/app_logic.dart +++ b/lib/logic/app_logic.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ui'; import 'package:desktop_window/desktop_window.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/common/platform_info.dart'; @@ -38,11 +39,21 @@ class AppLogic { await DesktopWindow.setMinWindowSize($styles.sizes.minAppSize); } + if (kIsWeb) { + // SB: This is intentionally not a debugPrint, as it's a message for users who open the console on web. + print( + '''Thanks for checking out Wonderous on the web! + If you encounter any issues please report them at https://github.com/gskinnerTeam/flutter-wonderous-app/issues.''', + ); + // Required on web to automatically enable accessibility features + WidgetsFlutterBinding.ensureInitialized().ensureSemantics(); + } + // Load any bitmaps the views might need await AppBitmaps.init(); // Set preferred refresh rate to the max possible (the OS may ignore this) - if (PlatformInfo.isAndroid) { + if (!kIsWeb && PlatformInfo.isAndroid) { await FlutterDisplayMode.setHighRefreshRate(); } @@ -93,9 +104,6 @@ class AppLogic { bool shouldUseNavRail() => _appSize.width > _appSize.height && _appSize.height > 250; - /// Enable landscape, portrait or both. Views can call this method to override the default settings. - /// For example, the [FullscreenVideoViewer] always wants to enable both landscape and portrait. - /// If a view overrides this, it is responsible for setting it back to [supportedOrientations] when disposed. void _updateSystemOrientation() { final axisList = _supportedOrientationsOverride ?? supportedOrientations; //debugPrint('updateDeviceOrientation, supportedAxis: $axisList'); diff --git a/lib/logic/wallpaper_logic.dart b/lib/logic/wallpaper_logic.dart deleted file mode 100644 index 558eb07c..00000000 --- a/lib/logic/wallpaper_logic.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:ui' as ui; - -import 'package:flutter/rendering.dart'; -import 'package:image_gallery_saver/image_gallery_saver.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:share_plus/share_plus.dart'; -import 'package:wonders/common_libs.dart'; -import 'package:wonders/logic/common/platform_info.dart'; -import 'package:wonders/ui/common/modals/app_modals.dart'; - -class WallPaperLogic { - /// Walks user through flow to save a Wonder Poster to their gallery - Future save(State state, RenderRepaintBoundary boundary, {required String name}) async { - // Time to create an image! - Uint8List? pngBytes = await _getPngFromBoundary(boundary); - final context = state.context, mounted = state.mounted; - if (pngBytes != null && mounted) { - bool? result = await showModal(context, - child: OkCancelModal( - msg: $strings.wallpaperModalSave, - )); - if (result == true && mounted) { - showModal(context, child: LoadingModal(msg: $strings.wallpaperModalSaving)); - if (PlatformInfo.isMobile) { - await ImageGallerySaver.saveImage(pngBytes, quality: 95, name: name); - } else { - await Future.delayed(500.ms); - } - if (state.mounted) { - Navigator.pop(context); - showModal(context, child: OkModal(msg: $strings.wallpaperModalSaveComplete)); - } - } - } - } - - Future share(BuildContext context, RenderRepaintBoundary boundary, - {required String name, String wonderName = 'Wonderous'}) async { - Uint8List? pngBytes = await _getPngFromBoundary(boundary); - if (pngBytes != null) { - final directory = (await getApplicationDocumentsDirectory()).path; - File imgFile = File('$directory/$name.png'); - await imgFile.writeAsBytes(pngBytes); - Share.shareXFiles([XFile(imgFile.path)], - subject: '$wonderName Wallpaper', text: 'Check out this $wonderName wallpaper from the Wonderous app!'); - } - } -} - -Future _getPngFromBoundary(RenderRepaintBoundary boundary) async { - ui.Image uiImage = await boundary.toImage(); - ByteData? byteData = await uiImage.toByteData(format: ui.ImageByteFormat.png); - if (byteData != null) { - return byteData.buffer.asUint8List(); - } - return null; -} diff --git a/lib/main.dart b/lib/main.dart index 499218cc..f826298b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,14 +2,14 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:wonders/common_libs.dart'; -import 'package:wonders/logic/collectibles_logic.dart'; -import 'package:wonders/logic/locale_logic.dart'; import 'package:wonders/logic/artifact_api_logic.dart'; import 'package:wonders/logic/artifact_api_service.dart'; +import 'package:wonders/logic/collectibles_logic.dart'; +import 'package:wonders/logic/locale_logic.dart'; import 'package:wonders/logic/timeline_logic.dart'; import 'package:wonders/logic/unsplash_logic.dart'; -import 'package:wonders/logic/wallpaper_logic.dart'; import 'package:wonders/logic/wonders_logic.dart'; +import 'package:wonders/ui/common/app_shortcuts.dart'; void main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); @@ -37,6 +37,7 @@ class WondersApp extends StatelessWidget with GetItMixin { locale: locale == null ? null : Locale(locale), debugShowCheckedModeBanner: false, routerDelegate: appRouter.routerDelegate, + shortcuts: AppShortcuts.defaults, theme: ThemeData(fontFamily: $styles.text.body.fontFamily, useMaterial3: true), localizationsDelegates: const [ AppLocalizations.delegate, @@ -79,7 +80,6 @@ SettingsLogic get settingsLogic => GetIt.I.get(); UnsplashLogic get unsplashLogic => GetIt.I.get(); ArtifactAPILogic get artifactLogic => GetIt.I.get(); CollectiblesLogic get collectiblesLogic => GetIt.I.get(); -WallPaperLogic get wallpaperLogic => GetIt.I.get(); LocaleLogic get localeLogic => GetIt.I.get(); /// Global helpers for readability diff --git a/lib/router.dart b/lib/router.dart index 0f3febde..bee38ac3 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -9,7 +9,6 @@ import 'package:wonders/ui/screens/collection/collection_screen.dart'; import 'package:wonders/ui/screens/home/wonders_home_screen.dart'; import 'package:wonders/ui/screens/intro/intro_screen.dart'; import 'package:wonders/ui/screens/timeline/timeline_screen.dart'; -import 'package:wonders/ui/screens/wallpaper_photo/wallpaper_photo_screen.dart'; import 'package:wonders/ui/screens/wonder_details/wonders_details_screen.dart'; /// Shared paths / urls used across the app @@ -73,9 +72,6 @@ final appRouter = GoRouter( AppRoute('/maps/:type', (s) { return FullscreenMapsViewer(type: _parseWonderType(s.params['type'])); }), - AppRoute('/wallpaperPhoto/:type', (s) { - return WallpaperPhotoScreen(type: _parseWonderType(s.params['type'])); - }), ]), ], ); diff --git a/lib/styles/styles.dart b/lib/styles/styles.dart index 0a48907f..eddef519 100644 --- a/lib/styles/styles.dart +++ b/lib/styles/styles.dart @@ -160,7 +160,7 @@ class _Sizes { double get maxContentWidth1 => 800; double get maxContentWidth2 => 600; double get maxContentWidth3 => 500; - final Size minAppSize = Size(380, 250); + final Size minAppSize = Size(380, 650); } @immutable diff --git a/lib/ui/common/app_scroll_behavior.dart b/lib/ui/common/app_scroll_behavior.dart index f8f9d341..837d68db 100644 --- a/lib/ui/common/app_scroll_behavior.dart +++ b/lib/ui/common/app_scroll_behavior.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; +import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/common/platform_info.dart'; class AppScrollBehavior extends ScrollBehavior { @@ -15,12 +16,15 @@ class AppScrollBehavior extends ScrollBehavior { @override ScrollPhysics getScrollPhysics(BuildContext context) => const BouncingScrollPhysics(); - // TODO: Finalize scrollbar strategy (Do we use them at all? Where specifically?) @override Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) { - //return child; - return PlatformInfo.isAndroid - ? RawScrollbar(controller: details.controller, child: child) - : CupertinoScrollbar(controller: details.controller, child: child); + if (PlatformInfo.isMobile) return child; + return RawScrollbar( + controller: details.controller, + thumbVisibility: PlatformInfo.isDesktopOrWeb, + thickness: 8, + interactive: true, + child: child, + ); } } diff --git a/lib/ui/common/app_shortcuts.dart b/lib/ui/common/app_shortcuts.dart new file mode 100644 index 00000000..ff3d3186 --- /dev/null +++ b/lib/ui/common/app_shortcuts.dart @@ -0,0 +1,47 @@ +import 'package:flutter/foundation.dart'; +import 'package:wonders/common_libs.dart'; + +class AppShortcuts { + static final Map _defaultWebAndDesktopShortcuts = { + // Activation + if (kIsWeb) ...{ + // On the web, enter activates buttons, but not other controls. + SingleActivator(LogicalKeyboardKey.enter): ButtonActivateIntent(), + SingleActivator(LogicalKeyboardKey.numpadEnter): ButtonActivateIntent(), + } else ...{ + SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(), + SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(), + SingleActivator(LogicalKeyboardKey.space): ActivateIntent(), + SingleActivator(LogicalKeyboardKey.gameButtonA): ActivateIntent(), + }, + + // Dismissal + SingleActivator(LogicalKeyboardKey.escape): DismissIntent(), + + // Keyboard traversal. + SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(), + SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(), + + // Scrolling + SingleActivator(LogicalKeyboardKey.arrowUp): ScrollIntent(direction: AxisDirection.up), + SingleActivator(LogicalKeyboardKey.arrowDown): ScrollIntent(direction: AxisDirection.down), + SingleActivator(LogicalKeyboardKey.arrowLeft): ScrollIntent(direction: AxisDirection.left), + SingleActivator(LogicalKeyboardKey.arrowRight): ScrollIntent(direction: AxisDirection.right), + SingleActivator(LogicalKeyboardKey.pageUp): + ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page), + SingleActivator(LogicalKeyboardKey.pageDown): + ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page), + }; + + static Map? get defaults { + switch (defaultTargetPlatform) { + // fall back to default shortcuts for ios and android + case TargetPlatform.iOS: + case TargetPlatform.android: + return null; + // unify shortcuts for desktop/web + default: + return _defaultWebAndDesktopShortcuts; + } + } +} diff --git a/lib/ui/common/controls/buttons.dart b/lib/ui/common/controls/buttons.dart index f90c7fc6..7001048e 100644 --- a/lib/ui/common/controls/buttons.dart +++ b/lib/ui/common/controls/buttons.dart @@ -22,6 +22,8 @@ class AppBtn extends StatelessWidget { this.minimumSize, this.bgColor, this.border, + this.focusNode, + this.onFocusChanged, }) : _builder = null, super(key: key); @@ -36,6 +38,8 @@ class AppBtn extends StatelessWidget { this.minimumSize, this.bgColor, this.border, + this.focusNode, + this.onFocusChanged, String? semanticLabel, String? text, AppIcons? icon, @@ -76,6 +80,8 @@ class AppBtn extends StatelessWidget { this.isSecondary = false, this.circular = false, this.minimumSize, + this.focusNode, + this.onFocusChanged, }) : expand = false, bgColor = Colors.transparent, border = null, @@ -86,6 +92,8 @@ class AppBtn extends StatelessWidget { final VoidCallback? onPressed; late final String semanticLabel; final bool enableFeedback; + final FocusNode? focusNode; + final void Function(bool hasFocus)? onFocusChanged; // content: late final Widget? child; @@ -129,15 +137,20 @@ class AppBtn extends StatelessWidget { ); Widget button = _CustomFocusBuilder( + focusNode: focusNode, + onFocusChanged: onFocusChanged, builder: (context, focus) => Stack( children: [ - TextButton( - onPressed: onPressed, - style: style, - focusNode: focus, - child: DefaultTextStyle( - style: DefaultTextStyle.of(context).style.copyWith(color: textColor), - child: content, + Opacity( + opacity: onPressed == null ? 0.5 : 1.0, + child: TextButton( + onPressed: onPressed, + style: style, + focusNode: focus, + child: DefaultTextStyle( + style: DefaultTextStyle.of(context).style.copyWith(color: textColor), + child: content, + ), ), ), if (focus.hasFocus) @@ -154,7 +167,7 @@ class AppBtn extends StatelessWidget { ); // add press effect: - if (pressEffect) button = _ButtonPressEffect(button); + if (pressEffect && onPressed != null) button = _ButtonPressEffect(button); // add semantics? if (semanticLabel.isEmpty) return button; @@ -199,15 +212,28 @@ class _ButtonPressEffectState extends State<_ButtonPressEffect> { } class _CustomFocusBuilder extends StatefulWidget { - const _CustomFocusBuilder({Key? key, required this.builder}) : super(key: key); + const _CustomFocusBuilder({Key? key, required this.builder, this.focusNode, this.onFocusChanged}) : super(key: key); final Widget Function(BuildContext context, FocusNode focus) builder; - + final void Function(bool hasFocus)? onFocusChanged; + final FocusNode? focusNode; @override State<_CustomFocusBuilder> createState() => _CustomFocusBuilderState(); } class _CustomFocusBuilderState extends State<_CustomFocusBuilder> { - late final _focusNode = FocusNode()..addListener(() => setState(() {})); + late final FocusNode _focusNode; + + @override + void initState() { + _focusNode = widget.focusNode ?? FocusNode(); + _focusNode.addListener(_handleFocusChanged); + super.initState(); + } + + void _handleFocusChanged() { + widget.onFocusChanged?.call(_focusNode.hasFocus); + setState(() {}); + } @override Widget build(BuildContext context) { diff --git a/lib/ui/common/controls/circle_buttons.dart b/lib/ui/common/controls/circle_buttons.dart index 6f968eb5..b2583c84 100644 --- a/lib/ui/common/controls/circle_buttons.dart +++ b/lib/ui/common/controls/circle_buttons.dart @@ -1,5 +1,6 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/app_icons.dart'; +import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart'; class CircleBtn extends StatelessWidget { const CircleBtn({ @@ -14,7 +15,7 @@ class CircleBtn extends StatelessWidget { static double defaultSize = 48; - final VoidCallback onPressed; + final VoidCallback? onPressed; final Color? bgColor; final BorderSide? border; final Widget child; @@ -47,6 +48,7 @@ class CircleIconBtn extends StatelessWidget { this.color, this.size, this.iconSize, + this.flipIcon = false, required this.semanticLabel, }) : super(key: key); @@ -54,13 +56,14 @@ class CircleIconBtn extends StatelessWidget { static double defaultSize = 28; final AppIcons icon; - final VoidCallback onPressed; + final VoidCallback? onPressed; final BorderSide? border; final Color? bgColor; final Color? color; final String semanticLabel; final double? size; final double? iconSize; + final bool flipIcon; @override Widget build(BuildContext context) { @@ -72,7 +75,10 @@ class CircleIconBtn extends StatelessWidget { size: size, bgColor: bgColor ?? defaultColor, semanticLabel: semanticLabel, - child: AppIcon(icon, size: iconSize ?? defaultSize, color: iconColor), + child: Transform.scale( + scaleX: flipIcon ? -1 : 1, + child: AppIcon(icon, size: iconSize ?? defaultSize, color: iconColor), + ), ); } @@ -103,8 +109,18 @@ class BackBtn extends StatelessWidget { semanticLabel: $strings.circleButtonsSemanticClose, bgColor: bgColor, iconColor: iconColor); + + bool _handleKeyDown(BuildContext context, KeyDownEvent event) { + if (event.logicalKey == LogicalKeyboardKey.escape) { + _handleOnPressed(context); + return true; + } + return false; + } + @override Widget build(BuildContext context) { +<<<<<<< HEAD return CircleIconBtn( icon: icon, bgColor: bgColor, @@ -118,10 +134,29 @@ class BackBtn extends StatelessWidget { } }, semanticLabel: semanticLabel ?? $strings.circleButtonsSemanticBack, +======= + return FullscreenKeyboardListener( + onKeyDown: (event) => _handleKeyDown(context, event), + child: CircleIconBtn( + icon: icon, + bgColor: bgColor, + color: iconColor, + onPressed: () => _handleOnPressed(context), + semanticLabel: semanticLabel ?? $strings.circleButtonsSemanticBack, + ), +>>>>>>> main ); } Widget safe() => _SafeAreaWithPadding(child: this); + + void _handleOnPressed(BuildContext context) { + if (onPressed != null) { + onPressed?.call(); + } else { + Navigator.of(context).pop(); + } + } } class _SafeAreaWithPadding extends StatelessWidget { diff --git a/lib/ui/common/fullscreen_keyboard_list_scroller.dart b/lib/ui/common/fullscreen_keyboard_list_scroller.dart new file mode 100644 index 00000000..2ca190fa --- /dev/null +++ b/lib/ui/common/fullscreen_keyboard_list_scroller.dart @@ -0,0 +1,75 @@ +import 'package:wonders/common_libs.dart'; +import 'package:wonders/logic/common/throttler.dart'; +import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart'; + +class FullscreenKeyboardListScroller extends StatelessWidget { + FullscreenKeyboardListScroller({super.key, required this.child, required this.scrollController}); + + static const int _scrollAmountOnPress = 75; + static const int _scrollAmountOnHold = 30; + static final Duration _keyPressAnimationDuration = $styles.times.fast * .5; + + final Widget child; + final ScrollController scrollController; + final Throttler _throttler = Throttler(32.milliseconds); + + double clampOffset(px) => px.clamp(0, scrollController.position.maxScrollExtent).toDouble(); + + void _handleKeyDown(int px) { + scrollController.animateTo( + clampOffset(scrollController.offset + px), + duration: _keyPressAnimationDuration, + curve: Curves.easeOut, + ); + } + + void _handleKeyRepeat(int px) { + final offset = clampOffset(scrollController.offset + px); + _throttler.call(() => scrollController.jumpTo(offset)); + } + + @override + Widget build(BuildContext context) { + return FullscreenKeyboardListener( + child: child, + onKeyRepeat: (event) { + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + _handleKeyRepeat(-_scrollAmountOnHold); + return true; + } + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + _handleKeyRepeat(_scrollAmountOnHold); + return true; + } + return false; + }, + onKeyDown: (event) { + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + _handleKeyDown(-_scrollAmountOnPress); + return true; + } + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + _handleKeyDown(_scrollAmountOnPress); + return true; + } + if (event.logicalKey == LogicalKeyboardKey.pageUp) { + _handleKeyDown(-_getViewportSize(context)); + return true; + } + if (event.logicalKey == LogicalKeyboardKey.pageDown) { + _handleKeyDown(_getViewportSize(context)); + return true; + } + return false; + }, + ); + } + + int _getViewportSize(BuildContext context) { + final rb = context.findRenderObject() as RenderBox?; + if (rb != null) { + return rb.size.height.round() - 100; + } + return 0; + } +} diff --git a/lib/ui/common/fullscreen_keyboard_listener.dart b/lib/ui/common/fullscreen_keyboard_listener.dart new file mode 100644 index 00000000..979e452d --- /dev/null +++ b/lib/ui/common/fullscreen_keyboard_listener.dart @@ -0,0 +1,47 @@ +import 'package:wonders/common_libs.dart'; + +class FullscreenKeyboardListener extends StatefulWidget { + const FullscreenKeyboardListener({super.key, required this.child, this.onKeyDown, this.onKeyUp, this.onKeyRepeat}); + final Widget child; + final bool Function(KeyDownEvent event)? onKeyDown; + final bool Function(KeyUpEvent event)? onKeyUp; + final bool Function(KeyRepeatEvent event)? onKeyRepeat; + + @override + State createState() => _FullscreenKeyboardListenerState(); +} + +class _FullscreenKeyboardListenerState extends State { + @override + void initState() { + super.initState(); + ServicesBinding.instance.keyboard.addHandler(_handleKey); + } + + @override + void dispose() { + ServicesBinding.instance.keyboard.removeHandler(_handleKey); + super.dispose(); + } + + bool _handleKey(KeyEvent event) { + bool result = false; + + /// Exit early if we are not the current;y focused route (dialog on top?) + if (ModalRoute.of(context)?.isCurrent == false) return false; + + if (event is KeyDownEvent && widget.onKeyDown != null) { + result = widget.onKeyDown!.call(event); + } + if (event is KeyUpEvent && widget.onKeyUp != null) { + result = widget.onKeyUp!.call(event); + } + if (event is KeyRepeatEvent && widget.onKeyRepeat != null) { + result = widget.onKeyRepeat!.call(event); + } + return result; + } + + @override + Widget build(BuildContext context) => widget.child; +} diff --git a/lib/ui/common/google_maps_marker.dart b/lib/ui/common/google_maps_marker.dart index c621826f..07d4f1c0 100644 --- a/lib/ui/common/google_maps_marker.dart +++ b/lib/ui/common/google_maps_marker.dart @@ -1,4 +1,3 @@ -import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:wonders/assets.dart'; @@ -6,5 +5,4 @@ Marker getMapsMarker(LatLng position) => Marker( markerId: MarkerId('0'), position: position, icon: AppBitmaps.mapMarker, - anchor: Offset(.5, .7), ); diff --git a/lib/ui/common/modals/fullscreen_url_img_viewer.dart b/lib/ui/common/modals/fullscreen_url_img_viewer.dart index 29ee5351..c8dbe71c 100644 --- a/lib/ui/common/modals/fullscreen_url_img_viewer.dart +++ b/lib/ui/common/modals/fullscreen_url_img_viewer.dart @@ -1,5 +1,9 @@ +import 'dart:async'; + import 'package:wonders/common_libs.dart'; +import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/controls/app_header.dart'; +import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart'; class FullscreenUrlImgViewer extends StatefulWidget { @@ -15,7 +19,8 @@ class FullscreenUrlImgViewer extends StatefulWidget { class _FullscreenUrlImgViewerState extends State { final _isZoomed = ValueNotifier(false); - late final _controller = PageController(initialPage: widget.index); + late final _controller = PageController(initialPage: widget.index)..addListener(_handlePageChanged); + late final ValueNotifier _currentPage = ValueNotifier(widget.index); @override void dispose() { @@ -23,8 +28,35 @@ class _FullscreenUrlImgViewerState extends State { super.dispose(); } + bool _handleKeyDown(KeyDownEvent event) { + int dir = 0; + if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { + dir = -1; + } + if (event.logicalKey == LogicalKeyboardKey.arrowRight) { + dir = 1; + } + if (dir != 0) { + final focus = FocusManager.instance.primaryFocus; + _animateToPage(_currentPage.value + dir); + scheduleMicrotask(() { + focus?.requestFocus(); + }); + return true; + } + return false; + } + + void _handlePageChanged() => _currentPage.value = _controller.page!.round(); + void _handleBackPressed() => Navigator.pop(context, _controller.page!.round()); + void _animateToPage(int page) { + if (page >= 0 || page < widget.urls.length) { + _controller.animateToPage(page, duration: 300.ms, curve: Curves.easeOut); + } + } + @override Widget build(BuildContext context) { Widget content = AnimatedBuilder( @@ -48,13 +80,46 @@ class _FullscreenUrlImgViewerState extends State { child: ExcludeSemantics(child: content), ); - return Container( - color: $styles.colors.black, - child: Stack( - children: [ - Positioned.fill(child: content), - AppHeader(onBack: _handleBackPressed, isTransparent: true), - ], + return FullscreenKeyboardListener( + onKeyDown: _handleKeyDown, + child: Container( + color: $styles.colors.black, + child: Stack( + children: [ + Positioned.fill(child: content), + AppHeader(onBack: _handleBackPressed, isTransparent: true), + // Show next/previous btns if there are more than one image + if (widget.urls.length > 1) ...{ + BottomCenter( + child: Padding( + padding: EdgeInsets.only(bottom: $styles.insets.md), + child: ValueListenableBuilder( + valueListenable: _currentPage, + builder: (_, int page, __) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + CircleIconBtn( + icon: AppIcons.prev, + onPressed: page == 0 ? null : () => _animateToPage(page - 1), + semanticLabel: $strings.semanticsNext(''), + ), + Gap($styles.insets.xs), + CircleIconBtn( + icon: AppIcons.prev, + flipIcon: true, + onPressed: page == widget.urls.length - 1 ? null : () => _animateToPage(page + 1), + semanticLabel: $strings.semanticsNext(''), + ) + ], + ); + }, + ), + ), + ) + } + ], + ), ), ); } diff --git a/lib/ui/common/previous_next_navigation.dart b/lib/ui/common/previous_next_navigation.dart new file mode 100644 index 00000000..f832e6ca --- /dev/null +++ b/lib/ui/common/previous_next_navigation.dart @@ -0,0 +1,101 @@ +import 'package:flutter/gestures.dart'; +import 'package:wonders/common_libs.dart'; +import 'package:wonders/logic/common/platform_info.dart'; +import 'package:wonders/ui/common/app_icons.dart'; +import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart'; + +class PreviousNextNavigation extends StatefulWidget { + const PreviousNextNavigation( + {super.key, + required this.onPreviousPressed, + required this.onNextPressed, + required this.child, + this.maxWidth = 1000, + this.nextBtnColor, + this.previousBtnColor, + this.listenToMouseWheel = true}); + final VoidCallback? onPreviousPressed; + final VoidCallback? onNextPressed; + final Color? nextBtnColor; + final Color? previousBtnColor; + final Widget child; + final double? maxWidth; + final bool listenToMouseWheel; + + @override + State createState() => _PreviousNextNavigationState(); +} + +class _PreviousNextNavigationState extends State { + DateTime _lastMouseScrollTime = DateTime.now(); + final int _scrollCooldownMs = 300; + + bool _handleKeyDown(KeyDownEvent event) { + if (event.logicalKey == LogicalKeyboardKey.arrowLeft && widget.onPreviousPressed != null) { + widget.onPreviousPressed?.call(); + return true; + } + if (event.logicalKey == LogicalKeyboardKey.arrowRight && widget.onNextPressed != null) { + widget.onNextPressed?.call(); + return true; + } + return false; + } + + void _handleMouseScroll(event) { + if (event is PointerScrollEvent) { + // Cooldown, ignore scroll events that are too close together + if (DateTime.now().millisecondsSinceEpoch - _lastMouseScrollTime.millisecondsSinceEpoch < _scrollCooldownMs) { + return; + } + _lastMouseScrollTime = DateTime.now(); + if (event.scrollDelta.dy > 0 && widget.onPreviousPressed != null) { + widget.onPreviousPressed!(); + } else if (event.scrollDelta.dy < 0 && widget.onNextPressed != null) { + widget.onNextPressed!(); + } + } + } + + @override + Widget build(BuildContext context) { + if (PlatformInfo.isMobile) return widget.child; + return Listener( + onPointerSignal: widget.listenToMouseWheel ? _handleMouseScroll : null, + child: FullscreenKeyboardListener( + onKeyDown: _handleKeyDown, + child: Stack( + children: [ + widget.child, + Center( + child: SizedBox( + width: widget.maxWidth ?? double.infinity, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm), + child: Row( + children: [ + CircleIconBtn( + icon: AppIcons.prev, + onPressed: widget.onPreviousPressed, + semanticLabel: 'Previous', + bgColor: widget.previousBtnColor, + ), + Spacer(), + CircleIconBtn( + icon: AppIcons.prev, + onPressed: widget.onNextPressed, + semanticLabel: 'Next', + flipIcon: true, + bgColor: widget.nextBtnColor, + ) + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/ui/common/utils/app_haptics.dart b/lib/ui/common/utils/app_haptics.dart index 908bb523..cb3dc8ee 100644 --- a/lib/ui/common/utils/app_haptics.dart +++ b/lib/ui/common/utils/app_haptics.dart @@ -12,7 +12,7 @@ class AppHaptics { static void buttonPress() { // Android/Fuchsia expect haptics on all button presses, iOS does not. - if (PlatformInfo.isAndroid) { + if (!kIsWeb && PlatformInfo.isAndroid) { lightImpact(); } } diff --git a/lib/ui/screens/collection/widgets/_collection_list.dart b/lib/ui/screens/collection/widgets/_collection_list.dart index 75e42092..c8540468 100644 --- a/lib/ui/screens/collection/widgets/_collection_list.dart +++ b/lib/ui/screens/collection/widgets/_collection_list.dart @@ -37,6 +37,7 @@ class _CollectionListState extends State<_CollectionList> with GetItStateMixin { // Maintain scroll position when switching between vertical and horizontal orientation. // Multiplies or divides the current scroll position by the ratio of the vertical and horizontal card extents. void _maintainScrollPos() { + if (scrollController.hasClients == false) return; const extentFactor = _CollectionList._vtCardExtent / _CollectionList._hzCardExtent; final currentPx = scrollController.position.pixels; if (_vtMode.value == true) { diff --git a/lib/ui/screens/editorial/editorial_screen.dart b/lib/ui/screens/editorial/editorial_screen.dart index 4d8da6df..6c746da1 100644 --- a/lib/ui/screens/editorial/editorial_screen.dart +++ b/lib/ui/screens/editorial/editorial_screen.dart @@ -12,8 +12,8 @@ import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/blend_mask.dart'; import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/compass_divider.dart'; -import 'package:wonders/ui/common/controls/app_header.dart'; import 'package:wonders/ui/common/curved_clippers.dart'; +import 'package:wonders/ui/common/fullscreen_keyboard_list_scroller.dart'; import 'package:wonders/ui/common/google_maps_marker.dart'; import 'package:wonders/ui/common/gradient_container.dart'; import 'package:wonders/ui/common/hidden_collectible.dart'; @@ -72,7 +72,7 @@ class _WonderEditorialScreenState extends State { /// Attempt to maintain a similar aspect ratio for the image within the app-bar double maxAppBarHeight = min(context.widthPx, $styles.sizes.maxContentWidth1) * 1.2; - bool showBackBtn = appLogic.shouldUseNavRail() == false; + final backBtnAlign = appLogic.shouldUseNavRail() ? Alignment.topRight : Alignment.topLeft; return PopRouterOnOverScroll( controller: _scroller, child: ColoredBox( @@ -111,54 +111,56 @@ class _WonderEditorialScreenState extends State { padding: widget.contentPadding, child: SizedBox( child: FocusTraversalGroup( - child: CustomScrollView( - primary: false, - controller: _scroller, - scrollBehavior: ScrollConfiguration.of(context).copyWith(), - key: PageStorageKey('editorial'), - slivers: [ - /// Invisible padding at the top of the list, so the illustration shows through the btm - SliverToBoxAdapter( - child: SizedBox(height: illustrationHeight), - ), - - /// Text content, animates itself to hide behind the app bar as it scrolls up - SliverToBoxAdapter( - child: ValueListenableBuilder( - valueListenable: _scrollPos, - builder: (_, value, child) { - double offsetAmt = max(0, value * .3); - double opacity = (1 - offsetAmt / 150).clamp(0, 1); - return Transform.translate( - offset: Offset(0, offsetAmt), - child: Opacity(opacity: opacity, child: child), - ); - }, - child: _TitleText(widget.data, scroller: _scroller), + child: FullscreenKeyboardListScroller( + scrollController: _scroller, + child: CustomScrollView( + controller: _scroller, + scrollBehavior: ScrollConfiguration.of(context).copyWith(), + key: PageStorageKey('editorial'), + slivers: [ + /// Invisible padding at the top of the list, so the illustration shows through the btm + SliverToBoxAdapter( + child: SizedBox(height: illustrationHeight), ), - ), - /// Collapsing App bar, pins to the top of the list - SliverAppBar( - pinned: true, - collapsedHeight: minAppBarHeight, - toolbarHeight: minAppBarHeight, - expandedHeight: maxAppBarHeight, - backgroundColor: Colors.transparent, - elevation: 0, - leading: SizedBox.shrink(), - flexibleSpace: SizedBox.expand( - child: _AppBar( - widget.data.type, - scrollPos: _scrollPos, - sectionIndex: _sectionIndex, + /// Text content, animates itself to hide behind the app bar as it scrolls up + SliverToBoxAdapter( + child: ValueListenableBuilder( + valueListenable: _scrollPos, + builder: (_, value, child) { + double offsetAmt = max(0, value * .3); + double opacity = (1 - offsetAmt / 150).clamp(0, 1); + return Transform.translate( + offset: Offset(0, offsetAmt), + child: Opacity(opacity: opacity, child: child), + ); + }, + child: _TitleText(widget.data, scroller: _scroller), ), ), - ), - /// Editorial content (text and images) - _ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex), - ], + /// Collapsing App bar, pins to the top of the list + SliverAppBar( + pinned: true, + collapsedHeight: minAppBarHeight, + toolbarHeight: minAppBarHeight, + expandedHeight: maxAppBarHeight, + backgroundColor: Colors.transparent, + elevation: 0, + leading: SizedBox.shrink(), + flexibleSpace: SizedBox.expand( + child: _AppBar( + widget.data.type, + scrollPos: _scrollPos, + sectionIndex: _sectionIndex, + ), + ), + ), + + /// Editorial content (text and images) + _ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex), + ], + ), ), ), ), @@ -166,18 +168,23 @@ class _WonderEditorialScreenState extends State { ), /// Home Btn - if (showBackBtn) ...[ - AnimatedBuilder( - animation: _scroller, - builder: (_, child) { - return AnimatedOpacity( - opacity: _scrollPos.value > 0 ? 0 : 1, - duration: $styles.times.med, - child: child, - ); - }, - child: AppHeader(backIcon: AppIcons.north, isTransparent: true)) - ], + AnimatedBuilder( + animation: _scroller, + builder: (_, child) { + return AnimatedOpacity( + opacity: _scrollPos.value > 0 ? 0 : 1, + duration: $styles.times.med, + child: child, + ); + }, + child: Align( + alignment: backBtnAlign, + child: Padding( + padding: EdgeInsets.all($styles.insets.sm), + child: BackBtn(icon: AppIcons.north), + ), + ), + ) ], ), ), diff --git a/lib/ui/screens/home/wonders_home_screen.dart b/lib/ui/screens/home/wonders_home_screen.dart index 1b82d36b..8d7e6dcf 100644 --- a/lib/ui/screens/home/wonders_home_screen.dart +++ b/lib/ui/screens/home/wonders_home_screen.dart @@ -4,6 +4,7 @@ import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/controls/app_header.dart'; import 'package:wonders/ui/common/controls/app_page_indicator.dart'; import 'package:wonders/ui/common/gradient_container.dart'; +import 'package:wonders/ui/common/previous_next_navigation.dart'; import 'package:wonders/ui/common/themed_text.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/screens/home_menu/home_menu.dart'; @@ -87,11 +88,18 @@ class _HomeScreenState extends State with SingleTickerProviderStateM void _handlePageIndicatorDotPressed(int index) => _setPageIndex(index); - void _setPageIndex(int index) { + void _handlePrevNext(int i) => _setPageIndex(_wonderIndex + i, animate: true); + + void _setPageIndex(int index, {bool animate = false}) { if (index == _wonderIndex) return; // To support infinite scrolling, we can't jump directly to the pressed index. Instead, make it relative to our current position. final pos = ((_pageController.page ?? 0) / _numWonders).floor() * _numWonders; - _pageController.jumpToPage(pos + index); + final newIndex = pos + index; + if (animate == true) { + _pageController.animateToPage(newIndex, duration: $styles.times.med, curve: Curves.easeOutCubic); + } else { + _pageController.jumpToPage(newIndex); + } } void _showDetailsPage() async { @@ -125,24 +133,25 @@ class _HomeScreenState extends State with SingleTickerProviderStateM return _swipeController.wrapGestureDetector(Container( color: $styles.colors.black, - child: Stack( - children: [ - Stack( - children: [ - /// Background - ..._buildBgAndClouds(), + child: PreviousNextNavigation( + listenToMouseWheel: false, + onPreviousPressed: () => _handlePrevNext(-1), + onNextPressed: () => _handlePrevNext(1), + child: Stack( + children: [ + /// Background + ..._buildBgAndClouds(), - /// Wonders Illustrations (main content) - _buildMgPageView(), + /// Wonders Illustrations (main content) + _buildMgPageView(), - /// Foreground illustrations and gradients - _buildFgAndGradients(), + /// Foreground illustrations and gradients + _buildFgAndGradients(), - /// Controls that float on top of the various illustrations - _buildFloatingUi(), - ], - ).animate().fadeIn(), - ], + /// Controls that float on top of the various illustrations + _buildFloatingUi(), + ], + ).animate().fadeIn(), ), )); } diff --git a/lib/ui/screens/home_menu/about_dialog_content.dart b/lib/ui/screens/home_menu/about_dialog_content.dart index 9ab1230d..d9b251d8 100644 --- a/lib/ui/screens/home_menu/about_dialog_content.dart +++ b/lib/ui/screens/home_menu/about_dialog_content.dart @@ -1,5 +1,4 @@ import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:wonders/common_libs.dart'; @@ -12,12 +11,11 @@ class AboutDialogContent extends StatelessWidget { @override Widget build(BuildContext context) { void handleTap(String url) { - if(PlatformInfo.isDesktopOrWeb){ + if (PlatformInfo.isDesktopOrWeb) { launchUrl(Uri.parse(url)); } else { Navigator.push(context, CupertinoPageRoute(builder: (_) => FullscreenWebView(url))); } - } List buildSpan(String text, {Map>? linkSupplants}) { diff --git a/lib/ui/screens/intro/intro_screen.dart b/lib/ui/screens/intro/intro_screen.dart index 56e620ac..ab7678ae 100644 --- a/lib/ui/screens/intro/intro_screen.dart +++ b/lib/ui/screens/intro/intro_screen.dart @@ -1,9 +1,10 @@ -import 'package:flutter/gestures.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:wonders/common_libs.dart'; - import 'package:wonders/ui/common/app_icons.dart'; +import 'package:wonders/logic/common/platform_info.dart'; +import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/controls/app_page_indicator.dart'; import 'package:wonders/ui/common/gradient_container.dart'; +import 'package:wonders/ui/common/previous_next_navigation.dart'; import 'package:wonders/ui/common/static_text_scale.dart'; import 'package:wonders/ui/common/themed_text.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart'; @@ -24,13 +25,14 @@ class _IntroScreenState extends State { static List<_PageData> pageData = []; late final PageController _pageController = PageController()..addListener(_handlePageChanged); - final ValueNotifier _currentPage = ValueNotifier(0); + late final ValueNotifier _currentPage = ValueNotifier(0)..addListener(() => setState(() {})); bool get _isOnLastPage => _currentPage.value.round() == pageData.length - 1; bool get _isOnFirstPage => _currentPage.value.round() == 0; @override void dispose() { _pageController.dispose(); + _currentPage.dispose(); super.dispose(); } @@ -53,15 +55,13 @@ class _IntroScreenState extends State { void _handleNavTextSemanticTap() => _incrementPage(1); - void _incrementPage(int dir){ + void _incrementPage(int dir) { final int current = _pageController.page!.round(); if (_isOnLastPage && dir > 0) return; if (_isOnFirstPage && dir < 0) return; _pageController.animateToPage(current + dir, duration: 250.ms, curve: Curves.easeIn); } - void _handleScrollWheel(double delta) => _incrementPage(delta >0? 1 : -1); - @override Widget build(BuildContext context) { // Set the page data, as strings may have changed based on locale @@ -78,20 +78,25 @@ class _IntroScreenState extends State { final List pages = pageData.map((e) => _Page(data: e)).toList(); /// Return resulting widget tree - return Listener( - onPointerSignal: (signal){ - if(signal is PointerScrollEvent){ - _handleScrollWheel(signal.scrollDelta.dy); - } - }, - child: DefaultTextColor( - color: $styles.colors.offWhite, - child: Container( - color: $styles.colors.black, - child: SafeArea( - child: Animate( - delay: 500.ms, - effects: const [FadeEffect()], + return DefaultTextColor( + color: $styles.colors.offWhite, + child: ColoredBox( + color: $styles.colors.black, + child: SafeArea( + child: Animate( + delay: 500.ms, + effects: const [FadeEffect()], + child: PreviousNextNavigation( + maxWidth: 600, + nextBtnColor: _isOnLastPage ? $styles.colors.accent1 : null, + onPreviousPressed: _isOnFirstPage ? null : () => _incrementPage(-1), + onNextPressed: () { + if (_isOnLastPage) { + _handleIntroCompletePressed(); + } else { + _incrementPage(1); + } + }, child: Stack( children: [ // page view with title & description: @@ -159,20 +164,22 @@ class _IntroScreenState extends State { _buildHzGradientOverlay(left: true), _buildHzGradientOverlay(), - // finish button: - Positioned( - right: $styles.insets.lg, - bottom: $styles.insets.lg, - child: _buildFinishBtn(context), - ), - // nav help text: - BottomCenter( - child: Padding( - padding: EdgeInsets.only(bottom: $styles.insets.lg), - child: _buildNavText(context), + if (PlatformInfo.isMobile) ...[ + // finish button: + Positioned( + right: $styles.insets.lg, + bottom: $styles.insets.lg, + child: _buildFinishBtn(context), ), - ), + + BottomCenter( + child: Padding( + padding: EdgeInsets.only(bottom: $styles.insets.lg), + child: _buildNavText(context), + ), + ), + ] ], ), ), @@ -182,6 +189,24 @@ class _IntroScreenState extends State { ); } + Widget _buildFinishBtn(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _currentPage, + builder: (_, pageIndex, __) { + return AnimatedOpacity( + opacity: pageIndex == pageData.length - 1 ? 1 : 0, + duration: $styles.times.fast, + child: CircleIconBtn( + icon: AppIcons.next_large, + bgColor: $styles.colors.accent1, + onPressed: _handleIntroCompletePressed, + semanticLabel: $strings.introSemanticEnterApp, + ), + ); + }, + ); + } + Widget _buildHzGradientOverlay({bool left = false}) { return Align( alignment: Alignment(left ? -1 : 1, 0), @@ -203,24 +228,6 @@ class _IntroScreenState extends State { ); } - Widget _buildFinishBtn(BuildContext context) { - return ValueListenableBuilder( - valueListenable: _currentPage, - builder: (_, pageIndex, __) { - return AnimatedOpacity( - opacity: pageIndex == pageData.length - 1 ? 1 : 0, - duration: $styles.times.fast, - child: CircleIconBtn( - icon: AppIcons.next_large, - bgColor: $styles.colors.accent1, - onPressed: _handleIntroCompletePressed, - semanticLabel: $strings.introSemanticEnterApp, - ), - ); - }, - ); - } - Widget _buildNavText(BuildContext context) { return ValueListenableBuilder( valueListenable: _currentPage, diff --git a/lib/ui/screens/photo_gallery/photo_gallery.dart b/lib/ui/screens/photo_gallery/photo_gallery.dart index f54f503c..644649bd 100644 --- a/lib/ui/screens/photo_gallery/photo_gallery.dart +++ b/lib/ui/screens/photo_gallery/photo_gallery.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/data/unsplash_photo_data.dart'; import 'package:wonders/ui/common/controls/app_loading_indicator.dart'; import 'package:wonders/ui/common/controls/eight_way_swipe_detector.dart'; +import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart'; import 'package:wonders/ui/common/hidden_collectible.dart'; import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart'; import 'package:wonders/ui/common/unsplash_photo.dart'; @@ -33,10 +35,16 @@ class _PhotoGalleryState extends State { final _photoIds = ValueNotifier>([]); int get _imgCount => pow(_gridSize, 2).round(); + late final List _focusNodes = List.generate(_imgCount, (index) => FocusNode()); + + //TODO: Remove this field (and associated workarounds) once web properly supports ClipPath (https://github.com/flutter/flutter/issues/124675) + final bool useClipPathWorkAroundForWeb = kIsWeb; + @override void initState() { super.initState(); _initPhotoIds(); + _focusNodes[_index].requestFocus(); } Future _initPhotoIds() async { @@ -55,6 +63,7 @@ class _PhotoGalleryState extends State { if (value < 0 || value >= _imgCount) return; _skipNextOffsetTween = skipAnimation; setState(() => _index = value); + _focusNodes[value].requestFocus(); } /// Determine the required offset to show the current selected index. @@ -89,6 +98,37 @@ class _PhotoGalleryState extends State { } } + bool _handleKeyDown(KeyDownEvent event) { + var newIndex = -1; + bool handled = false; + final key = event.logicalKey; + Map keyActions = { + LogicalKeyboardKey.arrowUp: -_gridSize, + LogicalKeyboardKey.arrowDown: _gridSize, + LogicalKeyboardKey.arrowRight: 1, + LogicalKeyboardKey.arrowLeft: -1, + }; + + int? action = keyActions[key]; + if (action != null) { + newIndex = _index + action; + handled = true; + bool isRightSide = _index % _gridSize == _gridSize - 1; + if (isRightSide && key == LogicalKeyboardKey.arrowRight) { + newIndex = -1; + } + bool isLeftSide = _index % _gridSize == 0; + if (isLeftSide && key == LogicalKeyboardKey.arrowLeft) newIndex = -1; + if (newIndex > _gridSize * _gridSize) { + newIndex = -1; + } + if (newIndex >= 0) { + _setIndex(newIndex); + } + } + return handled; + } + /// Converts a swipe direction into a new index void _handleSwipe(Offset dir) { // Calculate new index, y swipes move by an entire row, x swipes move one index at a time @@ -104,7 +144,8 @@ class _PhotoGalleryState extends State { _setIndex(newIndex); } - Future _handleImageTapped(int index) async { + Future _handleImageTapped(int index, bool isSelected) async { + if (_checkCollectibleIndex(index) && isSelected) return; if (_index == index) { final urls = _photoIds.value.map((e) { return UnsplashPhotoData.getSelfHostedUrl(e, UnsplashPhotoSize.xl); @@ -122,119 +163,150 @@ class _PhotoGalleryState extends State { } } + void _handleImageFocusChanged(int index, bool isFocused) { + if (isFocused) { + _setIndex(index); + } + } + bool _checkCollectibleIndex(int index) { return index == _getCollectibleIndex() && collectiblesLogic.isLost(widget.wonderType, 1); } @override Widget build(BuildContext context) { - return ValueListenableBuilder>( - valueListenable: _photoIds, - builder: (_, value, __) { - if (value.isEmpty) { - return Center(child: AppLoadingIndicator()); - } - Size imgSize = context.isLandscape - ? Size(context.widthPx * .5, context.heightPx * .66) - : Size(context.widthPx * .66, context.heightPx * .5); - imgSize = (widget.imageSize ?? imgSize) * _scale; - // Get transform offset for the current _index - final padding = $styles.insets.md; - var gridOffset = _calculateCurrentOffset(padding, imgSize); - gridOffset += Offset(0, -context.mq.padding.top / 2); - final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration; - final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5; - return _AnimatedCutoutOverlay( - animationKey: ValueKey(_index), - cutoutSize: imgSize, - swipeDir: _lastSwipeDir, - duration: cutoutTweenDuration, - opacity: _scale == 1 ? .7 : .5, - child: SafeArea( - bottom: false, - // Place content in overflow box, to allow it to flow outside the parent - child: OverflowBox( - maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1), - maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1), - alignment: Alignment.center, - // Detect swipes in order to change index - child: EightWaySwipeDetector( - onSwipe: _handleSwipe, - threshold: 30, - // A tween animation builder moves from image to image based on current offset - child: TweenAnimationBuilder( - tween: Tween(begin: gridOffset, end: gridOffset), - duration: offsetTweenDuration, - curve: Curves.easeOut, - builder: (_, value, child) => Transform.translate(offset: value, child: child), - child: GridView.count( - physics: NeverScrollableScrollPhysics(), - crossAxisCount: _gridSize, - childAspectRatio: imgSize.aspectRatio, - mainAxisSpacing: padding, - crossAxisSpacing: padding, - children: List.generate(_imgCount, (i) => _buildImage(i, swipeDuration, imgSize)), + return FullscreenKeyboardListener( + onKeyDown: _handleKeyDown, + child: ValueListenableBuilder>( + valueListenable: _photoIds, + builder: (_, value, __) { + if (value.isEmpty) { + return Center(child: AppLoadingIndicator()); + } + Size imgSize = context.isLandscape + ? Size(context.widthPx * .5, context.heightPx * .66) + : Size(context.widthPx * .66, context.heightPx * .5); + imgSize = (widget.imageSize ?? imgSize) * _scale; + // Get transform offset for the current _index + final padding = $styles.insets.md; + var gridOffset = _calculateCurrentOffset(padding, imgSize); + gridOffset += Offset(0, -context.mq.padding.top / 2); + final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration; + final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5; + return _AnimatedCutoutOverlay( + animationKey: ValueKey(_index), + cutoutSize: imgSize, + swipeDir: _lastSwipeDir, + duration: cutoutTweenDuration, + opacity: _scale == 1 ? .7 : .5, + enabled: useClipPathWorkAroundForWeb == false, + child: SafeArea( + bottom: false, + // Place content in overflow box, to allow it to flow outside the parent + child: OverflowBox( + maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1), + maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1), + alignment: Alignment.center, + // Detect swipes in order to change index + child: EightWaySwipeDetector( + onSwipe: _handleSwipe, + threshold: 30, + // A tween animation builder moves from image to image based on current offset + child: TweenAnimationBuilder( + tween: Tween(begin: gridOffset, end: gridOffset), + duration: offsetTweenDuration, + curve: Curves.easeOut, + builder: (_, value, child) => Transform.translate(offset: value, child: child), + child: FocusTraversalGroup( + //policy: OrderedTraversalPolicy(), + child: GridView.count( + physics: NeverScrollableScrollPhysics(), + crossAxisCount: _gridSize, + childAspectRatio: imgSize.aspectRatio, + mainAxisSpacing: padding, + crossAxisSpacing: padding, + children: List.generate(_imgCount, (i) => _buildImage(i, swipeDuration, imgSize)), + ), + ), ), ), ), ), - ), - ); - }); + ); + }), + ); } Widget _buildImage(int index, Duration swipeDuration, Size imgSize) { /// Bind to collectibles.statesById because we might need to rebuild if a collectible is found. - return ValueListenableBuilder( - valueListenable: collectiblesLogic.statesById, - builder: (_, __, ___) { - bool selected = index == _index; - final imgUrl = _photoIds.value[index]; + return FocusTraversalOrder( + order: NumericFocusOrder(index.toDouble()), + child: ValueListenableBuilder( + valueListenable: collectiblesLogic.statesById, + builder: (_, __, ___) { + bool isSelected = index == _index; + final imgUrl = _photoIds.value[index]; + late String semanticLbl; + if (_checkCollectibleIndex(index)) { + semanticLbl = $strings.collectibleItemSemanticCollectible; + } else { + semanticLbl = !isSelected + ? $strings.photoGallerySemanticFocus(index + 1, _imgCount) + : $strings.photoGallerySemanticFullscreen(index + 1, _imgCount); + } - late String semanticLbl; - if (_checkCollectibleIndex(index)) { - semanticLbl = $strings.collectibleItemSemanticCollectible; - } else { - semanticLbl = !selected - ? $strings.photoGallerySemanticFocus(index + 1, _imgCount) - : $strings.photoGallerySemanticFullscreen(index + 1, _imgCount); - } - return MergeSemantics( - child: Semantics( - focused: selected, - image: !_checkCollectibleIndex(index), - liveRegion: selected, - onIncrease: () => _handleImageTapped(_index + 1), - onDecrease: () => _handleImageTapped(_index - 1), - child: AppBtn.basic( - semanticLabel: semanticLbl, - onPressed: () { - if (_checkCollectibleIndex(index) && selected) return; - _handleImageTapped(index); - }, - child: _checkCollectibleIndex(index) - ? HiddenCollectible(widget.wonderType, index: 1, size: 100) - : ClipRRect( - borderRadius: BorderRadius.circular(8), - child: SizedBox( - width: imgSize.width, - height: imgSize.height, - child: TweenAnimationBuilder( - duration: $styles.times.med, - curve: Curves.easeOut, - tween: Tween(begin: 1, end: selected ? 1.15 : 1), - builder: (_, value, child) => Transform.scale(scale: value, child: child), - child: UnsplashPhoto( - imgUrl, - fit: BoxFit.cover, - size: UnsplashPhotoSize.large, - ).animate().fade(), + final photoWidget = TweenAnimationBuilder( + duration: $styles.times.med, + curve: Curves.easeOut, + tween: Tween(begin: 1, end: isSelected ? 1.15 : 1), + builder: (_, value, child) => Transform.scale(scale: value, child: child), + child: UnsplashPhoto( + imgUrl, + fit: BoxFit.cover, + size: UnsplashPhotoSize.large, + ).animate().fade(), + ); + + return MergeSemantics( + child: Semantics( + focused: isSelected, + image: !_checkCollectibleIndex(index), + liveRegion: isSelected, + onIncrease: () => _handleImageTapped(_index + 1, false), + onDecrease: () => _handleImageTapped(_index - 1, false), + child: AppBtn.basic( + semanticLabel: semanticLbl, + focusNode: _focusNodes[index], + onFocusChanged: (isFocused) => _handleImageFocusChanged(index, isFocused), + onPressed: () => _handleImageTapped(index, isSelected), + child: _checkCollectibleIndex(index) + ? Center(child: HiddenCollectible(widget.wonderType, index: 1, size: 100)) + : ClipRRect( + borderRadius: BorderRadius.circular(8), + child: SizedBox( + width: imgSize.width, + height: imgSize.height, + child: (useClipPathWorkAroundForWeb == false) + ? photoWidget + : Stack( + children: [ + photoWidget, + // Because the web platform doesn't support clipPath, we use a workaround to highlight the selected image + Positioned.fill( + child: AnimatedOpacity( + duration: $styles.times.med, + opacity: isSelected ? 0 : .7, + child: ColoredBox(color: $styles.colors.black), + ), + ), + ], + ), ), ), - ), + ), ), - ), - ); - }); + ); + }), + ); } } diff --git a/lib/ui/screens/photo_gallery/widgets/_animated_cutout_overlay.dart b/lib/ui/screens/photo_gallery/widgets/_animated_cutout_overlay.dart index 5c3559d6..29aad469 100644 --- a/lib/ui/screens/photo_gallery/widgets/_animated_cutout_overlay.dart +++ b/lib/ui/screens/photo_gallery/widgets/_animated_cutout_overlay.dart @@ -4,23 +4,26 @@ part of '../photo_gallery.dart'; /// When animationKey changes, the box animates its size, shrinking then returning to its original size. /// Uses[_CutoutClipper] to create the cutout. class _AnimatedCutoutOverlay extends StatelessWidget { - const _AnimatedCutoutOverlay( - {Key? key, - required this.child, - required this.cutoutSize, - required this.animationKey, - this.duration, - required this.swipeDir, - required this.opacity}) - : super(key: key); + const _AnimatedCutoutOverlay({ + Key? key, + required this.child, + required this.cutoutSize, + required this.animationKey, + this.duration, + required this.swipeDir, + required this.opacity, + required this.enabled, + }) : super(key: key); final Widget child; final Size cutoutSize; final Key animationKey; final Offset swipeDir; final Duration? duration; final double opacity; + final bool enabled; @override Widget build(BuildContext context) { + if (!enabled) return child; return Stack( children: [ child, diff --git a/lib/ui/screens/wallpaper_photo/wallpaper_photo_screen.dart b/lib/ui/screens/wallpaper_photo/wallpaper_photo_screen.dart deleted file mode 100644 index 4a8fae95..00000000 --- a/lib/ui/screens/wallpaper_photo/wallpaper_photo_screen.dart +++ /dev/null @@ -1,176 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/rendering.dart'; -import 'package:wonders/common_libs.dart'; -import 'package:wonders/logic/common/platform_info.dart'; -import 'package:wonders/logic/data/wonder_data.dart'; -import 'package:wonders/ui/common/app_icons.dart'; -import 'package:wonders/ui/common/controls/checkbox.dart'; -import 'package:wonders/ui/wonder_illustrations/common/animated_clouds.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart'; -import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart'; - -class WallpaperPhotoScreen extends StatefulWidget { - const WallpaperPhotoScreen({Key? key, required this.type}) : super(key: key); - final WonderType type; - - @override - State createState() => _WallpaperPhotoScreenState(); -} - -class _WallpaperPhotoScreenState extends State { - final GlobalKey _containerKey = GlobalKey(); - Widget? _illustration; - - bool _showTitleText = true; - Timer? _photoRetryTimer; - - @override - void dispose() { - _photoRetryTimer?.cancel(); - super.dispose(); - } - - void _handleTakePhoto(BuildContext context, String wonderName) async { - final boundary = _containerKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; - if (boundary != null) { - wallpaperLogic.save(this, boundary, name: '${wonderName}_wallpaper'); - } - } - - void _handleSharePhoto(BuildContext context, String wonderName) async { - final boundary = _containerKey.currentContext!.findRenderObject() as RenderRepaintBoundary; - wallpaperLogic.share(context, boundary, name: '${wonderName}_wallpaper', wonderName: wonderName); - } - - void _handleTextToggle(bool? isActive) { - setState(() => _showTitleText = isActive ?? !_showTitleText); - } - - @override - Widget build(BuildContext context) { - WonderData wonderData = wondersLogic.getData(widget.type); - WonderIllustrationConfig bgConfig = WonderIllustrationConfig.bg( - enableAnims: false, - enableHero: false, - ); - WonderIllustrationConfig fgConfig = WonderIllustrationConfig( - enableAnims: false, - enableHero: false, - enableBg: false, - ); - Color fgColor = wonderData.type.bgColor; //.withOpacity(.5); - - _illustration = RepaintBoundary( - key: _containerKey, - child: ClipRect( - child: Stack( - children: [ - // Background - apply additional filter to make moon brighter - WonderIllustration( - widget.type, - config: bgConfig, - ), - - // Clouds - FractionallySizedBox( - widthFactor: 1, - heightFactor: .5, - child: AnimatedClouds( - wonderType: wonderData.type, - opacity: 1, - enableAnimations: false, - ), - ), - - // Wonder illustration - WonderIllustration( - widget.type, - config: fgConfig, - ), - - // Foreground gradient - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - fgColor.withOpacity(0), - fgColor.withOpacity(fgColor.opacity * .75), - ], - stops: const [0, 1], - ), - ), - ), - - // Title text - if (_showTitleText) - BottomCenter( - child: Transform.translate( - offset: Offset(0.0, -$styles.insets.xl * 2), - child: WonderTitleText(wonderData, enableShadows: true), - ), - ), - ], - ), - ), - ); - - return Stack(children: [ - Container( - decoration: BoxDecoration(backgroundBlendMode: BlendMode.color, color: Colors.blue), - child: _illustration ?? Container(), - ), - TopCenter( - child: SafeArea( - child: Padding( - padding: EdgeInsets.all($styles.insets.md), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: BackBtn.close( - bgColor: $styles.colors.offWhite, - iconColor: $styles.colors.black, - ), - ), - Expanded(child: Container()), - Padding( - padding: const EdgeInsets.only(top: 10.0, bottom: 10.0, right: 16.0), - child: CircleIconBtn( - icon: PlatformInfo.isIOS ? AppIcons.share_ios : AppIcons.share_android, - bgColor: $styles.colors.offWhite, - color: $styles.colors.black, - onPressed: () => _handleSharePhoto(context, wonderData.title), - semanticLabel: $strings.wallpaperSemanticSharePhoto, - size: 44, - ), - ), - CircleIconBtn( - icon: AppIcons.download, - onPressed: () => _handleTakePhoto(context, wonderData.title), - semanticLabel: $strings.wallpaperSemanticTakePhoto, - size: 64, - ), - ], - ), - ), - ), - ), - BottomCenter( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SimpleCheckbox( - active: _showTitleText, label: $strings.wallpaperCheckboxShowTitle, onToggled: _handleTextToggle), - Gap($styles.insets.xl), - ], - ), - ), - ]); - } -} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 25ce8448..884b3cc9 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,6 @@ import Foundation import desktop_window import package_info_plus import path_provider_foundation -import share_plus import shared_preferences_foundation import url_launcher_macos @@ -16,7 +15,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DesktopWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWindowPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 87e270e2..00000000 --- a/pubspec.lock +++ /dev/null @@ -1,1034 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - archive: - dependency: transitive - description: - name: archive - sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" - url: "https://pub.dev" - source: hosted - version: "3.4.6" - args: - dependency: transitive - description: - name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - build_config: - dependency: transitive - description: - name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 - url: "https://pub.dev" - source: hosted - version: "1.1.1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" - source: hosted - version: "2.0.3" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: "direct main" - description: - name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 - url: "https://pub.dev" - source: hosted - version: "1.17.2" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb - url: "https://pub.dev" - source: hosted - version: "0.3.3+5" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - csslib: - dependency: transitive - description: - name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d - url: "https://pub.dev" - source: hosted - version: "1.0.6" - dependency_validator: - dependency: "direct dev" - description: - name: dependency_validator - sha256: f727a5627aa405965fab4aef4f468e50a9b632ba0737fd2f98c932fec6d712b9 - url: "https://pub.dev" - source: hosted - version: "3.2.3" - desktop_window: - dependency: "direct main" - description: - name: desktop_window - sha256: "6256fb6feb7b5ec1311c76a3503f89202825bfe92c0458ec5fe7a728ffa216d5" - url: "https://pub.dev" - source: hosted - version: "0.4.0" - drop_cap_text: - dependency: "direct main" - description: - name: drop_cap_text - sha256: "9839d788ad6600c6c374fbc995a3cf784bbe9bf1823bcfa7dc49c4c50d918757" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - equatable: - dependency: "direct main" - description: - name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 - url: "https://pub.dev" - source: hosted - version: "2.0.5" - extra_alignments: - dependency: "direct main" - description: - name: extra_alignments - sha256: cc0425d08b3f1b3404439da939593aa3b7931c6f7f251030632e14337bf79e00 - url: "https://pub.dev" - source: hosted - version: "1.0.0+1" - ffi: - dependency: transitive - description: - name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - file: - dependency: transitive - description: - name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.dev" - source: hosted - version: "6.1.4" - flextras: - dependency: "direct main" - description: - name: flextras - sha256: e73b5c86dd9419569d2a48db470059b41b496012513e4e1bdc56ba2c661048d9 - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_animate: - dependency: "direct main" - description: - name: flutter_animate - sha256: "1f6fdee1f63eda3c35e04c07664593c31b7fdae290b6b0dc513d2fdfb2d27c76" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_circular_text: - dependency: "direct main" - description: - name: flutter_circular_text - sha256: "8b7a090198dc5f8651d97584e02fc121b51e92f4e3141c147a63243401609021" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - flutter_displaymode: - dependency: "direct main" - description: - name: flutter_displaymode - sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" - source: hosted - version: "2.0.3" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_native_splash: - dependency: "direct dev" - description: - name: flutter_native_splash - sha256: "91004565166dbbc7a85e7e99b84124a287839830ca957cfe45004793fe6fe69f" - url: "https://pub.dev" - source: hosted - version: "2.3.3" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c - url: "https://pub.dev" - source: hosted - version: "2.0.16" - flutter_staggered_grid_view: - dependency: "direct main" - description: - name: flutter_staggered_grid_view - sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" - url: "https://pub.dev" - source: hosted - version: "0.7.0" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - functional_listener: - dependency: transitive - description: - name: functional_listener - sha256: "026d1bd4f66367f11d9ec9f1f1ddb42b89e4484b356972c76d983266cf82f33f" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - gap: - dependency: "direct main" - description: - name: gap - sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d - url: "https://pub.dev" - source: hosted - version: "3.0.1" - get_it: - dependency: "direct main" - description: - name: get_it - sha256: f79870884de16d689cf9a7d15eedf31ed61d750e813c538a6efb92660fea83c3 - url: "https://pub.dev" - source: hosted - version: "7.6.4" - get_it_mixin: - dependency: "direct main" - description: - name: get_it_mixin - sha256: "0ab5c9f3cdaab813ec396de5d43ee3833c418424b3a99bec0071fcbf693c0bad" - url: "https://pub.dev" - source: hosted - version: "4.2.2" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: bd7e671d26fd39c78cba82070fa34ef1f830b0e7ed1aeebccabc6561302a7ee5 - url: "https://pub.dev" - source: hosted - version: "6.5.9" - google_maps: - dependency: transitive - description: - name: google_maps - sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" - url: "https://pub.dev" - source: hosted - version: "6.3.0" - google_maps_flutter: - dependency: "direct main" - description: - name: google_maps_flutter - sha256: d4914cb38b3dcb62c39c085d968d434de0f8050f00f4d9f5ba4a7c7e004934cb - url: "https://pub.dev" - source: hosted - version: "2.5.0" - google_maps_flutter_android: - dependency: transitive - description: - name: google_maps_flutter_android - sha256: e6cb018169e49332f88d23b1d2119b09e8ab4e7d3a1b889a1b7b3fd113e034ba - url: "https://pub.dev" - source: hosted - version: "2.5.1" - google_maps_flutter_ios: - dependency: transitive - description: - name: google_maps_flutter_ios - sha256: "2aa28eb9b9d5dfdce6932a7b7f096430bf83a1a09b4e21e81939351f407c787f" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - google_maps_flutter_platform_interface: - dependency: transitive - description: - name: google_maps_flutter_platform_interface - sha256: a3e9e6896501e566d902c6c69f010834d410ef4b7b5c18b90c77e871c86b7907 - url: "https://pub.dev" - source: hosted - version: "2.4.1" - google_maps_flutter_web: - dependency: "direct main" - description: - name: google_maps_flutter_web - sha256: f893d1542c6562bc8299ef768fbbe92ade83c220ab3209b9477ec9f81ad585e4 - url: "https://pub.dev" - source: hosted - version: "0.5.4+2" - home_widget: - dependency: "direct main" - description: - name: home_widget - sha256: "9a0ed6094823b07025727a39d3dc2d3e02c5281372af22d72e611137e0b3c10d" - url: "https://pub.dev" - source: hosted - version: "0.3.0" - html: - dependency: transitive - description: - name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" - url: "https://pub.dev" - source: hosted - version: "0.15.4" - http: - dependency: "direct main" - description: - name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - icons_launcher: - dependency: "direct dev" - description: - name: icons_launcher - sha256: "69de6373013966ea033f4cefbbbae258ccbfe790a6cfc69796cb33fda996298a" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - image: - dependency: "direct main" - description: - name: image - sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" - url: "https://pub.dev" - source: hosted - version: "4.1.3" - image_fade: - dependency: "direct main" - description: - name: image_fade - sha256: "7296c9c53cd5de98e675ef1e27bdaa4035d6c3a45cf5b86094b2e545689b4ea6" - url: "https://pub.dev" - source: hosted - version: "0.6.2" - image_gallery_saver: - dependency: "direct main" - description: - name: image_gallery_saver - sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816" - url: "https://pub.dev" - source: hosted - version: "2.0.3" - internet_connection_checker: - dependency: "direct main" - description: - name: internet_connection_checker - sha256: "1c683e63e89c9ac66a40748b1b20889fd9804980da732bf2b58d6d5456c8e876" - url: "https://pub.dev" - source: hosted - version: "1.0.0+1" - intl: - dependency: "direct main" - description: - name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" - url: "https://pub.dev" - source: hosted - version: "0.18.1" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - js_wrapping: - dependency: transitive - description: - name: js_wrapping - sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c - url: "https://pub.dev" - source: hosted - version: "0.7.4" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 - url: "https://pub.dev" - source: hosted - version: "4.8.1" - lints: - dependency: transitive - description: - name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - meta: - dependency: transitive - description: - name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - mime: - dependency: transitive - description: - name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e - url: "https://pub.dev" - source: hosted - version: "1.0.4" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - package_info_plus: - dependency: "direct main" - description: - name: package_info_plus - sha256: "0351aaba3b267c4962ed73058a5f62a84de7e39670a20e2916a6baff2ffcfbe5" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - particle_field: - dependency: "direct main" - description: - name: particle_field - sha256: "9a5ebdde32751f82aba64198e9bbd46738e789e275bf9e7a88318ed460be560e" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - path: - dependency: transitive - description: - name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" - url: "https://pub.dev" - source: hosted - version: "1.8.3" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf - url: "https://pub.dev" - source: hosted - version: "1.0.1" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa - url: "https://pub.dev" - source: hosted - version: "2.1.1" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" - url: "https://pub.dev" - source: hosted - version: "2.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 - url: "https://pub.dev" - source: hosted - version: "5.4.0" - platform: - dependency: transitive - description: - name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" - url: "https://pub.dev" - source: hosted - version: "3.1.3" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d - url: "https://pub.dev" - source: hosted - version: "2.1.6" - pointer_interceptor: - dependency: "direct main" - description: - name: pointer_interceptor - sha256: "7626e034489820fd599380d2bb4d3f4a0a5e3529370b62bfce53ab736b91adb2" - url: "https://pub.dev" - source: hosted - version: "0.9.3+6" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" - url: "https://pub.dev" - source: hosted - version: "3.7.3" - provider: - dependency: "direct main" - description: - name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f - url: "https://pub.dev" - source: hosted - version: "6.0.5" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 - url: "https://pub.dev" - source: hosted - version: "1.2.3" - rnd: - dependency: "direct main" - description: - name: rnd - sha256: "87799a8c447da2e728096264b2cc13679a11af49f0504331f4d588986a371fcf" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - sanitize_html: - dependency: transitive - description: - name: sanitize_html - sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - share_plus: - dependency: "direct main" - description: - name: share_plus - sha256: f86c5acc512b20e074137075824fc29e29b2cf395dcbfcc371e96e3e6290cce1 - url: "https://pub.dev" - source: hosted - version: "8.0.0" - share_plus_platform_interface: - dependency: transitive - description: - name: share_plus_platform_interface - sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" - url: "https://pub.dev" - source: hosted - version: "3.3.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" - url: "https://pub.dev" - source: hosted - version: "2.2.2" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" - url: "https://pub.dev" - source: hosted - version: "2.3.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f - url: "https://pub.dev" - source: hosted - version: "2.3.1" - sized_context: - dependency: "direct main" - description: - name: sized_context - sha256: "9921e6c09e018132c3e1c6a18e14febbc1cc5c87a200d64ff7578cb49991f6e7" - url: "https://pub.dev" - source: hosted - version: "1.0.0+4" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - smooth_page_indicator: - dependency: "direct main" - description: - name: smooth_page_indicator - sha256: "725bc638d5e79df0c84658e1291449996943f93bacbc2cec49963dbbab48d8ae" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - universal_io: - dependency: transitive - description: - name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" - url: "https://pub.dev" - source: hosted - version: "2.2.2" - universal_platform: - dependency: transitive - description: - name: universal_platform - sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc - url: "https://pub.dev" - source: hosted - version: "1.0.0+1" - unsplash_client: - dependency: "direct main" - description: - name: unsplash_client - sha256: "832011981ef358ef4f816f356375620791d24dc6e1afb33a37f066df9aaea537" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" - url: "https://pub.dev" - source: hosted - version: "6.1.14" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 - url: "https://pub.dev" - source: hosted - version: "6.1.0" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f" - url: "https://pub.dev" - source: hosted - version: "6.1.5" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e - url: "https://pub.dev" - source: hosted - version: "3.0.6" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88 - url: "https://pub.dev" - source: hosted - version: "3.0.7" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5" - url: "https://pub.dev" - source: hosted - version: "2.0.20" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069" - url: "https://pub.dev" - source: hosted - version: "3.0.8" - uuid: - dependency: transitive - description: - name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.dev" - source: hosted - version: "3.0.7" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f" - url: "https://pub.dev" - source: hosted - version: "1.1.7" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f" - url: "https://pub.dev" - source: hosted - version: "1.1.7" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e" - url: "https://pub.dev" - source: hosted - version: "1.1.7" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" - webview_flutter: - dependency: "direct main" - description: - name: webview_flutter - sha256: c1ab9b81090705c6069197d9fdc1625e587b52b8d70cdde2339d177ad0dbb98e - url: "https://pub.dev" - source: hosted - version: "4.4.1" - webview_flutter_android: - dependency: transitive - description: - name: webview_flutter_android - sha256: b0cd33dd7d3dd8e5f664e11a19e17ba12c352647269921a3b568406b001f1dff - url: "https://pub.dev" - source: hosted - version: "3.12.0" - webview_flutter_platform_interface: - dependency: transitive - description: - name: webview_flutter_platform_interface - sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f" - url: "https://pub.dev" - source: hosted - version: "2.6.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: "30b9af6bdd457b44c08748b9190d23208b5165357cc2eb57914fee1366c42974" - url: "https://pub.dev" - source: hosted - version: "3.9.1" - win32: - dependency: transitive - description: - name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" - url: "https://pub.dev" - source: hosted - version: "5.0.9" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - xml: - dependency: transitive - description: - name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" - url: "https://pub.dev" - source: hosted - version: "6.3.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" - youtube_player_iframe: - dependency: "direct main" - description: - name: youtube_player_iframe - sha256: d7aec9083430db4e5da83a3b5d7b7fcbb93cfa027d9f680ce3c7e7cd20724305 - url: "https://pub.dev" - source: hosted - version: "4.0.4" - youtube_player_iframe_web: - dependency: transitive - description: - name: youtube_player_iframe_web - sha256: c7020816031600349b56d2729d4e8be011fcb723ff7dc2dd0cdf72096a0e5ff4 - url: "https://pub.dev" - source: hosted - version: "2.0.2" -sdks: - dart: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index d7aeb2b6..3054a346 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,13 +23,14 @@ dependencies: flutter_animate: ^1.0.0 flutter_circular_text: ^0.3.1 flutter_displaymode: ^0.6.0 + flutter_native_splash: ^2.3.3 flutter_staggered_grid_view: ^0.7.0 flutter_svg: ^2.0.1 gap: ^3.0.1 get_it: ^7.2.0 get_it_mixin: ^4.2.2 - google_maps_flutter: ^2.5.0 - google_maps_flutter_web: ^0.5.4+2 + google_maps_flutter: ^2.5.3 + google_maps_flutter_web: ^0.5.4+3 go_router: ^6.5.5 home_widget: ^0.3.0 http: ^1.1.0 @@ -44,7 +45,6 @@ dependencies: pointer_interceptor: ^0.9.3+6 provider: ^6.0.5 rnd: ^0.2.0 - share_plus: ^8.0.0 shared_preferences: ^2.0.17 sized_context: ^1.0.0+1 smooth_page_indicator: ^1.0.1 @@ -56,7 +56,6 @@ dependencies: dev_dependencies: icons_launcher: ^2.1.3 flutter_lints: ^2.0.2 - flutter_native_splash: ^2.3.3 dependency_validator: ^3.2.2 icons_launcher: diff --git a/web/index.html b/web/index.html index d8f83033..e63f0f0a 100644 --- a/web/index.html +++ b/web/index.html @@ -1,17 +1,13 @@ - + + diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index dcda11a9..33bc361e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,14 +7,11 @@ #include "generated_plugin_registrant.h" #include -#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { DesktopWindowPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DesktopWindowPlugin")); - SharePlusWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index ad701266..d3bb5785 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_window - share_plus url_launcher_windows )