From c018fa3dd12639868e807bca162f51fb637998f5 Mon Sep 17 00:00:00 2001 From: Shawn Date: Thu, 18 Jan 2024 12:43:03 -0700 Subject: [PATCH 1/6] Create NativeWidgetService to seperate view layer from HomeWidget implementation details. --- lib/logic/collectibles_logic.dart | 27 +++++++++++++++------------ lib/logic/native_widget_service.dart | 28 ++++++++++++++++++++++++++++ lib/main.dart | 3 +++ 3 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 lib/logic/native_widget_service.dart diff --git a/lib/logic/collectibles_logic.dart b/lib/logic/collectibles_logic.dart index 976be536..6328f61a 100644 --- a/lib/logic/collectibles_logic.dart +++ b/lib/logic/collectibles_logic.dart @@ -2,15 +2,15 @@ import 'dart:convert'; import 'package:home_widget/home_widget.dart'; import 'package:wonders/common_libs.dart'; +import 'package:wonders/logic/common/platform_info.dart'; import 'package:wonders/logic/common/save_load_mixin.dart'; import 'package:wonders/logic/data/collectible_data.dart'; import 'package:http/http.dart' as http; +import 'package:wonders/logic/native_widget_service.dart'; class CollectiblesLogic with ThrottledSaveLoadMixin { @override String get fileName => 'collectibles.dat'; - static const _appGroupId = 'group.com.gskinner.flutter.wonders.widget'; - static const _appName = 'WonderousWidget'; /// Holds all collectibles that the views should care about final List all = collectiblesData; @@ -26,9 +26,9 @@ class CollectiblesLogic with ThrottledSaveLoadMixin { int get exploredCount => _exploredCount; - void init() { - HomeWidget.setAppGroupId(_appGroupId); - } + late final _nativeWidget = GetIt.I(); + + void init() => _nativeWidget.init(); CollectibleData? fromId(String? id) => id == null ? null : all.firstWhereOrNull((o) => o.id == id); @@ -58,9 +58,12 @@ class CollectiblesLogic with ThrottledSaveLoadMixin { if (state == CollectibleState.explored) _exploredCount++; }); final foundCount = discoveredCount + exploredCount; - HomeWidget.saveWidgetData('discoveredCount', foundCount).then((value) { - HomeWidget.updateWidget(iOSName: _appName); - }); + if (PlatformInfo.isIOS) { + _nativeWidget.save('discoveredCount', foundCount).then((value) { + _nativeWidget.markDirty(); + }); + } + debugPrint('setting discoveredCount for home widget $foundCount'); } @@ -100,14 +103,14 @@ class CollectiblesLogic with ThrottledSaveLoadMixin { Future _updateHomeWidgetTextData({String title = '', String id = '', String imageUrl = ''}) async { // Save title - await HomeWidget.saveWidgetData('lastDiscoveredTitle', title); + await _nativeWidget.save('lastDiscoveredTitle', title); // Subtitle String subTitle = ''; if (id.isNotEmpty) { final artifactData = await artifactLogic.getArtifactByID(id); subTitle = artifactData?.date ?? ''; } - await HomeWidget.saveWidgetData('lastDiscoveredSubTitle', subTitle); + await _nativeWidget.save('lastDiscoveredSubTitle', subTitle); // Image, // Download, convert to base64 string and write to shared widget data String imageBase64 = ''; @@ -116,8 +119,8 @@ class CollectiblesLogic with ThrottledSaveLoadMixin { imageBase64 = base64Encode(bytes); debugPrint('Saving base64 bytes: $imageBase64'); } - await HomeWidget.saveWidgetData('lastDiscoveredImageData', imageBase64); - await HomeWidget.updateWidget(iOSName: _appName); + await _nativeWidget.save('lastDiscoveredImageData', imageBase64); + await _nativeWidget.markDirty(); } @override diff --git a/lib/logic/native_widget_service.dart b/lib/logic/native_widget_service.dart new file mode 100644 index 00000000..ae86086f --- /dev/null +++ b/lib/logic/native_widget_service.dart @@ -0,0 +1,28 @@ +import 'package:home_widget/home_widget.dart'; +import 'package:wonders/logic/common/platform_info.dart'; + +/// Small facade for the HomeWidget package +class NativeWidgetService { + static const _iosAppGroupId = 'group.com.gskinner.flutter.wonders.widget'; + static const _iosAppName = 'WonderousWidget'; + + static final bool _isSupported = PlatformInfo.isIOS; + + Future init() async { + if (!_isSupported) return; + await HomeWidget.setAppGroupId(_iosAppGroupId); + } + + Future save(String s, T value, {void Function(bool?)? onSaveComplete}) async { + if (!_isSupported) return false; + return await HomeWidget.saveWidgetData(s, value).then((value) { + onSaveComplete?.call(value); + return null; + }); + } + + Future markDirty() async { + if (!_isSupported) return false; + return await HomeWidget.updateWidget(iOSName: _iosAppName); + } +} diff --git a/lib/main.dart b/lib/main.dart index f826298b..e486080e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:wonders/common_libs.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/native_widget_service.dart'; import 'package:wonders/logic/locale_logic.dart'; import 'package:wonders/logic/timeline_logic.dart'; import 'package:wonders/logic/unsplash_logic.dart'; @@ -69,6 +70,8 @@ void registerSingletons() { GetIt.I.registerLazySingleton(() => CollectiblesLogic()); // Localizations GetIt.I.registerLazySingleton(() => LocaleLogic()); + // Home Widget Service + GetIt.I.registerLazySingleton(() => NativeWidgetService()); } /// Add syntax sugar for quickly accessing the main "logic" controllers in the app From b28c6994f7a566994aa3ca268b32a505ce5c7660 Mon Sep 17 00:00:00 2001 From: Shawn Date: Fri, 19 Jan 2024 10:24:57 -0700 Subject: [PATCH 2/6] Closes #161, fix release builds not working --- android/app/build.gradle | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c3ffe6c3..3d248174 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -47,17 +47,9 @@ android { versionName flutterVersionName } - signingConfigs { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword keystoreProperties['storePassword'] - } - } buildTypes { release { - signingConfig signingConfigs.release + signingConfig signingConfigs.debug } } From 9d2409cc9d479de6a1be778217b4b558f9189f32 Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 22 Jan 2024 15:08:58 -0700 Subject: [PATCH 3/6] Cleanup --- lib/logic/collectibles_logic.dart | 19 +++++++++---------- lib/logic/native_widget_service.dart | 8 ++++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/logic/collectibles_logic.dart b/lib/logic/collectibles_logic.dart index 6328f61a..e57a9df0 100644 --- a/lib/logic/collectibles_logic.dart +++ b/lib/logic/collectibles_logic.dart @@ -1,11 +1,10 @@ import 'dart:convert'; -import 'package:home_widget/home_widget.dart'; +import 'package:http/http.dart' as http; import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/common/platform_info.dart'; import 'package:wonders/logic/common/save_load_mixin.dart'; import 'package:wonders/logic/data/collectible_data.dart'; -import 'package:http/http.dart' as http; import 'package:wonders/logic/native_widget_service.dart'; class CollectiblesLogic with ThrottledSaveLoadMixin { @@ -42,7 +41,7 @@ class CollectiblesLogic with ThrottledSaveLoadMixin { statesById.value = states; if (state == CollectibleState.discovered) { final data = fromId(id)!; - _updateHomeWidgetTextData( + _updateNativeWidgetTextData( title: data.title, id: data.id, imageUrl: data.imageUrlSmall, @@ -58,11 +57,9 @@ class CollectiblesLogic with ThrottledSaveLoadMixin { if (state == CollectibleState.explored) _exploredCount++; }); final foundCount = discoveredCount + exploredCount; - if (PlatformInfo.isIOS) { - _nativeWidget.save('discoveredCount', foundCount).then((value) { - _nativeWidget.markDirty(); - }); - } + _nativeWidget.save('discoveredCount', foundCount).then((value) { + _nativeWidget.markDirty(); + }); debugPrint('setting discoveredCount for home widget $foundCount'); } @@ -95,13 +92,15 @@ class CollectiblesLogic with ThrottledSaveLoadMixin { for (int i = 0; i < all.length; i++) { states[all[i].id] = CollectibleState.lost; } - _updateHomeWidgetTextData(); // clear home widget data + if (_nativeWidget.isSupported) { + _updateNativeWidgetTextData(); // clear home widget data + } statesById.value = states; debugPrint('collection reset'); scheduleSave(); } - Future _updateHomeWidgetTextData({String title = '', String id = '', String imageUrl = ''}) async { + Future _updateNativeWidgetTextData({String title = '', String id = '', String imageUrl = ''}) async { // Save title await _nativeWidget.save('lastDiscoveredTitle', title); // Subtitle diff --git a/lib/logic/native_widget_service.dart b/lib/logic/native_widget_service.dart index ae86086f..b66dbc00 100644 --- a/lib/logic/native_widget_service.dart +++ b/lib/logic/native_widget_service.dart @@ -6,15 +6,15 @@ class NativeWidgetService { static const _iosAppGroupId = 'group.com.gskinner.flutter.wonders.widget'; static const _iosAppName = 'WonderousWidget'; - static final bool _isSupported = PlatformInfo.isIOS; + final bool isSupported = PlatformInfo.isIOS; Future init() async { - if (!_isSupported) return; + if (!isSupported) return; await HomeWidget.setAppGroupId(_iosAppGroupId); } Future save(String s, T value, {void Function(bool?)? onSaveComplete}) async { - if (!_isSupported) return false; + if (!isSupported) return false; return await HomeWidget.saveWidgetData(s, value).then((value) { onSaveComplete?.call(value); return null; @@ -22,7 +22,7 @@ class NativeWidgetService { } Future markDirty() async { - if (!_isSupported) return false; + if (!isSupported) return false; return await HomeWidget.updateWidget(iOSName: _iosAppName); } } From 663ad161fd6301ec495b03f89386af0842f7a89d Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 22 Jan 2024 15:32:20 -0700 Subject: [PATCH 4/6] Expose focusNode for HiddenCollectible to improve keyboard UX in the photo_gallery Improve readability of _handleKeyDown method --- lib/ui/common/collectible_item.dart | 4 +- lib/ui/common/hidden_collectible.dart | 6 ++- .../screens/photo_gallery/photo_gallery.dart | 54 +++++++++---------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/lib/ui/common/collectible_item.dart b/lib/ui/common/collectible_item.dart index cd771361..8e3b7e88 100644 --- a/lib/ui/common/collectible_item.dart +++ b/lib/ui/common/collectible_item.dart @@ -6,7 +6,7 @@ import 'package:wonders/ui/common/utils/app_haptics.dart'; import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart'; class CollectibleItem extends StatelessWidget with GetItMixin { - CollectibleItem(this.collectible, {this.size = 64.0, Key? key}) : super(key: key) { + CollectibleItem(this.collectible, {this.size = 64.0, Key? key, this.focus}) : super(key: key) { // pre-fetch the image, so it's ready if we show the collectible found screen. _imageProvider = NetworkImage(collectible.imageUrl); _imageProvider.resolve(ImageConfiguration()).addListener(ImageStreamListener((_, __) {})); @@ -15,6 +15,7 @@ class CollectibleItem extends StatelessWidget with GetItMixin { final CollectibleData collectible; final double size; late final ImageProvider _imageProvider; + final FocusNode? focus; void _handleTap(BuildContext context) async { final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider); @@ -39,6 +40,7 @@ class CollectibleItem extends StatelessWidget with GetItMixin { // Note: In order for the collapse animation to run properly, we must return a non-zero height or width. closedBuilder: (_) => SizedBox(width: 1, height: 0), openBuilder: (_) => AppBtn.basic( + focusNode: focus, semanticLabel: $strings.collectibleItemSemanticCollectible, onPressed: () => _handleTap(context), enableFeedback: false, diff --git a/lib/ui/common/hidden_collectible.dart b/lib/ui/common/hidden_collectible.dart index ecab5e2e..dde15ac9 100644 --- a/lib/ui/common/hidden_collectible.dart +++ b/lib/ui/common/hidden_collectible.dart @@ -5,13 +5,15 @@ import 'package:wonders/ui/common/collectible_item.dart'; /// The item is looked up via index, and expects that 3 items always exist for each wonder. /// If `wonders` is empty, then the collectible is always shown. class HiddenCollectible extends StatelessWidget with GetItMixin { - HiddenCollectible(this.currentWonder, {Key? key, required this.index, this.matches = const [], this.size = 64}) + HiddenCollectible(this.currentWonder, + {Key? key, required this.index, this.matches = const [], this.size = 64, this.focus}) : assert(index <= 2, 'index should not exceed 2'), super(key: key); final int index; final double size; final List matches; final WonderType currentWonder; + final FocusNode? focus; @override Widget build(BuildContext context) { final data = collectiblesLogic.forWonder(currentWonder); @@ -19,6 +21,6 @@ class HiddenCollectible extends StatelessWidget with GetItMixin { if (matches.isNotEmpty && matches.contains(currentWonder) == false) { return SizedBox.shrink(); } - return CollectibleItem(data[index], size: size); + return CollectibleItem(data[index], size: size, focus: focus); } } diff --git a/lib/ui/screens/photo_gallery/photo_gallery.dart b/lib/ui/screens/photo_gallery/photo_gallery.dart index 644649bd..acdfcdfc 100644 --- a/lib/ui/screens/photo_gallery/photo_gallery.dart +++ b/lib/ui/screens/photo_gallery/photo_gallery.dart @@ -99,8 +99,6 @@ class _PhotoGalleryState extends State { } bool _handleKeyDown(KeyDownEvent event) { - var newIndex = -1; - bool handled = false; final key = event.logicalKey; Map keyActions = { LogicalKeyboardKey.arrowUp: -_gridSize, @@ -109,24 +107,22 @@ class _PhotoGalleryState extends State { 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); - } + // Apply key action, exit early if no action is defined + int? actionValue = keyActions[key]; + if (actionValue == null) return false; + int newIndex = _index + actionValue; + + // Block actions along edges of the grid + bool isRightSide = _index % _gridSize == _gridSize - 1; + bool isLeftSide = _index % _gridSize == 0; + bool outOfBounds = newIndex < 0 || newIndex >= _imgCount; + if ((isRightSide && key == LogicalKeyboardKey.arrowRight) || + (isLeftSide && key == LogicalKeyboardKey.arrowLeft) || + outOfBounds) { + return false; } - return handled; + _setIndex(newIndex); + return true; } /// Converts a swipe direction into a new index @@ -274,14 +270,16 @@ class _PhotoGalleryState extends State { 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( + child: _checkCollectibleIndex(index) + ? Center( + child: HiddenCollectible(widget.wonderType, index: 1, size: 100, focus: _focusNodes[index]), + ) + : AppBtn.basic( + semanticLabel: semanticLbl, + focusNode: _focusNodes[index], + onFocusChanged: (isFocused) => _handleImageFocusChanged(index, isFocused), + onPressed: () => _handleImageTapped(index, isSelected), + child: ClipRRect( borderRadius: BorderRadius.circular(8), child: SizedBox( width: imgSize.width, @@ -303,7 +301,7 @@ class _PhotoGalleryState extends State { ), ), ), - ), + ), ), ); }), From 2faa7d7aab385d17a0f0ef07df5fd46c4ade90eb Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 22 Jan 2024 15:35:19 -0700 Subject: [PATCH 5/6] Block potential bug that can occur during page transitions --- lib/ui/common/controls/buttons.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ui/common/controls/buttons.dart b/lib/ui/common/controls/buttons.dart index 7001048e..c210a511 100644 --- a/lib/ui/common/controls/buttons.dart +++ b/lib/ui/common/controls/buttons.dart @@ -232,7 +232,9 @@ class _CustomFocusBuilderState extends State<_CustomFocusBuilder> { void _handleFocusChanged() { widget.onFocusChanged?.call(_focusNode.hasFocus); - setState(() {}); + if (mounted) { + setState(() {}); + } } @override From 9194c8a41af6a8567876311a225825894f258f5f Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 22 Jan 2024 15:35:37 -0700 Subject: [PATCH 6/6] Lint warnings --- lib/logic/collectibles_logic.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/logic/collectibles_logic.dart b/lib/logic/collectibles_logic.dart index e57a9df0..bbae40e3 100644 --- a/lib/logic/collectibles_logic.dart +++ b/lib/logic/collectibles_logic.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:wonders/common_libs.dart'; -import 'package:wonders/logic/common/platform_info.dart'; import 'package:wonders/logic/common/save_load_mixin.dart'; import 'package:wonders/logic/data/collectible_data.dart'; import 'package:wonders/logic/native_widget_service.dart';