From ced76faae791ac1d56882c972fbf7b85cf6de8eb Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:15:44 +0800 Subject: [PATCH 1/7] feat: new learning page layout Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- lib/pages/learning_page.dart | 210 +++++++++++++++-------------------- 1 file changed, 87 insertions(+), 123 deletions(-) diff --git a/lib/pages/learning_page.dart b/lib/pages/learning_page.dart index d00a55b..58b5de1 100644 --- a/lib/pages/learning_page.dart +++ b/lib/pages/learning_page.dart @@ -1,3 +1,4 @@ +import 'package:arabic_learning/sub_pages_builder/setting_pages/questions_setting_page.dart' show QuestionsSettingPage; import 'package:arabic_learning/vars/config_structure.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -15,141 +16,104 @@ class LearningPage extends StatelessWidget { Widget build(BuildContext context) { context.read().uiLogger.fine("构建 LearningPage"); final mediaQuery = MediaQuery.of(context); - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - shadowColor: Colors.transparent, - fixedSize: Size(mediaQuery.size.width * 0.5, mediaQuery.size.height * 0.2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadiusGeometry.vertical(bottom: Radius.circular(25.0)), + return Column( + children: [ + SizedBox(height: mediaQuery.size.height * 0.05), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), + fixedSize: Size(mediaQuery.size.width * 0.4, mediaQuery.size.height * 0.2), + shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(top: Radius.circular(25.0))), ), - ), - onPressed: () { - shiftToStudy(context, 0); - }, - child: FittedBox( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.task_alt, size: 24.0), - Text( - '综合学习', - style: TextStyle(fontSize: 40.0, fontWeight: FontWeight.bold), - ), - Text("你可以在设置-题型配置页面自行配置题目~", style: TextStyle(color: Colors.grey, fontSize: 8)) - ], - ), - ), - ), - Column( - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer.withAlpha(150), - foregroundColor: Theme.of(context).colorScheme.onSurface.withAlpha(150), - shadowColor: Colors.transparent, - fixedSize: Size(mediaQuery.size.width * 0.35, mediaQuery.size.height * 0.0975), - shape: BeveledRectangleBorder(), + onPressed: () { + shiftToStudy(context); + }, + child: FittedBox( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.task_alt), + Text('学习',style: TextStyle(fontSize: 40.0, fontWeight: FontWeight.bold)), + ], ), - onPressed: () { - shiftToStudy(context, 2); - }, - child: FittedBox(fit: BoxFit.fitWidth ,child: Text("阿译中专项", style: TextStyle(fontSize: 32.0))), ), - SizedBox(height: mediaQuery.size.height * 0.005), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer.withAlpha(150), - foregroundColor: Theme.of(context).colorScheme.onSurface.withAlpha(150), - shadowColor: Colors.transparent, - fixedSize: Size(mediaQuery.size.width * 0.35, mediaQuery.size.height * 0.0975), - shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(bottom: Radius.circular(25.0))), - ), - onPressed: () { - shiftToStudy(context, 1); - }, - child: FittedBox(fit: BoxFit.fitWidth ,child: Text("中译阿专项", style: TextStyle(fontSize: 32.0))), - ), - ], - ), - ], - ), - SizedBox(height: mediaQuery.size.height * 0.05), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - shadowColor: Colors.transparent, - fixedSize: Size(mediaQuery.size.width * 0.4, mediaQuery.size.height * 0.2), - shape: RoundedRectangleBorder( - borderRadius: StaticsVar.br, + ), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.onSecondary.withAlpha(150), + fixedSize: Size(mediaQuery.size.width * 0.4, mediaQuery.size.height * 0.1), + shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(bottom: Radius.circular(25.0))) ), + onPressed: (){ + context.read().uiLogger.info("跳转: SettingPage => QuestionsSettingPage"); + Navigator.of(context).push(MaterialPageRoute(builder: (context) => QuestionsSettingPage())); + }, + icon: Icon(Icons.quiz), + label: Text("配置题型"), + ) + ], + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), + fixedSize: Size(mediaQuery.size.width * 0.4, mediaQuery.size.height * 0.3), + shape: RoundedRectangleBorder( + borderRadius: StaticsVar.br, ), - onPressed: (){ - context.read().uiLogger.info("跳转: LearningPage => ForeFSRSSettingPage"); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ForeFSRSSettingPage() - ) - ); - }, - child: FittedBox( - fit: BoxFit.contain, - child: Column( - children: [ - Icon(Icons.history_edu, size: 24.0), - Text("规律性学习", style: TextStyle(fontSize: 32.0)), - ], + ), + onPressed: (){ + context.read().uiLogger.info("跳转: LearningPage => ForeFSRSSettingPage"); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ForeFSRSSettingPage() ) - ), + ); + }, + child: FittedBox( + fit: BoxFit.contain, + child: Column( + children: [ + Icon(Icons.history_edu), + Text("复习",style: TextStyle(fontSize: 40.0, fontWeight: FontWeight.bold)), + ], + ) ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), - shadowColor: Colors.transparent, - fixedSize: Size(mediaQuery.size.width * 0.42, mediaQuery.size.height * 0.2), - shape: RoundedRectangleBorder(borderRadius: StaticsVar.br), - ), - onPressed: (){ - context.read().uiLogger.info("跳转: LearningPage => WordCardOverViewPage"); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => WordCardOverViewPage() - ) - ); - }, - child: FittedBox( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.abc, size: 24), - Text("词汇总览", style: TextStyle(fontSize: 40.0, fontWeight: FontWeight.bold)), - ], - ), - ), - ) - ] + ), + ], + ), + SizedBox(height: mediaQuery.size.height * 0.05), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), + fixedSize: Size(mediaQuery.size.width * 0.7, mediaQuery.size.height * 0.2), + shape: RoundedRectangleBorder(borderRadius: StaticsVar.br), ), - ] - ) + onPressed: (){ + context.read().uiLogger.info("跳转: LearningPage => WordCardOverViewPage"); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => WordCardOverViewPage() + ) + ); + }, + icon: Icon(Icons.abc, size: 24), + label: Text("词汇总览", style: TextStyle(fontSize: 40.0, fontWeight: FontWeight.bold)), + ), + ] ); } } -Future shiftToStudy(BuildContext context, int studyType) async { - context.read().uiLogger.info("准备转向学习页面: studyType: $studyType"); +Future shiftToStudy(BuildContext context) async { + context.read().uiLogger.info("准备转向学习页面"); final List selectedClasses = await popSelectClasses(context, withCache: false); if(selectedClasses.isEmpty || !context.mounted) return; final List words = getSelectedWords(context, doShuffle: false, doDouble: false, forceSelectClasses: selectedClasses); @@ -159,7 +123,7 @@ Future shiftToStudy(BuildContext context, int studyType) async { final bool? finished = await Navigator.push( context, MaterialPageRoute( - builder: (context) => InLearningPage(studyType: studyType, words: words), + builder: (context) => InLearningPage(words: words), ), ); if(!context.mounted) return; From a7ae2e1b88e848e0f204f8341061930a8a665436 Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:16:33 +0800 Subject: [PATCH 2/7] refactor: rebuild test item with class Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- .../learning_pages/learning_pages_build.dart | 132 +++++++++++------- 1 file changed, 79 insertions(+), 53 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 9d081b5..ee43e69 100644 --- a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart +++ b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart @@ -11,9 +11,8 @@ import 'package:arabic_learning/funcs/ui.dart'; -// 学习主入口页面 +/// 学习主入口页面 class InLearningPage extends StatefulWidget { - final int studyType; /* 题型说明 0: 单词卡片 @@ -22,14 +21,14 @@ class InLearningPage extends StatefulWidget { 3: 中译阿 拼写题 */ final List words; - const InLearningPage({super.key, required this.studyType, required this.words}); + const InLearningPage({super.key, required this.words}); @override State createState() => _InLearningPageState(); } class _InLearningPageState extends State { Random rnd = Random(); - List> testList = []; // [[word(Map), testType(int), [extraValues]]] + List testList = []; // [[word(Map), testType(int), [extraValues]]] bool clicked = false; int correctCount = 0; late final int startTime; @@ -39,39 +38,26 @@ class _InLearningPageState extends State { @override void initState() { // 加载测试词 - final SubQuizConfig questionsSetting = widget.studyType == 0 ? context.read().globalConfig.quiz.zhar - : widget.studyType == 1 ? context.read().globalConfig.quiz.zh - : context.read().globalConfig.quiz.ar; - List>> questionsInSections = List.generate(questionsSetting.questionSections.length, (_) => []); + final SubQuizConfig questionsSetting = context.read().globalConfig.quiz.zhar; + List> questionsInSections = List.generate(questionsSetting.questionSections.length, (_) => []); for(int sectionIndex = 0; sectionIndex < questionsSetting.questionSections.length; sectionIndex++) { for(WordItem wordItem in widget.words) { - late List extra; - final int testType = questionsSetting.questionSections[sectionIndex]; - if(testType == 0) { - // 单词卡片 没有额外数据 - extra = []; - } else if(testType == 1 || testType == 2) { - // 中译阿/阿译中 选择题 - List optionWords = getRandomWords(4, context.read().wordData, include: wordItem, preferClass: !questionsSetting.preferSimilar, rnd: rnd); - List strList = List.generate(4, (int index) => (testType == 1 ? optionWords[index].arabic : optionWords[index].chinese), growable: false); - extra = [optionWords.indexWhere((WordItem item) => item == wordItem), strList]; - } else if(testType == 3) { - // 拼写题 - extra = []; - } else if(testType == 4) { - // 听力题 - List optionWords = getRandomWords(4, context.read().wordData, include: wordItem, preferClass: !questionsSetting.preferSimilar, rnd: rnd); - List strList = List.generate(4, (int index) => (rnd.nextBool() ? optionWords[index].chinese : optionWords[index].arabic), growable: false); - extra = [optionWords.indexWhere((WordItem item) => item == wordItem), strList]; - } - questionsInSections[sectionIndex].add([wordItem, testType, extra]); + questionsInSections[sectionIndex].add( + TestItem.buildTestItem( + wordItem, + questionsSetting.questionSections[sectionIndex], + context.read().wordData, + questionsSetting.preferSimilar, + rnd + ) + ); } } // shuffle part if(questionsSetting.shuffleExternaly) questionsInSections.shuffle(); - for(List> testItems in questionsInSections) { + for(List testItems in questionsInSections) { if(questionsSetting.shuffleInternaly) testItems.shuffle(); testList.addAll(testItems); } @@ -176,12 +162,11 @@ class _InLearningPageState extends State { ]; return ConcludePage(data: data); } - List testItem = testList[index]; - // testItem 0:MainWord; 1:TestType; 2: (extra)[0:CorrectIndex; 1:strList] - if(testItem[1] == 0) { + final TestItem testItem = testList[index]; + if(testItem.testType == 0) { // wordCard return WordCardQuestion( - word: testItem[0], + word: testItem.testWord, hint: "尝试自行回忆以下单词", bottomWidget: ElevatedButton.icon( style: ElevatedButton.styleFrom( @@ -199,16 +184,16 @@ class _InLearningPageState extends State { label: Text("下一题"), ), ); - } else if(testItem[1] == 1 || testItem[1] == 2) { + } else if(testItem.testType == 1 || testItem.testType == 2) { // ar-zh choose questions return ChoiceQuestions( - mainWord: testItem[1] == 1 ? (testItem[0] as WordItem).chinese : (testItem[0] as WordItem).arabic, - choices: testItem[2][1], - allowAudio: testItem[1] == 2, + mainWord: testItem.testType == 1 ? testItem.testWord.chinese : testItem.testWord.arabic, + choices: testItem.options!, + allowAudio: testItem.testType == 2, onSelected: (value) { - bool ans = value == testItem[2][0]; + bool ans = value == testItem.correctIndex; if(!ans) { - Future.delayed(Duration(seconds: 1), (){if(context.mounted) viewAnswer(context, testItem[0]);}); + Future.delayed(Duration(seconds: 1), (){if(context.mounted) viewAnswer(context, testItem.testWord);}); } else { correctCount++; } @@ -218,7 +203,7 @@ class _InLearningPageState extends State { return ans; }, allowMutipleSelect: true, - hint: testItem[1] == 1 ? "通过中文选择阿拉伯语" : "通过阿拉伯语选择中文", + hint: testItem.testType == 1 ? "通过中文选择阿拉伯语" : "通过阿拉伯语选择中文", bottomWidget: BottomTip( isShowNext: clicked, isLast: controller.page?.ceil() == testList.length - 1, @@ -229,24 +214,24 @@ class _InLearningPageState extends State { }); }, onTipClicked: (){ - viewAnswer(context, testItem[0]); + viewAnswer(context, testItem.testWord); } ) ); - } else if(testItem[1] == 3) { + } else if(testItem.testType == 3) { // spell question return SpellQuestion( - word: testItem[0], + word: testItem.testWord, hint: "拼写以下单词", onCheck: (text) { setState(() { clicked = true; }); - if(text == testItem[0].arabic) { + if(text == testItem.testWord.arabic) { correctCount++; return true; } else { - viewAnswer(context, testItem[0]); + viewAnswer(context, testItem.testWord); return false; } }, @@ -260,26 +245,26 @@ class _InLearningPageState extends State { }); }, onTipClicked: (){ - viewAnswer(context, testItem[0]); + viewAnswer(context, testItem.testWord); } ) ); - } else if(testItem[1] == 4){ + } else if(testItem.testType == 4){ // 听力题 return ListeningQuestion( - mainWord: testItem[0].arabic, - choices: testItem[2][1], + mainWord: testItem.testWord.arabic, + choices: testItem.options!, onSelected: (value) { if(value == -1) { setState(() { - testList.removeWhere((List wtestItem) => (wtestItem[1] == 4 && index < testList.indexOf(wtestItem))); + testList.removeWhere((TestItem wtestItem) => (wtestItem.testType == 4 && index < testList.indexOf(wtestItem))); clicked = true; }); return false; } - bool ans = value == testItem[2][0]; + bool ans = value == testItem.correctIndex; if(!ans) { - Future.delayed(Duration(seconds: 1), (){if(context.mounted) viewAnswer(context, testItem[0]);}); + Future.delayed(Duration(seconds: 1), (){if(context.mounted) viewAnswer(context, testItem.testWord);}); } else { correctCount++; } @@ -300,7 +285,7 @@ class _InLearningPageState extends State { }); }, onTipClicked: (){ - viewAnswer(context, testItem[0]); + viewAnswer(context, testItem.testWord); } ) ); @@ -568,6 +553,47 @@ class _ConcludePageState extends State { } } +@immutable +class TestItem { + /// 测试单词 + final WordItem testWord; + + /// 测试类型 + /// 0: 单词卡片 + /// 1: 中译阿 选择题 + /// 2: 阿译中 选择题 + /// 3: 拼写题 + /// 4: 听力题 + final int testType; + + /// 选择题和听力题的选项 + final List? options; + + /// 选择题和听力题的正确血选项索引号 + final int? correctIndex; + + const TestItem({ + required this.testWord, + required this.testType, + this.options, + this.correctIndex + }); + + static TestItem buildTestItem(WordItem word, int testType, DictData wordData,bool preferSimilar,Random rnd){ + if(testType == 0 || testType == 3){ + return TestItem(testWord: word, testType: testType); + } else { + final List optionWords = getRandomWords(4, wordData, include: word, preferClass: !preferSimilar, rnd: rnd); + return TestItem( + testWord: word, + testType: testType, + options: List.generate(4, (int index) => ((testType == 2 || (testType == 4 && rnd.nextBool())) ? optionWords[index].chinese : optionWords[index].arabic), growable: false), + correctIndex: optionWords.indexOf(word) + ); + } + } +} + class WordCardOverViewPage extends StatefulWidget { const WordCardOverViewPage({super.key}); From 519711a009f2ab5d5a41e98303df690cfd6d4f21 Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:54:42 +0800 Subject: [PATCH 3/7] feat: attach fsrs to learning part Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- lib/funcs/fsrs_func.dart | 6 +-- lib/pages/home_page.dart | 4 +- .../learning_pages/fsrs_pages.dart | 2 +- .../learning_pages/learning_pages_build.dart | 40 +++++++++++++------ lib/vars/global.dart | 6 +++ 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/lib/funcs/fsrs_func.dart b/lib/funcs/fsrs_func.dart index faf497a..63e7177 100644 --- a/lib/funcs/fsrs_func.dart +++ b/lib/funcs/fsrs_func.dart @@ -83,13 +83,11 @@ class FSRS { } bool isContained(int wordId) { - for(Card card in config.cards) { - if(card.cardId == wordId) return true; - } - return false; + return config.cards.any((Card card) => card.cardId == wordId); } void addWordCard(int wordId) { + logger.fine("添加复习卡片: Id: $wordId"); // os the wordID == cardID config.cards.add(Card(cardId: wordId, state: State.learning)); config.reviewLogs.add(ReviewLog(cardId: wordId, rating: Rating.good, reviewDateTime: DateTime.now())); diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 3862de7..356dbdd 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -17,8 +17,8 @@ class HomePage extends StatelessWidget { Widget build(BuildContext context) { context.read().uiLogger.fine("构建 HomePage"); final themeColor = Theme.of(context).colorScheme; - final mediaQuery = MediaQuery.of(context); - final FSRS fsrs = FSRS()..init(outerPrefs: context.read().prefs); + final MediaQueryData mediaQuery = MediaQuery.of(context); + final FSRS fsrs = context.read().globalFSRS; return Column( children: [ DailyWord(), diff --git a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart index 212cdef..5daa54f 100644 --- a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart +++ b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart @@ -18,7 +18,7 @@ class ForeFSRSSettingPage extends StatelessWidget { Widget build(BuildContext context) { context.read().uiLogger.info("构建 ForeFSRSSettingPage"); MediaQueryData mediaQuery = MediaQuery.of(context); - FSRS fsrs = FSRS()..init(outerPrefs: context.read().prefs); + final FSRS fsrs = context.read().globalFSRS; if(fsrs.config.enabled && !forceChoosing) { return MainFSRSPage(fsrs: fsrs); } 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 ee43e69..6ef093b 100644 --- a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart +++ b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart @@ -1,5 +1,6 @@ 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/vars/config_structure.dart'; import 'package:flutter/material.dart'; @@ -31,10 +32,25 @@ class _InLearningPageState extends State { List testList = []; // [[word(Map), testType(int), [extraValues]]] bool clicked = false; int correctCount = 0; - late final int startTime; + late final DateTime startTime; bool finished = false; final PageController controller = PageController(initialPage: 0); + void onSolve({required WordItem targetWord, + required bool isCorrect, + required int takentime, + required FSRS fsrs}){ + if(isCorrect) correctCount++; + if(fsrs.config.enabled) { + if(fsrs.isContained(targetWord.id)){ + fsrs.reviewCard(targetWord.id, takentime, isCorrect); + } else { + if(isCorrect) fsrs.addWordCard(targetWord.id); + } + + } + } + @override void initState() { // 加载测试词 @@ -62,7 +78,7 @@ class _InLearningPageState extends State { testList.addAll(testItems); } if(questionsSetting.shuffleGlobally) testList.shuffle(); - startTime = DateTime.now().millisecondsSinceEpoch; + startTime = DateTime.now(); super.initState(); } @@ -158,11 +174,12 @@ class _InLearningPageState extends State { List data = [ testList.length, correctCount, - ((DateTime.now().millisecondsSinceEpoch - startTime)/1000.0).toInt() + DateTime.now().difference(startTime).inSeconds ]; return ConcludePage(data: data); } final TestItem testItem = testList[index]; + final DateTime quizStart = DateTime.now(); if(testItem.testType == 0) { // wordCard return WordCardQuestion( @@ -175,10 +192,8 @@ class _InLearningPageState extends State { ), onPressed: (){ controller.nextPage(duration: Duration(milliseconds: 500), curve: StaticsVar.curve); - setState(() { - correctCount++; - }); - + correctCount++; + setState(() {}); }, icon: Icon(Icons.arrow_forward), label: Text("下一题"), @@ -194,9 +209,8 @@ class _InLearningPageState extends State { bool ans = value == testItem.correctIndex; if(!ans) { Future.delayed(Duration(seconds: 1), (){if(context.mounted) viewAnswer(context, testItem.testWord);}); - } else { - correctCount++; } + onSolve(targetWord: testItem.testWord, isCorrect: ans, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS); Future.delayed(Duration(milliseconds: 700) ,(){setState(() { clicked = true; });}); @@ -228,9 +242,10 @@ class _InLearningPageState extends State { clicked = true; }); if(text == testItem.testWord.arabic) { - correctCount++; + onSolve(targetWord: testItem.testWord, isCorrect: true, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS); return true; } else { + onSolve(targetWord: testItem.testWord, isCorrect: false, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS); viewAnswer(context, testItem.testWord); return false; } @@ -265,9 +280,8 @@ class _InLearningPageState extends State { bool ans = value == testItem.correctIndex; if(!ans) { Future.delayed(Duration(seconds: 1), (){if(context.mounted) viewAnswer(context, testItem.testWord);}); - } else { - correctCount++; - } + } + onSolve(targetWord: testItem.testWord, isCorrect: ans, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS); Future.delayed(Duration(milliseconds: 700) ,(){setState(() { clicked = true; });}); diff --git a/lib/vars/global.dart b/lib/vars/global.dart index bccb950..f93894f 100644 --- a/lib/vars/global.dart +++ b/lib/vars/global.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:arabic_learning/funcs/fsrs_func.dart'; import 'package:arabic_learning/funcs/utili.dart'; import 'package:logging/logging.dart'; import 'package:flutter/material.dart'; @@ -27,6 +28,7 @@ class Global with ChangeNotifier { late bool updateLogRequire; //是否需要显示更新日志 late bool isWideScreen; // 设备是否是宽屏幕 late final SharedPreferences prefs; // 储存实例 + late FSRS globalFSRS; late ThemeData themeData; bool modelTTSDownloaded = false; late DictData wordData; @@ -60,8 +62,12 @@ class Global with ChangeNotifier { // 预处理一些版本更新的配置文件兼容 Future conveySetting() async { logger.info("处理配置文件"); + + // 在配置文件加载完成前可以做的 wordData = DictData.buildFromMap(jsonDecode(prefs.getString("wordData")!)); if(!BKSearch.isReady) BKSearch.init(wordData.words); + globalFSRS = FSRS()..init(outerPrefs: prefs); + Config oldConfig = Config.buildFromMap(jsonDecode(prefs.getString("settingData")!)); if(oldConfig.lastVersion != globalConfig.lastVersion) { logger.info("检测到当前版本与上次启动版本不同"); From 5ccac151af2123011ae6217af9281261415b059e Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:09:17 +0800 Subject: [PATCH 4/7] feat: let over the time limition on typing question Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/funcs/fsrs_func.dart | 4 ++-- .../learning_pages/learning_pages_build.dart | 16 +++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e1eb4..190aeee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - 优化了选择题单词挑选逻辑 [#48](https://github.com/OctagonalStar/arabic_learning/issues/48) - 修复了BKTree日志刷屏问题 - 移除了提醒配置的中间页面 +- 对于拼写题型放宽了时间要求 [#52](https://github.com/OctagonalStar/arabic_learning/issues/52) ### Fix diff --git a/lib/funcs/fsrs_func.dart b/lib/funcs/fsrs_func.dart index 63e7177..486cb5b 100644 --- a/lib/funcs/fsrs_func.dart +++ b/lib/funcs/fsrs_func.dart @@ -50,11 +50,11 @@ class FSRS { return config.cards[index].due.toLocal().difference(DateTime.now()).inDays; } - void reviewCard(int wordId, int duration, bool isCorrect) { + void reviewCard(int wordId, int duration, bool isCorrect, {Rating? forceRate}) { logger.fine("记录复习卡片: Id: $wordId; duration: $duration; isCorrect: $isCorrect"); int index = config.cards.indexWhere((Card card) => card.cardId == wordId); // 避免有时候cardId != wordId logger.fine("定位复习卡片地址: $index, 目前阶段: ${config.cards[index].step}, 难度: ${config.cards[index].difficulty}, 稳定: ${config.cards[index].stability}, 过期时间(+8): ${config.cards[index].due.toLocal()}"); - final (:card, :reviewLog) = config.scheduler!.reviewCard(config.cards[index], calculate(duration, isCorrect), reviewDateTime: DateTime.now().toUtc(), reviewDuration: duration); + final (:card, :reviewLog) = config.scheduler!.reviewCard(config.cards[index], forceRate ?? calculate(duration, isCorrect), reviewDateTime: DateTime.now().toUtc(), reviewDuration: duration); config.cards[index] = card; config.reviewLogs[index] = reviewLog; logger.fine("卡片 $index 复习后: 目前阶段: ${config.cards[index].step}, 难度: ${config.cards[index].difficulty}, 稳定: ${config.cards[index].stability}, 过期时间(+8): ${config.cards[index].due.toLocal()}"); 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 6ef093b..30f6899 100644 --- a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart +++ b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart @@ -4,6 +4,7 @@ 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/vars/config_structure.dart'; import 'package:flutter/material.dart'; +import 'package:fsrs/fsrs.dart' show Rating; import 'package:provider/provider.dart'; import 'package:arabic_learning/vars/statics_var.dart'; @@ -29,7 +30,7 @@ class InLearningPage extends StatefulWidget { class _InLearningPageState extends State { Random rnd = Random(); - List testList = []; // [[word(Map), testType(int), [extraValues]]] + List testList = []; bool clicked = false; int correctCount = 0; late final DateTime startTime; @@ -39,11 +40,16 @@ class _InLearningPageState extends State { void onSolve({required WordItem targetWord, required bool isCorrect, required int takentime, - required FSRS fsrs}){ + required FSRS fsrs, + bool isTypingQuestion = false}){ if(isCorrect) correctCount++; if(fsrs.config.enabled) { if(fsrs.isContained(targetWord.id)){ - fsrs.reviewCard(targetWord.id, takentime, isCorrect); + if(isTypingQuestion) { + fsrs.reviewCard(targetWord.id, takentime, isCorrect, forceRate: isCorrect ? Rating.good : Rating.again); + } else { + fsrs.reviewCard(targetWord.id, takentime, isCorrect); + } } else { if(isCorrect) fsrs.addWordCard(targetWord.id); } @@ -242,10 +248,10 @@ class _InLearningPageState extends State { clicked = true; }); if(text == testItem.testWord.arabic) { - onSolve(targetWord: testItem.testWord, isCorrect: true, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS); + onSolve(targetWord: testItem.testWord, isCorrect: true, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS, isTypingQuestion: true); return true; } else { - onSolve(targetWord: testItem.testWord, isCorrect: false, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS); + onSolve(targetWord: testItem.testWord, isCorrect: false, takentime: DateTime.now().difference(quizStart).inMilliseconds, fsrs: context.read().globalFSRS, isTypingQuestion: true); viewAnswer(context, testItem.testWord); return false; } From c670a9a1988147b6a79a772b66059f7ac4478a65 Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:05:01 +0800 Subject: [PATCH 5/7] feat: add tip for wheather count in review on learning Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/funcs/ui.dart | 56 ++++++++++++------- lib/pages/learning_page.dart | 8 +-- .../learning_pages/fsrs_pages.dart | 8 +-- .../learning_pages/learning_pages_build.dart | 5 +- .../test_pages/listening_test_page.dart | 10 ++-- lib/vars/config_structure.dart | 10 ++++ 7 files changed, 63 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 190aeee..11a5b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - 添加了常见问题页面 - 给部分文本框添加了动画 - 添加了听力题型 [#51](https://github.com/OctagonalStar/arabic_learning/issues/51) +- 为每次学习添加了是否计算复习的提示 [#52](https://github.com/OctagonalStar/arabic_learning/issues/52) ### Improvement diff --git a/lib/funcs/ui.dart b/lib/funcs/ui.dart index fcc865d..3e79b21 100644 --- a/lib/funcs/ui.dart +++ b/lib/funcs/ui.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:ui'; -import 'package:arabic_learning/vars/config_structure.dart' show ClassItem, SourceItem, WordItem; +import 'package:arabic_learning/vars/config_structure.dart' show ClassItem, SourceItem, WordItem, ClassSelection; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -36,7 +36,7 @@ import 'package:arabic_learning/funcs/utili.dart'; /// List> value = await popSelectClasses(context); /// List> words = getSelectedWords(context , forceSelectClasses: value); /// ``` -Future> popSelectClasses(BuildContext context, {bool withCache = false}) async { +Future popSelectClasses(BuildContext context, {bool withCache = false, bool withReviewChoose = true}) async { context.read().uiLogger.info("弹出课程选择(ClassSelectPage),withCache: $withCache"); final List beforeSelectedClasses = []; if(withCache) { @@ -59,15 +59,14 @@ Future> popSelectClasses(BuildContext context, {bool withCache = } context.read().uiLogger.fine("已缓存课程选择: $beforeSelectedClasses"); } - List? selectedClasses = await showModalBottomSheet>( + ClassSelection? selectedClasses = await showModalBottomSheet( context: context, - // 假装圆角... :) shape: RoundedRectangleBorder(side: BorderSide(width: 1.0, color: Theme.of(context).colorScheme.onSurface.withAlpha(150)), borderRadius: StaticsVar.br), isDismissible: false, isScrollControlled: context.read().isWideScreen, enableDrag: true, builder: (BuildContext context) { - return ClassSelectPage(beforeSelectedClasses: beforeSelectedClasses); + return ClassSelectPage(beforeSelectedClasses: beforeSelectedClasses, withReviewChoose: withReviewChoose); } ); if(withCache && selectedClasses != null && context.mounted) { @@ -78,7 +77,7 @@ Future> popSelectClasses(BuildContext context, {bool withCache = context.read().uiLogger.info("课程选择缓存完成"); } if(context.mounted) context.read().uiLogger.fine("选择的课程: $selectedClasses"); - return selectedClasses??[]; + return selectedClasses??ClassSelection(selectedClass: [], countInReview: false); } @@ -573,19 +572,23 @@ class WordCard extends StatelessWidget { /// 注意:如果你要进行课程选择,请先考虑 [popSelectClasses] 函数,这是一个已经基本成熟的实现 class ClassSelectPage extends StatelessWidget { final List beforeSelectedClasses; - const ClassSelectPage({super.key, this.beforeSelectedClasses = const []}); + final bool withReviewChoose; + const ClassSelectPage({super.key, this.beforeSelectedClasses = const [], this.withReviewChoose = false}); @override Widget build(BuildContext context) { final MediaQueryData mediaQuery = MediaQuery.of(context); - List selectedClass = beforeSelectedClasses.toList(); + ClassSelection classSelection = ClassSelection( + selectedClass: beforeSelectedClasses.toList(), + countInReview: context.read().globalFSRS.config.enabled + ); void addClass(ClassItem classInfo) { - selectedClass.add(classInfo); + classSelection.selectedClass.add(classInfo); } void removeClass(ClassItem classInfo) { - selectedClass.remove(classInfo); + classSelection.selectedClass.remove(classInfo); } bool isClassSelected(ClassItem classInfo) { - return selectedClass.any((e) => e==classInfo); + return classSelection.selectedClass.any((e) => e==classInfo); } void onClassChanged(ClassItem classInfo) { if(isClassSelected(classInfo)) { @@ -594,14 +597,6 @@ class ClassSelectPage extends StatelessWidget { addClass(classInfo); } } - // 和监听器脱钩... - // if(!context.watch().initialized) { - // return Scaffold( - // body: Center( - // child: CircularProgressIndicator(), - // ), - // ); - // } return Scaffold( appBar: AppBar( @@ -614,6 +609,27 @@ class ClassSelectPage extends StatelessWidget { children: classesSelectionList(context, onClassChanged, isClassSelected) ), ), + if(withReviewChoose) StatefulBuilder( + builder: (context, setLocalState) { + return Row( + children: [ + Expanded(child: Text("本次学习${classSelection.countInReview?"将":"不会"}计入复习系统")), + Switch( + value: classSelection.countInReview, + onChanged: (value){ + if(value == true && !context.read().globalFSRS.config.enabled) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("请先启用复习系统"))); + return ; + } + setLocalState(() { + classSelection.countInReview = value; + }); + } + ) + ], + ); + } + ), ElevatedButton( style: ElevatedButton.styleFrom( fixedSize: Size(mediaQuery.size.width, mediaQuery.size.height * 0.08), @@ -621,7 +637,7 @@ class ClassSelectPage extends StatelessWidget { ), child: Text('确认'), onPressed: () { - Navigator.pop(context, selectedClass); + Navigator.pop(context, classSelection); }, ), ], diff --git a/lib/pages/learning_page.dart b/lib/pages/learning_page.dart index 58b5de1..2f35f45 100644 --- a/lib/pages/learning_page.dart +++ b/lib/pages/learning_page.dart @@ -114,16 +114,16 @@ class LearningPage extends StatelessWidget { Future shiftToStudy(BuildContext context) async { context.read().uiLogger.info("准备转向学习页面"); - final List selectedClasses = await popSelectClasses(context, withCache: false); - if(selectedClasses.isEmpty || !context.mounted) return; - final List words = getSelectedWords(context, doShuffle: false, doDouble: false, forceSelectClasses: selectedClasses); + final ClassSelection classSelection = await popSelectClasses(context, withCache: false, withReviewChoose: true); + if(classSelection.selectedClass.isEmpty || !context.mounted) return; + final List words = getSelectedWords(context, doShuffle: false, doDouble: false, forceSelectClasses: classSelection.selectedClass); context.read().uiLogger.info("完成单词挑拣,共${words.length}个"); if(words.isEmpty) return; context.read().uiLogger.info("跳转: LearningPage => InLearningPage"); final bool? finished = await Navigator.push( context, MaterialPageRoute( - builder: (context) => InLearningPage(words: words), + builder: (context) => InLearningPage(words: words, countInReview: classSelection.countInReview), ), ); if(!context.mounted) return; diff --git a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart index 5daa54f..c27a398 100644 --- a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart +++ b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart @@ -390,11 +390,11 @@ class _FSRSOverViewPageState extends State { icon: const Icon(Icons.label_important, size: 24.0), label: const Text("去学习新单词"), onPressed: () async { - late List selectedClasses; + late ClassSelection selectedClasses; late List words; - selectedClasses = await popSelectClasses(context, withCache: false); - if(!context.mounted || selectedClasses.isEmpty) return; - words = getSelectedWords(context, forceSelectClasses: selectedClasses, doShuffle: true, doDouble: false); + selectedClasses = await popSelectClasses(context, withCache: false, withReviewChoose: false); + if(!context.mounted || selectedClasses.selectedClass.isEmpty) return; + words = getSelectedWords(context, forceSelectClasses: selectedClasses.selectedClass, doShuffle: true, doDouble: false); // 去除已经学习的项目 words.removeWhere((WordItem item) => widget.fsrs.isContained(item.id)); context.read().uiLogger.info("跳转: FSRSOverViewPage => FSRSLearningPage"); 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 30f6899..104f7ad 100644 --- a/lib/sub_pages_builder/learning_pages/learning_pages_build.dart +++ b/lib/sub_pages_builder/learning_pages/learning_pages_build.dart @@ -23,7 +23,8 @@ class InLearningPage extends StatefulWidget { 3: 中译阿 拼写题 */ final List words; - const InLearningPage({super.key, required this.words}); + final bool countInReview; + const InLearningPage({super.key, required this.words, required this.countInReview}); @override State createState() => _InLearningPageState(); } @@ -43,7 +44,7 @@ class _InLearningPageState extends State { required FSRS fsrs, bool isTypingQuestion = false}){ if(isCorrect) correctCount++; - if(fsrs.config.enabled) { + if(widget.countInReview && fsrs.config.enabled) { if(fsrs.isContained(targetWord.id)){ if(isTypingQuestion) { fsrs.reviewCard(targetWord.id, takentime, isCorrect, forceRate: isCorrect ? Rating.good : Rating.again); diff --git a/lib/sub_pages_builder/test_pages/listening_test_page.dart b/lib/sub_pages_builder/test_pages/listening_test_page.dart index f3eda16..96add9f 100644 --- a/lib/sub_pages_builder/test_pages/listening_test_page.dart +++ b/lib/sub_pages_builder/test_pages/listening_test_page.dart @@ -19,14 +19,14 @@ class _ForeListeningSettingPage extends State { int playTimes = 3; int interval = 5; int intervalBetweenWords = 10; - List selectedClasses = []; + ClassSelection selectedClasses = ClassSelection(selectedClass: List.empty(), countInReview: false); @override Widget build(BuildContext context) { context.read().uiLogger.info("构建 ForeListeningSettingPage"); MediaQueryData mediaQuery = MediaQuery.of(context); - int wordCount = getSelectedWords(context, forceSelectClasses: selectedClasses).length; + int wordCount = getSelectedWords(context, forceSelectClasses: selectedClasses.selectedClass).length; return Scaffold( appBar: AppBar( title: Text('自主听写预设置'), @@ -66,7 +66,7 @@ class _ForeListeningSettingPage extends State { ), ), onPressed: () async { - selectedClasses = await popSelectClasses(context, withCache: false); + selectedClasses = await popSelectClasses(context, withCache: false, withReviewChoose: false); setState(() {}); }, child: Column( @@ -196,7 +196,7 @@ class _ForeListeningSettingPage extends State { icon: Icon(Icons.rocket_launch, size: 32.0,), label: Text("听写,启动!", style: TextStyle(fontSize: 24.0),), onPressed: () { - if(selectedClasses.isEmpty) { + if(selectedClasses.selectedClass.isEmpty) { alart(context, "是哪个小可爱没选课程就来听写了"); return; } @@ -210,7 +210,7 @@ class _ForeListeningSettingPage extends State { playTimes: playTimes, interval: interval, intervalBetweenWords: intervalBetweenWords, - words: getSelectedWords(context, forceSelectClasses: selectedClasses, doShuffle: true) + words: getSelectedWords(context, forceSelectClasses: selectedClasses.selectedClass, doShuffle: true) ) ) ); diff --git a/lib/vars/config_structure.dart b/lib/vars/config_structure.dart index 691ad9d..c7e47f8 100644 --- a/lib/vars/config_structure.dart +++ b/lib/vars/config_structure.dart @@ -751,4 +751,14 @@ class WordItem { id: id ); } +} + +class ClassSelection { + List selectedClass; + bool countInReview; + + ClassSelection({ + required this.selectedClass, + required this.countInReview + }); } \ No newline at end of file From 0545deb9bcbedbce4514ea9cf082ede9fb3ce222 Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:46:44 +0800 Subject: [PATCH 6/7] feat: add selfEvaluation solution in review part Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/funcs/fsrs_func.dart | 19 +- lib/funcs/ui.dart | 4 +- .../learning_pages/fsrs_pages.dart | 75 +++- pubspec.lock | 376 +++++++++--------- 5 files changed, 265 insertions(+), 210 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a5b1f..be52ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - 给部分文本框添加了动画 - 添加了听力题型 [#51](https://github.com/OctagonalStar/arabic_learning/issues/51) - 为每次学习添加了是否计算复习的提示 [#52](https://github.com/OctagonalStar/arabic_learning/issues/52) +- 在复习部分添加了自我评级方案 [#52](https://github.com/OctagonalStar/arabic_learning/issues/52) ### Improvement diff --git a/lib/funcs/fsrs_func.dart b/lib/funcs/fsrs_func.dart index 486cb5b..af4addd 100644 --- a/lib/funcs/fsrs_func.dart +++ b/lib/funcs/fsrs_func.dart @@ -123,6 +123,7 @@ class FSRSConfig { final int easyDuration; final int goodDuration; final bool preferSimilar; + final bool selfEvaluate; const FSRSConfig({ bool? enabled, @@ -132,7 +133,8 @@ class FSRSConfig { double? desiredRetention, int? easyDuration, int? goodDuration, - bool? preferSimilar + bool? preferSimilar, + bool? selfEvaluate }) : enabled = enabled??false, cards = cards??const [], @@ -140,7 +142,8 @@ class FSRSConfig { desiredRetention = desiredRetention??0.9, easyDuration = easyDuration??3000, goodDuration = goodDuration??6000, - preferSimilar = preferSimilar??false; + preferSimilar = preferSimilar??false, + selfEvaluate = selfEvaluate??false; Map toMap(){ return { @@ -151,7 +154,8 @@ class FSRSConfig { "desiredRetention": desiredRetention, "easyDuration": easyDuration, "goodDuration": goodDuration, - "preferSimilar": preferSimilar + "preferSimilar": preferSimilar, + "selfEvaluate": selfEvaluate }; } @@ -163,7 +167,8 @@ class FSRSConfig { double? desiredRetention, int? easyDuration, int? goodDuration, - bool? preferSimilar + bool? preferSimilar, + bool? selfEvaluate }) { return FSRSConfig( enabled: enabled??this.enabled, @@ -173,7 +178,8 @@ class FSRSConfig { desiredRetention: desiredRetention??this.desiredRetention, easyDuration: easyDuration??this.easyDuration, goodDuration: goodDuration??this.goodDuration, - preferSimilar: preferSimilar??this.preferSimilar + preferSimilar: preferSimilar??this.preferSimilar, + selfEvaluate: selfEvaluate??this.selfEvaluate ); } @@ -187,7 +193,8 @@ class FSRSConfig { desiredRetention: configData["desiredRetention"], easyDuration: configData["easyDuration"], goodDuration: configData["goodDuration"], - preferSimilar: configData["preferSimilar"] + preferSimilar: configData["preferSimilar"], + selfEvaluate: configData["selfEvaluate"] ); } return FSRSConfig(enabled: false); diff --git a/lib/funcs/ui.dart b/lib/funcs/ui.dart index 3e79b21..f3ad5e7 100644 --- a/lib/funcs/ui.dart +++ b/lib/funcs/ui.dart @@ -716,6 +716,7 @@ class ChoiceQuestions extends StatefulWidget { final List choices; final bool? Function(int) onSelected; final String? hint; + final Widget? midWidget; final Widget? bottomWidget; final Function? onDisAllowMutipleSelect; final bool allowMutipleSelect; @@ -728,6 +729,7 @@ class ChoiceQuestions extends StatefulWidget { required this.allowAudio, required this.onSelected, this.hint, + this.midWidget, this.bottomWidget, this.onDisAllowMutipleSelect, this.bottonLayout = -1, @@ -758,7 +760,7 @@ class _ChoiceQuestions extends State { children: [ if(widget.hint!=null) TextContainer(text: widget.hint!, animated: true), Expanded( - child: StatefulBuilder( + child: widget.midWidget ?? StatefulBuilder( builder: (context, setLocalState) { return ElevatedButton.icon( icon: Icon(widget.allowAudio ? (playing ? Icons.multitrack_audio : Icons.volume_up) : Icons.short_text, size: 24.0), diff --git a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart index c27a398..2c3ef40 100644 --- a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart +++ b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:arabic_learning/vars/config_structure.dart'; import 'package:flutter/material.dart'; +import 'package:fsrs/fsrs.dart' show Rating; import 'package:provider/provider.dart'; import 'package:arabic_learning/vars/statics_var.dart'; @@ -143,6 +144,37 @@ class ForeFSRSSettingPage extends StatelessWidget { ), margin: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: Text("使用自我评级", style: Theme.of(context).textTheme.bodyLarge)), + Switch( + value: fsrs.config.selfEvaluate, + onChanged: (value){ + setState(() { + fsrs.config = fsrs.config.copyWith( + selfEvaluate: value + ); + }); + } + ) + ], + ), + Text("自我评级 开启时会向你展示遮挡了中文的单词卡片,由你自行选择你是 记得很清楚/还记得/回忆困难/忘了"), + Text("在此模式下,计时仅作展示,不作为评分依据"), + Text("适合清楚自己的实力的人启用") + ], + ), + ), + if(!fsrs.config.selfEvaluate) Container( + decoration: BoxDecoration( + borderRadius: StaticsVar.br, + color: Theme.of(context).colorScheme.onPrimary + ), + margin: EdgeInsets.all(8.0), + padding: EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -162,7 +194,8 @@ class ForeFSRSSettingPage extends StatelessWidget { ], ), Text("偏好易混词 开启时选择题的选项更多地按照词根寻找相似的单词进行测试"), - Text("关闭时选择题的选项更多地考察同课程的单词") + Text("关闭时选择题的选项更多地考察同课程的单词"), + Text("该选型仅在自我评级关闭时生效") ], ), ), @@ -225,7 +258,7 @@ class MainFSRSPage extends StatelessWidget { return Center( child: Column( children: [ - TextContainer(text: "你有${fsrs.getWillDueCount().toString()}个单词即将逾期!\n上滑页面开始复习",size: Size(mediaQuery.size.width * 0.8, mediaQuery.size.height * 0.4),textAlign: TextAlign.center), + TextContainer(text: "你有${fsrs.getWillDueCount().toString()}个单词需要复习!\n上滑页面开始复习",size: Size(mediaQuery.size.width * 0.8, mediaQuery.size.height * 0.4),textAlign: TextAlign.center), Icon(Icons.arrow_upward, size: 48.0, color: Colors.grey) ], ), @@ -269,20 +302,27 @@ class _FSRSReviewCardPage extends State { context.read().uiLogger.info("构建 FSRSReviewCardPage"); MediaQueryData mediaQuery = MediaQuery.of(context); final List wordData = context.read().wordData.words; + late final int correct; // 防止重建后选项丢失 if(options == null){ - List optionWords = getRandomWords(4, context.read().wordData, include: wordData[widget.wordID], preferClass: !widget.fsrs.config.preferSimilar, rnd: widget.rnd); - options ??= List.generate(4, (int index) => optionWords[index].chinese, growable: false); + if(widget.fsrs.config.selfEvaluate) { + options = const ["记得很清楚", "还记得", "回忆困难", "忘了"]; + correct = -1; + } else { + List optionWords = getRandomWords(4, context.read().wordData, include: wordData[widget.wordID], preferClass: !widget.fsrs.config.preferSimilar, rnd: widget.rnd); + options = List.generate(4, (int index) => optionWords[index].chinese, growable: false); + correct = options!.indexOf(context.read().wordData.words[widget.wordID].chinese); + } } - final int correct = options!.indexOf(context.read().wordData.words[widget.wordID].chinese); return Material( child: ChoiceQuestions( - mainWord: wordData[widget.wordID].arabic, + mainWord: widget.fsrs.config.selfEvaluate ? "[selfEvaluate]" : wordData[widget.wordID].arabic, + midWidget: widget.fsrs.config.selfEvaluate ? WordCard(word: wordData[widget.wordID]) : null, choices: options!, allowAudio: true, - allowAnitmation: true, + allowAnitmation: !widget.fsrs.config.selfEvaluate, allowMutipleSelect: false, hint: "单词ID: ${widget.wordID}${choosed ? " 用时: ${end.difference(start).inMilliseconds}毫秒" : ""}", onDisAllowMutipleSelect: (value) { @@ -295,13 +335,18 @@ class _FSRSReviewCardPage extends State { choosed = true; end = DateTime.now(); }); - if(correct == value) { - widget.fsrs.reviewCard(widget.wordID, end.difference(start).inMilliseconds, true); - context.read().updateLearningStreak(); + context.read().updateLearningStreak(); + if(widget.fsrs.config.selfEvaluate) { + widget.fsrs.reviewCard(widget.wordID, end.difference(start).inMilliseconds, true, forceRate: (const [Rating.easy, Rating.good, Rating.hard, Rating.again]).elementAt(value)); return true; } else { - widget.fsrs.reviewCard(widget.wordID, end.difference(start).inMilliseconds, false); - return false; + if(correct == value) { + widget.fsrs.reviewCard(widget.wordID, end.difference(start).inMilliseconds, true); + return true; + } else { + widget.fsrs.reviewCard(widget.wordID, end.difference(start).inMilliseconds, false); + return false; + } } }, bottomWidget: TweenAnimationBuilder( @@ -315,7 +360,7 @@ class _FSRSReviewCardPage extends State { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - ElevatedButton.icon( + if(!widget.fsrs.config.selfEvaluate) ElevatedButton.icon( style: ElevatedButton.styleFrom( fixedSize: Size(mediaQuery.size.width * 0.9 - mediaQuery.size.width * 0.5 * value, mediaQuery.size.height * 0.1), shape: RoundedRectangleBorder(borderRadius: StaticsVar.br) @@ -329,10 +374,10 @@ class _FSRSReviewCardPage extends State { icon: Icon(Icons.tips_and_updates), label: Text(value == 0.0 ? "忘了?" : "详解"), ), - SizedBox(width: mediaQuery.size.width*0.02*value), + if(!widget.fsrs.config.selfEvaluate) SizedBox(width: mediaQuery.size.width*0.02*value), if(value > 0.3) ElevatedButton.icon( style: ElevatedButton.styleFrom( - fixedSize: Size(mediaQuery.size.width * 0.5 * value, mediaQuery.size.height * 0.1), + fixedSize: Size(mediaQuery.size.width * (widget.fsrs.config.selfEvaluate ? 1 : 0.5) * value, mediaQuery.size.height * 0.1), shape: RoundedRectangleBorder(borderRadius: StaticsVar.br) ), onPressed: () { diff --git a/pubspec.lock b/pubspec.lock index d018780..f953dd1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: _fe_analyzer_shared sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "85.0.0" analyzer: @@ -14,7 +14,7 @@ packages: description: name: analyzer sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.7.1" archive: @@ -22,7 +22,7 @@ packages: description: name: archive sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.7" args: @@ -30,7 +30,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" async: @@ -38,7 +38,7 @@ packages: description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.13.0" audio_session: @@ -46,7 +46,7 @@ packages: description: name: audio_session sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.2" bk_tree: @@ -54,7 +54,7 @@ packages: description: name: bk_tree sha256: "25bc3ea0294d51759faa5575083658588dd7693a14b2a884b16061e10a98a102" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.2" boolean_selector: @@ -62,7 +62,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" change_app_package_name: @@ -70,7 +70,7 @@ packages: description: name: change_app_package_name sha256: "8e43b754fe960426904d77ed4c62fa8c9834deaf6e293ae40963fa447482c4c5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.5.0" characters: @@ -78,7 +78,7 @@ packages: description: name: characters sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" checked_yaml: @@ -86,7 +86,7 @@ packages: description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.4" cli_config: @@ -94,7 +94,7 @@ packages: description: name: cli_config sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" cli_util: @@ -102,7 +102,7 @@ packages: description: name: cli_util sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.2" clock: @@ -110,7 +110,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" collection: @@ -118,7 +118,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" convert: @@ -126,7 +126,7 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.2" coverage: @@ -134,7 +134,7 @@ packages: description: name: coverage sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.15.0" cross_file: @@ -142,7 +142,7 @@ packages: description: name: cross_file sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.3.5+1" crypto: @@ -150,7 +150,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.7" csslib: @@ -158,7 +158,7 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" cupertino_icons: @@ -166,7 +166,7 @@ packages: description: name: cupertino_icons sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.8" dart_pubspec_licenses: @@ -174,7 +174,7 @@ packages: description: name: dart_pubspec_licenses sha256: fa28071ec85d18260949553ab9f7ad33873b8002cd9d71b42b9967b74940ad67 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.15" dbus: @@ -182,23 +182,23 @@ packages: description: name: dbus sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.11" dio: dependency: "direct main" description: name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 - url: "https://pub.flutter-io.cn" + sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 + url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.9.1" dio_web_adapter: dependency: transitive description: name: dio_web_adapter sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" fake_async: @@ -206,7 +206,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" ffi: @@ -214,7 +214,7 @@ packages: description: name: ffi sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.5" file: @@ -222,23 +222,23 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" file_picker: dependency: "direct main" description: name: file_picker - sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde - url: "https://pub.flutter-io.cn" + sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" + url: "https://pub.dev" source: hosted - version: "10.3.8" + version: "10.3.10" fixnum: dependency: transitive description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" flutter: @@ -251,7 +251,7 @@ packages: description: name: flutter_launcher_icons sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.14.4" flutter_lints: @@ -259,7 +259,7 @@ packages: description: name: flutter_lints sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_local_notifications: @@ -267,7 +267,7 @@ packages: description: name: flutter_local_notifications sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "19.5.0" flutter_local_notifications_linux: @@ -275,7 +275,7 @@ packages: description: name: flutter_local_notifications_linux sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.0.0" flutter_local_notifications_platform_interface: @@ -283,7 +283,7 @@ packages: description: name: flutter_local_notifications_platform_interface sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.1.0" flutter_local_notifications_windows: @@ -291,7 +291,7 @@ packages: description: name: flutter_local_notifications_windows sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.3" flutter_markdown_plus: @@ -299,7 +299,7 @@ packages: description: name: flutter_markdown_plus sha256: "039177906850278e8fb1cd364115ee0a46281135932fa8ecea8455522166d2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.7" flutter_plugin_android_lifecycle: @@ -307,7 +307,7 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.33" flutter_test: @@ -320,7 +320,7 @@ packages: description: name: flutter_tts sha256: ce5eb209b40e95f2f4a1397116c87ab2fcdff32257d04ed7a764e75894c03775 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.2.5" flutter_web_plugins: @@ -333,7 +333,7 @@ packages: description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.0" fsrs: @@ -341,7 +341,7 @@ packages: description: name: fsrs sha256: b50dbee450a424dbac4fb0f49cae25ff2e4e6d01844caa1ec0c26de5baba8071 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" glob: @@ -349,7 +349,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.3" html: @@ -357,7 +357,7 @@ packages: description: name: html sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.15.6" http: @@ -365,7 +365,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.0" http_multi_server: @@ -373,7 +373,7 @@ packages: description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" http_parser: @@ -381,23 +381,23 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.2" idb_shim: dependency: "direct main" description: name: idb_shim - sha256: "071f3b05032fa62e60ca15db9939f8afbaf403b37e67747ac88f858c3e999228" - url: "https://pub.flutter-io.cn" + sha256: "7dcec5fa6bef3be4666384a28527e1476a84e0e3b72c44d9cfde9d2417bd1769" + url: "https://pub.dev" source: hosted - version: "2.6.7+1" + version: "2.8.1" image: dependency: transitive description: name: image sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.7.2" io: @@ -405,7 +405,7 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.5" js: @@ -413,23 +413,23 @@ packages: description: name: js sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.2" json_annotation: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.flutter-io.cn" + sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" + url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.10.0" just_audio: dependency: "direct main" description: name: just_audio sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.10.5" just_audio_platform_interface: @@ -437,7 +437,7 @@ packages: description: name: just_audio_platform_interface sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.6.0" just_audio_web: @@ -445,7 +445,7 @@ packages: description: name: just_audio_web sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.16" just_audio_windows: @@ -453,7 +453,7 @@ packages: description: name: just_audio_windows sha256: b1ba5305d841c0e3883644e20fc11aaa23f28cfdd43ec20236d1e119a402ef29 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.2" leak_tracker: @@ -461,7 +461,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -469,7 +469,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.10" leak_tracker_testing: @@ -477,7 +477,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" lints: @@ -485,7 +485,7 @@ packages: description: name: lints sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" logging: @@ -493,7 +493,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.0" markdown: @@ -501,7 +501,7 @@ packages: description: name: markdown sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.3.0" matcher: @@ -509,7 +509,7 @@ packages: description: name: matcher sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.12.17" material_color_utilities: @@ -517,23 +517,23 @@ packages: description: name: material_color_utilities sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" nested: @@ -541,7 +541,7 @@ packages: description: name: nested sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" node_preamble: @@ -549,7 +549,7 @@ packages: description: name: node_preamble sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.2" package_config: @@ -557,23 +557,23 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" pana: dependency: transitive description: name: pana - sha256: "424b9e8523030e21ceeb9712a8b09c49e8e0e50d55845af70efd3238f6a73fa2" - url: "https://pub.flutter-io.cn" + sha256: "43a77d3aca0eb0f89b49030308b3b32745955a4f1bf4875e7b564a12ea9bb6d7" + url: "https://pub.dev" source: hosted - version: "0.22.21" + version: "0.22.22" path: dependency: transitive description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" path_provider: @@ -581,7 +581,7 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.5" path_provider_android: @@ -589,7 +589,7 @@ packages: description: name: path_provider_android sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.22" path_provider_foundation: @@ -597,7 +597,7 @@ packages: description: name: path_provider_foundation sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.1" path_provider_linux: @@ -605,7 +605,7 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -613,7 +613,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" path_provider_windows: @@ -621,7 +621,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" petitparser: @@ -629,7 +629,7 @@ packages: description: name: petitparser sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" platform: @@ -637,7 +637,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.6" plugin_platform_interface: @@ -645,7 +645,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" pool: @@ -653,7 +653,7 @@ packages: description: name: pool sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.5.2" posix: @@ -661,7 +661,7 @@ packages: description: name: posix sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.0.3" provider: @@ -669,7 +669,7 @@ packages: description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.5+1" pub_semver: @@ -677,7 +677,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" pubspec_parse: @@ -685,7 +685,7 @@ packages: description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.5.0" retry: @@ -693,7 +693,7 @@ packages: description: name: retry sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.2" rxdart: @@ -701,7 +701,7 @@ packages: description: name: rxdart sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.28.0" safe_url_check: @@ -709,7 +709,7 @@ packages: description: name: safe_url_check sha256: "49a3e060a7869cbafc8f4845ca1ecbbaaa53179980a32f4fdfeab1607e90f41d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" screen_retriever: @@ -717,7 +717,7 @@ packages: description: name: screen_retriever sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_linux: @@ -725,7 +725,7 @@ packages: description: name: screen_retriever_linux sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_macos: @@ -733,7 +733,7 @@ packages: description: name: screen_retriever_macos sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_platform_interface: @@ -741,7 +741,7 @@ packages: description: name: screen_retriever_platform_interface sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_windows: @@ -749,39 +749,39 @@ packages: description: name: screen_retriever_windows sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" sembast: dependency: transitive description: name: sembast - sha256: c8063c3146c3c8d5f5b04230de7682c768440a575fbda2634f14d22f263197c3 - url: "https://pub.flutter-io.cn" + sha256: "139cf71496105de32e7a08a4e3a1ead0f81c4a616ec9703ed07e8f0d10cdd505" + url: "https://pub.dev" source: hosted - version: "3.8.5+2" + version: "3.8.6" shared_preferences: dependency: "direct main" description: name: shared_preferences sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" - url: "https://pub.flutter-io.cn" + sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f + url: "https://pub.dev" source: hosted - version: "2.4.18" + version: "2.4.20" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.6" shared_preferences_linux: @@ -789,7 +789,7 @@ packages: description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_platform_interface: @@ -797,7 +797,7 @@ packages: description: name: shared_preferences_platform_interface sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_web: @@ -805,7 +805,7 @@ packages: description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.3" shared_preferences_windows: @@ -813,7 +813,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shelf: @@ -821,7 +821,7 @@ packages: description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.2" shelf_packages_handler: @@ -829,7 +829,7 @@ packages: description: name: shelf_packages_handler sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" shelf_static: @@ -837,7 +837,7 @@ packages: description: name: shelf_static sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.3" shelf_web_socket: @@ -845,57 +845,57 @@ packages: description: name: shelf_web_socket sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" sherpa_onnx: dependency: "direct main" description: name: sherpa_onnx - sha256: e4650ea6e0dac80b1b5f9484c4349f3634c77c29c37bf55b0a2e1704574adf57 - url: "https://pub.flutter-io.cn" + sha256: b91bc69b880860f422ec50481be98ccf42efbfa286517ad64c16ce983090323b + url: "https://pub.dev" source: hosted - version: "1.12.20" + version: "1.12.23" sherpa_onnx_android: dependency: transitive description: name: sherpa_onnx_android - sha256: a15a33a6d7f17af083e098c2321509fc7ec79a6b780bdce41024a31cea75d460 - url: "https://pub.flutter-io.cn" + sha256: "2fe7ff442b6cbd50382f64029dab024e9c7ad5825e0dce84c6b2abf58992abbe" + url: "https://pub.dev" source: hosted - version: "1.12.20" + version: "1.12.23" sherpa_onnx_ios: dependency: transitive description: name: sherpa_onnx_ios - sha256: "7afe2179a34ce543eae532a32abbe6a71cafcb12580d5530bca816fd87853cc5" - url: "https://pub.flutter-io.cn" + sha256: c060412f49edbaab53433a9dff935653d26ecdf0cdd5ef3089dff4bb1ed03b28 + url: "https://pub.dev" source: hosted - version: "1.12.20" + version: "1.12.23" sherpa_onnx_linux: dependency: transitive description: name: sherpa_onnx_linux - sha256: bd40aebdaec335b3f01ea67ddf4f604486a075fd21d7fddf19c36a9aeb7835e4 - url: "https://pub.flutter-io.cn" + sha256: "6bb9e4e5a22455941015458a778350c22f43319b86924010c94aadff2f6437c7" + url: "https://pub.dev" source: hosted - version: "1.12.20" + version: "1.12.23" sherpa_onnx_macos: dependency: transitive description: name: sherpa_onnx_macos - sha256: e0e29acd57bb701f539aece224948bfcf8d7d6f3195674c26a6353ecd93addd7 - url: "https://pub.flutter-io.cn" + sha256: "098a47c97cbe6456ef7d6a956e2a3506305e5fb1821e72396740740d81f5233a" + url: "https://pub.dev" source: hosted - version: "1.12.20" + version: "1.12.23" sherpa_onnx_windows: dependency: transitive description: name: sherpa_onnx_windows - sha256: "1512243509d02713895067f960b9532c61bb43e125dcdf5e2780a5f967c5fe35" - url: "https://pub.flutter-io.cn" + sha256: df399a629733590be6d7ac2d08876c3a9a374310ad839c3f2378fe1bb38a9c60 + url: "https://pub.dev" source: hosted - version: "1.12.20" + version: "1.12.23" sky_engine: dependency: transitive description: flutter @@ -906,7 +906,7 @@ packages: description: name: source_map_stack_trace sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" source_maps: @@ -914,7 +914,7 @@ packages: description: name: source_maps sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.10.13" source_span: @@ -922,7 +922,7 @@ packages: description: name: source_span sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.1" stack_trace: @@ -930,7 +930,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" stream_channel: @@ -938,7 +938,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" string_scanner: @@ -946,7 +946,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" synchronized: @@ -954,7 +954,7 @@ packages: description: name: synchronized sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.0" tar: @@ -962,7 +962,7 @@ packages: description: name: tar sha256: b338bacfd24dae6cf527acb4242003a71fc88ce183a9002376fabbc4ebda30c9 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.2" term_glyph: @@ -970,39 +970,39 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" - url: "https://pub.flutter-io.cn" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.flutter-io.cn" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" - url: "https://pub.flutter-io.cn" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" timezone: dependency: transitive description: name: timezone sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.10.1" typed_data: @@ -1010,7 +1010,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" url_launcher: @@ -1018,7 +1018,7 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.2" url_launcher_android: @@ -1026,7 +1026,7 @@ packages: description: name: url_launcher_android sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.28" url_launcher_ios: @@ -1034,7 +1034,7 @@ packages: description: name: url_launcher_ios sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.6" url_launcher_linux: @@ -1042,7 +1042,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" url_launcher_macos: @@ -1050,7 +1050,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -1058,23 +1058,23 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" - url: "https://pub.flutter-io.cn" + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.5" uuid: @@ -1082,7 +1082,7 @@ packages: description: name: uuid sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.5.2" vector_math: @@ -1090,7 +1090,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" vm_service: @@ -1098,7 +1098,7 @@ packages: description: name: vm_service sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "15.0.2" watcher: @@ -1106,7 +1106,7 @@ packages: description: name: watcher sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" web: @@ -1114,7 +1114,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" web_socket: @@ -1122,7 +1122,7 @@ packages: description: name: web_socket sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" web_socket_channel: @@ -1130,7 +1130,7 @@ packages: description: name: web_socket_channel sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.3" webdav_client: @@ -1138,7 +1138,7 @@ packages: description: name: webdav_client sha256: "682fffc50b61dc0e8f46717171db03bf9caaa17347be41c0c91e297553bf86b2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" webkit_inspection_protocol: @@ -1146,7 +1146,7 @@ packages: description: name: webkit_inspection_protocol sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" win32: @@ -1154,7 +1154,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.15.0" window_manager: @@ -1162,7 +1162,7 @@ packages: description: name: window_manager sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.5.1" workmanager: @@ -1170,7 +1170,7 @@ packages: description: name: workmanager sha256: "065673b2a465865183093806925419d311a9a5e0995aa74ccf8920fd695e2d10" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.0+3" workmanager_android: @@ -1178,7 +1178,7 @@ packages: description: name: workmanager_android sha256: "9ae744db4ef891f5fcd2fb8671fccc712f4f96489a487a1411e0c8675e5e8cb7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.0+2" workmanager_apple: @@ -1186,7 +1186,7 @@ packages: description: name: workmanager_apple sha256: "1cc12ae3cbf5535e72f7ba4fde0c12dd11b757caf493a28e22d684052701f2ca" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.1+2" workmanager_platform_interface: @@ -1194,7 +1194,7 @@ packages: description: name: workmanager_platform_interface sha256: f40422f10b970c67abb84230b44da22b075147637532ac501729256fcea10a47 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.1+1" xdg_directories: @@ -1202,7 +1202,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" xml: @@ -1210,7 +1210,7 @@ packages: description: name: xml sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.6.1" yaml: @@ -1218,9 +1218,9 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.3" sdks: - dart: ">=3.9.2 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" From 3424dd4bad2bfd5f56119ec4958e6c5d65ab989d Mon Sep 17 00:00:00 2001 From: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:00:10 +0800 Subject: [PATCH 7/7] feat: add daily word push MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 综合学习和FSRS练习共通 Fixes #52 Signed-off-by: OctagonalStar <76486554+OctagonalStar@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/funcs/fsrs_func.dart | 19 ++- lib/pages/learning_page.dart | 59 +++++++-- .../learning_pages/fsrs_pages.dart | 112 ++++++------------ 4 files changed, 100 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be52ed0..c822057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - 添加了听力题型 [#51](https://github.com/OctagonalStar/arabic_learning/issues/51) - 为每次学习添加了是否计算复习的提示 [#52](https://github.com/OctagonalStar/arabic_learning/issues/52) - 在复习部分添加了自我评级方案 [#52](https://github.com/OctagonalStar/arabic_learning/issues/52) +- 添加了每日单词推送功能 [#52](https://github.com/OctagonalStar/arabic_learning/issues/52) ### Improvement diff --git a/lib/funcs/fsrs_func.dart b/lib/funcs/fsrs_func.dart index af4addd..938c9aa 100644 --- a/lib/funcs/fsrs_func.dart +++ b/lib/funcs/fsrs_func.dart @@ -124,6 +124,7 @@ class FSRSConfig { final int goodDuration; final bool preferSimilar; final bool selfEvaluate; + final int pushAmount; const FSRSConfig({ bool? enabled, @@ -134,7 +135,8 @@ class FSRSConfig { int? easyDuration, int? goodDuration, bool? preferSimilar, - bool? selfEvaluate + bool? selfEvaluate, + int? pushAmount }) : enabled = enabled??false, cards = cards??const [], @@ -143,7 +145,8 @@ class FSRSConfig { easyDuration = easyDuration??3000, goodDuration = goodDuration??6000, preferSimilar = preferSimilar??false, - selfEvaluate = selfEvaluate??false; + selfEvaluate = selfEvaluate??false, + pushAmount = pushAmount??0; Map toMap(){ return { @@ -155,7 +158,8 @@ class FSRSConfig { "easyDuration": easyDuration, "goodDuration": goodDuration, "preferSimilar": preferSimilar, - "selfEvaluate": selfEvaluate + "selfEvaluate": selfEvaluate, + "pushAmount": pushAmount }; } @@ -168,7 +172,8 @@ class FSRSConfig { int? easyDuration, int? goodDuration, bool? preferSimilar, - bool? selfEvaluate + bool? selfEvaluate, + int? pushAmount }) { return FSRSConfig( enabled: enabled??this.enabled, @@ -179,7 +184,8 @@ class FSRSConfig { easyDuration: easyDuration??this.easyDuration, goodDuration: goodDuration??this.goodDuration, preferSimilar: preferSimilar??this.preferSimilar, - selfEvaluate: selfEvaluate??this.selfEvaluate + selfEvaluate: selfEvaluate??this.selfEvaluate, + pushAmount: pushAmount??this.pushAmount ); } @@ -194,7 +200,8 @@ class FSRSConfig { easyDuration: configData["easyDuration"], goodDuration: configData["goodDuration"], preferSimilar: configData["preferSimilar"], - selfEvaluate: configData["selfEvaluate"] + selfEvaluate: configData["selfEvaluate"], + pushAmount: configData["pushAmount"] ); } return FSRSConfig(enabled: false); diff --git a/lib/pages/learning_page.dart b/lib/pages/learning_page.dart index 2f35f45..cd6276c 100644 --- a/lib/pages/learning_page.dart +++ b/lib/pages/learning_page.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:arabic_learning/sub_pages_builder/setting_pages/questions_setting_page.dart' show QuestionsSettingPage; import 'package:arabic_learning/vars/config_structure.dart'; import 'package:flutter/material.dart'; @@ -7,7 +9,7 @@ import 'package:arabic_learning/funcs/ui.dart'; import 'package:arabic_learning/funcs/utili.dart'; import 'package:arabic_learning/vars/global.dart'; import 'package:arabic_learning/vars/statics_var.dart'; -import 'package:arabic_learning/sub_pages_builder/learning_pages/fsrs_pages.dart' show ForeFSRSSettingPage; +import 'package:arabic_learning/sub_pages_builder/learning_pages/fsrs_pages.dart' show FSRSLearningPage, ForeFSRSSettingPage; import 'package:arabic_learning/sub_pages_builder/learning_pages/learning_pages_build.dart'; class LearningPage extends StatelessWidget { @@ -67,13 +69,19 @@ class LearningPage extends StatelessWidget { ), ), onPressed: (){ - context.read().uiLogger.info("跳转: LearningPage => ForeFSRSSettingPage"); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ForeFSRSSettingPage() - ) - ); + if(context.read().globalFSRS.getWillDueCount() != 0) { + context.read().uiLogger.info("跳转: LearningPage => ForeFSRSSettingPage"); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ForeFSRSSettingPage() + ) + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("目前没有要复习的单词"), duration: Duration(seconds: 1),), + ); + } }, child: FittedBox( fit: BoxFit.contain, @@ -88,6 +96,41 @@ class LearningPage extends StatelessWidget { ], ), SizedBox(height: mediaQuery.size.height * 0.05), + if(context.read().globalFSRS.config.pushAmount != 0) ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), + fixedSize: Size(mediaQuery.size.width * 0.7, mediaQuery.size.height * 0.2), + shape: RoundedRectangleBorder(borderRadius: StaticsVar.br), + ), + onPressed: (){ + final DateTime now = DateTime.now(); + final int seed = now.year * 10000 + now.month * 100 + now.day; + final List pushWords = []; + final Random rnd = Random(seed); + for(int i = 0; i < context.read().globalFSRS.config.pushAmount; i++){ + int chosen = rnd.nextInt(context.read().wordData.words.length); + if(!context.read().globalFSRS.isContained(chosen)) { + pushWords.add(context.read().wordData.words.elementAt(chosen)); + } + } + if(pushWords.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("今日的推送已完成"), duration: Duration(seconds: 1),), + ); + return; + } + context.read().uiLogger.info("跳转: LearningPage => FSRSLearningPage"); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FSRSLearningPage(fsrs: context.read().globalFSRS, words: pushWords) + ) + ); + }, + icon: Icon(Icons.push_pin, size: 24), + label: Text("学习推送单词", style: TextStyle(fontSize: 40.0, fontWeight: FontWeight.bold)), + ), + SizedBox(height: mediaQuery.size.height * 0.05), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150), diff --git a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart index 2c3ef40..52d0035 100644 --- a/lib/sub_pages_builder/learning_pages/fsrs_pages.dart +++ b/lib/sub_pages_builder/learning_pages/fsrs_pages.dart @@ -18,21 +18,18 @@ class ForeFSRSSettingPage extends StatelessWidget { @override Widget build(BuildContext context) { context.read().uiLogger.info("构建 ForeFSRSSettingPage"); - MediaQueryData mediaQuery = MediaQuery.of(context); final FSRS fsrs = context.read().globalFSRS; if(fsrs.config.enabled && !forceChoosing) { return MainFSRSPage(fsrs: fsrs); } return Scaffold( appBar: AppBar( - title: const Text("FSRS-抗遗忘学习 预设置"), + title: const Text("单词规律复习设置"), ), body: StatefulBuilder( builder: (context, setState) { return ListView( children: [ - TextContainer(text: "本软件通过FSRS(Forgetting Spaced Repetition System)遗忘曲线的间隔重复学习算法,帮助用户更有效地记忆单词。\n你可以通过调整以下参数来个性化学习方案:"), - SizedBox(height: mediaQuery.size.height * 0.02), TextContainer(text: "参数配置", textAlign: TextAlign.center), Container( decoration: BoxDecoration( @@ -168,6 +165,39 @@ class ForeFSRSSettingPage extends StatelessWidget { ], ), ), + Container( + decoration: BoxDecoration( + borderRadius: StaticsVar.br, + color: Theme.of(context).colorScheme.onSecondary + ), + margin: EdgeInsets.all(8.0), + padding: EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: Text("每日单词推送", style: Theme.of(context).textTheme.bodyLarge)), + Slider( + max: 20.0, + min: 0.0, + divisions: 20, + value: fsrs.config.pushAmount.toDouble(), + onChanged: (double value){ + setState(() { + fsrs.config = fsrs.config.copyWith(pushAmount: value.round()); + }); + }, + label: fsrs.config.pushAmount == 0 ? "禁用" : fsrs.config.pushAmount.toString(), + ) + ], + ), + Text("单词推送 开启后每天会推送新单词 但数量不一定是你所指定的(大概率会少几个) 你可以在学习页面入口进入推送单词学习"), + Text("学习的推送单词会加入复习中"), + Text("当天是否学习新单词对连胜计数没有影响 学不学可以看你心情") + ], + ), + ), if(!fsrs.config.selfEvaluate) Container( decoration: BoxDecoration( borderRadius: StaticsVar.br, @@ -226,13 +256,9 @@ class MainFSRSPage extends StatelessWidget { @override Widget build(BuildContext context) { context.read().uiLogger.info("构建 MainFSRSPage"); - bool isAnyDue = fsrs.getWillDueCount() != 0; MediaQueryData mediaQuery = MediaQuery.of(context); final PageController controller = PageController(); Random sharedRnd = Random(); - if(!isAnyDue) { - return FSRSOverViewPage(fsrs: fsrs); - } return Scaffold( appBar: AppBar( title: const Text("规律学习"), @@ -325,11 +351,6 @@ class _FSRSReviewCardPage extends State { allowAnitmation: !widget.fsrs.config.selfEvaluate, allowMutipleSelect: false, hint: "单词ID: ${widget.wordID}${choosed ? " 用时: ${end.difference(start).inMilliseconds}毫秒" : ""}", - onDisAllowMutipleSelect: (value) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("人要向前看 题要向下翻 :)"), duration: Duration(seconds: 1),), - ); - }, onSelected: (value) { setState(() { choosed = true; @@ -395,69 +416,6 @@ class _FSRSReviewCardPage extends State { } } -// 没有东西复习的时候 -class FSRSOverViewPage extends StatefulWidget { - final FSRS fsrs; - const FSRSOverViewPage({super.key, required this.fsrs}); - - @override - State createState() => _FSRSOverViewPageState(); -} - -class _FSRSOverViewPageState extends State { - @override - Widget build(BuildContext context) { - MediaQueryData mediaQuery = MediaQuery.of(context); - return Scaffold( - appBar: AppBar( - title: const Text("进度概览"), - actions: [ - IconButton( - icon: Icon(Icons.keyboard_option_key), - onPressed: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => ForeFSRSSettingPage(forceChoosing: true) - ); - }, - ), - ], - ), - body: ListView( - children: [ - TextContainer(text: "目前没有需要复习的单词!"), - SizedBox(height: mediaQuery.size.height * 0.01), - ElevatedButton.icon( - style: ElevatedButton.styleFrom( - fixedSize: Size.fromHeight(mediaQuery.size.height * 0.1) - ), - icon: const Icon(Icons.label_important, size: 24.0), - label: const Text("去学习新单词"), - onPressed: () async { - late ClassSelection selectedClasses; - late List words; - selectedClasses = await popSelectClasses(context, withCache: false, withReviewChoose: false); - if(!context.mounted || selectedClasses.selectedClass.isEmpty) return; - words = getSelectedWords(context, forceSelectClasses: selectedClasses.selectedClass, doShuffle: true, doDouble: false); - // 去除已经学习的项目 - words.removeWhere((WordItem item) => widget.fsrs.isContained(item.id)); - context.read().uiLogger.info("跳转: FSRSOverViewPage => FSRSLearningPage"); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FSRSLearningPage(words: words, fsrs: widget.fsrs,), - ) - ); - }, - ), - // TextContainer(text: "统计数据") - ], - ) - ); - } -} - // 学习新东西的页面: 展示释义 -> 选择题 class FSRSLearningPage extends StatefulWidget { final List words; @@ -491,7 +449,7 @@ class _FSRSLearningPageState extends State { if(widget.words.isEmpty) { return Scaffold( appBar: AppBar(), - body: Center(child: TextContainer(text: "你选择的课程中所有的单词都已经学习过了\n等复习吧")), + body: Center(child: TextContainer(text: "你选择的所有的单词都已经学习过了\n等复习吧")), ); } return Scaffold(