Skip to content

Commit 83f7f4d

Browse files
Merge pull request #32 from OctagonalStar/feat/add_logger
Feat/add_logger
2 parents 639016f + 85b9235 commit 83f7f4d

27 files changed

+633
-504
lines changed

CHANGELOG.md

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

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

5+
### Added
6+
7+
- 添加了日志捕获 [#23](https://github.com/OctagonalStar/arabic_learning/issues/23)
8+
- 添加了调试页面
9+
510
### Improvement
611

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

lib/funcs/fsrs_func.dart

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import 'dart:convert';
2-
import 'package:arabic_learning/package_replacement/storage.dart';
2+
3+
import 'package:arabic_learning/vars/global.dart';
4+
import 'package:flutter/material.dart' show BuildContext;
35
import 'package:fsrs/fsrs.dart';
6+
import 'package:logging/logging.dart';
7+
8+
import 'package:arabic_learning/package_replacement/storage.dart';
9+
import 'package:provider/provider.dart';
410

511
class FSRS {
612
List<Card> cards = [];
@@ -9,12 +15,15 @@ class FSRS {
915
late SharedPreferences prefs;
1016
late Rater rater;
1117
late Map<String, dynamic> settingData;
12-
18+
late final Logger logger;
1319
// index != cardId; cardId = wordId = the index of word in global.wordData[words]
1420

15-
Future<bool> init() async {
16-
prefs = await SharedPreferences.getInstance();
21+
Future<bool> init({BuildContext? context}) async {
22+
logger = Logger('FSRS');
23+
logger.fine("构建FSRS模块");
24+
prefs = context==null ? await SharedPreferences.getInstance() : context.read<Global>().prefs;
1725
if(!prefs.containsKey("fsrsData")) {
26+
logger.info("未发现FSRS配置,加载默认配置");
1827
settingData = {
1928
'enabled': false,
2029
'scheduler': {},
@@ -33,12 +42,15 @@ class FSRS {
3342
reviewLogs.add(ReviewLog.fromMap(settingData['reviewLog'][i]));
3443
}
3544
rater = Rater(settingData['rater']['scheme']);
45+
logger.info("FSRS配置加载完成");
3646
return true;
3747
}
48+
logger.info("FSRS未启用");
3849
return false;
3950
}
4051

4152
void save() async {
53+
logger.info("正在保存FSRS配置");
4254
settingData['scheduler'] = scheduler.toMap();
4355
List cardsCache = [];
4456
List logCache = [];
@@ -55,8 +67,9 @@ class FSRS {
5567
return settingData['enabled'];
5668
}
5769

58-
Future<void> createScheduler(int scheme) async {
59-
await init();
70+
Future<void> createScheduler(int scheme, {BuildContext? context}) async {
71+
await init(context: context);
72+
logger.info("初始化scheduler,选择方案 $scheme");
6073
List<double> desiredRetention = [0.85, 0.9, 0.95, 0.95, 0.99];
6174
scheduler = Scheduler(desiredRetention: desiredRetention[scheme]);
6275
settingData['rater']['scheme'] = scheme;
@@ -71,8 +84,11 @@ class FSRS {
7184
}
7285

7386
void reviewCard(int wordId, int duration, bool isCorrect) {
87+
logger.fine("记录复习卡片: Id: $wordId; duration: $duration; isCorrect: $isCorrect");
7488
int index = cards.indexWhere((Card card) => card.cardId == wordId); // 避免有时候cardId != wordId
75-
final (:card, :reviewLog) = scheduler.reviewCard(cards[index], rater.calculate(duration, isCorrect));
89+
logger.fine("定位复习卡片地址: $index, 目前阶段: ${cards[index].step}, 难度: ${cards[index].difficulty}, 稳定: ${cards[index].stability}, 过期时间(+8): ${cards[index].due.toLocal()}");
90+
final (:card, :reviewLog) = scheduler.reviewCard(cards[index], rater.calculate(duration, isCorrect), reviewDateTime: DateTime.now().toUtc(), reviewDuration: duration);
91+
logger.fine("卡片 $index 复习后: 目前阶段: ${cards[index].step}, 难度: ${cards[index].difficulty}, 稳定: ${cards[index].stability}, 过期时间(+8): ${cards[index].due.toLocal()}");
7692
cards[index] = card;
7793
reviewLogs[index] = reviewLog;
7894
save();

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
}

lib/funcs/ui.dart

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import 'dart:convert';
22
import 'dart:ui';
3-
import 'package:arabic_learning/vars/global.dart';
4-
import 'package:arabic_learning/vars/statics_var.dart';
3+
54
import 'package:flutter/material.dart';
65
import 'package:provider/provider.dart';
7-
import 'utili.dart';
6+
7+
import 'package:arabic_learning/vars/global.dart';
8+
import 'package:arabic_learning/vars/statics_var.dart';
9+
import 'package:arabic_learning/funcs/utili.dart';
810

911
// 该文件主要包含了对于UI有关的函数及多次在不同地方使用的Widget类或者函数
1012

@@ -34,20 +36,22 @@ import 'utili.dart';
3436
/// List<Map<String, dynamic>> words = getSelectedWords(context , forceSelectClasses: value);
3537
/// ```
3638
Future<List<List<String>>> popSelectClasses(BuildContext context, {bool withCache = false}) async {
39+
context.read<Global>().uiLogger.info("弹出课程选择(ClassSelectPage),withCache: $withCache");
3740
late final List<List<String>> beforeSelectedClasses;
3841
if(withCache) {
3942
final String tpcPrefs = context.read<Global>().prefs.getString("tempConfig") ?? jsonEncode(StaticsVar.tempConfig);
4043
beforeSelectedClasses = (jsonDecode(tpcPrefs)["SelectedClasses"] as List)
4144
.cast<List>()
4245
.map((e) => e.cast<String>().toList())
4346
.toList();
47+
context.read<Global>().uiLogger.fine("已缓存课程选择: $beforeSelectedClasses");
4448
} else {
4549
beforeSelectedClasses = [];
4650
}
4751
List<List<String>>? selectedClasses = await showModalBottomSheet<List<List<String>>>(
4852
context: context,
4953
// 假装圆角... :)
50-
shape: RoundedSuperellipseBorder(side: BorderSide(width: 1.0, color: Theme.of(context).colorScheme.onSurface.withAlpha(150)), borderRadius: StaticsVar.br),
54+
shape: RoundedRectangleBorder(side: BorderSide(width: 1.0, color: Theme.of(context).colorScheme.onSurface.withAlpha(150)), borderRadius: StaticsVar.br),
5155
isDismissible: false,
5256
isScrollControlled: context.read<Global>().isWideScreen,
5357
enableDrag: true,
@@ -60,7 +64,9 @@ Future<List<List<String>>> popSelectClasses(BuildContext context, {bool withCach
6064
Map<String, dynamic> tpcMap = jsonDecode(tpcPrefs);
6165
tpcMap["SelectedClasses"] = selectedClasses;
6266
context.read<Global>().prefs.setString("tempConfig", jsonEncode(tpcMap));
67+
context.read<Global>().uiLogger.info("课程选择缓存完成");
6368
}
69+
if(context.mounted) context.read<Global>().uiLogger.fine("选择的课程: $selectedClasses");
6470
return selectedClasses ?? [];
6571
}
6672

@@ -76,7 +82,8 @@ Future<List<List<String>>> popSelectClasses(BuildContext context, {bool withCach
7682
/// [isClassSelected] :需要一个函数判断该课程是否被选中
7783
///
7884
/// 一般情况下该函数只会在 [ClassSelectPage] 中被使用,若非必要你不应该使用此函数
79-
List<Widget> classesSelectionList(BuildContext context, MediaQueryData mediaQuery, Function (List<String>) onChanged, bool Function (List<String>) isClassSelected) {
85+
List<Widget> classesSelectionList(BuildContext context, Function (List<String>) onChanged, bool Function (List<String>) isClassSelected) {
86+
context.read<Global>().uiLogger.fine("构建课程选择列表");
8087
Map<String, dynamic> wordData = context.read<Global>().wordData;
8188
List<Widget> widgetList = [];
8289
for (String sourceName in wordData["Classes"].keys) {
@@ -125,10 +132,12 @@ List<Widget> classesSelectionList(BuildContext context, MediaQueryData mediaQuer
125132
}
126133
}
127134
if(widgetList.isEmpty) {
135+
context.read<Global>().uiLogger.warning("用户未导入可用词库");
128136
widgetList.add(
129137
Center(child: Text('啥啥词库都没导入,你学个啥呢?\n自己去 设置 -> 数据设置 -> 导入词库', style: TextStyle(fontSize: 24.0, color: Colors.redAccent),))
130138
);
131139
}
140+
context.read<Global>().uiLogger.info("课程选择列表构建完成");
132141
return widgetList;
133142
}
134143

@@ -149,7 +158,8 @@ List<Widget> classesSelectionList(BuildContext context, MediaQueryData mediaQuer
149158
/// alart(context, "你点击了按钮",onConfirmed: (){i++}, delayConfirm: Duration(seconds: 1));
150159
/// }
151160
/// ```
152-
void alart(context, String e, {Function? onConfirmed, Duration delayConfirm = const Duration(milliseconds: 0)}) {
161+
void alart(BuildContext context, String e, {Function? onConfirmed, Duration delayConfirm = const Duration(milliseconds: 0)}) {
162+
context.read<Global>().uiLogger.info("构建弹出窗口: 携带信息: $e ;确认参数: ${onConfirmed != null}; 延迟: ${delayConfirm.inMilliseconds}");
153163
showDialog(
154164
context: context,
155165
builder: (BuildContext context) {
@@ -179,6 +189,39 @@ void alart(context, String e, {Function? onConfirmed, Duration delayConfirm = co
179189
);
180190
}
181191

192+
/// 弹出详解页面
193+
void viewAnswer(BuildContext context, Map<String, dynamic> wordData) async {
194+
context.read<Global>().uiLogger.info("弹出详解页面");
195+
MediaQueryData mediaQuery = MediaQuery.of(context);
196+
showBottomSheet(
197+
context: context,
198+
shape: RoundedSuperellipseBorder(side: BorderSide(width: 1.0, color: Theme.of(context).colorScheme.onSurface), borderRadius: StaticsVar.br),
199+
enableDrag: true,
200+
builder: (context) {
201+
return Container(
202+
padding: EdgeInsets.only(top: mediaQuery.size.height * 0.05),
203+
decoration: BoxDecoration(
204+
color: Theme.of(context).colorScheme.onPrimary,
205+
borderRadius: StaticsVar.br,
206+
),
207+
child: Column(
208+
children: [
209+
Expanded(child: WordCard(word: wordData)),
210+
ElevatedButton(
211+
onPressed: () {Navigator.pop(context);},
212+
style: ElevatedButton.styleFrom(
213+
fixedSize: Size(mediaQuery.size.width * 0.8, mediaQuery.size.height * 0.1),
214+
shape: RoundedRectangleBorder(borderRadius: StaticsVar.br)
215+
),
216+
child: Text("我知道了"),
217+
)
218+
],
219+
),
220+
);
221+
},
222+
);
223+
}
224+
182225
// Base Widgets 较为基础的Widget
183226

184227
/// 文本框容器
@@ -537,7 +580,7 @@ class ClassSelectPage extends StatelessWidget {
537580
children: [
538581
Expanded(
539582
child: ListView(
540-
children: classesSelectionList(context, mediaQuery, onClassChanged, isClassSelected)
583+
children: classesSelectionList(context, onClassChanged, isClassSelected)
541584
),
542585
),
543586
ElevatedButton(
@@ -653,13 +696,16 @@ class _ChoiceQuestions extends State<ChoiceQuestions> {
653696

654697
@override
655698
Widget build(BuildContext context) {
699+
context.read<Global>().uiLogger.info("构建选择题页面: 主单词: ${widget.mainWord};选项: ${widget.choices}");
656700
MediaQueryData mediaQuery = MediaQuery.of(context);
657701
int showingMode = widget.bottonLayout;
658702
// showingMode 0: 1 Row, 1: 2 Rows, 2: 4 Rows
659703
if(showingMode == -1){
704+
context.read<Global>().uiLogger.fine("未指定布局,开始计算");
660705
bool overFlowPossible = false;
661706
for(int i = 1; i < 4; i++) {
662707
if(widget.choices[i].length * 16 > mediaQuery.size.width * (context.read<Global>().isWideScreen ? 0.21 : 0.8)){
708+
context.read<Global>().uiLogger.fine("在 [${widget.choices[i]}] 可能溢出,采用密集布局");
663709
overFlowPossible = true;
664710
break;
665711
}
@@ -678,6 +724,7 @@ class _ChoiceQuestions extends State<ChoiceQuestions> {
678724
showingMode = 1;
679725
}
680726
}
727+
context.read<Global>().uiLogger.info("最终采用布局方案: $showingMode");
681728
}
682729
return Material(
683730
child: Center(
@@ -696,6 +743,7 @@ class _ChoiceQuestions extends State<ChoiceQuestions> {
696743
),
697744
onPressed: () async {
698745
if (playing || !widget.allowAudio) {
746+
context.read<Global>().uiLogger.warning("${playing ? "正在播放" : "不准许音频"},中断TTS");
699747
return;
700748
}
701749
setLocalState(() {
@@ -759,6 +807,7 @@ class WordCardQuestion extends StatelessWidget {
759807

760808
@override
761809
Widget build(BuildContext context) {
810+
context.read<Global>().uiLogger.info("构建单词卡片页面,主单词: ${word["arabic"]}");
762811
MediaQueryData mediaQuery = MediaQuery.of(context);
763812
return Material(
764813
child: Column(
@@ -804,6 +853,7 @@ class _SpellQuestion extends State<SpellQuestion> {
804853

805854
@override
806855
Widget build(BuildContext context) {
856+
context.read<Global>().uiLogger.info("构建拼写题页面,主单词: ${widget.word["arabic"]}");
807857
MediaQueryData mediaQuery = MediaQuery.of(context);
808858
return Material(
809859
child: Column(
@@ -854,6 +904,7 @@ class _SpellQuestion extends State<SpellQuestion> {
854904
SizedBox(height: mediaQuery.size.height * 0.05),
855905
ElevatedButton(
856906
onPressed: () {
907+
context.read<Global>().uiLogger.info("提交单词检查: [${controller.text}]");
857908
check(controller.text);
858909
},
859910
style: ElevatedButton.styleFrom(

0 commit comments

Comments
 (0)