From 73ee5260a5c37688e1bc8f813f853a4727554b1f Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Mon, 11 May 2026 21:47:40 +0800 Subject: [PATCH 1/3] fix: unable read file on web MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [BUG] 网页版词库文件导入错误 Fixes #81 Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/pages/setting_page.dart | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc6a5e6..404a681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - 修复了可能的复习列表重复问题 - 补全控制器销毁逻辑,避免内存泄露 +- 修复了网页端中无法通过文件导入数据的问题 ## v1.0.0 - 2026-3-18 - (100000) diff --git a/lib/pages/setting_page.dart b/lib/pages/setting_page.dart index ce0b0b6..b15006f 100644 --- a/lib/pages/setting_page.dart +++ b/lib/pages/setting_page.dart @@ -229,15 +229,18 @@ class _SettingPage extends State { if (result != null) { String jsonString; PlatformFile platformFile = result.files.first; - if (platformFile.bytes != null){ - jsonString = utf8.decode(platformFile.bytes!); - } else if (platformFile.path != null && !kIsWeb) { - jsonString = await io.File(platformFile.path!).readAsString(); - } else { + try { + jsonString = await platformFile.xFile.readAsString(); + } catch(e) { if (!context.mounted) return; - context.read().uiLogger.warning("文件导入错误: bytes和path均为null"); - alart(context, "文件 \"${platformFile.name}\" \n无法读取:bytes和path均为null。"); - return; + if (platformFile.path != null && !kIsWeb) { + context.read().uiLogger.warning("文件导入错误: 常规方式读取失败:\n$e\n尝试路经读取"); + jsonString = await io.File(platformFile.path!).readAsString(); + } else { + context.read().uiLogger.severe("文件导入错误: $e"); + alart(context, "文件 \"${platformFile.name}\" \n无法读取:$e。"); + return; + } } if (!context.mounted) return; try{ From 7625da20842980199aa7a07a0672ea77af3dec3d Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Mon, 11 May 2026 22:08:26 +0800 Subject: [PATCH 2/3] perf: improve app config load Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- .../learning_pages/learning_pages_build.dart | 2 +- .../setting_pages/questions_setting_page.dart | 24 +-- lib/vars/config_structure.dart | 200 +++++------------- 3 files changed, 65 insertions(+), 161 deletions(-) diff --git a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart index f81d54c..7907eb0 100644 --- a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart +++ b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart @@ -56,7 +56,7 @@ class _InLearningPageState extends State { @override void initState() { // 加载测试词 - final SubQuizConfig questionsSetting = AppData().config.quiz.zhar; + final QuizConfig questionsSetting = AppData().config.quiz; List> questionsInSections = List.generate(questionsSetting.questionSections.length, (_) => []); for(int sectionIndex = 0; sectionIndex < questionsSetting.questionSections.length; sectionIndex++) { diff --git a/lib/sub_pages_builder/setting_pages/questions_setting_page.dart b/lib/sub_pages_builder/setting_pages/questions_setting_page.dart index 04bf25f..c2b40f0 100644 --- a/lib/sub_pages_builder/setting_pages/questions_setting_page.dart +++ b/lib/sub_pages_builder/setting_pages/questions_setting_page.dart @@ -20,9 +20,7 @@ class _QuestionsSettingPage extends State { void _updateConfig() { AppData().config = AppData().config.copyWith( quiz: AppData().config.quiz.copyWith( - zhar: AppData().config.quiz.zhar.copyWith( - questionSections: selectedTypes!.cast() - ) + questionSections: selectedTypes!.cast() ) ); } @@ -30,8 +28,8 @@ class _QuestionsSettingPage extends State { @override Widget build(BuildContext context) { context.read().uiLogger.info("构建 QuestionsSettingPage:$selectedTypes"); - late final SubQuizConfig section; - section = AppData().config.quiz.zhar; + late final QuizConfig section; + section = AppData().config.quiz; MediaQueryData mediaQuery = MediaQuery.of(context); selectedTypes ??= List.from(section.questionSections); List listTiles = []; @@ -106,9 +104,7 @@ class _QuestionsSettingPage extends State { setState(() { AppData().config = AppData().config.copyWith( quiz: AppData().config.quiz.copyWith( - zhar: AppData().config.quiz.zhar.copyWith( - shuffleInternaly: value - ) + shuffleInternaly: value ) ); }); @@ -126,9 +122,7 @@ class _QuestionsSettingPage extends State { setState(() { AppData().config = AppData().config.copyWith( quiz: AppData().config.quiz.copyWith( - zhar: AppData().config.quiz.zhar.copyWith( - shuffleExternaly: value - ) + shuffleExternaly: value ) ); }); @@ -146,9 +140,7 @@ class _QuestionsSettingPage extends State { setState(() { AppData().config = AppData().config.copyWith( quiz: AppData().config.quiz.copyWith( - zhar: AppData().config.quiz.zhar.copyWith( - shuffleGlobally: value - ) + shuffleGlobally: value ) ); }); @@ -166,9 +158,7 @@ class _QuestionsSettingPage extends State { setState(() { AppData().config = AppData().config.copyWith( quiz: AppData().config.quiz.copyWith( - zhar: AppData().config.quiz.zhar.copyWith( - preferSimilar: value - ) + preferSimilar: value ) ); }); diff --git a/lib/vars/config_structure.dart b/lib/vars/config_structure.dart index e4c56f6..9c86974 100644 --- a/lib/vars/config_structure.dart +++ b/lib/vars/config_structure.dart @@ -35,25 +35,16 @@ class Config { final EggConfig egg; const Config({ - String? user, - int? lastVersion, - DebugConfig? debug, - RegularConfig? regular, - AudioConfig? audio, - LearningConfig? learning, - QuizConfig? quiz, - SyncConfig? webSync, - EggConfig? egg - }) : // 空值合并 - user = user??"", - lastVersion = lastVersion??StaticsVar.appVersion, - debug = debug??const DebugConfig(), - regular = regular??const RegularConfig(), - audio = audio??const AudioConfig(), - learning = learning??const LearningConfig(), - quiz = quiz??const QuizConfig(), - webSync = webSync??const SyncConfig(), - egg = egg??const EggConfig(); + this.user = "", + this.lastVersion = StaticsVar.appVersion, + this.debug = const DebugConfig(), + this.regular = const RegularConfig(), + this.audio = const AudioConfig(), + this.learning = const LearningConfig(), + this.quiz = const QuizConfig(), + this.webSync = const SyncConfig(), + this.egg = const EggConfig() + }); /// 将设置转为Map格式 Map toMap() { @@ -138,11 +129,9 @@ class DebugConfig { final int internalLevel; const DebugConfig({ - bool? enableInternalLog, - int? internalLevel - }): - enableInternalLog = enableInternalLog??false, - internalLevel = internalLevel??0; + this.enableInternalLog = false, + this.internalLevel = 0 + }); Map toMap() { return { @@ -203,15 +192,11 @@ class RegularConfig { final bool hideAppDownloadButton; const RegularConfig({ - int? theme, - int? font, - bool? darkMode, - bool? hideAppDownloadButton - }): - theme = theme??9, - font = font??0, - darkMode = darkMode??false, - hideAppDownloadButton = hideAppDownloadButton??false; + this.theme = 9, + this.font = 0, + this.darkMode = false, + this.hideAppDownloadButton = false + }); Map toMap() { return { @@ -260,12 +245,14 @@ class AudioConfig { /// 播放速度 final double playRate; + /// 自动播放发音 + final bool autoPlay; + const AudioConfig({ - int? audioSource, - double? playRate - }): - audioSource = audioSource??0, - playRate = playRate??1.0; + this.audioSource = 0, + this.playRate = 1.0, + this.autoPlay = false + }); Map toMap(){ return { @@ -308,15 +295,11 @@ class LearningConfig { final bool wordLookupRealtime; const LearningConfig({ - int? startDate, - int? lastDate, - int? overviewForceColumn, - bool? wordLookupRealtime - }): - startDate = startDate??0, - lastDate = lastDate??0, - overviewForceColumn = overviewForceColumn??0, - wordLookupRealtime = wordLookupRealtime??true; + this.startDate = 0, + this.lastDate = 0, + this.overviewForceColumn = 0, + this.wordLookupRealtime = true + }); Map toMap(){ return { @@ -354,77 +337,6 @@ class LearningConfig { @immutable class QuizConfig { - /// 混合学习配置类 - final SubQuizConfig zhar; - - /// 阿译中专项配置类 - final SubQuizConfig ar; - - /// 中译阿专项配置类 - final SubQuizConfig zh; - - const QuizConfig({ - SubQuizConfig? zhar, - SubQuizConfig? ar, - SubQuizConfig? zh - }): - zhar = zhar??const SubQuizConfig( - questionSections: [1, 2], - shuffleGlobally: true, - shuffleInternaly: false, - shuffleExternaly: false, - modifyAllowed: true, - preferSimilar: false - ), - ar = ar??const SubQuizConfig( - questionSections: [2], - shuffleGlobally: true, - shuffleInternaly: false, - shuffleExternaly: false, - modifyAllowed: false, - preferSimilar: false - ), - zh = zh??const SubQuizConfig( - questionSections: [1], - shuffleGlobally: true, - shuffleInternaly: false, - shuffleExternaly: false, - modifyAllowed: false, - preferSimilar: false - ); - - Map toMap(){ - return { - "zh_ar": zhar.toMap(), - "ar": ar.toMap(), - "zh": zh.toMap() - }; - } - - static QuizConfig buildFromMap(Map? setting) { - if(setting == null) return QuizConfig(); - return QuizConfig( - zhar: SubQuizConfig.buildFromMap(setting["zh_ar"]), - ar: SubQuizConfig.buildFromMap(setting["ar"]), - zh: SubQuizConfig.buildFromMap(setting["zh"]) - ); - } - - QuizConfig copyWith({ - SubQuizConfig? zhar, - SubQuizConfig? ar, - SubQuizConfig? zh, - }) { - return QuizConfig( - zhar: zhar ?? this.zhar, - ar: ar ?? this.ar, - zh: zh ?? this.zh, - ); - } -} - -@immutable -class SubQuizConfig { /// 包含的题型 /// ``` /// 0: 单词卡片 @@ -460,13 +372,14 @@ class SubQuizConfig { /// 相比于同课程的单词,更偏向于相似的单词 final bool preferSimilar; - const SubQuizConfig ({ - required this.questionSections, - required this.shuffleGlobally, - required this.shuffleInternaly, - required this.shuffleExternaly, - required this.modifyAllowed, - required this.preferSimilar + + const QuizConfig ({ + this.questionSections = const [1, 2], + this.shuffleGlobally = true, + this.shuffleInternaly = false, + this.shuffleExternaly = false, + this.modifyAllowed = true, + this.preferSimilar = false }); Map toMap(){ @@ -480,9 +393,15 @@ class SubQuizConfig { }; } - static SubQuizConfig buildFromMap(Map? setting){ + static QuizConfig buildFromMap(Map? setting){ if(setting == null) throw Exception("no data for quiz load"); - return SubQuizConfig( + + // 旧版本兼容 + if(setting.containsKey("zh_ar")){ + return QuizConfig.buildFromMap(setting["zh_ar"]); + } + + return QuizConfig( questionSections: List.generate(setting["questionSections"].length, (index) => setting["questionSections"][index]), shuffleGlobally: setting["shuffleGlobally"], shuffleInternaly: setting["shuffleInternaly"], @@ -492,7 +411,7 @@ class SubQuizConfig { ); } - SubQuizConfig copyWith({ + QuizConfig copyWith({ List? questionSections, bool? shuffleGlobally, bool? shuffleInternaly, @@ -500,7 +419,7 @@ class SubQuizConfig { bool? modifyAllowed, bool? preferSimilar }) { - return SubQuizConfig( + return QuizConfig( questionSections: questionSections ?? this.questionSections, shuffleGlobally: shuffleGlobally ?? this.shuffleGlobally, shuffleInternaly: shuffleInternaly ?? this.shuffleInternaly, @@ -516,8 +435,8 @@ class EggConfig { final bool stella; const EggConfig({ - bool? stella - }):stella = stella??false; + this.stella = false + }); Map toMap(){ return { @@ -550,11 +469,9 @@ class SyncConfig { final SyncAccountConfig account; const SyncConfig({ - bool? enabled, - SyncAccountConfig? account - }): - enabled = enabled??false, - account = account??const SyncAccountConfig(); + this.enabled = false, + this.account = const SyncAccountConfig() + }); Map toMap(){ return { @@ -594,13 +511,10 @@ class SyncAccountConfig { final String passWord; const SyncAccountConfig({ - String? uri, - String? userName, - String? passWord - }): - uri = uri??"", - userName = userName??"", - passWord = passWord??""; + this.uri = "", + this.userName = "", + this.passWord = "" + }); Map toMap(){ return { From fc4c6ee362f0c7d07ae60afe82a11208d2dd9de1 Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Mon, 11 May 2026 22:48:08 +0800 Subject: [PATCH 3/3] feat: auto play audio on learning page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit only for ar to zh [feat] 自动发音 Fixes #82 Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/pages/setting_page.dart | 1091 ++++++++++------- .../learning_pages/learning_pages_build.dart | 11 +- lib/vars/config_structure.dart | 6 +- 4 files changed, 657 insertions(+), 452 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 404a681..b143771 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - 复习模块添加了强化学习选项 +- 添加了学习中单词自动播放发音的功能 ### Improvement diff --git a/lib/pages/setting_page.dart b/lib/pages/setting_page.dart index b15006f..4162968 100644 --- a/lib/pages/setting_page.dart +++ b/lib/pages/setting_page.dart @@ -9,17 +9,27 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:arabic_learning/funcs/ui.dart'; import 'package:arabic_learning/vars/global.dart'; -import 'package:arabic_learning/sub_pages_builder/setting_pages/help_page.dart' show HelpPage; -import 'package:arabic_learning/sub_pages_builder/setting_pages/item_widget.dart'show SettingItem; -import 'package:arabic_learning/sub_pages_builder/setting_pages/debug_page.dart' show DebugPage; -import 'package:arabic_learning/sub_pages_builder/setting_pages/about_page.dart' show AboutPage; -import 'package:arabic_learning/sub_pages_builder/setting_pages/data_download_page.dart' show DownloadPage; -import 'package:arabic_learning/sub_pages_builder/setting_pages/model_download_page.dart' show ModelDownload; -import 'package:arabic_learning/sub_pages_builder/setting_pages/questions_setting_page.dart' show QuestionsSettingPage; -import 'package:arabic_learning/sub_pages_builder/setting_pages/sync_page.dart' show DataSyncPage; -import 'package:arabic_learning/package_replacement/fake_dart_io.dart' if (dart.library.io) 'dart:io' as io; +import 'package:arabic_learning/sub_pages_builder/setting_pages/help_page.dart' + show HelpPage; +import 'package:arabic_learning/sub_pages_builder/setting_pages/item_widget.dart' + show SettingItem; +import 'package:arabic_learning/sub_pages_builder/setting_pages/debug_page.dart' + show DebugPage; +import 'package:arabic_learning/sub_pages_builder/setting_pages/about_page.dart' + show AboutPage; +import 'package:arabic_learning/sub_pages_builder/setting_pages/data_download_page.dart' + show DownloadPage; +import 'package:arabic_learning/sub_pages_builder/setting_pages/model_download_page.dart' + show ModelDownload; +import 'package:arabic_learning/sub_pages_builder/setting_pages/questions_setting_page.dart' + show QuestionsSettingPage; +import 'package:arabic_learning/sub_pages_builder/setting_pages/sync_page.dart' + show DataSyncPage; +import 'package:arabic_learning/package_replacement/fake_dart_io.dart' + if (dart.library.io) 'dart:io' + as io; -class SettingPage extends StatefulWidget { +class SettingPage extends StatefulWidget { const SettingPage({super.key}); @override State createState() => _SettingPage(); @@ -29,468 +39,649 @@ class _SettingPage extends State { @override Widget build(BuildContext context) { context.read().uiLogger.fine("构建 SettingPage"); + MediaQueryData mediaQuery = MediaQuery.of(context); + AppData appData = AppData(); + return Consumer( builder: (context, value, child) { return ListView( children: [ SettingItem( - title: "帮助", - children: helpEssay(context) + title: "帮助", + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withAlpha(150), + shape: RoundedRectangleBorder(borderRadius: StaticsVar.br), + ), + onPressed: () { + context.read().uiLogger.info( + "跳转: SettingPage => HelpPage", + ); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => HelpPage()), + ); + }, + child: Row( + children: [ + Icon(Icons.help, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("常见问题", textAlign: TextAlign.start)), + Icon(Icons.arrow_forward_ios), + ], + ), + ), + ], ), SettingItem( title: "常规设置", padding: EdgeInsets.all(8.0), - children: regularSetting(context), - ), - SettingItem( - title: "学习设置", - children: dataSetting(context), + children: [ + Row( + children: [ + Icon(Icons.color_lens, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("主题颜色")), + DropdownButton( + value: appData.config.regular.theme, + items: const [ + DropdownMenuItem(value: 0, child: Text('樱粉')), + DropdownMenuItem(value: 1, child: Text('海蓝')), + DropdownMenuItem(value: 2, child: Text('草绿')), + DropdownMenuItem(value: 3, child: Text('金黄')), + DropdownMenuItem(value: 4, child: Text('柑橘')), + DropdownMenuItem(value: 5, child: Text('雅紫')), + DropdownMenuItem(value: 6, child: Text('木棕')), + DropdownMenuItem(value: 7, child: Text('冷灰')), + DropdownMenuItem(value: 8, child: Text('茶香')), + DropdownMenuItem(value: 9, child: Text('烟蓝')), + DropdownMenuItem(value: 10, child: Text('星青')), + ], + onChanged: (value) async { + context.read().uiLogger.info("更新主题颜色: $value"); + AppData().config = AppData().config.copyWith( + regular: AppData().config.regular.copyWith( + theme: value, + ), + ); + context.read().updateSetting(); + }, + ), + ], + ), + Row( + children: [ + Icon(Icons.brightness_4, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("深色模式")), + Switch( + value: appData.config.regular.darkMode, + onChanged: (value) { + context.read().uiLogger.info( + "更新深色模式设置: $value", + ); + AppData().config = AppData().config.copyWith( + regular: AppData().config.regular.copyWith( + darkMode: value, + ), + ); + context.read().updateSetting(); + }, + ), + ], + ), + Row( + children: [ + Icon(Icons.font_download, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("字体设置")), + DropdownButton( + value: appData.config.regular.font, + items: [ + DropdownMenuItem(value: 0, child: Text('默认字体')), + DropdownMenuItem(value: 1, child: Text('仅阿语使用备用字体')), + DropdownMenuItem(value: 2, child: Text('中阿均使用备用字体')), + ], + onChanged: (value) { + context.read().uiLogger.info("更新字体设置: $value"); + if (value == 2 && kIsWeb) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("网页版加载中文字体需要较长时间,请先耐心等待"), + duration: Duration(seconds: 3), + ), + ); + } + AppData().config = AppData().config.copyWith( + regular: AppData().config.regular.copyWith( + font: value, + ), + ); + Provider.of( + context, + listen: false, + ).updateSetting(); + }, + ), + ], + ), + if (kIsWeb) + Row( + children: [ + Icon(Icons.hide_source, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("隐藏网页版右上角APP下载按钮")), + Switch( + value: AppData().config.regular.hideAppDownloadButton, + onChanged: (value) { + context.read().uiLogger.info( + "更新网页端APP下载按钮隐藏设置: $value", + ); + AppData().config = AppData().config.copyWith( + regular: AppData().config.regular.copyWith( + hideAppDownloadButton: value, + ), + ); + context.read().updateSetting(); + }, + ), + ], + ), + ], ), SettingItem( - title: "音频设置", - children: audioSetting(context), + title: "学习设置", + children: [ + Column( + children: [ + Row( + children: [ + SizedBox(width: mediaQuery.size.width * 0.02), + Icon(Icons.download, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("导入词库数据")), + Text("词库中现有: ${AppData().wordCount}"), + SizedBox(width: mediaQuery.size.width * 0.02), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + fixedSize: Size( + mediaQuery.size.width * 0.4, + mediaQuery.size.height * 0.06, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.horizontal( + left: Radius.circular(25.0), + ), + ), + ), + onPressed: () { + context.read().uiLogger.info( + "跳转: SettingPage => DownloadPage", + ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DownloadPage(), + ), + ); + }, + icon: Icon(Icons.cloud_download), + label: Text("线上下载"), + ), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + fixedSize: Size( + mediaQuery.size.width * 0.4, + mediaQuery.size.height * 0.06, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.horizontal( + right: Radius.circular(25.0), + ), + ), + ), + onPressed: () async { + context.read().uiLogger.info("选择手动导入单词"); + FilePickerResult? result = + await FilePicker.pickFiles( + allowMultiple: false, + type: FileType.custom, + allowedExtensions: ['json'], + ); + if (result != null) { + String jsonString; + PlatformFile platformFile = result.files.first; + try { + jsonString = await platformFile.xFile + .readAsString(); + } catch (e) { + if (!context.mounted) return; + if (platformFile.path != null && !kIsWeb) { + context.read().uiLogger.warning( + "文件导入错误: 常规方式读取失败:\n$e\n尝试路经读取", + ); + jsonString = await io.File( + platformFile.path!, + ).readAsString(); + } else { + context.read().uiLogger.severe( + "文件导入错误: $e", + ); + alart( + context, + "文件 \"${platformFile.name}\" \n无法读取:$e。", + ); + return; + } + } + if (!context.mounted) return; + try { + context.read().uiLogger.fine( + "文件读取完成,开始解析", + ); + Map jsonData = json.decode( + jsonString, + ); + AppData().importDictData( + jsonData, + platformFile.name, + ); + alart( + context, + "文件 \"${platformFile.name}\" \n已导入。", + ); + context.read().uiLogger.info("文件解析成功"); + } catch (e) { + if (!context.mounted) return; + context.read().uiLogger.severe( + "文件 ${platformFile.name} 无效: $e", + ); + alart( + context, + '文件 ${platformFile.name} 无效:\n$e', + ); + } + } + }, + icon: Icon(Icons.file_open), + label: Text("文件导入"), + ), + ], + ), + ], + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withAlpha(150), + shape: BeveledRectangleBorder(), + ), + onPressed: () { + context.read().uiLogger.info( + "跳转: SettingPage => QuestionsSettingPage", + ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => QuestionsSettingPage(), + ), + ); + }, + child: Row( + children: [ + Icon(Icons.quiz), + Expanded(child: Text("题型配置")), + Icon(Icons.arrow_forward_ios), + ], + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withAlpha(150), + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusGeometry.vertical( + bottom: Radius.circular(25.0), + ), + ), + ), + onPressed: () { + context.read().uiLogger.info( + "跳转: SettingPage => DataSyncPage", + ); + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => DataSyncPage()), + ); + }, + child: Row( + children: [ + Icon(Icons.sync), + Expanded(child: Text("数据备份及同步")), + Icon(Icons.arrow_forward_ios), + ], + ), + ), + ], ), SettingItem( - title: "关于", - children: aboutSetting(context), - ), - ], - ); - }, - ); - } - - List helpEssay(BuildContext context) { - MediaQueryData mediaQuery = MediaQuery.of(context); - return [ - ElevatedButton( - style: ElevatedButton.styleFrom( - minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - shape: RoundedRectangleBorder(borderRadius: StaticsVar.br) - ), - onPressed: (){ - context.read().uiLogger.info("跳转: SettingPage => HelpPage"); - Navigator.push(context, MaterialPageRoute(builder: (context) => HelpPage())); - }, - child: Row( - children: [ - Icon(Icons.help, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded(child: Text("常见问题", textAlign: TextAlign.start)), - Icon(Icons.arrow_forward_ios) - ] - ), - ) - ]; - } - - List regularSetting(BuildContext context) { - MediaQueryData mediaQuery = MediaQuery.of(context); - AppData appData = AppData(); - - return [ - Row( - children: [ - Icon(Icons.color_lens, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded(child: Text("主题颜色:")), - DropdownButton( - value: appData.config.regular.theme, - items: const [ - DropdownMenuItem(value: 0, child: Text('樱粉')), - DropdownMenuItem(value: 1, child: Text('海蓝')), - DropdownMenuItem(value: 2, child: Text('草绿')), - DropdownMenuItem(value: 3, child: Text('金黄')), - DropdownMenuItem(value: 4, child: Text('柑橘')), - DropdownMenuItem(value: 5, child: Text('雅紫')), - DropdownMenuItem(value: 6, child: Text('木棕')), - DropdownMenuItem(value: 7, child: Text('冷灰')), - DropdownMenuItem(value: 8, child: Text('茶香')), - DropdownMenuItem(value: 9, child: Text('烟蓝')), - DropdownMenuItem(value: 10, child: Text('星青')) - ], - onChanged: (value) async { - context.read().uiLogger.info("更新主题颜色: $value"); - AppData().config = AppData().config.copyWith( - regular: AppData().config.regular.copyWith(theme: value) - ); - context.read().updateSetting(); - }, - ), - ], - ), - Row( - children: [ - Icon(Icons.brightness_4, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded(child: Text("深色模式:")), - Switch( - value: appData.config.regular.darkMode, - onChanged: (value) { - context.read().uiLogger.info("更新深色模式设置: $value"); - AppData().config = AppData().config.copyWith( - regular: AppData().config.regular.copyWith(darkMode: value) - ); - context.read().updateSetting(); - }, - ) - ], - ), - Row( - children: [ - Icon(Icons.font_download, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded(child: Text("字体设置:")), - DropdownButton( - value: appData.config.regular.font, - items: [ - DropdownMenuItem(value: 0, child: Text('默认字体')), - DropdownMenuItem(value: 1, child: Text('仅阿语使用备用字体')), - DropdownMenuItem(value: 2, child: Text('中阿均使用备用字体')), - ], - onChanged: (value) { - context.read().uiLogger.info("更新字体设置: $value"); - if(value == 2 && kIsWeb) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("网页版加载中文字体需要较长时间,请先耐心等待"), duration: Duration(seconds: 3),), - ); - } - AppData().config = AppData().config.copyWith( - regular: AppData().config.regular.copyWith(font: value) - ); - Provider.of(context, listen: false).updateSetting(); - }, - ) - ] - ), - if(kIsWeb) Row( - children: [ - Icon(Icons.hide_source, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded(child: Text("隐藏网页版右上角APP下载按钮")), - Switch( - value: AppData().config.regular.hideAppDownloadButton, - onChanged: (value) { - context.read().uiLogger.info("更新网页端APP下载按钮隐藏设置: $value"); - AppData().config = AppData().config.copyWith( - regular: AppData().config.regular.copyWith(hideAppDownloadButton: value) - ); - context.read().updateSetting(); - }, - ) - ], - ), - ]; - } - - List dataSetting(BuildContext context) { - MediaQueryData mediaQuery = MediaQuery.of(context); - return [ - Column( - children: [ - Row( - children: [ - SizedBox(width: mediaQuery.size.width * 0.02), - Icon(Icons.download, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded(child: Text("导入词库数据")), - Text("词库中现有: ${AppData().wordCount}"), - SizedBox(width: mediaQuery.size.width * 0.02), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton.icon( - style: ElevatedButton.styleFrom( - fixedSize: Size(mediaQuery.size.width * 0.4, mediaQuery.size.height * 0.06), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(25.0))) + title: "音频设置", + children: [ + Column( + children: [ + Row( + children: [ + SizedBox(width: mediaQuery.size.width * 0.02), + Icon(Icons.api, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentGeometry.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("选择文本转语音接口"), + Text( + "默认使用系统自带的文本转语音接口,但有些厂商可能没有阿拉伯语支持\n若使用\"神经网络合成语音\"你必须使用APP端并下载模型。", + style: TextStyle( + fontSize: 8.0, + color: Colors.grey, + ), + ), + ], + ), + ), + ), + ], + ), + DropdownButton( + value: AppData().config.audio.audioSource, + onChanged: (value) { + context.read().uiLogger.info("更新音频接口: $value"); + if (value == 1) { + alart( + context, + "警告: \n来自\"TextReadTTS.com\"的音频不支持发音符号,且只能合成40字以内的文本。\n开启此功能请知悉。", + ); + } + AppData().config = AppData().config.copyWith( + audio: AppData().config.audio.copyWith( + audioSource: value, + ), + ); + context.read().updateSetting(); + }, + items: [ + DropdownMenuItem( + value: 0, + child: Text( + "系统文本转语音", + overflow: TextOverflow.ellipsis, + ), + ), + DropdownMenuItem( + value: 1, + child: Text( + "请求TextReadTTS.com的语音", + overflow: TextOverflow.ellipsis, + ), + ), + DropdownMenuItem( + value: 2, + enabled: kIsWeb + ? false + : (AppData().modelTTSDownloaded ? true : false), + child: Text( + "神经网络合成语音", + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: kIsWeb + ? Colors.grey + : (AppData().modelTTSDownloaded + ? null + : Colors.grey), + ), + ), + ), + ], + isExpanded: true, + ), + SizedBox(width: mediaQuery.size.width * 0.02), + ], ), - onPressed: () { - context.read().uiLogger.info("跳转: SettingPage => DownloadPage"); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => DownloadPage())); - }, - icon: Icon(Icons.cloud_download), - label: Text("线上下载") - ), - ElevatedButton.icon( - style: ElevatedButton.styleFrom( - fixedSize: Size(mediaQuery.size.width * 0.4, mediaQuery.size.height * 0.06), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(right: Radius.circular(25.0))) + Row( + children: [ + SizedBox(width: mediaQuery.size.width * 0.02), + Icon(Icons.speed, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("设置播放速度"), + Text( + "默认为1.0,即正常播放速度。", + style: TextStyle(fontSize: 8.0, color: Colors.grey), + ), + ], + ), + ), + Slider( + value: AppData().config.audio.playRate, + min: 0.5, + max: 1.5, + divisions: 10, + label: "${AppData().config.audio.playRate}", + onChanged: (value) { + setState(() { + AppData().config = AppData().config.copyWith( + audio: AppData().config.audio.copyWith( + playRate: value, + ), + ); + }); + }, + onChangeEnd: (value) { + context.read().uiLogger.info( + "更新音频速度设置: $value", + ); + context.read().updateSetting(); + }, + ), + SizedBox(width: mediaQuery.size.width * 0.02), + ], ), - onPressed: () async { - context.read().uiLogger.info("选择手动导入单词"); - FilePickerResult? result = await FilePicker.pickFiles( - allowMultiple: false, - type: FileType.custom, - allowedExtensions: ['json'], - ); - if (result != null) { - String jsonString; - PlatformFile platformFile = result.files.first; - try { - jsonString = await platformFile.xFile.readAsString(); - } catch(e) { - if (!context.mounted) return; - if (platformFile.path != null && !kIsWeb) { - context.read().uiLogger.warning("文件导入错误: 常规方式读取失败:\n$e\n尝试路经读取"); - jsonString = await io.File(platformFile.path!).readAsString(); - } else { - context.read().uiLogger.severe("文件导入错误: $e"); - alart(context, "文件 \"${platformFile.name}\" \n无法读取:$e。"); - return; - } - } - if (!context.mounted) return; - try{ - context.read().uiLogger.fine("文件读取完成,开始解析"); - Map jsonData = json.decode(jsonString); - AppData().importDictData(jsonData, platformFile.name); - alart(context, "文件 \"${platformFile.name}\" \n已导入。"); - context.read().uiLogger.info("文件解析成功"); - } catch (e) { - if (!context.mounted) return; - context.read().uiLogger.severe("文件 ${platformFile.name} 无效: $e"); - alart(context, '文件 ${platformFile.name} 无效:\n$e'); + Row( + children: [ + SizedBox(width: mediaQuery.size.width * 0.02), + Icon(Icons.play_arrow, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("自动播放发音"), + Text( + "开启后将会在<学习>模块中,每进入阿译中选择题时阅读当前单词发音。", + style: TextStyle(fontSize: 8.0, color: Colors.grey), + ), + ], + ), + ), + Switch( + value: appData.config.audio.autoPlay, + onChanged: (value) { + context.read().uiLogger.info( + "更新自动发音设置: $value", + ); + AppData().config = AppData().config.copyWith( + audio: AppData().config.audio.copyWith( + autoPlay: value + ), + ); + context.read().updateSetting(); + }, + ), + ], + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withAlpha(150), + minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusGeometry.vertical( + bottom: Radius.circular(25.0), + ), + ), + ), + onPressed: () { + if (kIsWeb) { + alart(context, "此功能仅支持APP端。\n要是你是果儿的话...那我没招了,抱歉"); + } else { + context.read().uiLogger.info( + "跳转: SettingPage => ModelDownload", + ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ModelDownload(), + ), + ); } - } - }, - icon: Icon(Icons.file_open), - label: Text("文件导入")) - ], - ), - ], - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - shape: BeveledRectangleBorder() - ), - onPressed: (){ - context.read().uiLogger.info("跳转: SettingPage => QuestionsSettingPage"); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => QuestionsSettingPage())); - }, - child: Row( - children: [ - Icon(Icons.quiz), - Expanded(child: Text("题型配置")), - Icon(Icons.arrow_forward_ios) - ], - ) - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(bottom: Radius.circular(25.0))) - ), - onPressed: (){ - context.read().uiLogger.info("跳转: SettingPage => DataSyncPage"); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => DataSyncPage())); - }, - child: Row( - children: [ - Icon(Icons.sync), - Expanded(child: Text("数据备份及同步")), - Icon(Icons.arrow_forward_ios) - ], - ) - ), - ]; - } - - List audioSetting(BuildContext context) { - MediaQueryData mediaQuery = MediaQuery.of(context); - return [ - Column( - children: [ - Row( - children: [ - SizedBox(width: mediaQuery.size.width * 0.02), - Icon(Icons.api, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentGeometry.centerLeft, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + }, + child: Row( children: [ - Text("选择文本转语音接口"), - Text("默认使用系统自带的文本转语音接口,但有些厂商可能没有阿拉伯语支持\n若使用\"神经网络合成语音\"你必须使用APP端并下载模型。", style: TextStyle(fontSize: 8.0, color: Colors.grey)), + Icon(Icons.model_training, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("下载神经网络文本转语音模型")), + Icon(Icons.arrow_forward_ios, size: 28.0), ], ), ), - ), - ], - ), - DropdownButton( - value: AppData().config.audio.audioSource, - onChanged: (value) { - context.read().uiLogger.info("更新音频接口: $value"); - if(value == 1) alart(context, "警告: \n来自\"TextReadTTS.com\"的音频不支持发音符号,且只能合成40字以内的文本。\n开启此功能请知悉。"); - AppData().config = AppData().config.copyWith( - audio: AppData().config.audio.copyWith(audioSource: value) - ); - context.read().updateSetting(); - }, - items: [ - DropdownMenuItem(value: 0, child: Text("系统文本转语音", overflow: TextOverflow.ellipsis,)), - DropdownMenuItem(value: 1, child: Text("请求TextReadTTS.com的语音", overflow: TextOverflow.ellipsis)), - DropdownMenuItem(value: 2, - enabled: kIsWeb ? false : (AppData().modelTTSDownloaded ? true : false), - child: Text("神经网络合成语音", - overflow: TextOverflow.ellipsis, - style: TextStyle(color: kIsWeb ? Colors.grey : (AppData().modelTTSDownloaded ? null : Colors.grey)) - ), - ) - ], - isExpanded: true, - ), - SizedBox(width: mediaQuery.size.width * 0.02), - ], - ), - Row( - children: [ - SizedBox(width: mediaQuery.size.width * 0.02), - Icon(Icons.speed, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("设置播放速度"), - Text("默认为1.0,即正常播放速度。", style: TextStyle(fontSize: 8.0, color: Colors.grey)) - ] - ) - ), - Slider( - value: AppData().config.audio.playRate, - min: 0.5, - max: 1.5, - divisions: 10, - label: "${AppData().config.audio.playRate}", - onChanged: (value) { - setState(() { - AppData().config = AppData().config.copyWith( - audio: AppData().config.audio.copyWith(playRate: value) - ); - }); - }, - onChangeEnd: (value) { - context.read().uiLogger.info("更新音频速度设置: $value"); - context.read().updateSetting(); - }, - ), - SizedBox(width: mediaQuery.size.width * 0.02), - ] - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), - shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(bottom: Radius.circular(25.0))), - ), - onPressed: () { - if(kIsWeb) { - alart(context, "此功能仅支持APP端。\n要是你是果儿的话...那我没招了,抱歉"); - } else { - context.read().uiLogger.info("跳转: SettingPage => ModelDownload"); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => ModelDownload())); - } - }, - child: Row( - children: [ - Icon(Icons.model_training, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded( - child: Text("下载神经网络文本转语音模型") + ], ), - Icon(Icons.arrow_forward_ios, size: 28.0) - ], - ), - ) - ]; - } - - List aboutSetting(BuildContext context) { - MediaQueryData mediaQuery = MediaQuery.of(context); - return [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), - shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(top: Radius.circular(25.0))), - ), - onPressed: (){ - context.read().uiLogger.info("跳转: SettingPage => DebugPage"); - Navigator.push(context, MaterialPageRoute(builder: (context)=>DebugPage())); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon(Icons.bug_report, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded( - child: Text("调试信息") - ), - Icon(Icons.arrow_forward_ios) - ], - ) - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), - shape: BeveledRectangleBorder(), - ), - onPressed: () { - context.read().uiLogger.info("打开Github项目网站"); - launchUrl(Uri.parse("https://github.com/OctagonalStar/arabic_learning/")); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon(Icons.star_rounded, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("项目地址"), - Text("去github上点个star~", style: TextStyle(fontSize: 8.0, color: Colors.grey)) - ], - ), + SettingItem( + title: "关于", + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withAlpha(150), + minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusGeometry.vertical( + top: Radius.circular(25.0), + ), + ), + ), + onPressed: () { + context.read().uiLogger.info( + "跳转: SettingPage => DebugPage", + ); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => DebugPage()), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.bug_report, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("调试信息")), + Icon(Icons.arrow_forward_ios), + ], + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withAlpha(150), + minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), + shape: BeveledRectangleBorder(), + ), + onPressed: () { + context.read().uiLogger.info("打开Github项目网站"); + launchUrl( + Uri.parse( + "https://github.com/OctagonalStar/arabic_learning/", + ), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.star_rounded, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("项目地址"), + Text( + "去github上点个star~", + style: TextStyle( + fontSize: 8.0, + color: Colors.grey, + ), + ), + ], + ), + ), + Icon(Icons.open_in_new), + ], + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of( + context, + ).colorScheme.onPrimary.withAlpha(150), + minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusGeometry.vertical( + bottom: Radius.circular(25.0), + ), + ), + ), + onPressed: () { + context.read().uiLogger.info( + "跳转: SettingPage => AboutPage", + ); + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => AboutPage()), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.adb, size: 24.0), + SizedBox(width: mediaQuery.size.width * 0.01), + Expanded(child: Text("关于该软件")), + ], + ), + ), + ], ), - Icon(Icons.open_in_new), ], - ), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08), - shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(bottom: Radius.circular(25.0))), - ), - onPressed: () { - context.read().uiLogger.info("跳转: SettingPage => AboutPage"); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => AboutPage())); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon(Icons.adb, size: 24.0), - SizedBox(width: mediaQuery.size.width * 0.01), - Expanded( - child: Text("关于该软件"), - ) - ], - ), - ), - ]; + ); + }, + ); } } diff --git a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart index 7907eb0..9929eca 100644 --- a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart +++ b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'package:arabic_learning/funcs/fsrs_func.dart' show FSRS; -import 'package:arabic_learning/funcs/utili.dart' show BKSearch, StringExtensions, getLevenshtein, getRandomWords; +import 'package:arabic_learning/funcs/utili.dart' show BKSearch, StringExtensions, getLevenshtein, getRandomWords, playTextToSpeech; import 'package:arabic_learning/vars/config_structure.dart'; import 'package:flutter/material.dart'; import 'package:fsrs/fsrs.dart' show Rating; @@ -37,6 +37,9 @@ class _InLearningPageState extends State { late final DateTime startTime; bool finished = false; final PageController controller = PageController(initialPage: 0); + final bool isAutoPlay = AppData().config.audio.autoPlay; + final List playedList = []; + void onSolve({required WordItem targetWord, required bool isCorrect, @@ -209,6 +212,12 @@ class _InLearningPageState extends State { ); } else if(testItem.testType == 1 || testItem.testType == 2) { // ar-zh choose questions + if(isAutoPlay && testItem.testType == 2 && !playedList.contains(testItem)) { + context.read().uiLogger.fine("自动播放单词发音: [${testItem.testWord.arabic}]"); + playTextToSpeech(testItem.testWord.arabic); + playedList.add(testItem); + } + return ChoiceQuestions( mainWord: testItem.testType == 1 ? testItem.testWord.chinese : testItem.testWord.arabic, choices: testItem.options!, diff --git a/lib/vars/config_structure.dart b/lib/vars/config_structure.dart index 9c86974..f02d4fa 100644 --- a/lib/vars/config_structure.dart +++ b/lib/vars/config_structure.dart @@ -258,6 +258,7 @@ class AudioConfig { return { "useBackupSource": audioSource, "playRate": playRate, + "autoPlay": autoPlay }; } @@ -265,17 +266,20 @@ class AudioConfig { if(setting == null) return AudioConfig(); return AudioConfig( audioSource: setting["useBackupSource"], - playRate: setting["playRate"] + playRate: setting["playRate"], + autoPlay: setting["autoPlay"]??false ); } AudioConfig copyWith({ int? audioSource, double? playRate, + bool? autoPlay }) { return AudioConfig( audioSource: audioSource ?? this.audioSource, playRate: playRate ?? this.playRate, + autoPlay: autoPlay ?? this.autoPlay ); } }