Merge branch 'main' into feature/routing_fixes
This commit is contained in:
commit
b7b9141682
@ -47,17 +47,9 @@ android {
|
|||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
|
||||||
release {
|
|
||||||
keyAlias keystoreProperties['keyAlias']
|
|
||||||
keyPassword keystoreProperties['keyPassword']
|
|
||||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
|
||||||
storePassword keystoreProperties['storePassword']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import 'dart:convert';
|
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/common_libs.dart';
|
||||||
import 'package:wonders/logic/common/save_load_mixin.dart';
|
import 'package:wonders/logic/common/save_load_mixin.dart';
|
||||||
import 'package:wonders/logic/data/collectible_data.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 {
|
class CollectiblesLogic with ThrottledSaveLoadMixin {
|
||||||
@override
|
@override
|
||||||
String get fileName => 'collectibles.dat';
|
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
|
/// Holds all collectibles that the views should care about
|
||||||
final List<CollectibleData> all = collectiblesData;
|
final List<CollectibleData> all = collectiblesData;
|
||||||
@ -26,9 +24,9 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
|||||||
|
|
||||||
int get exploredCount => _exploredCount;
|
int get exploredCount => _exploredCount;
|
||||||
|
|
||||||
void init() {
|
late final _nativeWidget = GetIt.I<NativeWidgetService>();
|
||||||
HomeWidget.setAppGroupId(_appGroupId);
|
|
||||||
}
|
void init() => _nativeWidget.init();
|
||||||
|
|
||||||
CollectibleData? fromId(String? id) => id == null ? null : all.firstWhereOrNull((o) => o.id == id);
|
CollectibleData? fromId(String? id) => id == null ? null : all.firstWhereOrNull((o) => o.id == id);
|
||||||
|
|
||||||
@ -42,7 +40,7 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
|||||||
statesById.value = states;
|
statesById.value = states;
|
||||||
if (state == CollectibleState.discovered) {
|
if (state == CollectibleState.discovered) {
|
||||||
final data = fromId(id)!;
|
final data = fromId(id)!;
|
||||||
_updateHomeWidgetTextData(
|
_updateNativeWidgetTextData(
|
||||||
title: data.title,
|
title: data.title,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
imageUrl: data.imageUrlSmall,
|
imageUrl: data.imageUrlSmall,
|
||||||
@ -58,9 +56,10 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
|||||||
if (state == CollectibleState.explored) _exploredCount++;
|
if (state == CollectibleState.explored) _exploredCount++;
|
||||||
});
|
});
|
||||||
final foundCount = discoveredCount + exploredCount;
|
final foundCount = discoveredCount + exploredCount;
|
||||||
HomeWidget.saveWidgetData<int>('discoveredCount', foundCount).then((value) {
|
_nativeWidget.save<int>('discoveredCount', foundCount).then((value) {
|
||||||
HomeWidget.updateWidget(iOSName: _appName);
|
_nativeWidget.markDirty();
|
||||||
});
|
});
|
||||||
|
|
||||||
debugPrint('setting discoveredCount for home widget $foundCount');
|
debugPrint('setting discoveredCount for home widget $foundCount');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,22 +91,24 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
|||||||
for (int i = 0; i < all.length; i++) {
|
for (int i = 0; i < all.length; i++) {
|
||||||
states[all[i].id] = CollectibleState.lost;
|
states[all[i].id] = CollectibleState.lost;
|
||||||
}
|
}
|
||||||
_updateHomeWidgetTextData(); // clear home widget data
|
if (_nativeWidget.isSupported) {
|
||||||
|
_updateNativeWidgetTextData(); // clear home widget data
|
||||||
|
}
|
||||||
statesById.value = states;
|
statesById.value = states;
|
||||||
debugPrint('collection reset');
|
debugPrint('collection reset');
|
||||||
scheduleSave();
|
scheduleSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateHomeWidgetTextData({String title = '', String id = '', String imageUrl = ''}) async {
|
Future<void> _updateNativeWidgetTextData({String title = '', String id = '', String imageUrl = ''}) async {
|
||||||
// Save title
|
// Save title
|
||||||
await HomeWidget.saveWidgetData<String>('lastDiscoveredTitle', title);
|
await _nativeWidget.save<String>('lastDiscoveredTitle', title);
|
||||||
// Subtitle
|
// Subtitle
|
||||||
String subTitle = '';
|
String subTitle = '';
|
||||||
if (id.isNotEmpty) {
|
if (id.isNotEmpty) {
|
||||||
final artifactData = await artifactLogic.getArtifactByID(id);
|
final artifactData = await artifactLogic.getArtifactByID(id);
|
||||||
subTitle = artifactData?.date ?? '';
|
subTitle = artifactData?.date ?? '';
|
||||||
}
|
}
|
||||||
await HomeWidget.saveWidgetData<String>('lastDiscoveredSubTitle', subTitle);
|
await _nativeWidget.save<String>('lastDiscoveredSubTitle', subTitle);
|
||||||
// Image,
|
// Image,
|
||||||
// Download, convert to base64 string and write to shared widget data
|
// Download, convert to base64 string and write to shared widget data
|
||||||
String imageBase64 = '';
|
String imageBase64 = '';
|
||||||
@ -116,8 +117,8 @@ class CollectiblesLogic with ThrottledSaveLoadMixin {
|
|||||||
imageBase64 = base64Encode(bytes);
|
imageBase64 = base64Encode(bytes);
|
||||||
debugPrint('Saving base64 bytes: $imageBase64');
|
debugPrint('Saving base64 bytes: $imageBase64');
|
||||||
}
|
}
|
||||||
await HomeWidget.saveWidgetData<String>('lastDiscoveredImageData', imageBase64);
|
await _nativeWidget.save<String>('lastDiscoveredImageData', imageBase64);
|
||||||
await HomeWidget.updateWidget(iOSName: _appName);
|
await _nativeWidget.markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
28
lib/logic/native_widget_service.dart
Normal file
28
lib/logic/native_widget_service.dart
Normal file
@ -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';
|
||||||
|
|
||||||
|
final bool isSupported = PlatformInfo.isIOS;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
if (!isSupported) return;
|
||||||
|
await HomeWidget.setAppGroupId(_iosAppGroupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool?> save<T>(String s, T value, {void Function(bool?)? onSaveComplete}) async {
|
||||||
|
if (!isSupported) return false;
|
||||||
|
return await HomeWidget.saveWidgetData<T>(s, value).then((value) {
|
||||||
|
onSaveComplete?.call(value);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool?> markDirty() async {
|
||||||
|
if (!isSupported) return false;
|
||||||
|
return await HomeWidget.updateWidget(iOSName: _iosAppName);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import 'package:wonders/common_libs.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/collectibles_logic.dart';
|
||||||
|
import 'package:wonders/logic/native_widget_service.dart';
|
||||||
import 'package:wonders/logic/locale_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';
|
||||||
@ -71,6 +72,8 @@ void registerSingletons() {
|
|||||||
GetIt.I.registerLazySingleton<CollectiblesLogic>(() => CollectiblesLogic());
|
GetIt.I.registerLazySingleton<CollectiblesLogic>(() => CollectiblesLogic());
|
||||||
// Localizations
|
// Localizations
|
||||||
GetIt.I.registerLazySingleton<LocaleLogic>(() => LocaleLogic());
|
GetIt.I.registerLazySingleton<LocaleLogic>(() => LocaleLogic());
|
||||||
|
// Home Widget Service
|
||||||
|
GetIt.I.registerLazySingleton<NativeWidgetService>(() => NativeWidgetService());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add syntax sugar for quickly accessing the main "logic" controllers in the app
|
/// Add syntax sugar for quickly accessing the main "logic" controllers in the app
|
||||||
|
@ -6,7 +6,7 @@ import 'package:wonders/ui/common/utils/app_haptics.dart';
|
|||||||
import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart';
|
import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart';
|
||||||
|
|
||||||
class CollectibleItem extends StatelessWidget with GetItMixin {
|
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.
|
// pre-fetch the image, so it's ready if we show the collectible found screen.
|
||||||
_imageProvider = NetworkImage(collectible.imageUrl);
|
_imageProvider = NetworkImage(collectible.imageUrl);
|
||||||
_imageProvider.resolve(ImageConfiguration()).addListener(ImageStreamListener((_, __) {}));
|
_imageProvider.resolve(ImageConfiguration()).addListener(ImageStreamListener((_, __) {}));
|
||||||
@ -15,6 +15,7 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
|
|||||||
final CollectibleData collectible;
|
final CollectibleData collectible;
|
||||||
final double size;
|
final double size;
|
||||||
late final ImageProvider _imageProvider;
|
late final ImageProvider _imageProvider;
|
||||||
|
final FocusNode? focus;
|
||||||
|
|
||||||
void _handleTap(BuildContext context) async {
|
void _handleTap(BuildContext context) async {
|
||||||
final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider);
|
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.
|
// 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),
|
closedBuilder: (_) => SizedBox(width: 1, height: 0),
|
||||||
openBuilder: (_) => AppBtn.basic(
|
openBuilder: (_) => AppBtn.basic(
|
||||||
|
focusNode: focus,
|
||||||
semanticLabel: $strings.collectibleItemSemanticCollectible,
|
semanticLabel: $strings.collectibleItemSemanticCollectible,
|
||||||
onPressed: () => _handleTap(context),
|
onPressed: () => _handleTap(context),
|
||||||
enableFeedback: false,
|
enableFeedback: false,
|
||||||
|
@ -232,7 +232,9 @@ class _CustomFocusBuilderState extends State<_CustomFocusBuilder> {
|
|||||||
|
|
||||||
void _handleFocusChanged() {
|
void _handleFocusChanged() {
|
||||||
widget.onFocusChanged?.call(_focusNode.hasFocus);
|
widget.onFocusChanged?.call(_focusNode.hasFocus);
|
||||||
setState(() {});
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -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.
|
/// 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.
|
/// If `wonders` is empty, then the collectible is always shown.
|
||||||
class HiddenCollectible extends StatelessWidget with GetItMixin {
|
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'),
|
: assert(index <= 2, 'index should not exceed 2'),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
final int index;
|
final int index;
|
||||||
final double size;
|
final double size;
|
||||||
final List<WonderType> matches;
|
final List<WonderType> matches;
|
||||||
final WonderType currentWonder;
|
final WonderType currentWonder;
|
||||||
|
final FocusNode? focus;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final data = collectiblesLogic.forWonder(currentWonder);
|
final data = collectiblesLogic.forWonder(currentWonder);
|
||||||
@ -19,6 +21,6 @@ class HiddenCollectible extends StatelessWidget with GetItMixin {
|
|||||||
if (matches.isNotEmpty && matches.contains(currentWonder) == false) {
|
if (matches.isNotEmpty && matches.contains(currentWonder) == false) {
|
||||||
return SizedBox.shrink();
|
return SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return CollectibleItem(data[index], size: size);
|
return CollectibleItem(data[index], size: size, focus: focus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,8 +99,6 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _handleKeyDown(KeyDownEvent event) {
|
bool _handleKeyDown(KeyDownEvent event) {
|
||||||
var newIndex = -1;
|
|
||||||
bool handled = false;
|
|
||||||
final key = event.logicalKey;
|
final key = event.logicalKey;
|
||||||
Map<LogicalKeyboardKey, int> keyActions = {
|
Map<LogicalKeyboardKey, int> keyActions = {
|
||||||
LogicalKeyboardKey.arrowUp: -_gridSize,
|
LogicalKeyboardKey.arrowUp: -_gridSize,
|
||||||
@ -109,24 +107,22 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
LogicalKeyboardKey.arrowLeft: -1,
|
LogicalKeyboardKey.arrowLeft: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
int? action = keyActions[key];
|
// Apply key action, exit early if no action is defined
|
||||||
if (action != null) {
|
int? actionValue = keyActions[key];
|
||||||
newIndex = _index + action;
|
if (actionValue == null) return false;
|
||||||
handled = true;
|
int newIndex = _index + actionValue;
|
||||||
bool isRightSide = _index % _gridSize == _gridSize - 1;
|
|
||||||
if (isRightSide && key == LogicalKeyboardKey.arrowRight) {
|
// Block actions along edges of the grid
|
||||||
newIndex = -1;
|
bool isRightSide = _index % _gridSize == _gridSize - 1;
|
||||||
}
|
bool isLeftSide = _index % _gridSize == 0;
|
||||||
bool isLeftSide = _index % _gridSize == 0;
|
bool outOfBounds = newIndex < 0 || newIndex >= _imgCount;
|
||||||
if (isLeftSide && key == LogicalKeyboardKey.arrowLeft) newIndex = -1;
|
if ((isRightSide && key == LogicalKeyboardKey.arrowRight) ||
|
||||||
if (newIndex > _gridSize * _gridSize) {
|
(isLeftSide && key == LogicalKeyboardKey.arrowLeft) ||
|
||||||
newIndex = -1;
|
outOfBounds) {
|
||||||
}
|
return false;
|
||||||
if (newIndex >= 0) {
|
|
||||||
_setIndex(newIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return handled;
|
_setIndex(newIndex);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a swipe direction into a new index
|
/// Converts a swipe direction into a new index
|
||||||
@ -274,14 +270,16 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
liveRegion: isSelected,
|
liveRegion: isSelected,
|
||||||
onIncrease: () => _handleImageTapped(_index + 1, false),
|
onIncrease: () => _handleImageTapped(_index + 1, false),
|
||||||
onDecrease: () => _handleImageTapped(_index - 1, false),
|
onDecrease: () => _handleImageTapped(_index - 1, false),
|
||||||
child: AppBtn.basic(
|
child: _checkCollectibleIndex(index)
|
||||||
semanticLabel: semanticLbl,
|
? Center(
|
||||||
focusNode: _focusNodes[index],
|
child: HiddenCollectible(widget.wonderType, index: 1, size: 100, focus: _focusNodes[index]),
|
||||||
onFocusChanged: (isFocused) => _handleImageFocusChanged(index, isFocused),
|
)
|
||||||
onPressed: () => _handleImageTapped(index, isSelected),
|
: AppBtn.basic(
|
||||||
child: _checkCollectibleIndex(index)
|
semanticLabel: semanticLbl,
|
||||||
? Center(child: HiddenCollectible(widget.wonderType, index: 1, size: 100))
|
focusNode: _focusNodes[index],
|
||||||
: ClipRRect(
|
onFocusChanged: (isFocused) => _handleImageFocusChanged(index, isFocused),
|
||||||
|
onPressed: () => _handleImageTapped(index, isSelected),
|
||||||
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: imgSize.width,
|
width: imgSize.width,
|
||||||
@ -303,7 +301,7 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user