JuLearn/lib/main.dart

363 lines
11 KiB
Dart
Raw Normal View History

2023-06-01 16:11:03 +02:00
import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:ju_learn/learn.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(const LearnApp());
class LearnApp extends StatelessWidget {
const LearnApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Learn App',
2023-06-01 16:38:20 +02:00
theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
2023-06-01 16:11:03 +02:00
home: const MainPage(),
);
}
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
2023-06-01 20:14:34 +02:00
MainPageState createState() => MainPageState();
2023-06-01 16:11:03 +02:00
}
2023-06-01 20:14:34 +02:00
class MainPageState extends State<MainPage> {
2023-06-01 16:11:03 +02:00
List<Vault> _vaults = [];
2023-06-01 20:14:34 +02:00
MainPageState() {
2023-06-01 16:11:03 +02:00
loadList();
}
2023-06-01 20:14:34 +02:00
Vault? getByName(String name) {
for (var v in _vaults) {
if (v.name == name) return v;
}
return null;
}
2023-06-01 16:11:03 +02:00
loadList() async {
final prefs = await SharedPreferences.getInstance();
List<String>? json = prefs.getStringList("vaults");
2023-06-01 20:14:34 +02:00
List<String>? jsonState = prefs.getStringList("vaultStates");
2023-06-01 16:11:03 +02:00
if (json == null) return;
setState(() {
_vaults = json.map((e) => Vault.fromJson(jsonDecode(e))).toList();
});
2023-06-01 20:14:34 +02:00
if (jsonState == null) return;
for (var s in jsonState) {
var sMap = jsonDecode(s);
Vault? v = getByName(sMap["name"]);
if (v == null) continue;
v.loadState(sMap);
}
2023-06-01 16:11:03 +02:00
}
saveList() async {
final prefs = await SharedPreferences.getInstance();
prefs.setStringList(
"vaults", _vaults.map((e) => jsonEncode(e.toJson())).toList());
}
2023-06-01 20:14:34 +02:00
saveStateList() async {
final prefs = await SharedPreferences.getInstance();
prefs.setStringList("vaultStates",
_vaults.map((e) => jsonEncode(e.toStateJson())).toList());
}
_vaultMenu(Vault v) async {
showModalBottomSheet(
context: context,
builder: (context) => BottomSheet(
builder: (context) => Column(children: [
Padding(
padding: const EdgeInsets.all(8),
2023-06-01 21:40:16 +02:00
child: Column(children: [
Text(v.name,
style: const TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
)),
Text('Stats: ${v.txtVals()}',
style: const TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.bold,
))
])),
2023-06-01 20:14:34 +02:00
ListTile(
leading: const Icon(Icons.restore),
title: const Text("Reset Vault"),
subtitle: const Text("Reset Vault learn Data to default."),
trailing: const Icon(Icons.chevron_right),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Reset Vault'),
content:
Text('Do you want to reset „${v.name}“ Vault?'),
actions: [
TextButton(
2023-06-02 13:06:48 +02:00
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel')),
FilledButton(
2023-06-01 20:14:34 +02:00
child: const Text('Reset'),
onPressed: () {
setState(() {
v.reset();
});
saveStateList();
Navigator.of(context).pop();
Navigator.of(context).pop();
},
),
],
);
},
);
},
),
ListTile(
leading: const Icon(Icons.delete),
title: const Text("Vault löschen"),
subtitle: Text('Do you want to delete „${v.name}“ Vault?'),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Delete Vault'),
content: Text(
'Do you want to delete „${v.name}“ Vault?'),
actions: [
TextButton(
2023-06-02 13:06:48 +02:00
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel')),
FilledButton(
2023-06-01 20:14:34 +02:00
child: const Text('Delete'),
onPressed: () {
setState(() {
_vaults.remove(v);
});
saveList();
saveStateList();
Navigator.of(context).pop();
Navigator.of(context).pop();
},
),
],
);
},
);
})
]),
onClosing: () {},
));
2023-06-01 16:11:03 +02:00
}
_pickFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
allowMultiple: false);
2023-06-02 13:34:54 +02:00
if (result != null) {
String data = "";
if (!kIsWeb && result.files.single.path != null) {
File file = File(result.files.single.path ?? "");
data = file.readAsStringSync();
} else {
PlatformFile pfile = result.files.first;
2023-06-07 20:52:05 +02:00
data = utf8.decode(pfile.bytes?.map((e) => e).toList() ?? []);
2023-06-02 13:34:54 +02:00
}
2023-06-01 16:11:03 +02:00
try {
2023-06-02 13:34:54 +02:00
if (data.isEmpty) throw ErrorDescription("Empty File!");
2023-06-01 16:11:03 +02:00
Vault v = Vault.fromJson(jsonDecode(data));
setState(() {
_vaults.add(v);
});
saveList();
} catch (e) {
2023-06-01 20:14:34 +02:00
print(e);
2023-06-01 16:11:03 +02:00
AlertDialog(
title: const Text('Error'),
content: const Text('An error has occurred.'),
actions: [
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
]);
return;
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Learn App'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child:
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
..._vaults.map((e) => Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
2023-06-01 21:40:16 +02:00
MaterialPageRoute(
builder: (context) => QuizPage(e, saveStateList)),
2023-06-01 16:11:03 +02:00
);
},
onLongPress: () {
2023-06-01 20:14:34 +02:00
_vaultMenu(e);
2023-06-01 16:11:03 +02:00
},
child: Text(e.name),
)))
]),
),
floatingActionButton: FloatingActionButton(
onPressed: _pickFile,
child: const Icon(Icons.add),
),
);
}
}
class Question {
String quest;
List<String> answers;
int correct;
String explanation;
2023-06-01 20:14:34 +02:00
int drawer = 0;
int lastInRun = 0;
2023-06-01 16:11:03 +02:00
Question(this.quest, this.answers, this.correct, this.explanation);
Map<String, dynamic> toJson() {
return {
"quest": quest,
"answers": answers,
"correct": correct,
"explanation": explanation,
};
}
factory Question.fromJson(Map<String, dynamic> json) {
if (json["quest"] is! String) {
throw ErrorDescription("fromJSON Error: quest");
}
if (json["answers"] is! List<dynamic>) {
throw ErrorDescription("fromJSON Error: awnsers");
}
if (json["correct"] is! int ||
json["correct"] < 0 ||
json["correct"] >= json["answers"].length) {
throw ErrorDescription("fromJSON Error: correct");
}
if (json["explanation"] is! String) {
throw ErrorDescription("fromJSON Error: explanation");
}
return Question(
json["quest"],
(json["answers"] as List<dynamic>).map((d) => d.toString()).toList(),
json["correct"],
json["explanation"],
);
}
}
class Vault {
String name;
List<Question> questions;
Vault(this.name, this.questions);
Map<String, dynamic> toJson() {
2023-06-01 16:38:20 +02:00
return {
"name": name,
"questions": questions.map((e) => e.toJson()).toList()
};
}
2023-06-01 20:14:34 +02:00
Map<String, dynamic> toStateJson() {
return {"name": name, "drawers": questions.map((d) => d.drawer).toList()};
}
void reset() {
for (var q in questions) {
q.drawer = 0;
q.lastInRun = 0;
}
}
void softReset() {
for (var q in questions) {
q.lastInRun = 0;
}
2023-06-01 16:11:03 +02:00
}
2023-06-01 21:40:16 +02:00
(int, double, int) vals() {
int min = 0xffffff;
int total = 0;
int max = 0;
for (var q in questions) {
if (q.drawer > max) max = q.drawer;
if (q.drawer < min) min = q.drawer;
total += q.drawer;
}
return (min, total.toDouble() / questions.length, max);
}
2023-06-02 13:06:48 +02:00
String txtVals() {
2023-06-01 21:40:16 +02:00
var (min, avg, max) = vals();
2023-06-02 13:06:48 +02:00
return '($min, ${(avg * 100).round() / 100.0}, $max)';
2023-06-01 21:40:16 +02:00
}
2023-06-01 16:11:03 +02:00
factory Vault.fromJson(Map<String, dynamic> json) {
if (json["name"] is! String) {
throw ErrorDescription("fromJSON Error: name");
}
if (json["questions"] is! List<dynamic>) {
throw ErrorDescription("fromJSON Error: questions");
}
return Vault(
json["name"],
(json["questions"] as List<dynamic>)
.map((d) => Question.fromJson(d))
.toList(),
);
}
2023-06-01 20:14:34 +02:00
loadState(Map<String, dynamic> json) {
if (json["name"] != name) throw ErrorDescription("Wrong Vault Error");
if (json["drawers"] is! List<dynamic>) {
throw ErrorDescription("fromJSON Error: questions");
}
var stateList = json["drawers"];
for (var i = 0; i < stateList.length; i++) {
var q = questions.elementAtOrNull(i);
if (q != null) {
q.drawer = stateList[i] as int;
}
}
}
2023-06-01 16:11:03 +02:00
}