scoring done
This commit is contained in:
@@ -1,9 +1,28 @@
|
||||
enum QuizMode { kanjiToEnglish, englishToKanji, reading }
|
||||
|
||||
class SrsItem {
|
||||
final int kanjiId;
|
||||
final QuizMode quizMode;
|
||||
final String? readingType; // 'onyomi' or 'kunyomi'
|
||||
int srsStage;
|
||||
DateTime lastAsked;
|
||||
|
||||
SrsItem({
|
||||
required this.kanjiId,
|
||||
required this.quizMode,
|
||||
this.readingType,
|
||||
this.srsStage = 0,
|
||||
DateTime? lastAsked,
|
||||
}) : lastAsked = lastAsked ?? DateTime.now();
|
||||
}
|
||||
|
||||
class KanjiItem {
|
||||
final int id;
|
||||
final String characters;
|
||||
final List<String> meanings;
|
||||
final List<String> onyomi;
|
||||
final List<String> kunyomi;
|
||||
final Map<String, SrsItem> srsItems = {};
|
||||
|
||||
KanjiItem({
|
||||
required this.id,
|
||||
|
||||
@@ -8,7 +8,7 @@ import '../widgets/kanji_card.dart';
|
||||
import '../widgets/options_grid.dart';
|
||||
import 'settings_screen.dart';
|
||||
|
||||
enum QuizMode { kanjiToEnglish, englishToKanji, reading }
|
||||
import '../models/kanji_item.dart';
|
||||
|
||||
class _ReadingInfo {
|
||||
final List<String> correctReadings;
|
||||
@@ -65,11 +65,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_status = 'Fetching deck...';
|
||||
});
|
||||
|
||||
final items = await repo.fetchAndCacheFromWk(apiKey);
|
||||
var items = await repo.loadKanji();
|
||||
if (items.isEmpty) {
|
||||
setState(() {
|
||||
_status = 'Fetching deck...';
|
||||
});
|
||||
items = await repo.fetchAndCacheFromWk(apiKey);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_deck = items;
|
||||
@@ -102,14 +104,24 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
|
||||
final pickedType = choices[_random.nextInt(choices.length)];
|
||||
final readingsList = pickedType == 'onyomi' ? item.onyomi : item.kunyomi;
|
||||
final hint = 'Select the ${pickedType == 'onyomi' ? "on'yomi" : "kunyomi"}';
|
||||
final hint = 'Select the ${pickedType == 'onyomi' ? "on\'yomi" : "kunyomi"}';
|
||||
|
||||
return _ReadingInfo(readingsList, hint);
|
||||
}
|
||||
|
||||
void _nextQuestion() {
|
||||
if (_deck.isEmpty) return;
|
||||
_current = (_deck..shuffle()).first;
|
||||
_deck.sort((a, b) {
|
||||
final aSrsItem = a.srsItems[_mode.toString()] ?? SrsItem(kanjiId: a.id, quizMode: _mode);
|
||||
final bSrsItem = b.srsItems[_mode.toString()] ?? SrsItem(kanjiId: b.id, quizMode: _mode);
|
||||
|
||||
final stageComparison = aSrsItem.srsStage.compareTo(bSrsItem.srsStage);
|
||||
if (stageComparison != 0) {
|
||||
return stageComparison;
|
||||
}
|
||||
return aSrsItem.lastAsked.compareTo(bSrsItem.lastAsked);
|
||||
});
|
||||
|
||||
_current = _deck.first;
|
||||
|
||||
_correctAnswers = [];
|
||||
_options = [];
|
||||
@@ -150,15 +162,42 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _answer(String option) {
|
||||
void _answer(String option) async {
|
||||
final isCorrect = _correctAnswers
|
||||
.map((a) => a.toLowerCase().trim())
|
||||
.contains(option.toLowerCase().trim());
|
||||
|
||||
final repo = Provider.of<DeckRepository>(context, listen: false);
|
||||
final current = _current!;
|
||||
|
||||
String readingType = '';
|
||||
if (_mode == QuizMode.reading) {
|
||||
readingType = _readingHint.contains("on'yomi") ? 'onyomi' : 'kunyomi';
|
||||
}
|
||||
final srsKey = _mode.toString() + readingType;
|
||||
|
||||
var srsItem = current.srsItems[srsKey];
|
||||
final isNew = srsItem == null;
|
||||
srsItem ??= SrsItem(kanjiId: current.id, quizMode: _mode, readingType: readingType);
|
||||
|
||||
setState(() {
|
||||
_asked += 1;
|
||||
if (isCorrect) _score += 1;
|
||||
if (isCorrect) {
|
||||
_score += 1;
|
||||
srsItem!.srsStage += 1;
|
||||
} else {
|
||||
srsItem!.srsStage = max(0, srsItem.srsStage - 1);
|
||||
}
|
||||
srsItem.lastAsked = DateTime.now();
|
||||
current.srsItems[srsKey] = srsItem;
|
||||
});
|
||||
|
||||
if (isNew) {
|
||||
await repo.insertSrsItem(srsItem);
|
||||
} else {
|
||||
await repo.updateSrsItem(srsItem);
|
||||
}
|
||||
|
||||
final correctDisplay = (_mode == QuizMode.kanjiToEnglish)
|
||||
? _toTitleCase(_correctAnswers.first)
|
||||
: (_mode == QuizMode.reading ? _correctAnswers.join(', ') : _correctAnswers.first);
|
||||
@@ -174,7 +213,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
backgroundColor: const Color(0xFF222222),
|
||||
duration: const Duration(milliseconds: 900),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snack);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(snack);
|
||||
}
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 900), _nextQuestion);
|
||||
}
|
||||
@@ -307,4 +348,4 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
backgroundColor: const Color(0xFF1E1E1E),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const HomeScreen()),
|
||||
MaterialPageRoute(builder: (_) => HomeScreen()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ class _StartScreenState extends State<StartScreen> {
|
||||
Future<void> _checkApiKey() async {
|
||||
final repo = Provider.of<DeckRepository>(context, listen: false);
|
||||
await repo.loadApiKey();
|
||||
// TODO: Remove this before release. This is for development purposes only.
|
||||
if (repo.apiKey == null || repo.apiKey!.isEmpty) {
|
||||
await repo.setApiKey('91932463-60d2-4552-95a7-4c23cf358189');
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_hasApiKey = repo.apiKey != null && repo.apiKey!.isNotEmpty;
|
||||
_loading = false;
|
||||
@@ -73,7 +78,7 @@ class _StartScreenState extends State<StartScreen> {
|
||||
onPressed: () {
|
||||
if (_hasApiKey) {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const HomeScreen()),
|
||||
MaterialPageRoute(builder: (_) => HomeScreen()),
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).push(
|
||||
@@ -84,7 +89,8 @@ class _StartScreenState extends State<StartScreen> {
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueAccent,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
|
||||
@@ -23,16 +23,29 @@ class DeckRepository {
|
||||
|
||||
_db = await openDatabase(
|
||||
path,
|
||||
version: 2,
|
||||
version: 4,
|
||||
onCreate: (db, version) async {
|
||||
await db.execute(
|
||||
'''CREATE TABLE kanji (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, onyomi TEXT, kunyomi TEXT)''');
|
||||
await db.execute(
|
||||
'''CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT)''');
|
||||
await db.execute(
|
||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''');
|
||||
},
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
await db.execute(
|
||||
'''CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)''');
|
||||
if (oldVersion < 2) {
|
||||
await db.execute(
|
||||
'''CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)''');
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// Migration from version 2 to 3 was flawed, so we just drop the columns if they exist
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
await db.execute(
|
||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''');
|
||||
// We are not migrating the old srs data, as it was not mode-specific.
|
||||
// Old columns will be dropped.
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -81,7 +94,7 @@ class DeckRepository {
|
||||
Future<List<KanjiItem>> loadKanji() async {
|
||||
final db = await _openDb();
|
||||
final rows = await db.query('kanji');
|
||||
return rows
|
||||
final kanjiItems = rows
|
||||
.map((r) => KanjiItem(
|
||||
id: r['id'] as int,
|
||||
characters: r['characters'] as String,
|
||||
@@ -99,6 +112,58 @@ class DeckRepository {
|
||||
.toList(),
|
||||
))
|
||||
.toList();
|
||||
|
||||
for (final item in kanjiItems) {
|
||||
final srsItems = await getSrsItems(item.id);
|
||||
for (final srsItem in srsItems) {
|
||||
final key = srsItem.quizMode.toString() + (srsItem.readingType ?? '');
|
||||
item.srsItems[key] = srsItem;
|
||||
}
|
||||
}
|
||||
|
||||
return kanjiItems;
|
||||
}
|
||||
|
||||
Future<List<SrsItem>> getSrsItems(int kanjiId) async {
|
||||
final db = await _openDb();
|
||||
final rows = await db.query('srs_items', where: 'kanjiId = ?', whereArgs: [kanjiId]);
|
||||
return rows.map((r) {
|
||||
return SrsItem(
|
||||
kanjiId: r['kanjiId'] as int,
|
||||
quizMode: QuizMode.values.firstWhere((e) => e.toString() == r['quizMode'] as String),
|
||||
readingType: r['readingType'] as String?,
|
||||
srsStage: r['srsStage'] as int,
|
||||
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<void> updateSrsItem(SrsItem item) async {
|
||||
final db = await _openDb();
|
||||
await db.update(
|
||||
'srs_items',
|
||||
{
|
||||
'srsStage': item.srsStage,
|
||||
'lastAsked': item.lastAsked.toIso8601String(),
|
||||
},
|
||||
where: 'kanjiId = ? AND quizMode = ? AND readingType = ?',
|
||||
whereArgs: [item.kanjiId, item.quizMode.toString(), item.readingType],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> insertSrsItem(SrsItem item) async {
|
||||
final db = await _openDb();
|
||||
await db.insert(
|
||||
'srs_items',
|
||||
{
|
||||
'kanjiId': item.kanjiId,
|
||||
'quizMode': item.quizMode.toString(),
|
||||
'readingType': item.readingType,
|
||||
'srsStage': item.srsStage,
|
||||
'lastAsked': item.lastAsked.toIso8601String(),
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<KanjiItem>> fetchAndCacheFromWk([String? apiKey]) async {
|
||||
|
||||
Reference in New Issue
Block a user