Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/funcs/fsrs_func.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class FSRS {
}

int getLeastDueCard() {
if (config.cards.isEmpty) return -1;
int leastDueIndex = 0;
for(int i = 1; i < config.cards.length; i++) {
if(config.cards[i].due.toLocal().isBefore(config.cards[leastDueIndex].due.toLocal()) && config.cards[i].due.toLocal().difference(DateTime.now()) < Duration(days: 1)) {
Expand All @@ -88,6 +89,9 @@ class FSRS {

void addWordCard(int wordId) {
logger.fine("添加复习卡片: Id: $wordId");
if (config.cards.isEmpty) {
config = config.copyWith(cards: [], reviewLogs: []);
}
// 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()));
Expand Down
33 changes: 33 additions & 0 deletions lib/funcs/utili.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,39 @@ extension StringExtensions on String {
res = res.replaceAll(RegExp(r'[،.][^]*$'), ""); // for "متواصل، متواصل"
return res;
}

/// 简单的中文释义交叉计算(字符 Jaccard 相似度)
bool hasSimilarMeaning(String other) {
// 1. 去除中文/英文常见标点符号和空格
String cleanString(String s) {
return s.replaceAll(RegExp(r'[ \(\)\.,/,。、;()\[\]【】]'), '');
}

String c1 = cleanString(this);
String c2 = cleanString(other);

if (c1.isEmpty || c2.isEmpty) return false;

// 如果一个释义完全包含了另一个,直接判定为相似(如:苹果 和 苹果,香蕉)
if(c1.contains(c2) || c2.contains(c1)) return true;

// 2. 将字串拆分为单字集合
Set<String> set1 = c1.split('').toSet();
Set<String> set2 = c2.split('').toSet();

// 3. 计算共有字符
int intersection = set1.intersection(set2).length;
// int union = set1.union(set2).length;

// 如果短词里包含任何相同的核心字,或共有汉字超过短词的 40% (应对同义替换)
int minLength = min(set1.length, set2.length);

// 如果它们很短,只要共享一个字就算(例如:走 / 行走)
if(minLength <= 2 && intersection >= 1) return true;

double similarity = intersection / minLength;
return similarity >= 0.4;
}
}

extension ListExtensions on List {
Expand Down
34 changes: 18 additions & 16 deletions lib/pages/learning_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,13 @@ class LearningPage extends StatelessWidget {
),
),
onPressed: (){
if(context.read<Global>().globalFSRS.getWillDueCount() != 0) {
context.read<Global>().uiLogger.info("跳转: LearningPage => ForeFSRSSettingPage");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ForeFSRSSettingPage()
)
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("目前没有要复习的单词"), duration: Duration(seconds: 1),),
);
}
context.read<Global>().uiLogger.info("跳转: LearningPage => ForeFSRSSettingPage");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ForeFSRSSettingPage()
)
);
},
child: FittedBox(
fit: BoxFit.contain,
Expand All @@ -103,15 +97,23 @@ class LearningPage extends StatelessWidget {
shape: RoundedRectangleBorder(borderRadius: StaticsVar.br),
),
onPressed: (){
if(context.read<Global>().wordData.words.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("词库为空,无法推送!请先导入词库"), duration: Duration(seconds: 1),),
);
return;
}
final DateTime now = DateTime.now();
final int seed = now.year * 10000 + now.month * 100 + now.day;
final List<WordItem> pushWords = [];
final Set<WordItem> pushWords = {};
final Random rnd = Random(seed);
for(int i = 0; i < context.read<Global>().globalFSRS.config.pushAmount; i++){
int tries = 0;
while(pushWords.length < context.read<Global>().globalFSRS.config.pushAmount && tries < context.read<Global>().globalFSRS.config.pushAmount * 10){
int chosen = rnd.nextInt(context.read<Global>().wordData.words.length);
if(!context.read<Global>().globalFSRS.isContained(chosen)) {
pushWords.add(context.read<Global>().wordData.words.elementAt(chosen));
}
tries++;
}
if(pushWords.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
Expand All @@ -123,7 +125,7 @@ class LearningPage extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FSRSLearningPage(fsrs: context.read<Global>().globalFSRS, words: pushWords)
builder: (context) => FSRSLearningPage(fsrs: context.read<Global>().globalFSRS, words: pushWords.toList())
)
);
},
Expand Down
36 changes: 33 additions & 3 deletions lib/sub_pages_builder/learning_pages/fsrs_pages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,38 @@ class ForeFSRSSettingPage extends StatelessWidget {
},
icon: Icon(Icons.done),
label: Text("确认"),
),
if (fsrs.config.enabled) Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.errorContainer,
fixedSize: Size.fromHeight(80),
shape: RoundedRectangleBorder(borderRadius: StaticsVar.br)
),
onPressed: (){
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("重置并停用 FSRS?"),
content: Text("这将永久清除所有规律学习进度及配置,且不可恢复!"),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: Text("取消")),
TextButton(
onPressed: () {
fsrs.config = const FSRSConfig(enabled: false);
fsrs.save();
Navigator.popUntil(context, (route) => route.isFirst);
},
child: Text("确认清空", style: TextStyle(color: Colors.red))
)
],
)
);
},
icon: Icon(Icons.delete_forever, color: Theme.of(context).colorScheme.error),
label: Text("重置并停用", style: TextStyle(color: Theme.of(context).colorScheme.error)),
),
)
]
);
Expand Down Expand Up @@ -292,10 +324,8 @@ class MainFSRSPage extends StatelessWidget {
}
final wordID = fsrs.getLeastDueCard();
if(wordID == -1) {
Future.delayed(
Duration(seconds: 1), (){if(context.mounted) alart(context, "今日复习任务已完成", onConfirmed: () {Navigator.pop(context);});});
return Center(
child: TextContainer(text: "今日复习任务已完成"),
child: TextContainer(text: "今日复习任务已完成\n(或当前无复习内容)\n点击右上角齿轮可修改配置"),
);
}
return FSRSReviewCardPage(wordID: wordID, fsrs: fsrs, rnd: sharedRnd, controller: controller,);
Expand Down
14 changes: 13 additions & 1 deletion lib/sub_pages_builder/setting_pages/questions_setting_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ class _QuestionsSettingPage extends State<QuestionsSettingPage> {
bool floatButtonFlod = true;
static const Map<int, String> castMap = {0: "单词卡片学习", 1: "中译阿 选择题", 2: "阿译中 选择题", 3: "中译阿 拼写题", 4: "听力题"};

void _updateConfig() {
context.read<Global>().globalConfig = context.read<Global>().globalConfig.copyWith(
quiz: context.read<Global>().globalConfig.quiz.copyWith(
zhar: context.read<Global>().globalConfig.quiz.zhar.copyWith(
questionSections: selectedTypes!.cast<int>()
)
)
);
}

@override
Widget build(BuildContext context) {
context.read<Global>().uiLogger.info("构建 QuestionsSettingPage:$selectedTypes");
late final SubQuizConfig section;
section = context.read<Global>().globalConfig.quiz.zhar;
MediaQueryData mediaQuery = MediaQuery.of(context);
selectedTypes ??= section.questionSections;
selectedTypes ??= List.from(section.questionSections);
List<Widget> listTiles = [];
bool isEven = true;
for(int index = 0; index < selectedTypes!.length; index++) {
Expand All @@ -48,6 +57,7 @@ class _QuestionsSettingPage extends State<QuestionsSettingPage> {
context.read<Global>().uiLogger.fine("移除题型项目: $index");
setState(() {
selectedTypes!.removeAt(index.toInt());
_updateConfig();
});
}
},
Expand Down Expand Up @@ -80,6 +90,7 @@ class _QuestionsSettingPage extends State<QuestionsSettingPage> {
if(oldIndex < newIndex) newIndex--; // 修正索引
int old = selectedTypes!.removeAt(oldIndex);
selectedTypes!.insert(newIndex, old);
_updateConfig();
});
},
children: listTiles,
Expand Down Expand Up @@ -194,6 +205,7 @@ class _QuestionsSettingPage extends State<QuestionsSettingPage> {
context.read<Global>().uiLogger.info("添加题型类型: $i");
setState(() {
selectedTypes!.add(i);
_updateConfig();
});
},
icon: Icon(Icons.add),
Expand Down
43 changes: 37 additions & 6 deletions lib/vars/global.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Global with ChangeNotifier {
await prefs.setString("wordData", jsonEncode({"Words": [], "Classes": {}}));
wordData = DictData(words: [], classes: []);
logger.info("首次启动: 配置表初始化完成");
globalFSRS = FSRS()..init(outerPrefs: prefs);
await postInit();
} else {
await conveySetting();
Expand Down Expand Up @@ -230,11 +231,20 @@ class Global with ChangeNotifier {
// }
DictData dataFormater(Map<String, dynamic> data, DictData exData, String sourceName) {
logger.info("开始词汇格式化");
List<String> wordList = [];
for(WordItem x in exData.words) {
wordList.add(x.arabic);

// Use Maps for O(1) lookup speed instead of O(N) List.indexOf
Map<String, int> rawWordMap = {};
Map<String, int> pureWordMap = {};
List<String> chineseList = [];

for(int i = 0; i < exData.words.length; i++) {
WordItem x = exData.words[i];
rawWordMap[x.arabic] = i;
pureWordMap[x.arabic.removeAracicExtensionPart().trim()] = i;
chineseList.add(x.chinese); // Keep list for indexing since it maps 1:1 with word id
}
int counter = wordList.length;

int counter = exData.words.length;

SourceItem? exSource;
// 查找已有数据中是否有同名的源数据组
Expand All @@ -249,9 +259,28 @@ class Global with ChangeNotifier {
for(var className in data.keys){
ClassItem exClass = ClassItem(className: className, wordIndexs: []);
for(var word in data[className]){
if(wordList.contains(word["arabic"])){
String newRaw = word["arabic"];
String newPure = newRaw.removeAracicExtensionPart().trim();
int existingIndex = -1;

if (rawWordMap.containsKey(newRaw)) {
existingIndex = rawWordMap[newRaw]!;
} else if (pureWordMap.containsKey(newPure)) {
int potentialIndex = pureWordMap[newPure]!;
// Pure arabic is the same, but different vowels. Are they the same meaning?
if (chineseList[potentialIndex].hasSimilarMeaning(word["chinese"])) {
existingIndex = potentialIndex;
}
}

if (existingIndex != -1) {
// If it already exists globally, just add it to this class
if(!exClass.wordIndexs.contains(existingIndex)) {
exClass.wordIndexs.add(existingIndex);
}
continue;
}

exClass.wordIndexs.add(counter);
exData.words.add(
WordItem(
Expand All @@ -262,7 +291,9 @@ class Global with ChangeNotifier {
id: counter
)
);
wordList.add(word["arabic"]);
rawWordMap[newRaw] = counter;
pureWordMap[newPure] = counter;
chineseList.add(word["chinese"]);
counter ++;
}
exSource.subClasses.add(exClass);
Expand Down