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
26 changes: 26 additions & 0 deletions assets/help/announce.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 软件说明

**欢迎使用本软件,请先阅读使用说明。**

软件开源地址:[https://github.com/OctagonalStar/arabic_learning](https://github.com/OctagonalStar/arabic_learning)

## 声明

由于该软件目前还处在开发阶段,有一些bug是不可避免的。所以在正式使用该软件前你应当阅读并理解以下条款:

1. 该软件仅供学习使用,**请勿用于商业用途**。
2. 该软件**不会**对你的阿拉伯语成绩做出任何担保~,若你出现阿拉伯语成绩不理想的情况请先考虑自己的问题 :)~
3. 由于软件在不同系统上运行可能存在兼容性问题,软件出错造成的任何损失(包含精神损伤),软件作者和其他贡献者不会担负任何责任
4. 你知晓并理解如果你错误地使用软件(如使用错误的数据集)造成的任何后果,~乃至二次宇宙大爆炸,~都需要你自行承担
5. 其他在*GNU AFFERO GENERAL PUBLIC LICENSE (Version 3)*开源协议下的条款

## 网页端特别声明

1. 由于网页端的一些限制,该软件**不一定**能按照预期工作
2. 软件使用中所有的数据均保存在浏览器缓存中,不会进行任何用户数据上传。清空网站缓存可导致数据永久丢失
3. 该网页部署于Github Pages,由Github Action自动构建,所以网站会不定期进行热更新,且版本快于发布版
4. 由于(3)的原因,你可以由此更早地体验到新版功能,但也可能遇到新bug,如果遇到了相关的bug请在Github上提交issue
5. 由于Github Pages服务器地区不可控,我**完全不能**保证你是否能正常链接网站
6. **网站展示效果不代表实际app发布版效果**

若你已理解并接受上述条款,请向下翻页,并在底部输入框中填写你的名字,并点击“我没异议”按钮以确认。
24 changes: 14 additions & 10 deletions lib/funcs/fsrs_func.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import 'dart:convert';

import 'package:arabic_learning/vars/global.dart';
import 'package:flutter/foundation.dart';
import 'package:fsrs/fsrs.dart';
import 'package:logging/logging.dart';

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

class FSRS {
late final SharedPreferences prefs;
// 作为单例
static final FSRS _instance = FSRS._internal();
factory FSRS() => _instance;
FSRS._internal();

late FSRSConfig config;
late final Logger logger;

final Logger logger = Logger("FSRS");
// index != cardId; cardId = wordId = the index of word in global.wordData[words]

bool init({required SharedPreferences outerPrefs}) {
prefs = outerPrefs;
logger = Logger('FSRS');
bool init() {
logger.fine("构建FSRS模块");

if(!prefs.containsKey("fsrsData")) {
AppData appData = AppData();
if(!appData.storage.containsKey("fsrsData")) {
logger.info("未发现FSRS配置,加载默认配置");
config = FSRSConfig();
prefs.setString("fsrsData", jsonEncode(config.toMap()));
appData.storage.setString("fsrsData", jsonEncode(config.toMap()));
return false;
} else {
config = FSRSConfig.buildFromMap(jsonDecode(prefs.getString("fsrsData")!));
config = FSRSConfig.buildFromMap(jsonDecode(appData.storage.getString("fsrsData")!));
logger.info("FSRS配置加载完成");
}

Expand All @@ -34,7 +38,7 @@ class FSRS {

void save() async {
logger.info("正在保存FSRS配置");
prefs.setString("fsrsData", jsonEncode(config.toMap()));
AppData().storage.setString("fsrsData", jsonEncode(config.toMap()));
}

void createScheduler({required SharedPreferences prefs}) {
Expand Down
21 changes: 11 additions & 10 deletions lib/funcs/local_pk_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:logging/logging.dart';
import 'package:arabic_learning/vars/statics_var.dart';
import 'package:flutter/foundation.dart' show ChangeNotifier;
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:provider/provider.dart';

class PKServer with ChangeNotifier{
final Logger logger = Logger("PKServer");
Expand All @@ -38,7 +37,6 @@ class PKServer with ChangeNotifier{
List<SourceItem> selectableSource = [];
ClassSelection? classSelection;
late int rndSeed;
late Global global;
Duration? delay;
bool preparedP1 = false;
bool preparedP2 = false;
Expand All @@ -50,7 +48,6 @@ class PKServer with ChangeNotifier{

Future<void> initHost(bool isHoster, BuildContext context, {String? offer}) async {
isServer = isHoster;
global = context.read<Global>();

try {
logger.info("正在初始化WebRTC");
Expand Down Expand Up @@ -187,10 +184,12 @@ class PKServer with ChangeNotifier{
void _questVersionCheck() async => _channel!.send(RTCDataChannelMessage(json.encode({"step": 0, "version": StaticsVar.appVersion})));

void _questDictExchange() async {
AppData appData = AppData();
_channel!.send(RTCDataChannelMessage(
json.encode({
"step": 1,
"dictSum": List<String>.generate(global.wordData.classes.length, (int index) => global.wordData.classes[index].getHash(global.wordData.words), growable: false)
"dictSum": List<String>.generate(appData.wordData.classes.length, (int index) =>
appData.wordData.classes[index].getHash(appData.wordData.words), growable: false)
})
));
}
Expand All @@ -208,7 +207,7 @@ class PKServer with ChangeNotifier{
void setPrepare() {
preparedP1 = true;
pkState = PKState(
testWords: getSelectedWords(global.wordData, classSelection!.selectedClass, doShuffle: true, shuffleSeed: rndSeed),
testWords: getSelectedWords(AppData().wordData, classSelection!.selectedClass, doShuffle: true, shuffleSeed: rndSeed),
selfProgress: [],
sideProgress: []
);
Expand Down Expand Up @@ -265,6 +264,8 @@ class PKServer with ChangeNotifier{
return;
}

AppData appData = AppData();

switch(step){
/// 版本号检查结果 from Client
case 0 when data.containsKey("accepted"): {
Expand Down Expand Up @@ -298,8 +299,8 @@ class PKServer with ChangeNotifier{
delay = DateTime.parse(data["time"]).difference(DateTime.now());
List sumList = data["dictSum"];
selectableSource.clear();
for(SourceItem source in global.wordData.classes) {
if(sumList.contains(source.getHash(global.wordData.words))) selectableSource.add(source);
for(SourceItem source in appData.wordData.classes) {
if(sumList.contains(source.getHash(appData.wordData.words))) selectableSource.add(source);
logger.fine("[$packageid] 计算得到${source.sourceJsonFileName}在哈希中有匹配");
}
pageController!.nextPage(duration: Durations.medium2, curve: StaticsVar.curve);
Expand All @@ -315,8 +316,8 @@ class PKServer with ChangeNotifier{
logger.fine("[$packageid] 进行词库检查");
List sumList = data["dictSum"];
selectableSource.clear();
for(SourceItem source in global.wordData.classes) {
if(sumList.contains(source.getHash(global.wordData.words))) {
for(SourceItem source in appData.wordData.classes) {
if(sumList.contains(source.getHash(appData.wordData.words))) {
selectableSource.add(source);
logger.fine("[$packageid] 计算得到${source.sourceJsonFileName}在哈希中有匹配");
}
Expand All @@ -325,7 +326,7 @@ class PKServer with ChangeNotifier{
_channel!.send(RTCDataChannelMessage(json.encode({
"step": 1,
"accepted": true,
"dictSum": List.generate(selectableSource.length, (int index) => selectableSource[index].getHash(global.wordData.words)),
"dictSum": List.generate(selectableSource.length, (int index) => selectableSource[index].getHash(appData.wordData.words)),
"time": DateTime.now().toIso8601String()
})));
pageController!.nextPage(duration: Durations.medium2, curve: StaticsVar.curve);
Expand Down
44 changes: 25 additions & 19 deletions lib/funcs/ui.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:ui';

import 'package:arabic_learning/funcs/fsrs_func.dart';
import 'package:arabic_learning/vars/config_structure.dart' show ClassItem, SourceItem, WordItem, ClassSelection;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -39,15 +40,16 @@ import 'package:arabic_learning/funcs/utili.dart';
Future<ClassSelection> popSelectClasses(BuildContext context, {bool withCache = false, bool withReviewChoose = true, List<SourceItem>? forceSelectRange}) async {
context.read<Global>().uiLogger.info("弹出课程选择(ClassSelectPage),withCache: $withCache");
final List<ClassItem> beforeSelectedClasses = [];
AppData appData = AppData();
if(withCache) {
if(forceSelectRange != null) throw Exception("popSelectClasses不允许forceSelectRange时使用withCache");
final String tpcPrefs = context.read<Global>().prefs.getString("tempConfig") ?? jsonEncode(StaticsVar.tempConfig);
final String tpcPrefs = appData.storage.getString("tempConfig") ?? jsonEncode(StaticsVar.tempConfig);
final List<List<String>> cacheList = (jsonDecode(tpcPrefs)["SelectedClasses"] as List)
.cast<List>()
.map((e) => e.cast<String>().toList())
.toList();
for(List<String> cachedClass in cacheList) {
for(SourceItem sourceItem in context.read<Global>().wordData.classes){
for(SourceItem sourceItem in appData.wordData.classes){
if(sourceItem.sourceJsonFileName != cachedClass[0]){
continue;
}
Expand All @@ -60,21 +62,23 @@ Future<ClassSelection> popSelectClasses(BuildContext context, {bool withCache =
}
context.read<Global>().uiLogger.fine("已缓存课程选择: $beforeSelectedClasses");
}

ClassSelection? selectedClasses = await showModalBottomSheet<ClassSelection>(
context: context,
shape: RoundedRectangleBorder(side: BorderSide(width: 1.0, color: Theme.of(context).colorScheme.onSurface.withAlpha(150)), borderRadius: StaticsVar.br),
isDismissible: false,
isScrollControlled: context.read<Global>().isWideScreen,
isScrollControlled: appData.isWideScreen,
enableDrag: true,
builder: (BuildContext context) {
return ClassSelectPage(beforeSelectedClasses: beforeSelectedClasses, withReviewChoose: withReviewChoose);
}
);

if(withCache && selectedClasses != null && context.mounted) {
final String tpcPrefs = context.read<Global>().prefs.getString("tempConfig") ?? jsonEncode(StaticsVar.tempConfig);
final String tpcPrefs = appData.storage.getString("tempConfig") ?? jsonEncode(StaticsVar.tempConfig);
Map<String, dynamic> tpcMap = jsonDecode(tpcPrefs);
tpcMap["SelectedClasses"] = selectedClasses;
context.read<Global>().prefs.setString("tempConfig", jsonEncode(tpcMap));
appData.storage.setString("tempConfig", jsonEncode(tpcMap));
context.read<Global>().uiLogger.info("课程选择缓存完成");
}
if(context.mounted) context.read<Global>().uiLogger.fine("选择的课程: $selectedClasses");
Expand All @@ -99,7 +103,7 @@ List<Widget> classesSelectionList(BuildContext context, Function (ClassItem) onC
if(forceSelectRange != null) {
sourcesList = forceSelectRange;
} else {
sourcesList = context.read<Global>().wordData.classes;
sourcesList = AppData().wordData.classes;
}
List<Widget> widgetList = [];
for (SourceItem source in sourcesList) {
Expand Down Expand Up @@ -507,7 +511,7 @@ class WordCard extends StatelessWidget {
icon: const Icon(Icons.volume_up, size: 24.0),
label: FittedBox(child: Text(word.arabic, style: TextStyle(fontSize: 64.0, fontFamily: context.read<Global>().arFont))),
onPressed: (){
playTextToSpeech(word.arabic, context);
playTextToSpeech(word.arabic);
},
),
Stack(
Expand Down Expand Up @@ -627,7 +631,7 @@ class ClassSelectPage extends StatelessWidget {
final MediaQueryData mediaQuery = MediaQuery.of(context);
ClassSelection classSelection = ClassSelection(
selectedClass: beforeSelectedClasses.toList(),
countInReview: context.read<Global>().globalFSRS.config.enabled
countInReview: FSRS().config.enabled
);
void addClass(ClassItem classInfo) {
classSelection.selectedClass.add(classInfo);
Expand Down Expand Up @@ -665,7 +669,7 @@ class ClassSelectPage extends StatelessWidget {
Switch(
value: classSelection.countInReview,
onChanged: (value){
if(value == true && !context.read<Global>().globalFSRS.config.enabled) {
if(value == true && !FSRS().config.enabled) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("请先启用复习系统")));
return ;
}
Expand Down Expand Up @@ -799,7 +803,7 @@ class _ChoiceQuestions extends State<ChoiceQuestions> {
// showingMode 0: 1 Row, 1: 2 Rows, 2: 4 Rows
if(showingMode == -1){
context.read<Global>().uiLogger.fine("未指定布局,开始计算");
showingMode = calculateButtonBoxLayout(widget.choices, mediaQuery.size.width, context.read<Global>().isWideScreen);
showingMode = calculateButtonBoxLayout(widget.choices, mediaQuery.size.width);
context.read<Global>().uiLogger.info("最终采用布局方案: $showingMode");
}
return Material(
Expand All @@ -825,11 +829,12 @@ class _ChoiceQuestions extends State<ChoiceQuestions> {
setLocalState(() {
playing = true;
});
late List<dynamic> temp;
temp = await playTextToSpeech(widget.mainWord, context);
if(!temp[0] && context.mounted) {
alart(context, temp[1]);
try {
await playTextToSpeech(widget.mainWord);
} catch (e) {
if(context.mounted) alart(context, e.toString());
}

setLocalState(() {
playing = false;
});
Expand Down Expand Up @@ -1035,7 +1040,7 @@ class _ListeningQuestion extends State<ListeningQuestion> {
int showingMode = widget.bottonLayout;
if(showingMode == -1){
context.read<Global>().uiLogger.fine("未指定布局,开始计算");
showingMode = calculateButtonBoxLayout(widget.choices, mediaQuery.size.width, context.read<Global>().isWideScreen);
showingMode = calculateButtonBoxLayout(widget.choices, mediaQuery.size.width);
context.read<Global>().uiLogger.info("最终采用布局方案: $showingMode");
}
return Material(
Expand All @@ -1060,11 +1065,12 @@ class _ListeningQuestion extends State<ListeningQuestion> {
setLocalState(() {
playing = true;
});
late List<dynamic> temp;
temp = await playTextToSpeech(widget.mainWord, context);
if(!temp[0] && context.mounted) {
alart(context, temp[1]);
try {
await playTextToSpeech(widget.mainWord);
} catch (e) {
if(context.mounted) alart(context, e.toString());
}

setLocalState(() {
playing = false;
});
Expand Down
Loading