Skip to content

Commit e1e1375

Browse files
Merge branch 'main' into main
2 parents f354bd8 + f782263 commit e1e1375

28 files changed

+1011
-795
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Flutter Build(PR)
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
7+
jobs:
8+
build-windows:
9+
name: Build Windows App
10+
runs-on: windows-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Setup Flutter
15+
uses: subosito/flutter-action@v2
16+
with:
17+
channel: stable
18+
flutter-version: '3.35.5'
19+
20+
- run: flutter pub get
21+
- run: flutter build windows
22+
23+
- uses: actions/upload-artifact@v4
24+
with:
25+
name: windows-build
26+
path: build/windows/x64/runner/Release
27+
28+
build-web:
29+
name: Build Flutter Web
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
- uses: subosito/flutter-action@v2
34+
with:
35+
flutter-version: '3.35.5'
36+
- run: flutter pub get
37+
- run: flutter build web --release
38+
- uses: actions/upload-artifact@v4
39+
with:
40+
name: web-build
41+
path: build/web

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@
22

33
## v?.?.?? - ????-??-?? - (??????)
44

5+
### Added
6+
7+
- 添加了日志捕获 [#23](https://github.com/OctagonalStar/arabic_learning/issues/23)
8+
- 添加了调试页面
9+
- 添加了个性化FSRS预设页面 [#26](https://github.com/OctagonalStar/arabic_learning/issues/26)
10+
511
### Improvement
612

713
- 优化了网页端字体加载逻辑
814

15+
### Fix
16+
17+
- 修复了FSRS算法对已经过期的单词无法计数的问题
18+
- 修复了日志中FSRS信息输出错误的问题
19+
920
## v0.1.11 - 2025-11-28 - (000111)
1021

1122
### Added

lib/funcs/fsrs_func.dart

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,59 @@
11
import 'dart:convert';
2-
import 'package:arabic_learning/package_replacement/storage.dart';
2+
3+
import 'package:arabic_learning/funcs/utili.dart';
34
import 'package:fsrs/fsrs.dart';
5+
import 'package:logging/logging.dart';
6+
7+
import 'package:arabic_learning/package_replacement/storage.dart';
48

59
class FSRS {
610
List<Card> cards = [];
711
List<ReviewLog> reviewLogs = [];
812
late Scheduler scheduler;
9-
late SharedPreferences prefs;
10-
late Rater rater;
13+
late final SharedPreferences prefs;
1114
late Map<String, dynamic> settingData;
12-
15+
late final Logger logger;
1316
// index != cardId; cardId = wordId = the index of word in global.wordData[words]
1417

15-
Future<bool> init() async {
16-
prefs = await SharedPreferences.getInstance();
18+
bool init({required SharedPreferences outerPrefs}) {
19+
prefs = outerPrefs;
20+
logger = Logger('FSRS');
21+
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+
};
33+
1734
if(!prefs.containsKey("fsrsData")) {
18-
settingData = {
19-
'enabled': false,
20-
'scheduler': {},
21-
'cards': [],
22-
'reviewLog': [],
23-
'rater': {'scheme': 0},
24-
};
35+
logger.info("未发现FSRS配置,加载默认配置");
2536
prefs.setString("fsrsData", jsonEncode(settingData));
2637
return false;
38+
} else {
39+
settingData = deepMerge(settingData, jsonDecode(prefs.getString("fsrsData")!));
2740
}
28-
settingData = jsonDecode(prefs.getString("fsrsData")!) as Map<String, dynamic>;
41+
2942
if(isEnabled()){
3043
scheduler = Scheduler.fromMap(settingData['scheduler']);
3144
for(int i = 0; i < settingData['cards'].length; i++) {
3245
cards.add(Card.fromMap(settingData['cards'][i]));
3346
reviewLogs.add(ReviewLog.fromMap(settingData['reviewLog'][i]));
3447
}
35-
rater = Rater(settingData['rater']['scheme']);
48+
logger.info("FSRS配置加载完成");
3649
return true;
3750
}
51+
logger.info("FSRS未启用");
3852
return false;
3953
}
4054

4155
void save() async {
56+
logger.info("正在保存FSRS配置");
4257
settingData['scheduler'] = scheduler.toMap();
4358
List cardsCache = [];
4459
List logCache = [];
@@ -55,33 +70,33 @@ class FSRS {
5570
return settingData['enabled'];
5671
}
5772

58-
Future<void> createScheduler(int scheme) async {
59-
await init();
60-
List<double> desiredRetention = [0.85, 0.9, 0.95, 0.95, 0.99];
61-
scheduler = Scheduler(desiredRetention: desiredRetention[scheme]);
62-
settingData['rater']['scheme'] = scheme;
73+
void createScheduler({required SharedPreferences prefs}) {
74+
logger.info("初始化scheduler,选择相关配置 ${settingData["rater"].toString()}");
75+
scheduler = Scheduler(desiredRetention: settingData["rater"]["desiredRetention"]);
6376
settingData['enabled'] = true;
6477
settingData['scheduler'] = scheduler.toMap();
65-
rater = Rater(scheme);
6678
save();
6779
}
6880

6981
int willDueIn(int index) {
70-
return cards[index].due.difference(DateTime.now()).inDays;
82+
return cards[index].due.toLocal().difference(DateTime.now()).inDays;
7183
}
7284

7385
void reviewCard(int wordId, int duration, bool isCorrect) {
86+
logger.fine("记录复习卡片: Id: $wordId; duration: $duration; isCorrect: $isCorrect");
7487
int index = cards.indexWhere((Card card) => card.cardId == wordId); // 避免有时候cardId != wordId
75-
final (:card, :reviewLog) = scheduler.reviewCard(cards[index], rater.calculate(duration, isCorrect));
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);
7690
cards[index] = card;
7791
reviewLogs[index] = reviewLog;
92+
logger.fine("卡片 $index 复习后: 目前阶段: ${cards[index].step}, 难度: ${cards[index].difficulty}, 稳定: ${cards[index].stability}, 过期时间(+8): ${cards[index].due.toLocal()}");
7893
save();
7994
}
8095

8196
int getWillDueCount() {
8297
int dueCards = 0;
8398
for(int i = 0; i < cards.length; i++) {
84-
if(willDueIn(i) == 0) {
99+
if(willDueIn(i) < 1) {
85100
dueCards++;
86101
}
87102
}
@@ -91,7 +106,7 @@ class FSRS {
91106
int getLeastDueCard() {
92107
int leastDueIndex = 0;
93108
for(int i = 1; i < cards.length; i++) {
94-
if(cards[i].due.isBefore(cards[leastDueIndex].due) && cards[i].due.difference(DateTime.now()) < Duration(days: 1)) {
109+
if(cards[i].due.toLocal().isBefore(cards[leastDueIndex].due.toLocal()) && cards[i].due.toLocal().difference(DateTime.now()) < Duration(days: 1)) {
95110
leastDueIndex = i;
96111
}
97112
}
@@ -112,31 +127,22 @@ class FSRS {
112127
reviewLogs.add(ReviewLog(cardId: wordId, rating: Rating.good, reviewDateTime: DateTime.now()));
113128
save();
114129
}
115-
}
116-
117-
class Rater {
118-
Rating get easy => Rating.easy;
119-
Rating get good => Rating.good;
120-
Rating get hard => Rating.hard;
121-
Rating get forget => Rating.again;
122-
123-
late int scheme;
124-
125-
Rater(this.scheme);
126-
127-
static const List<List<int>> _difficultyScheme = [
128-
[3000, 8000], // Easy
129-
[2000, 6000], // Fine
130-
[1500, 4000], // OK~
131-
[1000, 2000], // Emm...
132-
[1000, 1500], // Impossible
133-
];
134130

135131
Rating calculate(int duration, bool isCorrect) {
136132
// duration in milliseconds
137-
if (!isCorrect) return Rating.again;
138-
if (duration < _difficultyScheme[scheme][0]) return Rating.easy;
139-
if (duration < _difficultyScheme[scheme][1]) return Rating.good;
133+
if (!isCorrect) {
134+
logger.fine("计算得分: again");
135+
return Rating.again;
136+
}
137+
if (duration < settingData['rater']['easyDuration']) {
138+
logger.fine("计算得分: easy");
139+
return Rating.easy;
140+
}
141+
if (duration < settingData['rater']['goodDuration']) {
142+
logger.fine("计算得分: good");
143+
return Rating.good;
144+
}
145+
logger.fine("计算得分: hard");
140146
return Rating.hard;
141147
}
142148
}

lib/funcs/sync.dart

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'dart:convert';
22
import 'dart:typed_data' show Uint8List;
33

4-
import 'package:flutter/material.dart';
54
import 'package:webdav_client/webdav_client.dart';
5+
import 'package:logging/logging.dart';
66

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

@@ -17,8 +17,12 @@ class WebDAV {
1717
bool isWriteable = false;
1818

1919
late Client client;
20+
final Logger logger = Logger("WebDAV");
2021

2122
static Future<List<dynamic>> test(String uri, String user, {String password = ''}) async {
23+
final Logger tempLogger = Logger("WebDAV_Test");
24+
tempLogger.info("进行WebDAV测试");
25+
tempLogger.fine("测试uri: $uri");
2226
Client tempClient = newClient(
2327
uri,
2428
user: user,
@@ -34,29 +38,39 @@ class WebDAV {
3438
tempClient.setConnectTimeout(8000);
3539
tempClient.setSendTimeout(60000);
3640
tempClient.setReceiveTimeout(60000);
41+
tempLogger.fine("完成基础设置");
3742
} catch (e) {
43+
tempLogger.warning("基础设置中出现错误: $e");
3844
return [false, false, "base setting error: $e"];
3945
}
4046
try{
4147
await tempClient.ping(); // test for connection
48+
tempLogger.fine("PING: 可达性测试成功");
4249
} catch (e) {
50+
tempLogger.warning("PING: 可达性测试错误: $e");
4351
return [false, false, "remote server didn't response: $e"];
4452
}
4553
try {
4654
await tempClient.readDir('/'); // test for read
55+
tempLogger.fine("READ: 可读性测试成功");
4756
} catch (e) {
57+
tempLogger.warning("PING: 可读性测试错误: $e");
4858
return [true, false, 'no read access: $e'];
4959
}
5060
try{
5161
await tempClient.write("TestFile", Uint8List(64)); // test for write
5262
await tempClient.remove("TestFile");
63+
tempLogger.fine("WRITE: 可写性测试成功");
5364
} catch (e) {
65+
tempLogger.warning("PING: 可写性测试错误: $e");
5466
return [true, false, 'no write access: $e'];
5567
}
68+
tempLogger.info("所有测试均通过");
5669
return [true, true, 'ok'];
5770
}
5871

5972
Future<void> connect() async {
73+
logger.info("正在链接: $uri");
6074
try{
6175
client = newClient(
6276
uri,
@@ -75,24 +89,39 @@ class WebDAV {
7589
isWriteable = true;
7690
await client.remove("TestFile");
7791
} catch (e) {
78-
debugPrint(e.toString());
92+
logger.warning("链接中出现错误: $e");
93+
rethrow;
7994
}
8095
}
8196

82-
Future<bool> upload(SharedPreferences pref,{bool force = false}) async {
83-
if(isWriteable || force) {
84-
await client.write("arabic_learning.bak", utf8.encode(jsonEncode(pref.export())));
85-
return true;
97+
Future<void> upload(SharedPreferences pref,{bool force = false}) async {
98+
logger.info("开始上传文件: 服务可写: $isWriteable; force: $force");
99+
try {
100+
if(isWriteable || force) {
101+
await client.write("arabic_learning.bak", utf8.encode(jsonEncode(pref.export())));
102+
logger.info("文件上传成功");
103+
return;
104+
}
105+
throw Exception("服务不可写,取消上传");
106+
} catch (e) {
107+
logger.warning("WebDAV上传失败: $e");
108+
rethrow;
86109
}
87-
return false;
88110
}
89111

90-
Future<bool> download(SharedPreferences pref,{bool force = false}) async {
91-
if(isReadable || force) {
92-
Map<String, dynamic> file = jsonDecode(utf8.decode(await client.read("arabic_learning.bak")));
93-
pref.recovery(file);
94-
return true;
112+
Future<void> download(SharedPreferences pref,{bool force = false}) async {
113+
logger.info("开始恢复文件: 服务可读: $isReadable; force: $force");
114+
try {
115+
if(isReadable || force) {
116+
Map<String, dynamic> file = jsonDecode(utf8.decode(await client.read("arabic_learning.bak")));
117+
logger.fine("文件下载成功,开始恢复");
118+
pref.recovery(file);
119+
return;
120+
}
121+
throw Exception("服务不可读,取消恢复");
122+
} catch (e) {
123+
logger.warning("WebDAV恢复失败: $e");
124+
rethrow;
95125
}
96-
return false;
97126
}
98127
}

0 commit comments

Comments
 (0)