diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 59babdcc..697bedf8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,5 +1,6 @@ { "appName": "Wonderous", + "localeSwapButton": "简体中文", "animatedArrowSemanticSwipe": "Explore details about {title}.", "appBarTitleFactsHistory": "Facts and History", "appBarTitleConstruction": "Construction", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index b3e8fd0e..31ac6c93 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1,5 +1,6 @@ { "appName": "Wonderous", + "localeSwapButton": "English", "animatedArrowSemanticSwipe": "查看关于{title}的详细信息。", "appBarTitleFactsHistory": "历史与细节", "appBarTitleConstruction": "建造", diff --git a/lib/logic/app_logic.dart b/lib/logic/app_logic.dart index b1fd3e6f..1bc93ce3 100644 --- a/lib/logic/app_logic.dart +++ b/lib/logic/app_logic.dart @@ -30,8 +30,11 @@ class AppLogic { // Localizations await localeLogic.load(); + // Data load + wondersLogic.init(); + // Timeline - await timelineLogic.init(); + timelineLogic.init(); // Settings await settingsLogic.load(); diff --git a/lib/logic/locale_logic.dart b/lib/logic/locale_logic.dart index 9067fda9..509dd884 100644 --- a/lib/logic/locale_logic.dart +++ b/lib/logic/locale_logic.dart @@ -1,8 +1,7 @@ -import 'dart:ui'; - import 'package:flutter/foundation.dart'; -import 'package:intl/intl_standalone.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:intl/intl_standalone.dart'; +import 'package:wonders/common_libs.dart'; class LocaleLogic { AppLocalizations? _strings; @@ -10,6 +9,8 @@ class LocaleLogic { bool get isLoaded => _strings != null; + bool get isEnglish => strings.localeName == 'en'; + Future load() async { final localeCode = await findSystemLocale(); Locale locale = Locale(localeCode.split('_')[0]); @@ -20,6 +21,13 @@ class LocaleLogic { if (AppLocalizations.supportedLocales.contains(locale) == false) { locale = Locale('en'); } + settingsLogic.currentLocale.value = locale.languageCode; _strings = await AppLocalizations.delegate.load(locale); } + + Future refreshIfChanged(Locale locale) async { + if (_strings?.localeName != locale.languageCode && AppLocalizations.supportedLocales.contains(locale)) { + _strings = await AppLocalizations.delegate.load(locale); + } + } } diff --git a/lib/logic/settings_logic.dart b/lib/logic/settings_logic.dart index 451a1111..23a19059 100644 --- a/lib/logic/settings_logic.dart +++ b/lib/logic/settings_logic.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/common/save_load_mixin.dart'; class SettingsLogic with ThrottledSaveLoadMixin { @@ -7,6 +8,7 @@ class SettingsLogic with ThrottledSaveLoadMixin { late final hasCompletedOnboarding = ValueNotifier(false)..addListener(scheduleSave); late final hasDismissedSearchMessage = ValueNotifier(false)..addListener(scheduleSave); + late final currentLocale = ValueNotifier('en')..addListener(scheduleSave); final bool useBlurs = defaultTargetPlatform != TargetPlatform.android; @@ -23,4 +25,11 @@ class SettingsLogic with ThrottledSaveLoadMixin { 'hasDismissedSearchMessage': hasDismissedSearchMessage.value, }; } + + Future setLocale(Locale value) async { + currentLocale.value = value.languageCode; + await localeLogic.refreshIfChanged(value); + wondersLogic.init(); + timelineLogic.init(); + } } diff --git a/lib/logic/timeline_logic.dart b/lib/logic/timeline_logic.dart index 32dc2995..8e1e6808 100644 --- a/lib/logic/timeline_logic.dart +++ b/lib/logic/timeline_logic.dart @@ -3,18 +3,17 @@ import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/data/timeline_data.dart'; class TimelineLogic { - final List events = []; + List events = []; - Future init() async { - events.addAll(GlobalEventsData().globalEvents); - - for (var w in wondersLogic.all) { - events.add( - TimelineEvent( + void init() { + events = [ + ...GlobalEventsData().globalEvents, + ...wondersLogic.all.map( + (w) => TimelineEvent( w.startYr, StringUtils.supplant($strings.timelineLabelConstruction, {'{title}': w.title}), ), - ); - } + ) + ]; } } diff --git a/lib/logic/wonders_logic.dart b/lib/logic/wonders_logic.dart index 34362286..f84481c8 100644 --- a/lib/logic/wonders_logic.dart +++ b/lib/logic/wonders_logic.dart @@ -10,16 +10,7 @@ import 'package:wonders/logic/data/wonders_data/pyramids_giza_data.dart'; import 'package:wonders/logic/data/wonders_data/taj_mahal_data.dart'; class WondersLogic { - late List all = [ - GreatWallData(), - PetraData(), - ColosseumData(), - ChichenItzaData(), - MachuPicchuData(), - TajMahalData(), - ChristRedeemerData(), - PyramidsGizaData(), - ]; + List all = []; final int timelineStartYear = -3000; final int timelineEndYear = 2200; @@ -29,4 +20,17 @@ class WondersLogic { if (result == null) throw ('Could not find data for wonder type $value'); return result; } + + void init() { + all = [ + GreatWallData(), + PetraData(), + ColosseumData(), + ChichenItzaData(), + MachuPicchuData(), + TajMahalData(), + ChristRedeemerData(), + PyramidsGizaData(), + ]; + } } diff --git a/lib/main.dart b/lib/main.dart index fed31757..4b08f62e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,19 +30,25 @@ class WondersApp extends StatelessWidget { const WondersApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return MaterialApp.router( - debugShowCheckedModeBanner: false, - routerDelegate: appRouter.routerDelegate, - routeInformationProvider: appRouter.routeInformationProvider, - routeInformationParser: appRouter.routeInformationParser, - theme: ThemeData(fontFamily: $styles.text.body.fontFamily), - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: AppLocalizations.supportedLocales, + return ValueListenableBuilder( + valueListenable: settingsLogic.currentLocale, + builder: (_, localeCode, __) { + return MaterialApp.router( + locale: Locale(localeCode), + debugShowCheckedModeBanner: false, + routerDelegate: appRouter.routerDelegate, + routeInformationProvider: appRouter.routeInformationProvider, + routeInformationParser: appRouter.routeInformationParser, + theme: ThemeData(fontFamily: $styles.text.body.fontFamily), + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + ); + } ); } } diff --git a/lib/ui/common/collectible_item.dart b/lib/ui/common/collectible_item.dart index 25f45bbf..5fa39dc0 100644 --- a/lib/ui/common/collectible_item.dart +++ b/lib/ui/common/collectible_item.dart @@ -52,13 +52,12 @@ class CollectibleItem extends StatelessWidget with GetItMixin { ), ) .animate(onPlay: (controller) => controller.repeat()) - // TODO SB (Aug 17, 2022): Temporarily removed on Jonahs request, due to a bug in Impeller which should be fixed soon. Re-enable when fixed. - //.shimmer(delay: 4000.ms, duration: $styles.times.med * 3) - .shake(delay: 4000.ms, duration: $styles.times.med * 3, curve: Curves.easeInOutCubic, hz: 4) + .shimmer(delay: 4000.ms, duration: $styles.times.med * 3) + .shake(curve: Curves.easeInOutCubic, hz: 4) .scale(begin: 1.0, end: 1.1, duration: $styles.times.med) .then(delay: $styles.times.med) .scale(begin: 1.0, end: 1 / 1.1), - ), + ), ), ), ); diff --git a/lib/ui/common/controls/locale_switcher.dart b/lib/ui/common/controls/locale_switcher.dart new file mode 100644 index 00000000..9150b9c2 --- /dev/null +++ b/lib/ui/common/controls/locale_switcher.dart @@ -0,0 +1,19 @@ +import 'package:wonders/common_libs.dart'; + +class LocaleSwitcher extends StatelessWidget with GetItMixin { + LocaleSwitcher({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final locale = watchX((SettingsLogic s) => s.currentLocale); + Future handleSwapLocale() async { + final newLocale = Locale(locale == 'en' ? 'zh' : 'en'); + await settingsLogic.setLocale(newLocale); + } + + return AppBtn.from( + padding: EdgeInsets.symmetric(vertical: $styles.insets.sm, horizontal: $styles.insets.sm), + text: $strings.localeSwapButton, + onPressed: handleSwapLocale); + } +} diff --git a/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart b/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart index d2c89da2..ee891aca 100644 --- a/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart +++ b/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart @@ -31,11 +31,10 @@ class _ResultsGrid extends StatelessWidget { } Widget _buildLanguageMessage(BuildContext context) { - bool isEnglish = localeLogic.strings.localeName == 'en'; return ValueListenableBuilder( valueListenable: settingsLogic.hasDismissedSearchMessage, builder: (_, value, __) { - if (isEnglish || value) return SizedBox(); + if (localeLogic.isEnglish || value) return SizedBox(); return AppBtn.basic( onPressed: () => settingsLogic.hasDismissedSearchMessage.value = true, semanticLabel: $strings.resultsSemanticDismiss, diff --git a/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart b/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart index 2d90c1c3..3b24528e 100644 --- a/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart +++ b/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart @@ -9,7 +9,7 @@ class _CollapsingPullQuoteImage extends StatelessWidget { Widget build(BuildContext context) { // Start transitioning when we are halfway up the screen final collapseStartPx = context.heightPx * 1; - final collapseEndPx = context.heightPx * .35; + final collapseEndPx = context.heightPx * .15; const double imgHeight = 430; const double outerPadding = 100; diff --git a/lib/ui/screens/editorial/widgets/_scrolling_content.dart b/lib/ui/screens/editorial/widgets/_scrolling_content.dart index 9acf8a99..de320a02 100644 --- a/lib/ui/screens/editorial/widgets/_scrolling_content.dart +++ b/lib/ui/screens/editorial/widgets/_scrolling_content.dart @@ -28,8 +28,7 @@ class _ScrollingContent extends StatelessWidget { final String dropChar = value.substring(0, 1); final textScale = MediaQuery.of(context).textScaleFactor; final double dropCapWidth = StringUtils.measure(dropChar, dropStyle).width * textScale; - final bool isEnglish = localeLogic.strings.localeName == 'en'; //TODO EC: Helper method for localLogic.isEnglish? - final bool skipCaps = !isEnglish || MediaQuery.of(context).accessibleNavigation; + final bool skipCaps = !localeLogic.isEnglish || MediaQuery.of(context).accessibleNavigation; return Semantics( label: value, child: !skipCaps diff --git a/lib/ui/screens/home_menu/home_menu.dart b/lib/ui/screens/home_menu/home_menu.dart index 6b023cdf..138e0694 100644 --- a/lib/ui/screens/home_menu/home_menu.dart +++ b/lib/ui/screens/home_menu/home_menu.dart @@ -4,6 +4,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/ui/common/app_backdrop.dart'; import 'package:wonders/ui/common/app_icons.dart'; +import 'package:wonders/ui/common/controls/locale_switcher.dart'; import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart'; class HomeMenu extends StatelessWidget { @@ -49,11 +50,23 @@ class HomeMenu extends StatelessWidget { ), ), - /// Back btn - BackBtn.close( - bgColor: Colors.transparent, - iconColor: $styles.colors.offWhite, - ).safe(), + SafeArea( + child: PaddedRow( + padding: EdgeInsets.symmetric( + horizontal: $styles.insets.md, + vertical: $styles.insets.sm, + ), + children: [ + /// Back btn + BackBtn.close( + bgColor: Colors.transparent, + iconColor: $styles.colors.offWhite, + ), + Spacer(), + LocaleSwitcher() + ], + ), + ), /// Content Positioned.fill( @@ -75,7 +88,7 @@ class HomeMenu extends StatelessWidget { ), ), ), - ) + ), ], ); } @@ -106,31 +119,35 @@ class HomeMenu extends StatelessWidget { } Widget _buildBottomBtns(BuildContext context) { - return SeparatedColumn( - separatorBuilder: () => Divider(thickness: 1.5, height: 1).animate().scale( - duration: $styles.times.slow, - delay: $styles.times.pageTransition + 200.ms, - curve: Curves.easeOutBack, - ), - children: [ - _MenuTextBtn( - label: $strings.homeMenuButtonExplore, - icon: AppIcons.timeline, - onPressed: () => _handleTimelinePressed(context)), - _MenuTextBtn( - label: $strings.homeMenuButtonView, - icon: AppIcons.collection, - onPressed: () => _handleCollectionPressed(context)), - _MenuTextBtn( - label: $strings.homeMenuButtonAbout, - icon: AppIcons.info, - onPressed: () => _handleAboutPressed(context), - ), - ] - .animate(interval: 50.ms) - .fade(delay: $styles.times.pageTransition + 50.ms) - .slide(begin: Offset(0, .1), curve: Curves.easeOut), - ); + return ValueListenableBuilder( + valueListenable: settingsLogic.currentLocale, + builder: (_, __, ___) { + return SeparatedColumn( + separatorBuilder: () => Divider(thickness: 1.5, height: 1).animate().scale( + duration: $styles.times.slow, + delay: $styles.times.pageTransition + 200.ms, + curve: Curves.easeOutBack, + ), + children: [ + _MenuTextBtn( + label: $strings.homeMenuButtonExplore, + icon: AppIcons.timeline, + onPressed: () => _handleTimelinePressed(context)), + _MenuTextBtn( + label: $strings.homeMenuButtonView, + icon: AppIcons.collection, + onPressed: () => _handleCollectionPressed(context)), + _MenuTextBtn( + label: $strings.homeMenuButtonAbout, + icon: AppIcons.info, + onPressed: () => _handleAboutPressed(context), + ), + ] + .animate(interval: 50.ms) + .fade(delay: $styles.times.pageTransition + 50.ms) + .slide(begin: Offset(0, .1), curve: Curves.easeOut), + ); + }); } Widget _buildGridBtn(BuildContext context, WonderData btnData) { diff --git a/lib/ui/screens/intro/intro_screen.dart b/lib/ui/screens/intro/intro_screen.dart index 37955fef..d0afe413 100644 --- a/lib/ui/screens/intro/intro_screen.dart +++ b/lib/ui/screens/intro/intro_screen.dart @@ -19,11 +19,7 @@ class _IntroScreenState extends State { static const double _textHeight = 155; static const double _pageIndicatorHeight = 55; - static List<_PageData> pageData = [ - _PageData($strings.introTitleJourney, $strings.introDescriptionNavigate, 'camel', '1'), - _PageData($strings.introTitleExplore, $strings.introDescriptionUncover, 'petra', '2'), - _PageData($strings.introTitleDiscover, $strings.introDescriptionLearn, 'statue', '3'), - ]; + static List<_PageData> pageData = []; late final PageController _pageController = PageController()..addListener(_handlePageChanged); final ValueNotifier _currentPage = ValueNotifier(0); @@ -51,6 +47,13 @@ class _IntroScreenState extends State { @override Widget build(BuildContext context) { + // Set the page data, as strings may have changed based on locale + pageData = [ + _PageData($strings.introTitleJourney, $strings.introDescriptionNavigate, 'camel', '1'), + _PageData($strings.introTitleExplore, $strings.introDescriptionUncover, 'petra', '2'), + _PageData($strings.introTitleDiscover, $strings.introDescriptionLearn, 'statue', '3'), + ]; + // This view uses a full screen PageView to enable swipe navigation. // However, we only want the title / description to actually swipe, // so we stack a PageView with that content over top of all the other diff --git a/release_notes.txt b/release_notes.txt index 7c5a0593..8083fe0a 100644 --- a/release_notes.txt +++ b/release_notes.txt @@ -1,6 +1,9 @@ # 1.9.0 - Improved support for dynamic text scaling - Improved performance for blend modes and blurs +- Added LocalSwitcher btn in the main Wonders Menu +- Request higher preferred FPS for Android devices +- Fix rendering issues with Chinese fonts # 1.8.0 - Initial release \ No newline at end of file