Skip to content

Commit 500cade

Browse files
Merge pull request #37 from OctagonalStar/refactor/setting-structure
Refactor/setting-structure
2 parents 4cffb21 + bca1af2 commit 500cade

22 files changed

+1248
-545
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
### Improvement
1212

1313
- 优化了网页端字体加载逻辑
14+
- 重构了Config数据结构 [#27](https://github.com/OctagonalStar/arabic_learning/issues/27)
15+
- 重构了软件运行时数据结构 [#16](https://github.com/OctagonalStar/arabic_learning/issues/16)
1416

1517
### Fix
1618

lib/funcs/fsrs_func.dart

Lines changed: 104 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,69 @@
11
import 'dart:convert';
22

3-
import 'package:arabic_learning/funcs/utili.dart';
3+
import 'package:flutter/foundation.dart';
44
import 'package:fsrs/fsrs.dart';
55
import 'package:logging/logging.dart';
66

77
import 'package:arabic_learning/package_replacement/storage.dart';
88

99
class FSRS {
10-
List<Card> cards = [];
11-
List<ReviewLog> reviewLogs = [];
12-
late Scheduler scheduler;
1310
late final SharedPreferences prefs;
14-
late Map<String, dynamic> settingData;
11+
late FSRSConfig config;
1512
late final Logger logger;
1613
// index != cardId; cardId = wordId = the index of word in global.wordData[words]
1714

1815
bool init({required SharedPreferences outerPrefs}) {
1916
prefs = outerPrefs;
2017
logger = Logger('FSRS');
2118
logger.fine("构建FSRS模块");
22-
settingData = {
23-
'enabled': false,
24-
'scheduler': {},
25-
'cards': [],
26-
'reviewLog': [],
27-
'rater': {
28-
"desiredRetention": 0.9,
29-
"easyDuration": 3000,
30-
"goodDuration": 6000
31-
},
32-
};
3319

3420
if(!prefs.containsKey("fsrsData")) {
3521
logger.info("未发现FSRS配置,加载默认配置");
36-
prefs.setString("fsrsData", jsonEncode(settingData));
22+
config = FSRSConfig();
23+
prefs.setString("fsrsData", jsonEncode(config));
3724
return false;
3825
} else {
39-
settingData = deepMerge(settingData, jsonDecode(prefs.getString("fsrsData")!));
40-
}
41-
42-
if(isEnabled()){
43-
scheduler = Scheduler.fromMap(settingData['scheduler']);
44-
for(int i = 0; i < settingData['cards'].length; i++) {
45-
cards.add(Card.fromMap(settingData['cards'][i]));
46-
reviewLogs.add(ReviewLog.fromMap(settingData['reviewLog'][i]));
47-
}
26+
config = FSRSConfig.buildFromMap(jsonDecode(prefs.getString("fsrsData")!));
4827
logger.info("FSRS配置加载完成");
49-
return true;
5028
}
29+
30+
if(config.enabled) return true;
5131
logger.info("FSRS未启用");
5232
return false;
5333
}
5434

5535
void save() async {
5636
logger.info("正在保存FSRS配置");
57-
settingData['scheduler'] = scheduler.toMap();
58-
List cardsCache = [];
59-
List logCache = [];
60-
for(int i = 0; i < cards.length; i++) {
61-
cardsCache.add(cards[i].toMap());
62-
logCache.add(reviewLogs[i].toMap());
63-
}
64-
settingData['cards'] = cardsCache;
65-
settingData['reviewLog'] = logCache;
66-
prefs.setString("fsrsData", jsonEncode(settingData));
67-
}
68-
69-
bool isEnabled() {
70-
return settingData['enabled'];
37+
prefs.setString("fsrsData", jsonEncode(config.toMap()));
7138
}
7239

7340
void createScheduler({required SharedPreferences prefs}) {
74-
logger.info("初始化scheduler,选择相关配置 ${settingData["rater"].toString()}");
75-
scheduler = Scheduler(desiredRetention: settingData["rater"]["desiredRetention"]);
76-
settingData['enabled'] = true;
77-
settingData['scheduler'] = scheduler.toMap();
41+
logger.info("初始化scheduler,选择相关配置 ${config.toMap().toString()}");
42+
config = config.copyWith(
43+
enabled: true,
44+
scheduler: Scheduler(desiredRetention: config.desiredRetention)
45+
);
7846
save();
7947
}
8048

8149
int willDueIn(int index) {
82-
return cards[index].due.toLocal().difference(DateTime.now()).inDays;
50+
return config.cards[index].due.toLocal().difference(DateTime.now()).inDays;
8351
}
8452

8553
void reviewCard(int wordId, int duration, bool isCorrect) {
8654
logger.fine("记录复习卡片: Id: $wordId; duration: $duration; isCorrect: $isCorrect");
87-
int index = cards.indexWhere((Card card) => card.cardId == wordId); // 避免有时候cardId != wordId
88-
logger.fine("定位复习卡片地址: $index, 目前阶段: ${cards[index].step}, 难度: ${cards[index].difficulty}, 稳定: ${cards[index].stability}, 过期时间(+8): ${cards[index].due.toLocal()}");
89-
final (:card, :reviewLog) = scheduler.reviewCard(cards[index], calculate(duration, isCorrect), reviewDateTime: DateTime.now().toUtc(), reviewDuration: duration);
90-
cards[index] = card;
91-
reviewLogs[index] = reviewLog;
92-
logger.fine("卡片 $index 复习后: 目前阶段: ${cards[index].step}, 难度: ${cards[index].difficulty}, 稳定: ${cards[index].stability}, 过期时间(+8): ${cards[index].due.toLocal()}");
55+
int index = config.cards.indexWhere((Card card) => card.cardId == wordId); // 避免有时候cardId != wordId
56+
logger.fine("定位复习卡片地址: $index, 目前阶段: ${config.cards[index].step}, 难度: ${config.cards[index].difficulty}, 稳定: ${config.cards[index].stability}, 过期时间(+8): ${config.cards[index].due.toLocal()}");
57+
final (:card, :reviewLog) = config.scheduler!.reviewCard(config.cards[index], calculate(duration, isCorrect), reviewDateTime: DateTime.now().toUtc(), reviewDuration: duration);
58+
config.cards[index] = card;
59+
config.reviewLogs[index] = reviewLog;
60+
logger.fine("卡片 $index 复习后: 目前阶段: ${config.cards[index].step}, 难度: ${config.cards[index].difficulty}, 稳定: ${config.cards[index].stability}, 过期时间(+8): ${config.cards[index].due.toLocal()}");
9361
save();
9462
}
9563

9664
int getWillDueCount() {
9765
int dueCards = 0;
98-
for(int i = 0; i < cards.length; i++) {
66+
for(int i = 0; i < config.cards.length; i++) {
9967
if(willDueIn(i) < 1) {
10068
dueCards++;
10169
}
@@ -105,26 +73,26 @@ class FSRS {
10573

10674
int getLeastDueCard() {
10775
int leastDueIndex = 0;
108-
for(int i = 1; i < cards.length; i++) {
109-
if(cards[i].due.toLocal().isBefore(cards[leastDueIndex].due.toLocal()) && cards[i].due.toLocal().difference(DateTime.now()) < Duration(days: 1)) {
76+
for(int i = 1; i < config.cards.length; i++) {
77+
if(config.cards[i].due.toLocal().isBefore(config.cards[leastDueIndex].due.toLocal()) && config.cards[i].due.toLocal().difference(DateTime.now()) < Duration(days: 1)) {
11078
leastDueIndex = i;
11179
}
11280
}
113-
if(cards[leastDueIndex].due.difference(DateTime.now()) > Duration(days: 1)) return -1;
114-
return cards[leastDueIndex].cardId;
81+
if(config.cards[leastDueIndex].due.difference(DateTime.now()) > Duration(days: 1)) return -1;
82+
return config.cards[leastDueIndex].cardId;
11583
}
11684

11785
bool isContained(int wordId) {
118-
for(Card card in cards) {
86+
for(Card card in config.cards) {
11987
if(card.cardId == wordId) return true;
12088
}
12189
return false;
12290
}
12391

12492
void addWordCard(int wordId) {
12593
// os the wordID == cardID
126-
cards.add(Card(cardId: wordId, state: State.learning));
127-
reviewLogs.add(ReviewLog(cardId: wordId, rating: Rating.good, reviewDateTime: DateTime.now()));
94+
config.cards.add(Card(cardId: wordId, state: State.learning));
95+
config.reviewLogs.add(ReviewLog(cardId: wordId, rating: Rating.good, reviewDateTime: DateTime.now()));
12896
save();
12997
}
13098

@@ -134,15 +102,89 @@ class FSRS {
134102
logger.fine("计算得分: again");
135103
return Rating.again;
136104
}
137-
if (duration < settingData['rater']['easyDuration']) {
105+
if (duration < config.easyDuration) {
138106
logger.fine("计算得分: easy");
139107
return Rating.easy;
140108
}
141-
if (duration < settingData['rater']['goodDuration']) {
109+
if (duration < config.goodDuration) {
142110
logger.fine("计算得分: good");
143111
return Rating.good;
144112
}
145113
logger.fine("计算得分: hard");
146114
return Rating.hard;
147115
}
116+
}
117+
118+
@immutable
119+
class FSRSConfig {
120+
final bool enabled;
121+
final Scheduler? scheduler;
122+
final List<Card> cards;
123+
final List<ReviewLog> reviewLogs;
124+
final double desiredRetention;
125+
final int easyDuration;
126+
final int goodDuration;
127+
128+
const FSRSConfig({
129+
bool? enabled,
130+
this.scheduler,
131+
List<Card>? cards,
132+
List<ReviewLog>? reviewLogs,
133+
double? desiredRetention,
134+
int? easyDuration,
135+
int? goodDuration
136+
}) :
137+
enabled = enabled??false,
138+
cards = cards??const [],
139+
reviewLogs = reviewLogs??const [],
140+
desiredRetention = desiredRetention??0.9,
141+
easyDuration = easyDuration??3000,
142+
goodDuration = goodDuration??6000;
143+
144+
Map<String, dynamic> toMap(){
145+
return {
146+
'enabled': enabled,
147+
'scheduler': scheduler?.toMap() ?? {},
148+
'cards': List<Map>.generate(cards.length, (index) => cards[index].toMap(), growable: false),
149+
'reviewLog': List<Map>.generate(reviewLogs.length, (index) => reviewLogs[index].toMap(), growable: false),
150+
"desiredRetention": desiredRetention,
151+
"easyDuration": easyDuration,
152+
"goodDuration": goodDuration
153+
};
154+
}
155+
156+
FSRSConfig copyWith({
157+
bool? enabled,
158+
Scheduler? scheduler,
159+
List<Card>? cards,
160+
List<ReviewLog>? reviewLogs,
161+
double? desiredRetention,
162+
int? easyDuration,
163+
int? goodDuration
164+
}) {
165+
return FSRSConfig(
166+
enabled: enabled??this.enabled,
167+
scheduler: scheduler??this.scheduler,
168+
cards: cards??this.cards,
169+
reviewLogs: reviewLogs??this.reviewLogs,
170+
desiredRetention: desiredRetention??this.desiredRetention,
171+
easyDuration: easyDuration??this.easyDuration,
172+
goodDuration: goodDuration??this.goodDuration
173+
);
174+
}
175+
176+
static FSRSConfig buildFromMap(Map<String, dynamic> configData){
177+
if(configData["enabled"]??false) {
178+
return FSRSConfig(
179+
enabled: configData["enabled"],
180+
scheduler: Scheduler.fromMap(configData["scheduler"]),
181+
cards: List<Card>.generate(configData["cards"].length,(index) => Card.fromMap(configData["cards"][index]), growable: true),
182+
reviewLogs: List<ReviewLog>.generate(configData["reviewLog"].length,(index) => ReviewLog.fromMap(configData["reviewLog"][index]), growable: true),
183+
desiredRetention: configData["desiredRetention"],
184+
easyDuration: configData["easyDuration"],
185+
goodDuration: configData["goodDuration"]
186+
);
187+
}
188+
return FSRSConfig(enabled: false);
189+
}
148190
}

lib/funcs/sync.dart

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'dart:convert';
2-
import 'dart:typed_data' show Uint8List;
32

43
import 'package:webdav_client/webdav_client.dart';
54
import 'package:logging/logging.dart';
@@ -14,7 +13,6 @@ class WebDAV {
1413

1514
bool isReachable = false;
1615
bool isReadable = false;
17-
bool isWriteable = false;
1816

1917
late Client client;
2018
final Logger logger = Logger("WebDAV");
@@ -57,14 +55,6 @@ class WebDAV {
5755
tempLogger.warning("PING: 可读性测试错误: $e");
5856
return [true, false, 'no read access: $e'];
5957
}
60-
try{
61-
await tempClient.write("TestFile", Uint8List(64)); // test for write
62-
await tempClient.remove("TestFile");
63-
tempLogger.fine("WRITE: 可写性测试成功");
64-
} catch (e) {
65-
tempLogger.warning("PING: 可写性测试错误: $e");
66-
return [true, false, 'no write access: $e'];
67-
}
6858
tempLogger.info("所有测试均通过");
6959
return [true, true, 'ok'];
7060
}
@@ -85,24 +75,18 @@ class WebDAV {
8575
isReachable = true;
8676
await client.readDir(''); // test for read
8777
isReadable = true;
88-
await client.write("TestFile", Uint8List(64)); // test for write
89-
isWriteable = true;
90-
await client.remove("TestFile");
9178
} catch (e) {
9279
logger.warning("链接中出现错误: $e");
9380
rethrow;
9481
}
9582
}
9683

97-
Future<void> upload(SharedPreferences pref,{bool force = false}) async {
98-
logger.info("开始上传文件: 服务可写: $isWriteable; force: $force");
84+
Future<void> upload(SharedPreferences pref) async {
85+
logger.info("开始上传文件:");
9986
try {
100-
if(isWriteable || force) {
10187
await client.write("arabic_learning.bak", utf8.encode(jsonEncode(pref.export())));
10288
logger.info("文件上传成功");
10389
return;
104-
}
105-
throw Exception("服务不可写,取消上传");
10690
} catch (e) {
10791
logger.warning("WebDAV上传失败: $e");
10892
rethrow;

0 commit comments

Comments
 (0)