From 51d652c251ac50d8895c5171fba7e0914581efcf Mon Sep 17 00:00:00 2001 From: hwh97 <974815768@qq.com> Date: Mon, 13 Jul 2020 11:49:37 +0800 Subject: [PATCH] add: add localizations support. --- README.md | 5 + lib/consts/config_constants.dart | 9 + lib/generated/intl/messages_all.dart | 67 ++++ lib/generated/intl/messages_en.dart | 55 ++++ lib/generated/intl/messages_zh.dart | 55 ++++ lib/generated/l10n.dart | 344 ++++++++++++++++++++ lib/l10n/intl_en.arb | 30 ++ lib/l10n/intl_zh.arb | 30 ++ lib/main.dart | 74 ++++- lib/routers/home_router.dart | 19 +- lib/service_locator.dart | 5 +- lib/ui/views/bored/bored_page.dart | 24 +- lib/ui/views/bored/bored_todo_item.dart | 9 +- lib/ui/views/splash_page.dart | 6 + lib/utils/date_util.dart | 56 ++-- lib/utils/sp_util.dart | 120 +++++++ lib/view_models/app_view_model.dart | 45 ++- lib/view_models/splash_page_view_model.dart | 25 +- pubspec.lock | 91 +++++- pubspec.yaml | 11 +- 20 files changed, 1011 insertions(+), 69 deletions(-) create mode 100644 lib/consts/config_constants.dart create mode 100644 lib/generated/intl/messages_all.dart create mode 100644 lib/generated/intl/messages_en.dart create mode 100644 lib/generated/intl/messages_zh.dart create mode 100644 lib/generated/l10n.dart create mode 100644 lib/l10n/intl_en.arb create mode 100644 lib/l10n/intl_zh.arb create mode 100644 lib/utils/sp_util.dart diff --git a/README.md b/README.md index 485eacd..9cdc160 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ Bored App (Developing) +# IDEA plugin used +- [FlutterJsonBeanFactory](https://plugins.jetbrains.com/plugin/11415-flutterjsonbeanfactory) +- [Flutter Intl](https://plugins.jetbrains.com/plugin/13666-flutter-intl/) + + ## Getting Started This project is a starting point for a Flutter application. diff --git a/lib/consts/config_constants.dart b/lib/consts/config_constants.dart new file mode 100644 index 0000000..ce5e3e1 --- /dev/null +++ b/lib/consts/config_constants.dart @@ -0,0 +1,9 @@ +import 'package:bored/generated/l10n.dart'; +import 'package:flutter/cupertino.dart'; + +class ConfigConstants { + static String appName(BuildContext context) => S.of(context).app_name; + + // share preferences keys + static String get languageKey => "cur_language"; +} \ No newline at end of file diff --git a/lib/generated/intl/messages_all.dart b/lib/generated/intl/messages_all.dart new file mode 100644 index 0000000..6a63947 --- /dev/null +++ b/lib/generated/intl/messages_all.dart @@ -0,0 +1,67 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that looks up messages for specific locales by +// delegating to the appropriate library. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:implementation_imports, file_names, unnecessary_new +// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering +// ignore_for_file:argument_type_not_assignable, invalid_assignment +// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases +// ignore_for_file:comment_references + +import 'dart:async'; + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; +import 'package:intl/src/intl_helpers.dart'; + +import 'messages_en.dart' as messages_en; +import 'messages_zh.dart' as messages_zh; + +typedef Future LibraryLoader(); +Map _deferredLibraries = { + 'en': () => new Future.value(null), + 'zh': () => new Future.value(null), +}; + +MessageLookupByLibrary _findExact(String localeName) { + switch (localeName) { + case 'en': + return messages_en.messages; + case 'zh': + return messages_zh.messages; + default: + return null; + } +} + +/// User programs should call this before using [localeName] for messages. +Future initializeMessages(String localeName) async { + var availableLocale = Intl.verifiedLocale( + localeName, + (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); + if (availableLocale == null) { + return new Future.value(false); + } + var lib = _deferredLibraries[availableLocale]; + await (lib == null ? new Future.value(false) : lib()); + initializeInternalMessageLookup(() => new CompositeMessageLookup()); + messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); + return new Future.value(true); +} + +bool _messagesExistFor(String locale) { + try { + return _findExact(locale) != null; + } catch (e) { + return false; + } +} + +MessageLookupByLibrary _findGeneratedMessagesFor(String locale) { + var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, + onFailure: (_) => null); + if (actualLocale == null) return null; + return _findExact(actualLocale); +} diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart new file mode 100644 index 0000000..4309a62 --- /dev/null +++ b/lib/generated/intl/messages_en.dart @@ -0,0 +1,55 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a en locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'en'; + + static m0(p1, p2, p3, p4) => "${p1} ${p2} ${p3} ${p4}"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static _notInlinedMessages(_) => { + "accessibility" : MessageLookupByLibrary.simpleMessage("accessibility:"), + "activity" : MessageLookupByLibrary.simpleMessage("Activity"), + "app_name" : MessageLookupByLibrary.simpleMessage("Boring Remover"), + "april" : MessageLookupByLibrary.simpleMessage("april"), + "august" : MessageLookupByLibrary.simpleMessage("august"), + "december" : MessageLookupByLibrary.simpleMessage("december"), + "february" : MessageLookupByLibrary.simpleMessage("february"), + "format_date" : m0, + "friday" : MessageLookupByLibrary.simpleMessage("Friday"), + "january" : MessageLookupByLibrary.simpleMessage("january"), + "july" : MessageLookupByLibrary.simpleMessage("july"), + "june" : MessageLookupByLibrary.simpleMessage("june"), + "loading" : MessageLookupByLibrary.simpleMessage("Loading"), + "march" : MessageLookupByLibrary.simpleMessage("march"), + "may" : MessageLookupByLibrary.simpleMessage("may"), + "monday" : MessageLookupByLibrary.simpleMessage("Monday"), + "november" : MessageLookupByLibrary.simpleMessage("november"), + "october" : MessageLookupByLibrary.simpleMessage("october"), + "participants" : MessageLookupByLibrary.simpleMessage("participants:"), + "price" : MessageLookupByLibrary.simpleMessage("price:"), + "saturday" : MessageLookupByLibrary.simpleMessage("Saturday"), + "september" : MessageLookupByLibrary.simpleMessage("september"), + "sunday" : MessageLookupByLibrary.simpleMessage("Sunday"), + "thursday" : MessageLookupByLibrary.simpleMessage("Thursday"), + "todoList" : MessageLookupByLibrary.simpleMessage("TODO List"), + "tuesday" : MessageLookupByLibrary.simpleMessage("Tuesday"), + "unknown" : MessageLookupByLibrary.simpleMessage("Unknown"), + "wednesday" : MessageLookupByLibrary.simpleMessage("Wednesday") + }; +} diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart new file mode 100644 index 0000000..ca6edd5 --- /dev/null +++ b/lib/generated/intl/messages_zh.dart @@ -0,0 +1,55 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a zh locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'zh'; + + static m0(p1, p2, p3, p4) => "${p1}年${p2}${p3}号${p4}"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static _notInlinedMessages(_) => { + "accessibility" : MessageLookupByLibrary.simpleMessage("可行性:"), + "activity" : MessageLookupByLibrary.simpleMessage("活动"), + "app_name" : MessageLookupByLibrary.simpleMessage("找点事做"), + "april" : MessageLookupByLibrary.simpleMessage("4月"), + "august" : MessageLookupByLibrary.simpleMessage("8月"), + "december" : MessageLookupByLibrary.simpleMessage("12月"), + "february" : MessageLookupByLibrary.simpleMessage("2月"), + "format_date" : m0, + "friday" : MessageLookupByLibrary.simpleMessage("星期五"), + "january" : MessageLookupByLibrary.simpleMessage("1月"), + "july" : MessageLookupByLibrary.simpleMessage("7月"), + "june" : MessageLookupByLibrary.simpleMessage("6月"), + "loading" : MessageLookupByLibrary.simpleMessage("加载中..."), + "march" : MessageLookupByLibrary.simpleMessage("3月"), + "may" : MessageLookupByLibrary.simpleMessage("5月"), + "monday" : MessageLookupByLibrary.simpleMessage("星期一"), + "november" : MessageLookupByLibrary.simpleMessage("11月"), + "october" : MessageLookupByLibrary.simpleMessage("10月"), + "participants" : MessageLookupByLibrary.simpleMessage("人员数:"), + "price" : MessageLookupByLibrary.simpleMessage("费用:"), + "saturday" : MessageLookupByLibrary.simpleMessage("星期六"), + "september" : MessageLookupByLibrary.simpleMessage("9月"), + "sunday" : MessageLookupByLibrary.simpleMessage("星期日"), + "thursday" : MessageLookupByLibrary.simpleMessage("星期四"), + "todoList" : MessageLookupByLibrary.simpleMessage("待办列表"), + "tuesday" : MessageLookupByLibrary.simpleMessage("星期二"), + "unknown" : MessageLookupByLibrary.simpleMessage("未知"), + "wednesday" : MessageLookupByLibrary.simpleMessage("星期三") + }; +} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart new file mode 100644 index 0000000..4e167fc --- /dev/null +++ b/lib/generated/l10n.dart @@ -0,0 +1,344 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'intl/messages_all.dart'; + +// ************************************************************************** +// Generator: Flutter Intl IDE plugin +// Made by Localizely +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars + +class S { + S(); + + static S current; + + static const AppLocalizationDelegate delegate = + AppLocalizationDelegate(); + + static Future load(Locale locale) { + final name = (locale.countryCode?.isEmpty ?? false) ? locale.languageCode : locale.toString(); + final localeName = Intl.canonicalizedLocale(name); + return initializeMessages(localeName).then((_) { + Intl.defaultLocale = localeName; + S.current = S(); + + return S.current; + }); + } + + static S of(BuildContext context) { + return Localizations.of(context, S); + } + + /// `Boring Remover` + String get app_name { + return Intl.message( + 'Boring Remover', + name: 'app_name', + desc: '', + args: [], + ); + } + + /// `Activity` + String get activity { + return Intl.message( + 'Activity', + name: 'activity', + desc: '', + args: [], + ); + } + + /// `Loading` + String get loading { + return Intl.message( + 'Loading', + name: 'loading', + desc: '', + args: [], + ); + } + + /// `accessibility:` + String get accessibility { + return Intl.message( + 'accessibility:', + name: 'accessibility', + desc: '', + args: [], + ); + } + + /// `price:` + String get price { + return Intl.message( + 'price:', + name: 'price', + desc: '', + args: [], + ); + } + + /// `participants:` + String get participants { + return Intl.message( + 'participants:', + name: 'participants', + desc: '', + args: [], + ); + } + + /// `TODO List` + String get todoList { + return Intl.message( + 'TODO List', + name: 'todoList', + desc: '', + args: [], + ); + } + + /// `{p1} {p2} {p3} {p4}` + String format_date(Object p1, Object p2, Object p3, Object p4) { + return Intl.message( + '$p1 $p2 $p3 $p4', + name: 'format_date', + desc: '', + args: [p1, p2, p3, p4], + ); + } + + /// `Monday` + String get monday { + return Intl.message( + 'Monday', + name: 'monday', + desc: '', + args: [], + ); + } + + /// `Tuesday` + String get tuesday { + return Intl.message( + 'Tuesday', + name: 'tuesday', + desc: '', + args: [], + ); + } + + /// `Wednesday` + String get wednesday { + return Intl.message( + 'Wednesday', + name: 'wednesday', + desc: '', + args: [], + ); + } + + /// `Thursday` + String get thursday { + return Intl.message( + 'Thursday', + name: 'thursday', + desc: '', + args: [], + ); + } + + /// `Friday` + String get friday { + return Intl.message( + 'Friday', + name: 'friday', + desc: '', + args: [], + ); + } + + /// `Saturday` + String get saturday { + return Intl.message( + 'Saturday', + name: 'saturday', + desc: '', + args: [], + ); + } + + /// `Sunday` + String get sunday { + return Intl.message( + 'Sunday', + name: 'sunday', + desc: '', + args: [], + ); + } + + /// `january` + String get january { + return Intl.message( + 'january', + name: 'january', + desc: '', + args: [], + ); + } + + /// `february` + String get february { + return Intl.message( + 'february', + name: 'february', + desc: '', + args: [], + ); + } + + /// `march` + String get march { + return Intl.message( + 'march', + name: 'march', + desc: '', + args: [], + ); + } + + /// `april` + String get april { + return Intl.message( + 'april', + name: 'april', + desc: '', + args: [], + ); + } + + /// `may` + String get may { + return Intl.message( + 'may', + name: 'may', + desc: '', + args: [], + ); + } + + /// `june` + String get june { + return Intl.message( + 'june', + name: 'june', + desc: '', + args: [], + ); + } + + /// `july` + String get july { + return Intl.message( + 'july', + name: 'july', + desc: '', + args: [], + ); + } + + /// `august` + String get august { + return Intl.message( + 'august', + name: 'august', + desc: '', + args: [], + ); + } + + /// `september` + String get september { + return Intl.message( + 'september', + name: 'september', + desc: '', + args: [], + ); + } + + /// `october` + String get october { + return Intl.message( + 'october', + name: 'october', + desc: '', + args: [], + ); + } + + /// `november` + String get november { + return Intl.message( + 'november', + name: 'november', + desc: '', + args: [], + ); + } + + /// `december` + String get december { + return Intl.message( + 'december', + name: 'december', + desc: '', + args: [], + ); + } + + /// `Unknown` + String get unknown { + return Intl.message( + 'Unknown', + name: 'unknown', + desc: '', + args: [], + ); + } +} + +class AppLocalizationDelegate extends LocalizationsDelegate { + const AppLocalizationDelegate(); + + List get supportedLocales { + return const [ + Locale.fromSubtags(languageCode: 'en'), + Locale.fromSubtags(languageCode: 'zh'), + ]; + } + + @override + bool isSupported(Locale locale) => _isSupported(locale); + @override + Future load(Locale locale) => S.load(locale); + @override + bool shouldReload(AppLocalizationDelegate old) => false; + + bool _isSupported(Locale locale) { + if (locale != null) { + for (var supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale.languageCode) { + return true; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb new file mode 100644 index 0000000..daa4e48 --- /dev/null +++ b/lib/l10n/intl_en.arb @@ -0,0 +1,30 @@ +{ + "app_name": "Boring Remover", + "activity": "Activity", + "loading": "Loading", + "accessibility": "accessibility:", + "price": "price:", + "participants": "participants:", + "todoList": "TODO List", + "format_date": "{p1} {p2} {p3} {p4}", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "january": "january", + "february": "february", + "march": "march", + "april": "april", + "may": "may", + "june": "june", + "july": "july", + "august": "august", + "september": "september", + "october": "october", + "november": "november", + "december": "december", + "unknown": "Unknown" +} \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb new file mode 100644 index 0000000..0fbdbeb --- /dev/null +++ b/lib/l10n/intl_zh.arb @@ -0,0 +1,30 @@ +{ + "app_name": "找点事做", + "activity": "活动", + "loading": "加载中...", + "accessibility": "可行性:", + "price": "费用:", + "format_date": "{p1}年{p2}{p3}号{p4}", + "participants": "人员数:", + "todoList": "待办列表", + "monday": "星期一", + "tuesday": "星期二", + "wednesday": "星期三", + "thursday": "星期四", + "friday": "星期五", + "saturday": "星期六", + "sunday": "星期日", + "january": "1月", + "february": "2月", + "march": "3月", + "april": "4月", + "may": "5月", + "june": "6月", + "july": "7月", + "august": "8月", + "september": "9月", + "october": "10月", + "november": "11月", + "december": "12月", + "unknown": "未知" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 6fead91..4ba456e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,13 @@ import 'dart:io'; -import 'package:bored/utils/db_util.dart'; +import 'package:bored/consts/config_constants.dart'; +import 'package:bored/generated/l10n.dart'; import 'package:bored/view_models/app_view_model.dart'; import 'package:bored/routers/home_router.dart'; import 'package:bored/service_locator.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'routers/Routers.dart'; @@ -32,14 +34,68 @@ void main() { class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Boring Destoryer', - color: Colors.redAccent, - theme: Provider.of(context).getThemeData(darkTheme: false), - darkTheme: - Provider.of(context).getThemeData(darkTheme: true), - onGenerateRoute: Routers.router.generator, - home: SplashPage(), + return LocalListener( + child: MaterialApp( + onGenerateTitle: (BuildContext context) { + return ConfigConstants.appName(context); + }, + color: Colors.redAccent, + theme: + Provider.of(context).getThemeData(darkTheme: false), + darkTheme: + Provider.of(context).getThemeData(darkTheme: true), + locale: Provider.of(context).locale, + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate + ], + supportedLocales: S.delegate.supportedLocales, + onGenerateRoute: Routers.router.generator, + home: SplashPage(), + ), ); } } + +class LocalListener extends StatefulWidget { + final Widget child; + + const LocalListener({Key key, this.child}) : super(key: key); + + @override + State createState() => LocalListenerState(); +} + +class LocalListenerState extends State + with WidgetsBindingObserver { + @override + Widget build(BuildContext context) => widget.child; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeLocales(List locale) { + super.didChangeLocales(locale); + // call when locales changed + // eg. [zh_Hans_CN, en_US] eg. [en_US, zh_Hans_CN] + if (Provider.of(context, listen: false).appLanguage == + AppLanguage.System) { + Provider.of(context, listen: false).setLocale( + locale: locale[0], + isFollowSystem: true, + ); + } + } +} diff --git a/lib/routers/home_router.dart b/lib/routers/home_router.dart index 3e12ccb..60cd230 100644 --- a/lib/routers/home_router.dart +++ b/lib/routers/home_router.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:bored/generated/json/base/json_convert_content.dart'; import 'package:bored/models/bored_entity.dart'; import 'package:bored/routers/base_router.dart'; import 'package:bored/ui/views/bored/bored_page.dart'; @@ -14,7 +15,6 @@ class HomeRouter extends BaseRouter { handlers = { home: Handler(handlerFunc: (c, p) { return BoredPage( - title: getRouterParams(p, "title"), boredEntity: getRouterParams(p, "data"), ); }), @@ -31,11 +31,18 @@ class HomeRouter extends BaseRouter { @override T getRouterParams(Map> parameters, String key) { T t = super.getRouterParams(parameters, key); - final data = jsonDecode(parameters[key].first); - if (t == null && data != null) { - // 序列化对象处理 - if (T.toString() == "BoredEntity") { - t = BoredEntity().fromJson(data) as T; + if (t == null) { + final data = jsonDecode(parameters[key].first); + // 自定义序列化对象处理 + if (data != null) { + if (T.toString() == "BoredEntity") { + t = BoredEntity().fromJson(data) as T; + } +// else if (T.toString() == "List") { +// t = data +// .map((e) => BoredEntity().fromJson(e)) +// .toList().cast() as T; +// } } } return t; diff --git a/lib/service_locator.dart b/lib/service_locator.dart index 2a16562..781ea71 100644 --- a/lib/service_locator.dart +++ b/lib/service_locator.dart @@ -1,7 +1,7 @@ -import 'package:bored/services/db/base_table.dart'; import 'package:bored/services/db/bored_todo_table.dart'; import 'package:bored/utils/db_util.dart'; import 'package:bored/utils/path_util.dart'; +import 'package:bored/utils/sp_util.dart'; import 'package:bored/view_models/bored_page_view_model.dart'; import 'package:bored/view_models/app_view_model.dart'; import 'package:bored/view_models/splash_page_view_model.dart'; @@ -24,8 +24,9 @@ setUpServiceLocator() { // register utils serviceLocator.registerSingletonAsync(() => PathUtil().init()); + serviceLocator.registerSingletonAsync(() => SpUtil().init()); serviceLocator.registerSingletonAsync(() => DbUtil().init(), dependsOn: [PathUtil]); - // register table services + // register database services serviceLocator.registerLazySingleton(() => BoredTodoTable()); } diff --git a/lib/ui/views/bored/bored_page.dart b/lib/ui/views/bored/bored_page.dart index 3fc0ee4..e8ae34a 100644 --- a/lib/ui/views/bored/bored_page.dart +++ b/lib/ui/views/bored/bored_page.dart @@ -1,3 +1,5 @@ +import 'package:bored/consts/config_constants.dart'; +import 'package:bored/generated/l10n.dart'; import 'package:bored/models/bored_entity.dart'; import 'package:bored/models/bored_todo_entity.dart'; import 'package:bored/ui/views/bored/bored_todo_item.dart'; @@ -7,6 +9,7 @@ import 'package:bored/consts/asset_constants.dart'; import 'package:bored/service_locator.dart'; import 'package:bored/ui/widget/progress_bar.dart'; import 'package:bored/utils/date_util.dart'; +import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -16,9 +19,8 @@ import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; class BoredPage extends StatefulWidget { - BoredPage({Key key, this.title, this.boredEntity}) : super(key: key); + BoredPage({Key key, this.boredEntity}) : super(key: key); - final String title; final BoredEntity boredEntity; @override @@ -100,7 +102,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.title, + ConfigConstants.appName(context), style: TextStyle( fontSize: 28.sp, color: Theme.of(context).primaryColor, @@ -111,7 +113,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { height: 8.w, ), Text( - DateUtil.formatDate(), + DateUtil.formatDate(context, Provider.of(context, listen: false).locale), style: TextStyle( fontSize: 14.sp, color: Theme.of(context).hintColor, @@ -159,7 +161,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - "Activity", + S.of(context).activity, style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 16.sp, @@ -236,7 +238,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { margin: EdgeInsets.zero, padding: EdgeInsets.symmetric( vertical: 10.w, - horizontal: 6.w, + horizontal: 8.w, ), width: double.maxFinite, child: Column( @@ -250,7 +252,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { child: SizedBox( width: double.maxFinite, child: Text( - model.boredEntity?.activity ?? "Loading", + model.boredEntity?.activity ?? S.of(context).loading, style: TextStyle( color: Provider.of(context) .isDark(context) @@ -268,7 +270,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - descTextWidget("accessibility:"), + descTextWidget(S.of(context).accessibility), SizedBox( width: 10.w, ), @@ -291,7 +293,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - descTextWidget("price:"), + descTextWidget(S.of(context).price), SizedBox( width: 10.w, ), @@ -315,7 +317,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ - descTextWidget("participants:"), + descTextWidget(S.of(context).participants), Expanded( child: ListView( padding: EdgeInsets.symmetric( @@ -369,7 +371,7 @@ class _BoredPageState extends State with TickerProviderStateMixin { } Widget get _todoListHeader => Text( - "TODO List", + S.of(context).todoList, style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 16.sp, diff --git a/lib/ui/views/bored/bored_todo_item.dart b/lib/ui/views/bored/bored_todo_item.dart index bcb457d..b651f7b 100644 --- a/lib/ui/views/bored/bored_todo_item.dart +++ b/lib/ui/views/bored/bored_todo_item.dart @@ -1,3 +1,4 @@ +import 'package:bored/generated/l10n.dart'; import 'package:bored/models/bored_todo_entity.dart'; import 'package:bored/view_models/app_view_model.dart'; import 'package:flutter/cupertino.dart'; @@ -45,7 +46,7 @@ class TodoItem extends StatelessWidget { onTap: onClickItem, borderRadius: BorderRadius.circular(10.w), child: Padding( - padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.w), + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.w), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -66,10 +67,10 @@ class TodoItem extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.max, children: [ - SizedBox( - width: 70.w, + Padding( + padding: EdgeInsets.only(right: 4.w, bottom: 2.w), child: Text( - 'participants:', + '${S.of(context).participants}', style: TextStyle( color: Theme.of(context).hintColor, fontSize: 12.sp, diff --git a/lib/ui/views/splash_page.dart b/lib/ui/views/splash_page.dart index 4b7e617..a344bb6 100644 --- a/lib/ui/views/splash_page.dart +++ b/lib/ui/views/splash_page.dart @@ -34,6 +34,12 @@ class SplashPageState extends State { @override void initState() { super.initState(); + _initState(); + } + + void _initState() async { + // load language + await _splashPageViewModel.loadLanguageFromCache(context); // prepare data and go to main page _splashPageViewModel.prepareData(context); } diff --git a/lib/utils/date_util.dart b/lib/utils/date_util.dart index 2b21e07..409674d 100644 --- a/lib/utils/date_util.dart +++ b/lib/utils/date_util.dart @@ -1,59 +1,65 @@ +import 'package:bored/generated/l10n.dart'; +import 'package:flutter/cupertino.dart'; + class DateUtil { // get format date - static String formatDate({DateTime time}) { + static String formatDate(BuildContext context, Locale locale, {DateTime time}) { DateTime _time = time ?? DateTime.now().toLocal(); - return "${_getWeekDay(_time.weekday)} ${_time.day} ${_getMonth(_time.month)} ${_time.year}"; + if (locale.languageCode == "zh") { + return S.of(context).format_date("${_time.year}", _getMonth(context, _time.month), "${_time.day}", _getWeekDay(context, _time.weekday)); + } + return S.of(context).format_date(_getWeekDay(context, _time.weekday), _time.day, _getMonth(context, _time.month), _time.year); } - static String _getWeekDay(int day) { + static String _getWeekDay(BuildContext context, int day) { switch (day) { case DateTime.monday: - return "Monday"; + return S.of(context).monday; case DateTime.tuesday: - return "Tuesday"; + return S.of(context).tuesday; case DateTime.wednesday: - return "Wednesday"; + return S.of(context).wednesday; case DateTime.thursday: - return "Thursday"; + return S.of(context).thursday; case DateTime.friday: - return "Friday"; + return S.of(context).friday; case DateTime.saturday: - return "Saturday"; + return S.of(context).saturday; case DateTime.sunday: - return "Sunday"; + return S.of(context).sunday; default: - return "Unknown"; + return S.of(context).unknown; } } - static String _getMonth(int month) { + static String _getMonth(BuildContext context, int month) { switch (month) { case DateTime.january: - return "January"; + return S.of(context).january; case DateTime.february: - return "February"; + return S.of(context).february; case DateTime.march: - return "March"; + return S.of(context).march; case DateTime.april: - return "April"; + return S.of(context).april; case DateTime.may: - return "May"; + return S.of(context).may; case DateTime.june: - return "June"; + return S.of(context).june; case DateTime.july: - return "July"; + return S.of(context).july; case DateTime.august: - return "August"; + return S.of(context).august; case DateTime.september: - return "September"; + return S.of(context).september; case DateTime.october: - return "October"; + return S.of(context).october; case DateTime.november: - return "November"; + return S.of(context).november; case DateTime.december: - return "December"; + return S.of(context).december; default: - return "Unknown"; + return S.of(context).unknown; } } } diff --git a/lib/utils/sp_util.dart b/lib/utils/sp_util.dart new file mode 100644 index 0000000..a134bb4 --- /dev/null +++ b/lib/utils/sp_util.dart @@ -0,0 +1,120 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; + +class SpUtil { + SharedPreferences _prefs; + + Future init() async { + _prefs = await SharedPreferences.getInstance(); + return this; + } + + /// set object. + Future setObject(String key, Object value) { + return _prefs?.setString(key, value == null ? "" : json.encode(value)); + } + + /// get object. + Map getObject(String key) { + String _data = _prefs?.getString(key); + return (_data == null || _data.isEmpty) ? null : json.decode(_data); + } + + /// set object list. + Future setObjectList(String key, List list) { + List _dataList = list?.map((value) { + return json.encode(value); + })?.toList(); + return _prefs?.setStringList(key, _dataList); + } + + /// get object list. + List getObjectList(String key) { + List _dataList = _prefs?.getStringList(key); + return _dataList?.map((value) { + Map _dataMap = json.decode(value); + return _dataMap; + })?.toList(); + } + + /// get string. + String getString(String key) { + return _prefs?.getString(key); + } + + /// set string. + Future setString(String key, String value) { + return _prefs?.setString(key, value); + } + + /// get bool. + bool getBool(String key) { + return _prefs?.getBool(key); + } + + /// set bool. + Future setBool(String key, bool value) { + return _prefs?.setBool(key, value); + } + + /// get int. + int getInt(String key) { + return _prefs?.getInt(key); + } + + /// set int. + Future setInt(String key, int value) { + return _prefs?.setInt(key, value); + } + + /// get double. + double getDouble(String key) { + return _prefs?.getDouble(key); + } + + /// set double. + Future setDouble(String key, double value) { + return _prefs?.setDouble(key, value); + } + + /// get string list. + List getStringList(String key) { + return _prefs?.getStringList(key); + } + + /// set string list. + Future setStringList(String key, List value) { + return _prefs?.setStringList(key, value); + } + + /// get dynamic. + dynamic getDynamic(String key) { + return _prefs?.get(key); + } + + /// has key. + bool hasKey(String key) { + return _prefs?.getKeys()?.contains(key); + } + + /// get keys. + Set getKeys() { + return _prefs?.getKeys(); + } + + /// remove. + Future remove(String key) { + return _prefs?.remove(key); + } + + /// clear. + Future clear() { + return _prefs?.clear(); + } + + ///Sp is initialized. + bool isInitialized() { + return _prefs != null; + } +} diff --git a/lib/view_models/app_view_model.dart b/lib/view_models/app_view_model.dart index 4601c0d..331fa5e 100644 --- a/lib/view_models/app_view_model.dart +++ b/lib/view_models/app_view_model.dart @@ -1,22 +1,31 @@ +import 'package:bored/consts/config_constants.dart'; +import 'package:bored/generated/l10n.dart'; +import 'package:bored/service_locator.dart'; +import 'package:bored/utils/sp_util.dart'; import 'package:flutter/material.dart'; -enum ThemeModel { System, Light, Dark } +enum AppThemeModel { System, Light, Dark } +enum AppLanguage {System, Chinese, English} class AppViewModel extends ChangeNotifier { - ThemeModel _themeModel; + AppThemeModel _themeModel; + AppLanguage _appLanguage = AppLanguage.System; + Locale _locale; - ThemeModel get themeModel => _themeModel; + AppThemeModel get themeModel => _themeModel; + AppLanguage get appLanguage => _appLanguage; + Locale get locale => _locale; bool isDark(BuildContext context) { - if (_themeModel != null && _themeModel != ThemeModel.System) { - return _themeModel == ThemeModel.Dark; + if (_themeModel != null && _themeModel != AppThemeModel.System) { + return _themeModel == AppThemeModel.Dark; } return Theme.of(context).brightness == Brightness.dark; } ThemeData getThemeData({bool darkTheme}) { - if (_themeModel != null && _themeModel != ThemeModel.System) { - return _getTheme(_themeModel == ThemeModel.Dark); + if (_themeModel != null && _themeModel != AppThemeModel.System) { + return _getTheme(_themeModel == AppThemeModel.Dark); } return _getTheme(darkTheme); } @@ -57,8 +66,28 @@ class AppViewModel extends ChangeNotifier { ); } - void setDarkModel(ThemeModel _themeModel) { + void setDarkModel(AppThemeModel _themeModel) { this._themeModel = _themeModel; notifyListeners(); } + + Future setLocale({Locale locale, bool isFollowSystem}) async { + if (isFollowSystem) { + _appLanguage = AppLanguage.System; + } else { + _appLanguage = locale.languageCode == "zh" ? AppLanguage.Chinese : AppLanguage.English; + await serviceLocator.get().setString(ConfigConstants.languageKey, locale.languageCode); + } + + switch (locale.languageCode) { + case "zh": + _locale = Locale("zh", "CN"); + break; + default: + _locale = Locale("en", "US"); + break; + } +// S.load(locale); + notifyListeners(); + } } diff --git a/lib/view_models/splash_page_view_model.dart b/lib/view_models/splash_page_view_model.dart index de89ffb..463e682 100644 --- a/lib/view_models/splash_page_view_model.dart +++ b/lib/view_models/splash_page_view_model.dart @@ -1,10 +1,15 @@ +import 'package:bored/consts/config_constants.dart'; import 'package:bored/models/bored_entity.dart'; import 'package:bored/routers/Routers.dart'; import 'package:bored/routers/home_router.dart'; import 'package:bored/service_locator.dart'; import 'package:bored/services/bored/bored_api.dart'; +import 'package:bored/utils/sp_util.dart'; +import 'package:bored/view_models/app_view_model.dart'; +import 'package:devicelocale/devicelocale.dart'; import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class SplashPageViewModel extends ChangeNotifier { final BoredApi _boredApi = serviceLocator(); @@ -19,10 +24,28 @@ class SplashPageViewModel extends ChangeNotifier { Routers.navigateTo( context, HomeRouter.home, - params: {"title": "Boring destroyer", "data": _boredEntity?.toJson()}, + params: {"data": _boredEntity?.toJson()}, replace: true, transition: TransitionType.fadeIn, ); } } + + // load from share preference + Future loadLanguageFromCache(BuildContext context) async { + // wait util service all registered + await serviceLocator.allReady(); + String languageCode = serviceLocator.get().getString(ConfigConstants.languageKey); + if (languageCode != null && languageCode.isNotEmpty) { + Provider.of(context, listen: false).setLocale( + locale: Locale(languageCode), + isFollowSystem: false, + ); + } else { + Provider.of(context, listen: false).setLocale( + locale: await Devicelocale.currentAsLocale, + isFollowSystem: true, + ); + } + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 0ed0ba6..bcc4f4a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -64,6 +64,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.1.3" + devicelocale: + dependency: "direct main" + description: + name: devicelocale + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.1" dio: dependency: "direct main" description: @@ -71,6 +78,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.9" + file: + dependency: transitive + description: + name: file + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.2.1" flare_dart: dependency: transitive description: @@ -97,6 +111,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_screenutil: dependency: "direct main" description: @@ -123,13 +142,18 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" get_it: dependency: "direct main" description: name: get_it url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.1" + version: "4.0.2" http_parser: dependency: transitive description: @@ -144,6 +168,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.12" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.16.1" logger: dependency: "direct main" description: @@ -200,6 +231,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.6.7" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.1+2" path_provider_macos: dependency: transitive description: @@ -235,6 +273,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.13" provider: dependency: "direct main" description: @@ -249,6 +294,41 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.8" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.2+1" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.1+10" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2+7" sky_engine: dependency: transitive description: flutter @@ -331,6 +411,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.8" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.0" xml: dependency: transitive description: @@ -340,4 +427,4 @@ packages: version: "3.6.1" sdks: dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.6 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index ed837bd..1b6d14f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 # 服务注册 - get_it: ^4.0.1 + get_it: ^4.0.2 # 状态管理 provider: ^4.0.5 # 网络访问 @@ -45,6 +45,13 @@ dependencies: path_provider: ^1.6.7 # 日志打印 logger: ^0.9.1 + # 国际化 + flutter_localizations: + sdk: flutter + # shared_preferences + shared_preferences: ^0.5.8 + # device locale https://pub.dev/packages/devicelocale + devicelocale: ^0.3.1 dev_dependencies: flutter_test: @@ -92,3 +99,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages +flutter_intl: + enabled: true