@@ -29,6 +29,28 @@ class FSRS {
2929 } else {
3030 config = FSRSConfig .buildFromMap (jsonDecode (appData.storage.getString ("fsrsData" )! ));
3131 logger.info ("FSRS配置加载完成" );
32+
33+ // 清洗潜在的重复脏数据 (Deduplication)
34+ final Set <int > seenIds = {};
35+ final List <Card > uniqueCards = [];
36+ final List <ReviewLog > uniqueLogs = [];
37+
38+ for (int i = 0 ; i < config.cards.length; i++ ) {
39+ final currentCardId = config.cards[i].cardId;
40+ if (! seenIds.contains (currentCardId)) {
41+ seenIds.add (currentCardId);
42+ uniqueCards.add (config.cards[i]);
43+ if (i < config.reviewLogs.length) {
44+ uniqueLogs.add (config.reviewLogs[i]);
45+ }
46+ }
47+ }
48+
49+ if (uniqueCards.length < config.cards.length) {
50+ logger.warning ("发现并清理了 ${config .cards .length - uniqueCards .length } 条重复复习记录" );
51+ config = config.copyWith (cards: uniqueCards, reviewLogs: uniqueLogs);
52+ save ();
53+ }
3254 }
3355
3456 if (config.enabled) return true ;
@@ -50,58 +72,66 @@ class FSRS {
5072 save ();
5173 }
5274
53- int willDueIn (int index ) {
54- return config.cards[index] .due.toLocal ().difference (DateTime .now ()).inDays;
75+ int willDueIn (Card card ) {
76+ return card .due.toLocal ().difference (DateTime .now ()).inDays;
5577 }
5678
57- void reviewCard (int wordId, int duration, bool isCorrect, {Rating ? forceRate}) {
58- logger.fine ("记录复习卡片: Id: $wordId ; duration: $duration ; isCorrect: $isCorrect " );
59- int index = config.cards.indexWhere ((Card card) => card.cardId == wordId); // 避免有时候cardId != wordId
60- logger.fine ("定位复习卡片地址: $index , 目前阶段: ${config .cards [index ].step }, 难度: ${config .cards [index ].difficulty }, 稳定: ${config .cards [index ].stability }, 过期时间(+8): ${config .cards [index ].due .toLocal ()}" );
61- final (: card, : reviewLog) = config.scheduler! .reviewCard (config.cards[index], forceRate ?? calculate (duration, isCorrect), reviewDateTime: DateTime .now ().toUtc (), reviewDuration: duration);
62- config.cards[index] = card;
63- config.reviewLogs[index] = reviewLog;
64- logger.fine ("卡片 $index 复习后: 目前阶段: ${config .cards [index ].step }, 难度: ${config .cards [index ].difficulty }, 稳定: ${config .cards [index ].stability }, 过期时间(+8): ${config .cards [index ].due .toLocal ()}" );
79+ void produceCard (int wordId, {int ? duration, bool ? isCorrect, Rating ? forceRate}) {
80+ logger.fine ("记录复习卡片: Id: $wordId ; duration: $duration ; isCorrect: $isCorrect ; forceRate: $forceRate " );
81+ final int index = config.cards.indexWhere ((Card card) => card.cardId == wordId);
82+ if (index == - 1 ) {
83+ // 卡片不存在 进行添加
84+ logger.fine ("添加复习卡片: Id: $wordId " );
85+ if (config.cards.isEmpty) {
86+ config = config.copyWith (
87+ cards: [],
88+ reviewLogs: []
89+ );
90+ }
91+ config.cards.add (Card (cardId: wordId, state: State .learning));
92+ config.reviewLogs.add (ReviewLog (cardId: wordId, rating: Rating .good, reviewDateTime: DateTime .now ()));
93+ } else {
94+ // 卡片存在 进行复习
95+ if ((duration == null || isCorrect == null ) && forceRate == null ) {
96+ logger.shout ("传入信息缺失: wordId: $wordId ; duration: $duration ; isCorrect: $isCorrect ; forceRate: $forceRate " );
97+ return ; // 避免错误信息导入
98+ }
99+ logger.fine ("定位复习卡片地址: $index , 目前阶段: ${config .cards [index ].step }, 难度: ${config .cards [index ].difficulty }, 稳定: ${config .cards [index ].stability }, 过期时间(+8): ${config .cards [index ].due .toLocal ()}" );
100+ final (: card, : reviewLog) = config.scheduler! .reviewCard (config.cards[index], forceRate ?? calculate (duration! , isCorrect! ), reviewDateTime: DateTime .now ().toUtc (), reviewDuration: duration);
101+ config.cards[index] = card;
102+ config.reviewLogs[index] = reviewLog;
103+ logger.fine ("卡片 $index 复习后: 目前阶段: ${config .cards [index ].step }, 难度: ${config .cards [index ].difficulty }, 稳定: ${config .cards [index ].stability }, 过期时间(+8): ${config .cards [index ].due .toLocal ()}" );
104+ }
65105 save ();
66106 }
67107
68108 int getWillDueCount () {
69109 int dueCards = 0 ;
70- for (int i = 0 ; i < config.cards.length; i ++ ) {
71- if (willDueIn (i ) < 1 ) {
110+ for (Card card in config.cards) {
111+ if (willDueIn (card ) < 1 ) {
72112 dueCards++ ;
73113 }
74114 }
75115 return dueCards;
76116 }
77117
78118 int getLeastDueCard () {
79- if (config.cards.isEmpty) return - 1 ;
80- int leastDueIndex = 0 ;
81- for (int i = 1 ; i < config.cards.length; i++ ) {
82- if (config.cards[i].due.toLocal ().isBefore (config.cards[leastDueIndex].due.toLocal ()) && config.cards[i].due.toLocal ().difference (DateTime .now ()) < Duration (days: 1 )) {
83- leastDueIndex = i;
119+ Card ? leastDueCard;
120+ for (Card card in config.cards) {
121+ if (willDueIn (card) < 1 ) {
122+ if (leastDueCard == null || card.due.toLocal ().isBefore (leastDueCard.due.toLocal ())) {
123+ leastDueCard = card;
124+ }
84125 }
85126 }
86- if (config.cards[leastDueIndex].due. difference ( DateTime . now ()) > Duration (days : 1 ) ) return - 1 ;
87- return config.cards[leastDueIndex] .cardId;
127+ if (leastDueCard == null ) return - 1 ;
128+ return leastDueCard .cardId;
88129 }
89130
90131 bool isContained (int wordId) {
91132 return config.cards.any ((Card card) => card.cardId == wordId);
92133 }
93134
94- void addWordCard (int wordId) {
95- logger.fine ("添加复习卡片: Id: $wordId " );
96- if (config.cards.isEmpty) {
97- config = config.copyWith (cards: [], reviewLogs: []);
98- }
99- // os the wordID == cardID
100- config.cards.add (Card (cardId: wordId, state: State .learning));
101- config.reviewLogs.add (ReviewLog (cardId: wordId, rating: Rating .good, reviewDateTime: DateTime .now ()));
102- save ();
103- }
104-
105135 Rating calculate (int duration, bool isCorrect) {
106136 // duration in milliseconds
107137 if (! isCorrect) {
@@ -133,6 +163,7 @@ class FSRSConfig {
133163 final bool preferSimilar;
134164 final bool selfEvaluate;
135165 final int pushAmount;
166+ final bool reinforceMemory;
136167
137168 const FSRSConfig ({
138169 bool ? enabled,
@@ -144,7 +175,8 @@ class FSRSConfig {
144175 int ? goodDuration,
145176 bool ? preferSimilar,
146177 bool ? selfEvaluate,
147- int ? pushAmount
178+ int ? pushAmount,
179+ bool ? reinforceMemory
148180 }) :
149181 enabled = enabled?? false ,
150182 cards = cards?? const [],
@@ -154,7 +186,8 @@ class FSRSConfig {
154186 goodDuration = goodDuration?? 6000 ,
155187 preferSimilar = preferSimilar?? false ,
156188 selfEvaluate = selfEvaluate?? false ,
157- pushAmount = pushAmount?? 0 ;
189+ pushAmount = pushAmount?? 0 ,
190+ reinforceMemory = reinforceMemory?? false ;
158191
159192 Map <String , dynamic > toMap (){
160193 return {
@@ -167,7 +200,8 @@ class FSRSConfig {
167200 "goodDuration" : goodDuration,
168201 "preferSimilar" : preferSimilar,
169202 "selfEvaluate" : selfEvaluate,
170- "pushAmount" : pushAmount
203+ "pushAmount" : pushAmount,
204+ "reinforceMemory" : reinforceMemory
171205 };
172206 }
173207
@@ -181,7 +215,8 @@ class FSRSConfig {
181215 int ? goodDuration,
182216 bool ? preferSimilar,
183217 bool ? selfEvaluate,
184- int ? pushAmount
218+ int ? pushAmount,
219+ bool ? reinforceMemory
185220 }) {
186221 return FSRSConfig (
187222 enabled: enabled?? this .enabled,
@@ -193,7 +228,8 @@ class FSRSConfig {
193228 goodDuration: goodDuration?? this .goodDuration,
194229 preferSimilar: preferSimilar?? this .preferSimilar,
195230 selfEvaluate: selfEvaluate?? this .selfEvaluate,
196- pushAmount: pushAmount?? this .pushAmount
231+ pushAmount: pushAmount?? this .pushAmount,
232+ reinforceMemory: reinforceMemory?? this .reinforceMemory
197233 );
198234 }
199235
@@ -209,7 +245,8 @@ class FSRSConfig {
209245 goodDuration: configData["goodDuration" ],
210246 preferSimilar: configData["preferSimilar" ],
211247 selfEvaluate: configData["selfEvaluate" ],
212- pushAmount: configData["pushAmount" ]
248+ pushAmount: configData["pushAmount" ],
249+ reinforceMemory: configData["reinforceMemory" ]
213250 );
214251 }
215252 return FSRSConfig (enabled: false );
0 commit comments