From 8eab769b74dc0304883e8b9ba184322f387b309b Mon Sep 17 00:00:00 2001 From: JingChiang Date: Sun, 10 Nov 2024 13:08:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=A6=E4=BD=9C=E8=AA=9E=E8=A8=80?= =?UTF-8?q?=E5=88=87=E6=8F=9B=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/BottomNavBar.dart | 49 ++- lib/HistoricalRecord.dart | 23 +- lib/HomePage.dart | 27 +- lib/KnowledgePage.dart | 4 +- lib/MessagePage.dart | 4 +- lib/PersonalInfo.dart | 97 ++++- lib/generated/intl/messages_all.dart | 67 ++++ lib/generated/intl/messages_en.dart | 81 +++++ lib/generated/intl/messages_zh_Hant.dart | 76 ++++ lib/generated/l10n.dart | 429 +++++++++++++++++++++++ lib/l10n/intl_en.arb | 43 +++ lib/l10n/intl_zh_Hant.arb | 45 +++ lib/main.dart | 40 ++- pubspec.yaml | 4 + 14 files changed, 933 insertions(+), 56 deletions(-) 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_Hant.dart create mode 100644 lib/generated/l10n.dart create mode 100644 lib/l10n/intl_en.arb create mode 100644 lib/l10n/intl_zh_Hant.arb diff --git a/lib/BottomNavBar.dart b/lib/BottomNavBar.dart index a1c78c1..a3aa329 100644 --- a/lib/BottomNavBar.dart +++ b/lib/BottomNavBar.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:topic/HomePage.dart'; import 'HistoricalRecord.dart'; import 'KnowledgePage.dart'; import 'MessagePage.dart'; import 'PersonalInfo.dart'; +import 'generated/l10n.dart'; -class BottomNavBar extends StatelessWidget { +class BottomNavBar extends StatefulWidget { const BottomNavBar({ super.key, required this.email, @@ -16,64 +19,86 @@ class BottomNavBar extends StatelessWidget { final String email; final int initTabIndex; + @override + State createState() => _BottomNavBarState(); +} + +class _BottomNavBarState extends State { List _tabs(BuildContext context) => [ PersistentTabConfig.noScreen( item: ItemConfig( icon: Icon(Icons.home), - title: "首頁", + title: S.of(context).home, activeForegroundColor: Colors.orange, inactiveForegroundColor: Colors.grey, ), onPressed: (context) { - Navigator.pop(context); // Now you can use context here + Navigator.push( + context, + MaterialPageRoute(builder: (context) => HomePage(email: widget.email)), + ).then((value) => setState(() {})); }, ), PersistentTabConfig( screen: HistoricalRecord( - email: email, + email: widget.email, ), item: ItemConfig( icon: Icon(Icons.history_edu), - title: "跌倒紀錄", + title: S.of(context).fall_record, activeForegroundColor: Colors.orange, inactiveForegroundColor: Colors.grey, ), ), PersistentTabConfig( screen: KnowledgePage( - email: email, + email: widget.email, ), item: ItemConfig( icon: Icon(Icons.lightbulb_outline), - title: "知識補充", + title: S.of(context).additional_information, activeForegroundColor: Colors.orange, inactiveForegroundColor: Colors.grey, ), ), PersistentTabConfig( screen: MessagePage( - email: email, + email: widget.email, ), item: ItemConfig( icon: Icon(Icons.monitor_outlined), - title: "切換畫面", + title: S.of(context).switch_camera, activeForegroundColor: Colors.orange, inactiveForegroundColor: Colors.grey, ), ), PersistentTabConfig( screen: PersonalInfo( - email: email, + email: widget.email, ), item: ItemConfig( icon: Icon(Icons.person_sharp), - title: "個人資料", + title: S.of(context).personal_information, activeForegroundColor: Colors.orange, inactiveForegroundColor: Colors.grey, ), ), ]; + @override + void initState() { + super.initState(); + _setLanguage(); + } + + void _setLanguage() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String preLanguage = prefs.getString("language") ?? ""; + Locale locale = preLanguage == "" ? Locale('zh_Hant') : Locale(preLanguage) ; + S.load(locale); + setState(() {}); + } + @override Widget build(BuildContext context) { return PersistentTabView( @@ -81,7 +106,7 @@ class BottomNavBar extends StatelessWidget { navBarBuilder: (navBarConfig) => Style1BottomNavBar( navBarConfig: navBarConfig, ), - controller: PersistentTabController(initialIndex: initTabIndex), + controller: PersistentTabController(initialIndex: widget.initTabIndex), ); } } diff --git a/lib/HistoricalRecord.dart b/lib/HistoricalRecord.dart index 67e38d9..5a44062 100644 --- a/lib/HistoricalRecord.dart +++ b/lib/HistoricalRecord.dart @@ -3,6 +3,8 @@ import 'package:mysql_client/mysql_client.dart'; import 'package:video_player/video_player.dart'; import 'package:better_player_plus/better_player_plus.dart'; +import 'generated/l10n.dart'; + class HistoricalRecord extends StatefulWidget { final String email; // 接收來自上個頁面的 email HistoricalRecord({required this.email}); @@ -65,6 +67,7 @@ class _HistoricalRecordState extends State { _results = fallResult.rows .map((row) => { + '跌倒編號': row.colAt(0), '跌倒時間': row.colAt(1), '跌倒原因': row.colAt(2), '跌倒地點': row.colAt(6), @@ -97,7 +100,7 @@ class _HistoricalRecordState extends State { padding: EdgeInsets.all(10.0), child: Center( child: Text( - '跌倒紀錄', + S.of(context).fall_record, style: TextStyle(fontSize: 24, height: 5), ), ), @@ -133,7 +136,7 @@ class _HistoricalRecordState extends State { ), child: ListTile( title: Text( - '編號: ${displayIndex}', // 使用倒序顯示編號 + S.of(context).fall_id(displayIndex), // 使用倒序顯示編號 style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, @@ -143,9 +146,11 @@ class _HistoricalRecordState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '跌倒時間: ${_results[index]['跌倒時間']}\n' - '跌倒原因: ${_results[index]['跌倒原因']}\n' - '跌倒地點: ${_results[index]['跌倒地點']}\n', + S.of(context).fall_detail( + _results[index]['跌倒時間'], + _results[index]['跌倒原因'], + _results[index]['跌倒地點'], + ), style: TextStyle(fontSize: 16, color: Colors.black), // 統一字體樣式 ), ], @@ -245,18 +250,18 @@ class _FallDetailPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('詳細資料'), + title: Text(S.of(context).fall_detail_title), ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('跌倒時間: ${widget.fallDetail['跌倒時間']}', style: TextStyle(fontSize: 18)), + Text(S.of(context).fall_detail_time(widget.fallDetail['跌倒時間']), style: TextStyle(fontSize: 18)), SizedBox(height: 10), - Text('跌倒原因: ${widget.fallDetail['跌倒原因']}', style: TextStyle(fontSize: 18)), + Text(S.of(context).fall_detail_reason(widget.fallDetail['跌倒原因']), style: TextStyle(fontSize: 18)), SizedBox(height: 10), - Text('跌倒地點: ${widget.fallDetail['跌倒地點']}', style: TextStyle(fontSize: 18)), + Text(S.of(context).fall_detail_location(widget.fallDetail['跌倒地點']), style: TextStyle(fontSize: 18)), if (fhvideoId != '') AspectRatio( aspectRatio: 16 / 9, child: BetterPlayer(controller: _betterPlayerController), diff --git a/lib/HomePage.dart b/lib/HomePage.dart index 02f48f2..2e09bec 100644 --- a/lib/HomePage.dart +++ b/lib/HomePage.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'BottomNavBar.dart'; +import 'generated/l10n.dart'; class HomePage extends StatefulWidget { final String email; @@ -18,6 +20,13 @@ class _HomePageState extends State { @override void initState() { super.initState(); + // _setLanguage(); + } + void _setLanguage() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String preLanguage = prefs.getString("language") ?? ""; + Locale locale = preLanguage == "" ? Locale(preLanguage) : Localizations.localeOf(context); + S.load(locale); } @override @@ -88,7 +97,7 @@ class _HomePageState extends State { ), ), Text( - '即時畫面', + S.of(context).realtime_video, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), ], @@ -101,10 +110,10 @@ class _HomePageState extends State { physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(16), children: [ - _buildGridItem(Icons.history_edu, '跌倒紀錄', context), - _buildGridItem(Icons.lightbulb_outline, '知識補充', context), - _buildGridItem(Icons.monitor_outlined, '切換畫面', context), - _buildGridItem(Icons.person_sharp, '個人資料', context), + _buildGridItem(Icons.history_edu, S.of(context).fall_record, context), + _buildGridItem(Icons.lightbulb_outline, S.of(context).additional_information, context), + _buildGridItem(Icons.monitor_outlined, S.of(context).switch_camera, context), + _buildGridItem(Icons.person_sharp, S.of(context).personal_information, context), ], ), ), @@ -124,7 +133,7 @@ class _HomePageState extends State { color: Colors.white, child: InkWell( onTap: () { - if (label == '跌倒紀錄') { + if (label == S.of(context).fall_record) { Navigator.push( context, MaterialPageRoute( @@ -133,7 +142,7 @@ class _HomePageState extends State { initTabIndex: 1, )), ); - } else if (label == '知識補充') { + } else if (label == S.of(context).additional_information) { Navigator.push( context, MaterialPageRoute( @@ -142,7 +151,7 @@ class _HomePageState extends State { initTabIndex: 2, )), ); - } else if (label == '切換畫面') { + } else if (label == S.of(context).switch_camera) { Navigator.push( context, MaterialPageRoute @@ -151,7 +160,7 @@ class _HomePageState extends State { initTabIndex: 3, )), ); - } else if (label == '個人資料') { + } else if (label == S.of(context).personal_information) { Navigator.push( context, MaterialPageRoute( diff --git a/lib/KnowledgePage.dart b/lib/KnowledgePage.dart index 2c9b620..bf3f8a3 100644 --- a/lib/KnowledgePage.dart +++ b/lib/KnowledgePage.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:topic/HomePage.dart'; import 'package:url_launcher/url_launcher.dart'; + +import 'generated/l10n.dart'; /*void main() { runApp(MaterialApp( home: KnowledgePage(), @@ -27,7 +29,7 @@ class KnowledgePage extends StatelessWidget { padding: EdgeInsets.all(10.0), child: Center( child: Text( - '知識補充', + S.of(context).additional_information, style: TextStyle(fontSize: 24, height: 5), ), ), diff --git a/lib/MessagePage.dart b/lib/MessagePage.dart index 56d70bb..8a86472 100644 --- a/lib/MessagePage.dart +++ b/lib/MessagePage.dart @@ -3,6 +3,8 @@ import 'package:flutter/services.dart'; import 'package:mysql_client/mysql_client.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import 'generated/l10n.dart'; + class MessagePage extends StatefulWidget { final String email; // 接收來自上個頁面的 email @@ -114,7 +116,7 @@ class _MessagePageState extends State { ), ), Text( - '即時畫面', + S.of(context).realtime_video, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), ], diff --git a/lib/PersonalInfo.dart b/lib/PersonalInfo.dart index 3771e18..2416416 100644 --- a/lib/PersonalInfo.dart +++ b/lib/PersonalInfo.dart @@ -1,11 +1,17 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:mysql_client/mysql_client.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:topic/HomePage.dart'; import 'package:topic/NoSwipeBackRoute.dart'; import 'package:topic/main.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'generated/l10n.dart'; + class PersonalInfo extends StatefulWidget { final String email; // 接收來自上個頁面的 email PersonalInfo({required this.email}); @@ -136,6 +142,11 @@ class _PersonalInfoState extends State { ); } + void _setLanguage(Locale locale) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString("language", locale.languageCode); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -161,7 +172,7 @@ class _PersonalInfoState extends State { children: [ ListTile( leading: Icon(Icons.manage_accounts_outlined), - title: Text('基本資料', style: TextStyle(fontSize: 18)), + title: Text(S.of(context).basic_information_setting, style: TextStyle(fontSize: 18)), onTap: () { Navigator.push( context, @@ -181,7 +192,7 @@ class _PersonalInfoState extends State { Divider(), ListTile( leading: Icon(Icons.lock_open_outlined), - title: Text('帳號設定', style: TextStyle(fontSize: 18)), + title: Text(S.of(context).account_setting, style: TextStyle(fontSize: 18)), onTap: () { Navigator.push( context, @@ -196,15 +207,69 @@ class _PersonalInfoState extends State { }, ), Divider(), + ListTile( leading: Icon(Icons.language_outlined), - title: Text('語言設定', style: TextStyle(fontSize: 18)), - onTap: () {}, + title: Text(S.of(context).language_setting, style: TextStyle(fontSize: 18)), + onTap: () { + if(S.current.login_button == 'Login'){ + S.load(Locale('zh_Hant')); + _setLanguage(Locale('zh_Hant')); + }else{ + S.load(Locale('en', 'US')); + _setLanguage(Locale('en', 'US')); + } + showDialog( + context: context, + builder: (BuildContext context) { + if (Platform.isAndroid) { + // Android-specific code + return AlertDialog( + title: Text(S.of(context).language_setting_alert_title), + content: Text(S.of(context).language_alert_android), + actions: [ + TextButton( + onPressed: () { + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + }, + child: Text(S.of(context).confirm), + ), + ], + ); + } else { + // iOS-specific code + return AlertDialog( + title: Text(S.of(context).language_setting_alert_title), + content: Text(S.of(context).language_alert), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + setState(() {}); + }, + child: Text(S.of(context).cancel), + ), + TextButton( + onPressed: () { + // SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + pushReplacementWithoutNavBar(context, MaterialPageRoute(builder:(context) => HomePage( + email: widget.email)), + ); + }, + child: Text(S.of(context).confirm), + ), + ], + ); + } + + }, + ); + }, ), Divider(), ListTile( leading: Icon(Icons.local_phone_rounded), - title: Text('撥打電話', style: TextStyle(fontSize: 18)), + title: Text(S.of(context).call_phone, style: TextStyle(fontSize: 18)), onTap: () { _showDialerDialog(context); // 顯示對話框,確認是否撥打電話 }, @@ -213,7 +278,7 @@ class _PersonalInfoState extends State { ElevatedButton( onPressed: _loginOut, - child: Text('登出'), + child: Text(S.of(context).logout_button), style: TextButton.styleFrom( backgroundColor: Color(0xFFF5E3C3), textStyle: TextStyle(fontSize: 18), @@ -330,7 +395,7 @@ class _BasicInfoPageState extends State { onPressed: () { Navigator.of(context).pop(); }, - ),title: Text('基本資料', style: TextStyle(fontSize: 28)), + ),title: Text(S.of(context).basic_information_setting, style: TextStyle(fontSize: 28)), onTap: () { }, ),), @@ -339,7 +404,7 @@ class _BasicInfoPageState extends State { padding: EdgeInsets.all(16), children: [ ListTile( - title: Text('姓名'), + title: Text(S.of(context).name), subtitle: _isEditing ? TextField( controller: _realnameController, @@ -348,7 +413,7 @@ class _BasicInfoPageState extends State { ), Divider(), ListTile( - title: Text('手機'), + title: Text(S.of(context).phone), subtitle: _isEditing ? TextField( controller: _phoneController, @@ -357,7 +422,7 @@ class _BasicInfoPageState extends State { ), Divider(), ListTile( - title: Text('性別'), + title: Text(S.of(context).gender), subtitle: _isEditing ? TextField( controller: _genderController, @@ -366,7 +431,7 @@ class _BasicInfoPageState extends State { ), Divider(), ListTile( - title: Text('地址'), + title: Text(S.of(context).address), subtitle: _isEditing ? TextField( controller: _addressController, @@ -382,7 +447,7 @@ class _BasicInfoPageState extends State { _toggleEdit(); } }, - child: Text(_isEditing ? '儲存變更' : '修改資料'), + child: Text(_isEditing ? S.of(context).save : S.of(context).edit), style: TextButton.styleFrom( backgroundColor: Color(0xFFF5E3C3), textStyle: TextStyle(fontSize: 18), @@ -488,7 +553,7 @@ class _AccountPageState extends State { Navigator.of(context).pop(); }, ), - title: Text('帳號設定', style: TextStyle(fontSize: 28)), + title: Text(S.of(context).account_setting, style: TextStyle(fontSize: 28)), onTap: () { }, ), @@ -498,7 +563,7 @@ class _AccountPageState extends State { padding: EdgeInsets.all(16), children: [ ListTile( - title: Text('電子信箱'), + title: Text(S.of(context).email), subtitle: _isEditing ? TextField( controller: _emailController, @@ -507,7 +572,7 @@ class _AccountPageState extends State { ), Divider(), ListTile( - title: Text('密碼'), + title: Text(S.of(context).password_label), subtitle: _isEditing ? TextField( controller: _passwordController, @@ -535,7 +600,7 @@ class _AccountPageState extends State { _toggleEdit(); } }, - child: Text(_isEditing ? '儲存變更' : '修改資料'), + child: Text(_isEditing ? S.of(context).save : S.of(context).edit), style: TextButton.styleFrom( backgroundColor: Color(0xFFF5E3C3), textStyle: TextStyle(fontSize: 18), diff --git a/lib/generated/intl/messages_all.dart b/lib/generated/intl/messages_all.dart new file mode 100644 index 0000000..03cc224 --- /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:flutter/foundation.dart'; +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_Hant.dart' as messages_zh_hant; + +typedef Future LibraryLoader(); +Map _deferredLibraries = { + 'en': () => new SynchronousFuture(null), + 'zh_Hant': () => new SynchronousFuture(null), +}; + +MessageLookupByLibrary? _findExact(String localeName) { + switch (localeName) { + case 'en': + return messages_en.messages; + case 'zh_Hant': + return messages_zh_hant.messages; + default: + return null; + } +} + +/// User programs should call this before using [localeName] for messages. +Future initializeMessages(String localeName) { + var availableLocale = Intl.verifiedLocale( + localeName, (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); + if (availableLocale == null) { + return new SynchronousFuture(false); + } + var lib = _deferredLibraries[availableLocale]; + lib == null ? new SynchronousFuture(false) : lib(); + initializeInternalMessageLookup(() => new CompositeMessageLookup()); + messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); + return new SynchronousFuture(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..4c783b0 --- /dev/null +++ b/lib/generated/intl/messages_en.dart @@ -0,0 +1,81 @@ +// 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, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +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 String m0(time, reason, location) => + "Time: ${time}\nReason: ${reason},\nLocation: ${location}\n"; + + static String m1(location) => "Location: ${location}"; + + static String m2(reason) => "Reason: ${reason}"; + + static String m3(time) => "Time: ${time}"; + + static String m4(displayIndex) => "ID: ${displayIndex}"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "account_setting": MessageLookupByLibrary.simpleMessage("Account"), + "additional_information": + MessageLookupByLibrary.simpleMessage("Knowledge"), + "address": MessageLookupByLibrary.simpleMessage("Address"), + "basic_information_setting": + MessageLookupByLibrary.simpleMessage("Basic Information"), + "call_phone": + MessageLookupByLibrary.simpleMessage("Call Emergency Phone"), + "cancel": MessageLookupByLibrary.simpleMessage("Cancel"), + "confirm": MessageLookupByLibrary.simpleMessage("Confirm"), + "edit": MessageLookupByLibrary.simpleMessage("Edit"), + "email": MessageLookupByLibrary.simpleMessage("Email"), + "email_label": MessageLookupByLibrary.simpleMessage("Email/Username"), + "fall_detail": m0, + "fall_detail_location": m1, + "fall_detail_reason": m2, + "fall_detail_time": m3, + "fall_detail_title": + MessageLookupByLibrary.simpleMessage("Fall Detail"), + "fall_id": m4, + "fall_record": MessageLookupByLibrary.simpleMessage("Fall Record"), + "fall_record_empty": + MessageLookupByLibrary.simpleMessage("No fall record"), + "forgot_password": + MessageLookupByLibrary.simpleMessage("Forgot password"), + "gender": MessageLookupByLibrary.simpleMessage("Gender"), + "home": MessageLookupByLibrary.simpleMessage("Home"), + "language_alert": MessageLookupByLibrary.simpleMessage( + "Return to the home page to complete the language setting(Confirm to redirect), otherwise some languages will not be updated in real time!"), + "language_alert_android": MessageLookupByLibrary.simpleMessage( + "Reopen the APP to complete the language setting, otherwise some languages will not be updated in real time!"), + "language_setting": MessageLookupByLibrary.simpleMessage("Language"), + "language_setting_alert_title": + MessageLookupByLibrary.simpleMessage("Language Setting"), + "login_button": MessageLookupByLibrary.simpleMessage("Login"), + "logout_button": MessageLookupByLibrary.simpleMessage("Logout"), + "name": MessageLookupByLibrary.simpleMessage("Name"), + "password_label": MessageLookupByLibrary.simpleMessage("Password"), + "personal_information": MessageLookupByLibrary.simpleMessage("Profile"), + "phone": MessageLookupByLibrary.simpleMessage("Phone Number"), + "realtime_video": MessageLookupByLibrary.simpleMessage("Live View"), + "register_button": MessageLookupByLibrary.simpleMessage("Register"), + "save": MessageLookupByLibrary.simpleMessage("Save"), + "switch_camera": MessageLookupByLibrary.simpleMessage("Switch View") + }; +} diff --git a/lib/generated/intl/messages_zh_Hant.dart b/lib/generated/intl/messages_zh_Hant.dart new file mode 100644 index 0000000..0b41f6c --- /dev/null +++ b/lib/generated/intl/messages_zh_Hant.dart @@ -0,0 +1,76 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a zh_Hant 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, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +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_Hant'; + + static String m0(time, reason, location) => + "跌倒時間: ${time}\n跌倒原因: ${reason},\n跌倒地點: ${location}\n"; + + static String m1(location) => "跌倒地點: ${location}"; + + static String m2(reason) => "跌倒原因: ${reason}"; + + static String m3(time) => "跌倒時間: ${time}"; + + static String m4(displayIndex) => "編號: ${displayIndex}"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "account_setting": MessageLookupByLibrary.simpleMessage("帳號設定"), + "additional_information": MessageLookupByLibrary.simpleMessage("知識補充"), + "address": MessageLookupByLibrary.simpleMessage("地址"), + "basic_information_setting": + MessageLookupByLibrary.simpleMessage("基本資料"), + "call_phone": MessageLookupByLibrary.simpleMessage("撥打電話"), + "cancel": MessageLookupByLibrary.simpleMessage("取消"), + "confirm": MessageLookupByLibrary.simpleMessage("確認"), + "edit": MessageLookupByLibrary.simpleMessage("修改資料"), + "email": MessageLookupByLibrary.simpleMessage("電子信箱"), + "email_label": MessageLookupByLibrary.simpleMessage("電子信箱/帳號"), + "fall_detail": m0, + "fall_detail_location": m1, + "fall_detail_reason": m2, + "fall_detail_time": m3, + "fall_detail_title": MessageLookupByLibrary.simpleMessage("詳細資料"), + "fall_id": m4, + "fall_record": MessageLookupByLibrary.simpleMessage("跌倒紀錄"), + "fall_record_empty": MessageLookupByLibrary.simpleMessage("尚無跌倒紀錄"), + "forgot_password": MessageLookupByLibrary.simpleMessage("忘記密碼"), + "gender": MessageLookupByLibrary.simpleMessage("性別"), + "home": MessageLookupByLibrary.simpleMessage("首頁"), + "language_alert": MessageLookupByLibrary.simpleMessage( + "跳轉回首頁以完成語言設定,否則會有部分語言無法即時更新!"), + "language_alert_android": MessageLookupByLibrary.simpleMessage( + "重新開啟APP以完成語言設定,否則會有部分語言無法即時更新!"), + "language_setting": MessageLookupByLibrary.simpleMessage("語言設定"), + "language_setting_alert_title": + MessageLookupByLibrary.simpleMessage("語言設定"), + "login_button": MessageLookupByLibrary.simpleMessage("登入"), + "logout_button": MessageLookupByLibrary.simpleMessage("登出"), + "name": MessageLookupByLibrary.simpleMessage("姓名"), + "password_label": MessageLookupByLibrary.simpleMessage("密碼"), + "personal_information": MessageLookupByLibrary.simpleMessage("個人資料"), + "phone": MessageLookupByLibrary.simpleMessage("電話"), + "realtime_video": MessageLookupByLibrary.simpleMessage("即時畫面"), + "register_button": MessageLookupByLibrary.simpleMessage("立即註冊"), + "save": MessageLookupByLibrary.simpleMessage("儲存變更"), + "switch_camera": MessageLookupByLibrary.simpleMessage("切換畫面") + }; +} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart new file mode 100644 index 0000000..a2d4adb --- /dev/null +++ b/lib/generated/l10n.dart @@ -0,0 +1,429 @@ +// 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 +// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each +// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes + +class S { + S(); + + static S? _current; + + static S get current { + assert(_current != null, + 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); + return _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; + final instance = S(); + S._current = instance; + + return instance; + }); + } + + static S of(BuildContext context) { + final instance = S.maybeOf(context); + assert(instance != null, + 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); + return instance!; + } + + static S? maybeOf(BuildContext context) { + return Localizations.of(context, S); + } + + /// `Email/Username` + String get email_label { + return Intl.message( + 'Email/Username', + name: 'email_label', + desc: '', + args: [], + ); + } + + /// `Password` + String get password_label { + return Intl.message( + 'Password', + name: 'password_label', + desc: '', + args: [], + ); + } + + /// `Login` + String get login_button { + return Intl.message( + 'Login', + name: 'login_button', + desc: '', + args: [], + ); + } + + /// `Logout` + String get logout_button { + return Intl.message( + 'Logout', + name: 'logout_button', + desc: '', + args: [], + ); + } + + /// `Register` + String get register_button { + return Intl.message( + 'Register', + name: 'register_button', + desc: '', + args: [], + ); + } + + /// `Forgot password` + String get forgot_password { + return Intl.message( + 'Forgot password', + name: 'forgot_password', + desc: '', + args: [], + ); + } + + /// `Live View` + String get realtime_video { + return Intl.message( + 'Live View', + name: 'realtime_video', + desc: '', + args: [], + ); + } + + /// `Fall Record` + String get fall_record { + return Intl.message( + 'Fall Record', + name: 'fall_record', + desc: '', + args: [], + ); + } + + /// `Switch View` + String get switch_camera { + return Intl.message( + 'Switch View', + name: 'switch_camera', + desc: '', + args: [], + ); + } + + /// `Knowledge` + String get additional_information { + return Intl.message( + 'Knowledge', + name: 'additional_information', + desc: '', + args: [], + ); + } + + /// `Profile` + String get personal_information { + return Intl.message( + 'Profile', + name: 'personal_information', + desc: '', + args: [], + ); + } + + /// `Home` + String get home { + return Intl.message( + 'Home', + name: 'home', + desc: '', + args: [], + ); + } + + /// `Basic Information` + String get basic_information_setting { + return Intl.message( + 'Basic Information', + name: 'basic_information_setting', + desc: '', + args: [], + ); + } + + /// `Account` + String get account_setting { + return Intl.message( + 'Account', + name: 'account_setting', + desc: '', + args: [], + ); + } + + /// `Language` + String get language_setting { + return Intl.message( + 'Language', + name: 'language_setting', + desc: '', + args: [], + ); + } + + /// `Call Emergency Phone` + String get call_phone { + return Intl.message( + 'Call Emergency Phone', + name: 'call_phone', + desc: '', + args: [], + ); + } + + /// `Name` + String get name { + return Intl.message( + 'Name', + name: 'name', + desc: '', + args: [], + ); + } + + /// `Phone Number` + String get phone { + return Intl.message( + 'Phone Number', + name: 'phone', + desc: '', + args: [], + ); + } + + /// `Gender` + String get gender { + return Intl.message( + 'Gender', + name: 'gender', + desc: '', + args: [], + ); + } + + /// `Address` + String get address { + return Intl.message( + 'Address', + name: 'address', + desc: '', + args: [], + ); + } + + /// `Edit` + String get edit { + return Intl.message( + 'Edit', + name: 'edit', + desc: '', + args: [], + ); + } + + /// `Save` + String get save { + return Intl.message( + 'Save', + name: 'save', + desc: '', + args: [], + ); + } + + /// `Email` + String get email { + return Intl.message( + 'Email', + name: 'email', + desc: '', + args: [], + ); + } + + /// `ID: {displayIndex}` + String fall_id(Object displayIndex) { + return Intl.message( + 'ID: $displayIndex', + name: 'fall_id', + desc: '', + args: [displayIndex], + ); + } + + /// `Time: {time}\nReason: {reason},\nLocation: {location}\n` + String fall_detail(Object time, Object reason, Object location) { + return Intl.message( + 'Time: $time\nReason: $reason,\nLocation: $location\n', + name: 'fall_detail', + desc: '', + args: [time, reason, location], + ); + } + + /// `Fall Detail` + String get fall_detail_title { + return Intl.message( + 'Fall Detail', + name: 'fall_detail_title', + desc: '', + args: [], + ); + } + + /// `Reason: {reason}` + String fall_detail_reason(Object reason) { + return Intl.message( + 'Reason: $reason', + name: 'fall_detail_reason', + desc: '', + args: [reason], + ); + } + + /// `Location: {location}` + String fall_detail_location(Object location) { + return Intl.message( + 'Location: $location', + name: 'fall_detail_location', + desc: '', + args: [location], + ); + } + + /// `Time: {time}` + String fall_detail_time(Object time) { + return Intl.message( + 'Time: $time', + name: 'fall_detail_time', + desc: '', + args: [time], + ); + } + + /// `No fall record` + String get fall_record_empty { + return Intl.message( + 'No fall record', + name: 'fall_record_empty', + desc: '', + args: [], + ); + } + + /// `Language Setting` + String get language_setting_alert_title { + return Intl.message( + 'Language Setting', + name: 'language_setting_alert_title', + desc: '', + args: [], + ); + } + + /// `Return to the home page to complete the language setting(Confirm to redirect), otherwise some languages will not be updated in real time!` + String get language_alert { + return Intl.message( + 'Return to the home page to complete the language setting(Confirm to redirect), otherwise some languages will not be updated in real time!', + name: 'language_alert', + desc: '', + args: [], + ); + } + + /// `Reopen the APP to complete the language setting, otherwise some languages will not be updated in real time!` + String get language_alert_android { + return Intl.message( + 'Reopen the APP to complete the language setting, otherwise some languages will not be updated in real time!', + name: 'language_alert_android', + desc: '', + args: [], + ); + } + + /// `Cancel` + String get cancel { + return Intl.message( + 'Cancel', + name: 'cancel', + desc: '', + args: [], + ); + } + + /// `Confirm` + String get confirm { + return Intl.message( + 'Confirm', + name: 'confirm', + desc: '', + args: [], + ); + } +} + +class AppLocalizationDelegate extends LocalizationsDelegate { + const AppLocalizationDelegate(); + + List get supportedLocales { + return const [ + Locale.fromSubtags(languageCode: 'en'), + Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), + ]; + } + + @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) { + for (var supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale.languageCode) { + return true; + } + } + return false; + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb new file mode 100644 index 0000000..24f4590 --- /dev/null +++ b/lib/l10n/intl_en.arb @@ -0,0 +1,43 @@ +{ + "email_label": "Email/Username", + "password_label": "Password", + "login_button": "Login", + "logout_button": "Logout", + "register_button": "Register", + "forgot_password": "Forgot password", + "realtime_video": "Live View", + "fall_record": "Fall Record", + "switch_camera": "Switch View", + "additional_information": "Knowledge", + "personal_information": "Profile", + + "home": "Home", + + "basic_information_setting": "Basic Information", + "account_setting": "Account", + "language_setting": "Language", + "call_phone": "Call Emergency Phone", + + "name": "Name", + "phone" : "Phone Number", + "gender": "Gender", + "address": "Address", + "edit": "Edit", + "save": "Save", + + "email": "Email", + + "fall_id": "ID: {displayIndex}", + "fall_detail": "Time: {time}\nReason: {reason},\nLocation: {location}\n", + "fall_detail_title": "Fall Detail", + "fall_detail_reason": "Reason: {reason}", + "fall_detail_location": "Location: {location}", + "fall_detail_time": "Time: {time}", + "fall_record_empty": "No fall record", + + "language_setting_alert_title": "Language Setting", + "language_alert": "Return to the home page to complete the language setting(Confirm to redirect), otherwise some languages will not be updated in real time!", + "language_alert_android": "Reopen the APP to complete the language setting, otherwise some languages will not be updated in real time!", + "cancel": "Cancel", + "confirm": "Confirm" +} \ No newline at end of file diff --git a/lib/l10n/intl_zh_Hant.arb b/lib/l10n/intl_zh_Hant.arb new file mode 100644 index 0000000..7483c2e --- /dev/null +++ b/lib/l10n/intl_zh_Hant.arb @@ -0,0 +1,45 @@ +{ + "email_label": "電子信箱/帳號", + "password_label": "密碼", + "login_button": "登入", + "logout_button": "登出", + "register_button": "立即註冊", + "forgot_password": "忘記密碼", + + "realtime_video": "即時畫面", + "fall_record": "跌倒紀錄", + "switch_camera": "切換畫面", + "additional_information": "知識補充", + "personal_information": "個人資料", + + "home": "首頁", + + "basic_information_setting": "基本資料", + "account_setting": "帳號設定", + "language_setting": "語言設定", + "call_phone": "撥打電話", + + "name": "姓名", + "phone": "電話", + "gender": "性別", + "address": "地址", + "edit": "修改資料", + "save": "儲存變更", + + "email": "電子信箱", + + "fall_id": "編號: {displayIndex}", + "fall_detail": "跌倒時間: {time}\n跌倒原因: {reason},\n跌倒地點: {location}\n", + "fall_detail_title": "詳細資料", + "fall_detail_reason": "跌倒原因: {reason}", + "fall_detail_location": "跌倒地點: {location}", + "fall_detail_time": "跌倒時間: {time}", + "fall_record_empty": "尚無跌倒紀錄", + + "language_setting_alert_title": "語言設定", + "language_alert": "跳轉回首頁以完成語言設定,否則會有部分語言無法即時更新!", + "language_alert_android": "重新開啟APP以完成語言設定,否則會有部分語言無法即時更新!", + "cancel": "取消", + "confirm": "確認" + +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 42428cd..4c3fd67 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,8 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:topic/HomePage.dart'; import 'package:mysql_client/mysql_client.dart'; @@ -8,11 +11,23 @@ import 'package:topic/RegisterPage.dart'; import 'package:topic/ForgetPasswordPage.dart'; import 'package:validators/validators.dart' as validator; +import 'generated/l10n.dart'; + + void main() { - runApp(MaterialApp( - home: LoginPage(), - )); + runApp( + MaterialApp( + localizationsDelegates: [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], //add + supportedLocales: S.delegate.supportedLocales, // add + home: LoginPage(), + ) + ); } class LoginPage extends StatefulWidget { @@ -28,6 +43,7 @@ class _LoginPageState extends State { @override void initState() { //初始化 + _setLanguage(); //設定語言 super.initState(); _fetchData(); //連資料庫 _CheckPreLoginInfo(); //確定有先前有無登入 @@ -66,6 +82,14 @@ class _LoginPageState extends State { } } + void _setLanguage() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String preLanguage = prefs.getString("language") ?? ""; + Locale locale = preLanguage == "" ? Localizations.localeOf(context) : Locale(preLanguage); + S.load(locale); + setState(() {}); + } + void _setLoginInfo(String email) async { //save user email into share preference to let app can auto login next open SharedPreferences prefs = await SharedPreferences.getInstance(); @@ -176,7 +200,7 @@ class _LoginPageState extends State { border: OutlineInputBorder(), prefixIcon: Icon(Icons.email_outlined), //https://www.fluttericon.cn/v - labelText: '電子信箱/帳號', + labelText: S.of(context).email_label, ), ), //電子信箱 SizedBox(height: 20), @@ -197,7 +221,7 @@ class _LoginPageState extends State { ); }, ), - labelText: '密碼', + labelText: S.of(context).password_label, ), obscureText: _passwordNotVisible, ), //密碼 @@ -210,7 +234,7 @@ class _LoginPageState extends State { MaterialPageRoute(builder: (context) => HomePage()), ); },*/ - child: Text(' 登入'), + child: Text(S.of(context).login_button), style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF4FC3F7), padding: @@ -227,7 +251,7 @@ class _LoginPageState extends State { builder: (context) => RegisterPage()), ); }, - child: Text('立即註冊'), + child: Text(S.of(context).register_button), style: TextButton.styleFrom( // TODO: In physical devices, the background color of the button is transparent backgroundColor: Colors.transparent, // 無背景颜色 @@ -243,7 +267,7 @@ class _LoginPageState extends State { builder: (context) => ForgetPasswordPage()), ); }, - child: Text('忘記密碼'), + child: Text(S.of(context).forgot_password), style: TextButton.styleFrom( backgroundColor: Colors.transparent, // 無背景颜色 textStyle: TextStyle(fontSize: 18), diff --git a/pubspec.yaml b/pubspec.yaml index 70d3594..7bb7cf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,8 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter mysql_client: ^0.0.27 url_launcher: ^6.0.10 @@ -103,3 +105,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages +flutter_intl: + enabled: true