Merge main

This commit is contained in:
Shawn 2024-01-18 11:36:12 -07:00
commit 6f7a69d50d
35 changed files with 797 additions and 1578 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -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/petra_data.dart';
import 'package:wonders/logic/data/wonders_data/pyramids_giza_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/taj_mahal_data.dart';
import 'package:wonders/logic/data/wonders_data/colosseum_data.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/data/wonders_data/colosseum_data.dart';
class ArtifactDownloadHelper extends StatefulWidget { class ArtifactDownloadHelper extends StatefulWidget {
const ArtifactDownloadHelper({super.key}); const ArtifactDownloadHelper({super.key});
@ -82,16 +82,16 @@ class _ArtifactDownloadHelperState extends State<ArtifactDownloadHelper> {
PyramidsGizaData().searchData + PyramidsGizaData().searchData +
TajMahalData().searchData; TajMahalData().searchData;
// for (var a in searchData) { for (var a in searchData) {
// final id = a.id.toString(); final id = a.id.toString();
// if (await downloadImageAndJson(id) == false) { if (await downloadImageAndJson(id) == false) {
// missingIds.add(id); missingIds.add(id);
// } }
// final index = searchData.indexOf(a) + 1; final index = searchData.indexOf(a) + 1;
// if (index % 100 == 0) { if (index % 100 == 0) {
// debugPrint('$index/${searchData.length}'); debugPrint('$index/${searchData.length}');
// } }
// } }
debugPrint('Download complete :) Missing IDs: $missingIds'); debugPrint('Download complete :) Missing IDs: $missingIds');
} }

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'package:desktop_window/desktop_window.dart'; import 'package:desktop_window/desktop_window.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/common/platform_info.dart'; import 'package:wonders/logic/common/platform_info.dart';
@ -38,11 +39,21 @@ class AppLogic {
await DesktopWindow.setMinWindowSize($styles.sizes.minAppSize); 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 // Load any bitmaps the views might need
await AppBitmaps.init(); await AppBitmaps.init();
// Set preferred refresh rate to the max possible (the OS may ignore this) // Set preferred refresh rate to the max possible (the OS may ignore this)
if (PlatformInfo.isAndroid) { if (!kIsWeb && PlatformInfo.isAndroid) {
await FlutterDisplayMode.setHighRefreshRate(); await FlutterDisplayMode.setHighRefreshRate();
} }
@ -93,9 +104,6 @@ class AppLogic {
bool shouldUseNavRail() => _appSize.width > _appSize.height && _appSize.height > 250; 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() { void _updateSystemOrientation() {
final axisList = _supportedOrientationsOverride ?? supportedOrientations; final axisList = _supportedOrientationsOverride ?? supportedOrientations;
//debugPrint('updateDeviceOrientation, supportedAxis: $axisList'); //debugPrint('updateDeviceOrientation, supportedAxis: $axisList');

View File

@ -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<void> 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<void> 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<Uint8List?> _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;
}

View File

@ -2,14 +2,14 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:wonders/common_libs.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_logic.dart';
import 'package:wonders/logic/artifact_api_service.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/timeline_logic.dart';
import 'package:wonders/logic/unsplash_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/logic/wonders_logic.dart';
import 'package:wonders/ui/common/app_shortcuts.dart';
void main() async { void main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
@ -37,6 +37,7 @@ class WondersApp extends StatelessWidget with GetItMixin {
locale: locale == null ? null : Locale(locale), locale: locale == null ? null : Locale(locale),
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
routerDelegate: appRouter.routerDelegate, routerDelegate: appRouter.routerDelegate,
shortcuts: AppShortcuts.defaults,
theme: ThemeData(fontFamily: $styles.text.body.fontFamily, useMaterial3: true), theme: ThemeData(fontFamily: $styles.text.body.fontFamily, useMaterial3: true),
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizations.delegate,
@ -79,7 +80,6 @@ SettingsLogic get settingsLogic => GetIt.I.get<SettingsLogic>();
UnsplashLogic get unsplashLogic => GetIt.I.get<UnsplashLogic>(); UnsplashLogic get unsplashLogic => GetIt.I.get<UnsplashLogic>();
ArtifactAPILogic get artifactLogic => GetIt.I.get<ArtifactAPILogic>(); ArtifactAPILogic get artifactLogic => GetIt.I.get<ArtifactAPILogic>();
CollectiblesLogic get collectiblesLogic => GetIt.I.get<CollectiblesLogic>(); CollectiblesLogic get collectiblesLogic => GetIt.I.get<CollectiblesLogic>();
WallPaperLogic get wallpaperLogic => GetIt.I.get<WallPaperLogic>();
LocaleLogic get localeLogic => GetIt.I.get<LocaleLogic>(); LocaleLogic get localeLogic => GetIt.I.get<LocaleLogic>();
/// Global helpers for readability /// Global helpers for readability

View File

@ -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/home/wonders_home_screen.dart';
import 'package:wonders/ui/screens/intro/intro_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/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'; import 'package:wonders/ui/screens/wonder_details/wonders_details_screen.dart';
/// Shared paths / urls used across the app /// Shared paths / urls used across the app
@ -73,9 +72,6 @@ final appRouter = GoRouter(
AppRoute('/maps/:type', (s) { AppRoute('/maps/:type', (s) {
return FullscreenMapsViewer(type: _parseWonderType(s.params['type'])); return FullscreenMapsViewer(type: _parseWonderType(s.params['type']));
}), }),
AppRoute('/wallpaperPhoto/:type', (s) {
return WallpaperPhotoScreen(type: _parseWonderType(s.params['type']));
}),
]), ]),
], ],
); );

View File

@ -160,7 +160,7 @@ class _Sizes {
double get maxContentWidth1 => 800; double get maxContentWidth1 => 800;
double get maxContentWidth2 => 600; double get maxContentWidth2 => 600;
double get maxContentWidth3 => 500; double get maxContentWidth3 => 500;
final Size minAppSize = Size(380, 250); final Size minAppSize = Size(380, 650);
} }
@immutable @immutable

View File

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/common/platform_info.dart'; import 'package:wonders/logic/common/platform_info.dart';
class AppScrollBehavior extends ScrollBehavior { class AppScrollBehavior extends ScrollBehavior {
@ -15,12 +16,15 @@ class AppScrollBehavior extends ScrollBehavior {
@override @override
ScrollPhysics getScrollPhysics(BuildContext context) => const BouncingScrollPhysics(); ScrollPhysics getScrollPhysics(BuildContext context) => const BouncingScrollPhysics();
// TODO: Finalize scrollbar strategy (Do we use them at all? Where specifically?)
@override @override
Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) { Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
//return child; if (PlatformInfo.isMobile) return child;
return PlatformInfo.isAndroid return RawScrollbar(
? RawScrollbar(controller: details.controller, child: child) controller: details.controller,
: CupertinoScrollbar(controller: details.controller, child: child); thumbVisibility: PlatformInfo.isDesktopOrWeb,
thickness: 8,
interactive: true,
child: child,
);
} }
} }

View File

@ -0,0 +1,47 @@
import 'package:flutter/foundation.dart';
import 'package:wonders/common_libs.dart';
class AppShortcuts {
static final Map<ShortcutActivator, Intent> _defaultWebAndDesktopShortcuts = <ShortcutActivator, Intent>{
// 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<ShortcutActivator, Intent>? 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;
}
}
}

View File

@ -22,6 +22,8 @@ class AppBtn extends StatelessWidget {
this.minimumSize, this.minimumSize,
this.bgColor, this.bgColor,
this.border, this.border,
this.focusNode,
this.onFocusChanged,
}) : _builder = null, }) : _builder = null,
super(key: key); super(key: key);
@ -36,6 +38,8 @@ class AppBtn extends StatelessWidget {
this.minimumSize, this.minimumSize,
this.bgColor, this.bgColor,
this.border, this.border,
this.focusNode,
this.onFocusChanged,
String? semanticLabel, String? semanticLabel,
String? text, String? text,
AppIcons? icon, AppIcons? icon,
@ -76,6 +80,8 @@ class AppBtn extends StatelessWidget {
this.isSecondary = false, this.isSecondary = false,
this.circular = false, this.circular = false,
this.minimumSize, this.minimumSize,
this.focusNode,
this.onFocusChanged,
}) : expand = false, }) : expand = false,
bgColor = Colors.transparent, bgColor = Colors.transparent,
border = null, border = null,
@ -86,6 +92,8 @@ class AppBtn extends StatelessWidget {
final VoidCallback? onPressed; final VoidCallback? onPressed;
late final String semanticLabel; late final String semanticLabel;
final bool enableFeedback; final bool enableFeedback;
final FocusNode? focusNode;
final void Function(bool hasFocus)? onFocusChanged;
// content: // content:
late final Widget? child; late final Widget? child;
@ -129,15 +137,20 @@ class AppBtn extends StatelessWidget {
); );
Widget button = _CustomFocusBuilder( Widget button = _CustomFocusBuilder(
focusNode: focusNode,
onFocusChanged: onFocusChanged,
builder: (context, focus) => Stack( builder: (context, focus) => Stack(
children: [ children: [
TextButton( Opacity(
onPressed: onPressed, opacity: onPressed == null ? 0.5 : 1.0,
style: style, child: TextButton(
focusNode: focus, onPressed: onPressed,
child: DefaultTextStyle( style: style,
style: DefaultTextStyle.of(context).style.copyWith(color: textColor), focusNode: focus,
child: content, child: DefaultTextStyle(
style: DefaultTextStyle.of(context).style.copyWith(color: textColor),
child: content,
),
), ),
), ),
if (focus.hasFocus) if (focus.hasFocus)
@ -154,7 +167,7 @@ class AppBtn extends StatelessWidget {
); );
// add press effect: // add press effect:
if (pressEffect) button = _ButtonPressEffect(button); if (pressEffect && onPressed != null) button = _ButtonPressEffect(button);
// add semantics? // add semantics?
if (semanticLabel.isEmpty) return button; if (semanticLabel.isEmpty) return button;
@ -199,15 +212,28 @@ class _ButtonPressEffectState extends State<_ButtonPressEffect> {
} }
class _CustomFocusBuilder extends StatefulWidget { 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 Widget Function(BuildContext context, FocusNode focus) builder;
final void Function(bool hasFocus)? onFocusChanged;
final FocusNode? focusNode;
@override @override
State<_CustomFocusBuilder> createState() => _CustomFocusBuilderState(); State<_CustomFocusBuilder> createState() => _CustomFocusBuilderState();
} }
class _CustomFocusBuilderState extends State<_CustomFocusBuilder> { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,5 +1,6 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/app_icons.dart';
import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
class CircleBtn extends StatelessWidget { class CircleBtn extends StatelessWidget {
const CircleBtn({ const CircleBtn({
@ -14,7 +15,7 @@ class CircleBtn extends StatelessWidget {
static double defaultSize = 48; static double defaultSize = 48;
final VoidCallback onPressed; final VoidCallback? onPressed;
final Color? bgColor; final Color? bgColor;
final BorderSide? border; final BorderSide? border;
final Widget child; final Widget child;
@ -47,6 +48,7 @@ class CircleIconBtn extends StatelessWidget {
this.color, this.color,
this.size, this.size,
this.iconSize, this.iconSize,
this.flipIcon = false,
required this.semanticLabel, required this.semanticLabel,
}) : super(key: key); }) : super(key: key);
@ -54,13 +56,14 @@ class CircleIconBtn extends StatelessWidget {
static double defaultSize = 28; static double defaultSize = 28;
final AppIcons icon; final AppIcons icon;
final VoidCallback onPressed; final VoidCallback? onPressed;
final BorderSide? border; final BorderSide? border;
final Color? bgColor; final Color? bgColor;
final Color? color; final Color? color;
final String semanticLabel; final String semanticLabel;
final double? size; final double? size;
final double? iconSize; final double? iconSize;
final bool flipIcon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -72,7 +75,10 @@ class CircleIconBtn extends StatelessWidget {
size: size, size: size,
bgColor: bgColor ?? defaultColor, bgColor: bgColor ?? defaultColor,
semanticLabel: semanticLabel, 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, semanticLabel: $strings.circleButtonsSemanticClose,
bgColor: bgColor, bgColor: bgColor,
iconColor: iconColor); iconColor: iconColor);
bool _handleKeyDown(BuildContext context, KeyDownEvent event) {
if (event.logicalKey == LogicalKeyboardKey.escape) {
_handleOnPressed(context);
return true;
}
return false;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
<<<<<<< HEAD
return CircleIconBtn( return CircleIconBtn(
icon: icon, icon: icon,
bgColor: bgColor, bgColor: bgColor,
@ -118,10 +134,29 @@ class BackBtn extends StatelessWidget {
} }
}, },
semanticLabel: semanticLabel ?? $strings.circleButtonsSemanticBack, 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); Widget safe() => _SafeAreaWithPadding(child: this);
void _handleOnPressed(BuildContext context) {
if (onPressed != null) {
onPressed?.call();
} else {
Navigator.of(context).pop();
}
}
} }
class _SafeAreaWithPadding extends StatelessWidget { class _SafeAreaWithPadding extends StatelessWidget {

View File

@ -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;
}
}

View File

@ -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<FullscreenKeyboardListener> createState() => _FullscreenKeyboardListenerState();
}
class _FullscreenKeyboardListenerState extends State<FullscreenKeyboardListener> {
@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;
}

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:wonders/assets.dart'; import 'package:wonders/assets.dart';
@ -6,5 +5,4 @@ Marker getMapsMarker(LatLng position) => Marker(
markerId: MarkerId('0'), markerId: MarkerId('0'),
position: position, position: position,
icon: AppBitmaps.mapMarker, icon: AppBitmaps.mapMarker,
anchor: Offset(.5, .7),
); );

View File

@ -1,5 +1,9 @@
import 'dart:async';
import 'package:wonders/common_libs.dart'; 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/controls/app_header.dart';
import 'package:wonders/ui/common/fullscreen_keyboard_listener.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart';
class FullscreenUrlImgViewer extends StatefulWidget { class FullscreenUrlImgViewer extends StatefulWidget {
@ -15,7 +19,8 @@ class FullscreenUrlImgViewer extends StatefulWidget {
class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> { class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
final _isZoomed = ValueNotifier(false); final _isZoomed = ValueNotifier(false);
late final _controller = PageController(initialPage: widget.index); late final _controller = PageController(initialPage: widget.index)..addListener(_handlePageChanged);
late final ValueNotifier<int> _currentPage = ValueNotifier(widget.index);
@override @override
void dispose() { void dispose() {
@ -23,8 +28,35 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
super.dispose(); 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 _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget content = AnimatedBuilder( Widget content = AnimatedBuilder(
@ -48,13 +80,46 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
child: ExcludeSemantics(child: content), child: ExcludeSemantics(child: content),
); );
return Container( return FullscreenKeyboardListener(
color: $styles.colors.black, onKeyDown: _handleKeyDown,
child: Stack( child: Container(
children: [ color: $styles.colors.black,
Positioned.fill(child: content), child: Stack(
AppHeader(onBack: _handleBackPressed, isTransparent: true), 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(''),
)
],
);
},
),
),
)
}
],
),
), ),
); );
} }

View File

@ -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<PreviousNextNavigation> createState() => _PreviousNextNavigationState();
}
class _PreviousNextNavigationState extends State<PreviousNextNavigation> {
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,
)
],
),
),
),
),
],
),
),
);
}
}

View File

@ -12,7 +12,7 @@ class AppHaptics {
static void buttonPress() { static void buttonPress() {
// Android/Fuchsia expect haptics on all button presses, iOS does not. // Android/Fuchsia expect haptics on all button presses, iOS does not.
if (PlatformInfo.isAndroid) { if (!kIsWeb && PlatformInfo.isAndroid) {
lightImpact(); lightImpact();
} }
} }

View File

@ -37,6 +37,7 @@ class _CollectionListState extends State<_CollectionList> with GetItStateMixin {
// Maintain scroll position when switching between vertical and horizontal orientation. // 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. // Multiplies or divides the current scroll position by the ratio of the vertical and horizontal card extents.
void _maintainScrollPos() { void _maintainScrollPos() {
if (scrollController.hasClients == false) return;
const extentFactor = _CollectionList._vtCardExtent / _CollectionList._hzCardExtent; const extentFactor = _CollectionList._vtCardExtent / _CollectionList._hzCardExtent;
final currentPx = scrollController.position.pixels; final currentPx = scrollController.position.pixels;
if (_vtMode.value == true) { if (_vtMode.value == true) {

View File

@ -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/blend_mask.dart';
import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/centered_box.dart';
import 'package:wonders/ui/common/compass_divider.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/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/google_maps_marker.dart';
import 'package:wonders/ui/common/gradient_container.dart'; import 'package:wonders/ui/common/gradient_container.dart';
import 'package:wonders/ui/common/hidden_collectible.dart'; import 'package:wonders/ui/common/hidden_collectible.dart';
@ -72,7 +72,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
/// Attempt to maintain a similar aspect ratio for the image within the app-bar /// Attempt to maintain a similar aspect ratio for the image within the app-bar
double maxAppBarHeight = min(context.widthPx, $styles.sizes.maxContentWidth1) * 1.2; 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( return PopRouterOnOverScroll(
controller: _scroller, controller: _scroller,
child: ColoredBox( child: ColoredBox(
@ -111,54 +111,56 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
padding: widget.contentPadding, padding: widget.contentPadding,
child: SizedBox( child: SizedBox(
child: FocusTraversalGroup( child: FocusTraversalGroup(
child: CustomScrollView( child: FullscreenKeyboardListScroller(
primary: false, scrollController: _scroller,
controller: _scroller, child: CustomScrollView(
scrollBehavior: ScrollConfiguration.of(context).copyWith(), controller: _scroller,
key: PageStorageKey('editorial'), scrollBehavior: ScrollConfiguration.of(context).copyWith(),
slivers: [ key: PageStorageKey('editorial'),
/// Invisible padding at the top of the list, so the illustration shows through the btm slivers: [
SliverToBoxAdapter( /// Invisible padding at the top of the list, so the illustration shows through the btm
child: SizedBox(height: illustrationHeight), SliverToBoxAdapter(
), child: SizedBox(height: illustrationHeight),
/// Text content, animates itself to hide behind the app bar as it scrolls up
SliverToBoxAdapter(
child: ValueListenableBuilder<double>(
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),
), ),
),
/// Collapsing App bar, pins to the top of the list /// Text content, animates itself to hide behind the app bar as it scrolls up
SliverAppBar( SliverToBoxAdapter(
pinned: true, child: ValueListenableBuilder<double>(
collapsedHeight: minAppBarHeight, valueListenable: _scrollPos,
toolbarHeight: minAppBarHeight, builder: (_, value, child) {
expandedHeight: maxAppBarHeight, double offsetAmt = max(0, value * .3);
backgroundColor: Colors.transparent, double opacity = (1 - offsetAmt / 150).clamp(0, 1);
elevation: 0, return Transform.translate(
leading: SizedBox.shrink(), offset: Offset(0, offsetAmt),
flexibleSpace: SizedBox.expand( child: Opacity(opacity: opacity, child: child),
child: _AppBar( );
widget.data.type, },
scrollPos: _scrollPos, child: _TitleText(widget.data, scroller: _scroller),
sectionIndex: _sectionIndex,
), ),
), ),
),
/// Editorial content (text and images) /// Collapsing App bar, pins to the top of the list
_ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex), 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<WonderEditorialScreen> {
), ),
/// Home Btn /// Home Btn
if (showBackBtn) ...[ AnimatedBuilder(
AnimatedBuilder( animation: _scroller,
animation: _scroller, builder: (_, child) {
builder: (_, child) { return AnimatedOpacity(
return AnimatedOpacity( opacity: _scrollPos.value > 0 ? 0 : 1,
opacity: _scrollPos.value > 0 ? 0 : 1, duration: $styles.times.med,
duration: $styles.times.med, child: child,
child: child, );
); },
}, child: Align(
child: AppHeader(backIcon: AppIcons.north, isTransparent: true)) alignment: backBtnAlign,
], child: Padding(
padding: EdgeInsets.all($styles.insets.sm),
child: BackBtn(icon: AppIcons.north),
),
),
)
], ],
), ),
), ),

View File

@ -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_header.dart';
import 'package:wonders/ui/common/controls/app_page_indicator.dart'; import 'package:wonders/ui/common/controls/app_page_indicator.dart';
import 'package:wonders/ui/common/gradient_container.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/themed_text.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart';
import 'package:wonders/ui/screens/home_menu/home_menu.dart'; import 'package:wonders/ui/screens/home_menu/home_menu.dart';
@ -87,11 +88,18 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
void _handlePageIndicatorDotPressed(int index) => _setPageIndex(index); 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; 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. // 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; 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 { void _showDetailsPage() async {
@ -125,24 +133,25 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
return _swipeController.wrapGestureDetector(Container( return _swipeController.wrapGestureDetector(Container(
color: $styles.colors.black, color: $styles.colors.black,
child: Stack( child: PreviousNextNavigation(
children: [ listenToMouseWheel: false,
Stack( onPreviousPressed: () => _handlePrevNext(-1),
children: [ onNextPressed: () => _handlePrevNext(1),
/// Background child: Stack(
..._buildBgAndClouds(), children: [
/// Background
..._buildBgAndClouds(),
/// Wonders Illustrations (main content) /// Wonders Illustrations (main content)
_buildMgPageView(), _buildMgPageView(),
/// Foreground illustrations and gradients /// Foreground illustrations and gradients
_buildFgAndGradients(), _buildFgAndGradients(),
/// Controls that float on top of the various illustrations /// Controls that float on top of the various illustrations
_buildFloatingUi(), _buildFloatingUi(),
], ],
).animate().fadeIn(), ).animate().fadeIn(),
],
), ),
)); ));
} }

View File

@ -1,5 +1,4 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
@ -12,12 +11,11 @@ class AboutDialogContent extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
void handleTap(String url) { void handleTap(String url) {
if(PlatformInfo.isDesktopOrWeb){ if (PlatformInfo.isDesktopOrWeb) {
launchUrl(Uri.parse(url)); launchUrl(Uri.parse(url));
} else { } else {
Navigator.push(context, CupertinoPageRoute(builder: (_) => FullscreenWebView(url))); Navigator.push(context, CupertinoPageRoute(builder: (_) => FullscreenWebView(url)));
} }
} }
List<TextSpan> buildSpan(String text, {Map<String, List<String>>? linkSupplants}) { List<TextSpan> buildSpan(String text, {Map<String, List<String>>? linkSupplants}) {

View File

@ -1,9 +1,10 @@
import 'package:flutter/gestures.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:wonders/common_libs.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/controls/app_page_indicator.dart';
import 'package:wonders/ui/common/gradient_container.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/static_text_scale.dart';
import 'package:wonders/ui/common/themed_text.dart'; import 'package:wonders/ui/common/themed_text.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/common/utils/app_haptics.dart';
@ -24,13 +25,14 @@ class _IntroScreenState extends State<IntroScreen> {
static List<_PageData> pageData = []; static List<_PageData> pageData = [];
late final PageController _pageController = PageController()..addListener(_handlePageChanged); late final PageController _pageController = PageController()..addListener(_handlePageChanged);
final ValueNotifier<int> _currentPage = ValueNotifier(0); late final ValueNotifier<int> _currentPage = ValueNotifier(0)..addListener(() => setState(() {}));
bool get _isOnLastPage => _currentPage.value.round() == pageData.length - 1; bool get _isOnLastPage => _currentPage.value.round() == pageData.length - 1;
bool get _isOnFirstPage => _currentPage.value.round() == 0; bool get _isOnFirstPage => _currentPage.value.round() == 0;
@override @override
void dispose() { void dispose() {
_pageController.dispose(); _pageController.dispose();
_currentPage.dispose();
super.dispose(); super.dispose();
} }
@ -53,15 +55,13 @@ class _IntroScreenState extends State<IntroScreen> {
void _handleNavTextSemanticTap() => _incrementPage(1); void _handleNavTextSemanticTap() => _incrementPage(1);
void _incrementPage(int dir){ void _incrementPage(int dir) {
final int current = _pageController.page!.round(); final int current = _pageController.page!.round();
if (_isOnLastPage && dir > 0) return; if (_isOnLastPage && dir > 0) return;
if (_isOnFirstPage && dir < 0) return; if (_isOnFirstPage && dir < 0) return;
_pageController.animateToPage(current + dir, duration: 250.ms, curve: Curves.easeIn); _pageController.animateToPage(current + dir, duration: 250.ms, curve: Curves.easeIn);
} }
void _handleScrollWheel(double delta) => _incrementPage(delta >0? 1 : -1);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Set the page data, as strings may have changed based on locale // Set the page data, as strings may have changed based on locale
@ -78,20 +78,25 @@ class _IntroScreenState extends State<IntroScreen> {
final List<Widget> pages = pageData.map((e) => _Page(data: e)).toList(); final List<Widget> pages = pageData.map((e) => _Page(data: e)).toList();
/// Return resulting widget tree /// Return resulting widget tree
return Listener( return DefaultTextColor(
onPointerSignal: (signal){ color: $styles.colors.offWhite,
if(signal is PointerScrollEvent){ child: ColoredBox(
_handleScrollWheel(signal.scrollDelta.dy); color: $styles.colors.black,
} child: SafeArea(
}, child: Animate(
child: DefaultTextColor( delay: 500.ms,
color: $styles.colors.offWhite, effects: const [FadeEffect()],
child: Container( child: PreviousNextNavigation(
color: $styles.colors.black, maxWidth: 600,
child: SafeArea( nextBtnColor: _isOnLastPage ? $styles.colors.accent1 : null,
child: Animate( onPreviousPressed: _isOnFirstPage ? null : () => _incrementPage(-1),
delay: 500.ms, onNextPressed: () {
effects: const [FadeEffect()], if (_isOnLastPage) {
_handleIntroCompletePressed();
} else {
_incrementPage(1);
}
},
child: Stack( child: Stack(
children: [ children: [
// page view with title & description: // page view with title & description:
@ -159,20 +164,22 @@ class _IntroScreenState extends State<IntroScreen> {
_buildHzGradientOverlay(left: true), _buildHzGradientOverlay(left: true),
_buildHzGradientOverlay(), _buildHzGradientOverlay(),
// finish button:
Positioned(
right: $styles.insets.lg,
bottom: $styles.insets.lg,
child: _buildFinishBtn(context),
),
// nav help text: // nav help text:
BottomCenter( if (PlatformInfo.isMobile) ...[
child: Padding( // finish button:
padding: EdgeInsets.only(bottom: $styles.insets.lg), Positioned(
child: _buildNavText(context), 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<IntroScreen> {
); );
} }
Widget _buildFinishBtn(BuildContext context) {
return ValueListenableBuilder<int>(
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}) { Widget _buildHzGradientOverlay({bool left = false}) {
return Align( return Align(
alignment: Alignment(left ? -1 : 1, 0), alignment: Alignment(left ? -1 : 1, 0),
@ -203,24 +228,6 @@ class _IntroScreenState extends State<IntroScreen> {
); );
} }
Widget _buildFinishBtn(BuildContext context) {
return ValueListenableBuilder<int>(
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) { Widget _buildNavText(BuildContext context) {
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: _currentPage, valueListenable: _currentPage,

View File

@ -1,9 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/data/unsplash_photo_data.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/app_loading_indicator.dart';
import 'package:wonders/ui/common/controls/eight_way_swipe_detector.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/hidden_collectible.dart';
import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart'; import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart';
import 'package:wonders/ui/common/unsplash_photo.dart'; import 'package:wonders/ui/common/unsplash_photo.dart';
@ -33,10 +35,16 @@ class _PhotoGalleryState extends State<PhotoGallery> {
final _photoIds = ValueNotifier<List<String>>([]); final _photoIds = ValueNotifier<List<String>>([]);
int get _imgCount => pow(_gridSize, 2).round(); int get _imgCount => pow(_gridSize, 2).round();
late final List<FocusNode> _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 @override
void initState() { void initState() {
super.initState(); super.initState();
_initPhotoIds(); _initPhotoIds();
_focusNodes[_index].requestFocus();
} }
Future<void> _initPhotoIds() async { Future<void> _initPhotoIds() async {
@ -55,6 +63,7 @@ class _PhotoGalleryState extends State<PhotoGallery> {
if (value < 0 || value >= _imgCount) return; if (value < 0 || value >= _imgCount) return;
_skipNextOffsetTween = skipAnimation; _skipNextOffsetTween = skipAnimation;
setState(() => _index = value); setState(() => _index = value);
_focusNodes[value].requestFocus();
} }
/// Determine the required offset to show the current selected index. /// Determine the required offset to show the current selected index.
@ -89,6 +98,37 @@ class _PhotoGalleryState extends State<PhotoGallery> {
} }
} }
bool _handleKeyDown(KeyDownEvent event) {
var newIndex = -1;
bool handled = false;
final key = event.logicalKey;
Map<LogicalKeyboardKey, int> 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 /// Converts a swipe direction into a new index
void _handleSwipe(Offset dir) { void _handleSwipe(Offset dir) {
// Calculate new index, y swipes move by an entire row, x swipes move one index at a time // 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<PhotoGallery> {
_setIndex(newIndex); _setIndex(newIndex);
} }
Future<void> _handleImageTapped(int index) async { Future<void> _handleImageTapped(int index, bool isSelected) async {
if (_checkCollectibleIndex(index) && isSelected) return;
if (_index == index) { if (_index == index) {
final urls = _photoIds.value.map((e) { final urls = _photoIds.value.map((e) {
return UnsplashPhotoData.getSelfHostedUrl(e, UnsplashPhotoSize.xl); return UnsplashPhotoData.getSelfHostedUrl(e, UnsplashPhotoSize.xl);
@ -122,119 +163,150 @@ class _PhotoGalleryState extends State<PhotoGallery> {
} }
} }
void _handleImageFocusChanged(int index, bool isFocused) {
if (isFocused) {
_setIndex(index);
}
}
bool _checkCollectibleIndex(int index) { bool _checkCollectibleIndex(int index) {
return index == _getCollectibleIndex() && collectiblesLogic.isLost(widget.wonderType, 1); return index == _getCollectibleIndex() && collectiblesLogic.isLost(widget.wonderType, 1);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<List<String>>( return FullscreenKeyboardListener(
valueListenable: _photoIds, onKeyDown: _handleKeyDown,
builder: (_, value, __) { child: ValueListenableBuilder<List<String>>(
if (value.isEmpty) { valueListenable: _photoIds,
return Center(child: AppLoadingIndicator()); builder: (_, value, __) {
} if (value.isEmpty) {
Size imgSize = context.isLandscape return Center(child: AppLoadingIndicator());
? Size(context.widthPx * .5, context.heightPx * .66) }
: Size(context.widthPx * .66, context.heightPx * .5); Size imgSize = context.isLandscape
imgSize = (widget.imageSize ?? imgSize) * _scale; ? Size(context.widthPx * .5, context.heightPx * .66)
// Get transform offset for the current _index : Size(context.widthPx * .66, context.heightPx * .5);
final padding = $styles.insets.md; imgSize = (widget.imageSize ?? imgSize) * _scale;
var gridOffset = _calculateCurrentOffset(padding, imgSize); // Get transform offset for the current _index
gridOffset += Offset(0, -context.mq.padding.top / 2); final padding = $styles.insets.md;
final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration; var gridOffset = _calculateCurrentOffset(padding, imgSize);
final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5; gridOffset += Offset(0, -context.mq.padding.top / 2);
return _AnimatedCutoutOverlay( final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration;
animationKey: ValueKey(_index), final cutoutTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration * .5;
cutoutSize: imgSize, return _AnimatedCutoutOverlay(
swipeDir: _lastSwipeDir, animationKey: ValueKey(_index),
duration: cutoutTweenDuration, cutoutSize: imgSize,
opacity: _scale == 1 ? .7 : .5, swipeDir: _lastSwipeDir,
child: SafeArea( duration: cutoutTweenDuration,
bottom: false, opacity: _scale == 1 ? .7 : .5,
// Place content in overflow box, to allow it to flow outside the parent enabled: useClipPathWorkAroundForWeb == false,
child: OverflowBox( child: SafeArea(
maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1), bottom: false,
maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1), // Place content in overflow box, to allow it to flow outside the parent
alignment: Alignment.center, child: OverflowBox(
// Detect swipes in order to change index maxWidth: _gridSize * imgSize.width + padding * (_gridSize - 1),
child: EightWaySwipeDetector( maxHeight: _gridSize * imgSize.height + padding * (_gridSize - 1),
onSwipe: _handleSwipe, alignment: Alignment.center,
threshold: 30, // Detect swipes in order to change index
// A tween animation builder moves from image to image based on current offset child: EightWaySwipeDetector(
child: TweenAnimationBuilder<Offset>( onSwipe: _handleSwipe,
tween: Tween(begin: gridOffset, end: gridOffset), threshold: 30,
duration: offsetTweenDuration, // A tween animation builder moves from image to image based on current offset
curve: Curves.easeOut, child: TweenAnimationBuilder<Offset>(
builder: (_, value, child) => Transform.translate(offset: value, child: child), tween: Tween(begin: gridOffset, end: gridOffset),
child: GridView.count( duration: offsetTweenDuration,
physics: NeverScrollableScrollPhysics(), curve: Curves.easeOut,
crossAxisCount: _gridSize, builder: (_, value, child) => Transform.translate(offset: value, child: child),
childAspectRatio: imgSize.aspectRatio, child: FocusTraversalGroup(
mainAxisSpacing: padding, //policy: OrderedTraversalPolicy(),
crossAxisSpacing: padding, child: GridView.count(
children: List.generate(_imgCount, (i) => _buildImage(i, swipeDuration, imgSize)), 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) { Widget _buildImage(int index, Duration swipeDuration, Size imgSize) {
/// Bind to collectibles.statesById because we might need to rebuild if a collectible is found. /// Bind to collectibles.statesById because we might need to rebuild if a collectible is found.
return ValueListenableBuilder( return FocusTraversalOrder(
valueListenable: collectiblesLogic.statesById, order: NumericFocusOrder(index.toDouble()),
builder: (_, __, ___) { child: ValueListenableBuilder(
bool selected = index == _index; valueListenable: collectiblesLogic.statesById,
final imgUrl = _photoIds.value[index]; 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; final photoWidget = TweenAnimationBuilder<double>(
if (_checkCollectibleIndex(index)) { duration: $styles.times.med,
semanticLbl = $strings.collectibleItemSemanticCollectible; curve: Curves.easeOut,
} else { tween: Tween(begin: 1, end: isSelected ? 1.15 : 1),
semanticLbl = !selected builder: (_, value, child) => Transform.scale(scale: value, child: child),
? $strings.photoGallerySemanticFocus(index + 1, _imgCount) child: UnsplashPhoto(
: $strings.photoGallerySemanticFullscreen(index + 1, _imgCount); imgUrl,
} fit: BoxFit.cover,
return MergeSemantics( size: UnsplashPhotoSize.large,
child: Semantics( ).animate().fade(),
focused: selected, );
image: !_checkCollectibleIndex(index),
liveRegion: selected, return MergeSemantics(
onIncrease: () => _handleImageTapped(_index + 1), child: Semantics(
onDecrease: () => _handleImageTapped(_index - 1), focused: isSelected,
child: AppBtn.basic( image: !_checkCollectibleIndex(index),
semanticLabel: semanticLbl, liveRegion: isSelected,
onPressed: () { onIncrease: () => _handleImageTapped(_index + 1, false),
if (_checkCollectibleIndex(index) && selected) return; onDecrease: () => _handleImageTapped(_index - 1, false),
_handleImageTapped(index); child: AppBtn.basic(
}, semanticLabel: semanticLbl,
child: _checkCollectibleIndex(index) focusNode: _focusNodes[index],
? HiddenCollectible(widget.wonderType, index: 1, size: 100) onFocusChanged: (isFocused) => _handleImageFocusChanged(index, isFocused),
: ClipRRect( onPressed: () => _handleImageTapped(index, isSelected),
borderRadius: BorderRadius.circular(8), child: _checkCollectibleIndex(index)
child: SizedBox( ? Center(child: HiddenCollectible(widget.wonderType, index: 1, size: 100))
width: imgSize.width, : ClipRRect(
height: imgSize.height, borderRadius: BorderRadius.circular(8),
child: TweenAnimationBuilder<double>( child: SizedBox(
duration: $styles.times.med, width: imgSize.width,
curve: Curves.easeOut, height: imgSize.height,
tween: Tween(begin: 1, end: selected ? 1.15 : 1), child: (useClipPathWorkAroundForWeb == false)
builder: (_, value, child) => Transform.scale(scale: value, child: child), ? photoWidget
child: UnsplashPhoto( : Stack(
imgUrl, children: [
fit: BoxFit.cover, photoWidget,
size: UnsplashPhotoSize.large, // Because the web platform doesn't support clipPath, we use a workaround to highlight the selected image
).animate().fade(), Positioned.fill(
child: AnimatedOpacity(
duration: $styles.times.med,
opacity: isSelected ? 0 : .7,
child: ColoredBox(color: $styles.colors.black),
),
),
],
),
), ),
), ),
), ),
), ),
), );
); }),
}); );
} }
} }

View File

@ -4,23 +4,26 @@ part of '../photo_gallery.dart';
/// When animationKey changes, the box animates its size, shrinking then returning to its original size. /// When animationKey changes, the box animates its size, shrinking then returning to its original size.
/// Uses[_CutoutClipper] to create the cutout. /// Uses[_CutoutClipper] to create the cutout.
class _AnimatedCutoutOverlay extends StatelessWidget { class _AnimatedCutoutOverlay extends StatelessWidget {
const _AnimatedCutoutOverlay( const _AnimatedCutoutOverlay({
{Key? key, Key? key,
required this.child, required this.child,
required this.cutoutSize, required this.cutoutSize,
required this.animationKey, required this.animationKey,
this.duration, this.duration,
required this.swipeDir, required this.swipeDir,
required this.opacity}) required this.opacity,
: super(key: key); required this.enabled,
}) : super(key: key);
final Widget child; final Widget child;
final Size cutoutSize; final Size cutoutSize;
final Key animationKey; final Key animationKey;
final Offset swipeDir; final Offset swipeDir;
final Duration? duration; final Duration? duration;
final double opacity; final double opacity;
final bool enabled;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!enabled) return child;
return Stack( return Stack(
children: [ children: [
child, child,

View File

@ -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<WallpaperPhotoScreen> createState() => _WallpaperPhotoScreenState();
}
class _WallpaperPhotoScreenState extends State<WallpaperPhotoScreen> {
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),
],
),
),
]);
}
}

View File

@ -8,7 +8,6 @@ import Foundation
import desktop_window import desktop_window
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
import share_plus
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
@ -16,7 +15,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWindowPlugin")) DesktopWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWindowPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
} }

File diff suppressed because it is too large Load Diff

View File

@ -23,13 +23,14 @@ dependencies:
flutter_animate: ^1.0.0 flutter_animate: ^1.0.0
flutter_circular_text: ^0.3.1 flutter_circular_text: ^0.3.1
flutter_displaymode: ^0.6.0 flutter_displaymode: ^0.6.0
flutter_native_splash: ^2.3.3
flutter_staggered_grid_view: ^0.7.0 flutter_staggered_grid_view: ^0.7.0
flutter_svg: ^2.0.1 flutter_svg: ^2.0.1
gap: ^3.0.1 gap: ^3.0.1
get_it: ^7.2.0 get_it: ^7.2.0
get_it_mixin: ^4.2.2 get_it_mixin: ^4.2.2
google_maps_flutter: ^2.5.0 google_maps_flutter: ^2.5.3
google_maps_flutter_web: ^0.5.4+2 google_maps_flutter_web: ^0.5.4+3
go_router: ^6.5.5 go_router: ^6.5.5
home_widget: ^0.3.0 home_widget: ^0.3.0
http: ^1.1.0 http: ^1.1.0
@ -44,7 +45,6 @@ dependencies:
pointer_interceptor: ^0.9.3+6 pointer_interceptor: ^0.9.3+6
provider: ^6.0.5 provider: ^6.0.5
rnd: ^0.2.0 rnd: ^0.2.0
share_plus: ^8.0.0
shared_preferences: ^2.0.17 shared_preferences: ^2.0.17
sized_context: ^1.0.0+1 sized_context: ^1.0.0+1
smooth_page_indicator: ^1.0.1 smooth_page_indicator: ^1.0.1
@ -56,7 +56,6 @@ dependencies:
dev_dependencies: dev_dependencies:
icons_launcher: ^2.1.3 icons_launcher: ^2.1.3
flutter_lints: ^2.0.2 flutter_lints: ^2.0.2
flutter_native_splash: ^2.3.3
dependency_validator: ^3.2.2 dependency_validator: ^3.2.2
icons_launcher: icons_launcher:

View File

@ -1,17 +1,13 @@
<!DOCTYPE html><html><head> <!DOCTYPE html><html><head>
<!-- <!-- Google tag (gtag.js) -->
If you are serving your web app in a path other than the root, change the <script async src="https://www.googletagmanager.com/gtag/js?id=G-24FD0ZMQPW"></script>
href value below to reflect the base path you are serving from. <script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
The path provided below has to start and end with a slash "/" in order for gtag('config', 'G-24FD0ZMQPW');
it to work correctly. </script>
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="/web/"> <base href="/web/">
<meta charset="UTF-8"> <meta charset="UTF-8">

View File

@ -7,14 +7,11 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <desktop_window/desktop_window_plugin.h> #include <desktop_window/desktop_window_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
DesktopWindowPluginRegisterWithRegistrar( DesktopWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWindowPlugin")); registry->GetRegistrarForPlugin("DesktopWindowPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
desktop_window desktop_window
share_plus
url_launcher_windows url_launcher_windows
) )