Merge pull request #17 from gskinnerTeam/feature/swappable-locale

Swapping locales feature
This commit is contained in:
Shawn 2022-09-07 13:45:42 -06:00 committed by GitHub
commit 08a2c215f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 144 additions and 73 deletions

View File

@ -1,5 +1,6 @@
{
"appName": "Wonderous",
"localeSwapButton": "简体中文",
"animatedArrowSemanticSwipe": "Explore details about {title}.",
"appBarTitleFactsHistory": "Facts and History",
"appBarTitleConstruction": "Construction",

View File

@ -1,5 +1,6 @@
{
"appName": "Wonderous",
"localeSwapButton": "English",
"animatedArrowSemanticSwipe": "查看关于{title}的详细信息。",
"appBarTitleFactsHistory": "历史与细节",
"appBarTitleConstruction": "建造",

View File

@ -30,8 +30,11 @@ class AppLogic {
// Localizations
await localeLogic.load();
// Data load
wondersLogic.init();
// Timeline
await timelineLogic.init();
timelineLogic.init();
// Settings
await settingsLogic.load();

View File

@ -3,6 +3,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:wonders/common_libs.dart';
class LocaleLogic {
AppLocalizations? _strings;
@ -10,6 +11,8 @@ class LocaleLogic {
bool get isLoaded => _strings != null;
bool get isEnglish => strings.localeName == 'en';
Future<void> load() async {
final localeCode = await findSystemLocale();
Locale locale = Locale(localeCode.split('_')[0]);
@ -20,6 +23,13 @@ class LocaleLogic {
if (AppLocalizations.supportedLocales.contains(locale) == false) {
locale = Locale('en');
}
settingsLogic.currentLocale.value = locale.languageCode;
_strings = await AppLocalizations.delegate.load(locale);
}
Future<void> refreshIfChanged(Locale locale) async {
if (_strings?.localeName != locale.languageCode && AppLocalizations.supportedLocales.contains(locale)) {
_strings = await AppLocalizations.delegate.load(locale);
}
}
}

View File

@ -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<bool>(false)..addListener(scheduleSave);
late final hasDismissedSearchMessage = ValueNotifier<bool>(false)..addListener(scheduleSave);
late final currentLocale = ValueNotifier<String>('en')..addListener(scheduleSave);
final bool useBlurs = defaultTargetPlatform != TargetPlatform.android;
@ -23,4 +25,11 @@ class SettingsLogic with ThrottledSaveLoadMixin {
'hasDismissedSearchMessage': hasDismissedSearchMessage.value,
};
}
Future<void> setLocale(Locale value) async {
currentLocale.value = value.languageCode;
await localeLogic.refreshIfChanged(value);
wondersLogic.init();
timelineLogic.init();
}
}

View File

@ -3,18 +3,17 @@ import 'package:wonders/logic/common/string_utils.dart';
import 'package:wonders/logic/data/timeline_data.dart';
class TimelineLogic {
final List<TimelineEvent> events = [];
List<TimelineEvent> events = [];
Future<void> 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}),
),
);
}
)
];
}
}

View File

@ -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<WonderData> all = [
GreatWallData(),
PetraData(),
ColosseumData(),
ChichenItzaData(),
MachuPicchuData(),
TajMahalData(),
ChristRedeemerData(),
PyramidsGizaData(),
];
List<WonderData> 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(),
];
}
}

View File

@ -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<String>(
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,
);
}
);
}
}

View File

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

View File

@ -31,11 +31,10 @@ class _ResultsGrid extends StatelessWidget {
}
Widget _buildLanguageMessage(BuildContext context) {
bool isEnglish = localeLogic.strings.localeName == 'en';
return ValueListenableBuilder<bool>(
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,

View File

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

View File

@ -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) {

View File

@ -2,6 +2,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:wonders/common_libs.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/locale_switcher.dart';
import 'package:wonders/ui/common/static_text_scale.dart';
import 'package:wonders/ui/common/themed_text.dart';
import 'package:wonders/ui/common/utils/app_haptics.dart';
@ -19,11 +20,7 @@ class _IntroScreenState extends State<IntroScreen> {
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<int> _currentPage = ValueNotifier(0);
@ -51,6 +48,13 @@ class _IntroScreenState extends State<IntroScreen> {
@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