217 lines
5.6 KiB
Dart
217 lines
5.6 KiB
Dart
|
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',
|
||
|
theme: ThemeData(
|
||
|
primarySwatch: Colors.blue,
|
||
|
),
|
||
|
home: const MainPage(),
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class MainPage extends StatefulWidget {
|
||
|
const MainPage({super.key});
|
||
|
|
||
|
@override
|
||
|
_MainPageState createState() => _MainPageState();
|
||
|
}
|
||
|
|
||
|
class _MainPageState extends State<MainPage> {
|
||
|
List<Vault> _vaults = [];
|
||
|
_MainPageState() {
|
||
|
loadList();
|
||
|
}
|
||
|
loadList() async {
|
||
|
final prefs = await SharedPreferences.getInstance();
|
||
|
List<String>? json = prefs.getStringList("vaults");
|
||
|
if (json == null) return;
|
||
|
setState(() {
|
||
|
_vaults = json.map((e) => Vault.fromJson(jsonDecode(e))).toList();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
saveList() async {
|
||
|
final prefs = await SharedPreferences.getInstance();
|
||
|
prefs.setStringList(
|
||
|
"vaults", _vaults.map((e) => jsonEncode(e.toJson())).toList());
|
||
|
}
|
||
|
|
||
|
askToDeleteVault(Vault v) 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(
|
||
|
child: const Text('Delete'),
|
||
|
onPressed: () {
|
||
|
setState(() {
|
||
|
_vaults.remove(v);
|
||
|
});
|
||
|
saveList();
|
||
|
Navigator.of(context).pop();
|
||
|
},
|
||
|
),
|
||
|
TextButton(
|
||
|
onPressed: () {
|
||
|
Navigator.of(context).pop();
|
||
|
},
|
||
|
child: const Text('Cancle'))
|
||
|
],
|
||
|
);
|
||
|
},
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_pickFile() async {
|
||
|
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||
|
type: FileType.custom,
|
||
|
allowedExtensions: ['json'],
|
||
|
allowMultiple: false);
|
||
|
if (result != null && result.files.single.path != null) {
|
||
|
File file = File(result.files.single.path ?? "");
|
||
|
String data = file.readAsStringSync();
|
||
|
try {
|
||
|
Vault v = Vault.fromJson(jsonDecode(data));
|
||
|
setState(() {
|
||
|
_vaults.add(v);
|
||
|
});
|
||
|
saveList();
|
||
|
} catch (e) {
|
||
|
if (kDebugMode) {
|
||
|
print(e);
|
||
|
}
|
||
|
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,
|
||
|
MaterialPageRoute(builder: (context) => QuizPage(e)),
|
||
|
);
|
||
|
},
|
||
|
onLongPress: () {
|
||
|
askToDeleteVault(e);
|
||
|
},
|
||
|
child: Text(e.name),
|
||
|
)))
|
||
|
]),
|
||
|
),
|
||
|
floatingActionButton: FloatingActionButton(
|
||
|
onPressed: _pickFile,
|
||
|
backgroundColor: Colors.blue,
|
||
|
child: const Icon(Icons.add),
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class Question {
|
||
|
String quest;
|
||
|
List<String> answers;
|
||
|
int correct;
|
||
|
String explanation;
|
||
|
|
||
|
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() {
|
||
|
return {"name": name, "questions": questions.map((e) => e.toJson()).toList()};
|
||
|
}
|
||
|
|
||
|
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(),
|
||
|
);
|
||
|
}
|
||
|
}
|