some build stuff and quick fix
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,3 +43,6 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
*.jks
|
||||||
|
gradle.properties
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("org.jetbrains.kotlin.android")
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.untitled1"
|
namespace = "com.crylia.hirameki"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@@ -20,17 +20,31 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.crylia.wanikani_kanji_srs"
|
applicationId = "com.crylia.hirameki_srs"
|
||||||
// You can update the following values to match your application needs.
|
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file("hirameki-release-key.jks")
|
||||||
|
storePassword = project.findProperty("KEYSTORE_PASSWORD")?.toString()
|
||||||
|
keyAlias = project.findProperty("KEY_ALIAS")?.toString()
|
||||||
|
keyPassword = project.findProperty("KEY_PASSWORD")?.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
getByName("release") {
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<application
|
<application
|
||||||
android:label="Hirameki SRS">
|
android:label="Hirameki SRS"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
// Ensure these match your Flutter Gradle plugin requirements
|
||||||
|
id("com.android.application") version "8.9.1" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||||
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
@@ -5,18 +13,17 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val newBuildDir: Directory =
|
// Optional: custom build directory (keep this if you really need a shared build folder)
|
||||||
rootProject.layout.buildDirectory
|
val newBuildDir = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||||
.dir("../../build")
|
rootProject.layout.buildDirectory.set(newBuildDir)
|
||||||
.get()
|
|
||||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
val newSubprojectBuildDir = newBuildDir.dir(name)
|
||||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
layout.buildDirectory.set(newSubprojectBuildDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
project.evaluationDependsOn(":app")
|
evaluationDependsOn(":app")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
tasks.register<Delete>("clean") {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class WkApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'WaniKani SRS',
|
title: 'Hirameki SRS',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData.dark(useMaterial3: true),
|
theme: ThemeData.dark(useMaterial3: true),
|
||||||
home: const StartScreen(),
|
home: const StartScreen(),
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'screens/home_screen.dart';
|
|
||||||
import 'services/deck_repository.dart';
|
|
||||||
|
|
||||||
class WkApp extends StatelessWidget {
|
|
||||||
const WkApp({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return MultiProvider(
|
|
||||||
providers: [
|
|
||||||
Provider<DeckRepository>(create: (_) => DeckRepository()),
|
|
||||||
],
|
|
||||||
child: MaterialApp(
|
|
||||||
title: 'WaniKani SRS',
|
|
||||||
theme: ThemeData(useMaterial3: true, colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple)),
|
|
||||||
home: const HomeScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
import '../models/kanji_item.dart';
|
import '../models/kanji_item.dart';
|
||||||
import '../api/wk_client.dart';
|
import '../api/wk_client.dart';
|
||||||
|
|
||||||
class DeckRepository with ChangeNotifier {
|
class DeckRepository {
|
||||||
Database? _db;
|
Database? _db;
|
||||||
String? _apiKey;
|
String? _apiKey;
|
||||||
|
|
||||||
@@ -28,39 +27,50 @@ class DeckRepository with ChangeNotifier {
|
|||||||
version: 7,
|
version: 7,
|
||||||
onCreate: (db, version) async {
|
onCreate: (db, version) async {
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE kanji (id INTEGER PRIMARY KEY, level INTEGER, characters TEXT, meanings TEXT, onyomi TEXT, kunyomi TEXT)''');
|
'''CREATE TABLE kanji (id INTEGER PRIMARY KEY, level INTEGER, characters TEXT, meanings TEXT, onyomi TEXT, kunyomi TEXT)''',
|
||||||
|
);
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT)''');
|
'''CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT)''',
|
||||||
|
);
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''');
|
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''',
|
||||||
|
);
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, level INTEGER, characters TEXT, meanings TEXT, readings TEXT, pronunciation_audios TEXT)''');
|
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, level INTEGER, characters TEXT, meanings TEXT, readings TEXT, pronunciation_audios TEXT)''',
|
||||||
|
);
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE srs_vocab_items (vocabId INTEGER, quizMode TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (vocabId, quizMode))''');
|
'''CREATE TABLE srs_vocab_items (vocabId INTEGER, quizMode TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (vocabId, quizMode))''',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onUpgrade: (db, oldVersion, newVersion) async {
|
onUpgrade: (db, oldVersion, newVersion) async {
|
||||||
if (oldVersion < 2) {
|
if (oldVersion < 2) {
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)''');
|
'''CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)''',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (oldVersion < 3) {
|
if (oldVersion < 3) {
|
||||||
// Migration from version 2 to 3 was flawed, so we just drop the columns if they exist
|
// Migration from version 2 to 3 was flawed, so we just drop the columns if they exist
|
||||||
}
|
}
|
||||||
if (oldVersion < 4) {
|
if (oldVersion < 4) {
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''');
|
'''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.
|
// We are not migrating the old srs data, as it was not mode-specific.
|
||||||
// Old columns will be dropped.
|
// Old columns will be dropped.
|
||||||
}
|
}
|
||||||
if (oldVersion < 5) {
|
if (oldVersion < 5) {
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, readings TEXT)''');
|
'''CREATE TABLE vocabulary (id INTEGER PRIMARY KEY, characters TEXT, meanings TEXT, readings TEXT)''',
|
||||||
|
);
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''CREATE TABLE srs_vocab_items (vocabId INTEGER, quizMode TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (vocabId, quizMode))''');
|
'''CREATE TABLE srs_vocab_items (vocabId INTEGER, quizMode TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (vocabId, quizMode))''',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (oldVersion < 6) {
|
if (oldVersion < 6) {
|
||||||
try {
|
try {
|
||||||
await db.execute('ALTER TABLE vocabulary ADD COLUMN pronunciation_audios TEXT');
|
await db.execute(
|
||||||
|
'ALTER TABLE vocabulary ADD COLUMN pronunciation_audios TEXT',
|
||||||
|
);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Ignore error, column might already exist
|
// Ignore error, column might already exist
|
||||||
}
|
}
|
||||||
@@ -81,17 +91,19 @@ class DeckRepository with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> saveApiKey(String apiKey) async {
|
Future<void> saveApiKey(String apiKey) async {
|
||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
await db.insert(
|
await db.insert('settings', {
|
||||||
'settings',
|
'key': 'apiKey',
|
||||||
{'key': 'apiKey', 'value': apiKey},
|
'value': apiKey,
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> loadApiKey() async {
|
Future<String?> loadApiKey() async {
|
||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
final rows =
|
final rows = await db.query(
|
||||||
await db.query('settings', where: 'key = ?', whereArgs: ['apiKey']);
|
'settings',
|
||||||
|
where: 'key = ?',
|
||||||
|
whereArgs: ['apiKey'],
|
||||||
|
);
|
||||||
if (rows.isNotEmpty) {
|
if (rows.isNotEmpty) {
|
||||||
_apiKey = rows.first['value'] as String;
|
_apiKey = rows.first['value'] as String;
|
||||||
return _apiKey;
|
return _apiKey;
|
||||||
@@ -103,18 +115,14 @@ class DeckRepository with ChangeNotifier {
|
|||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
final batch = db.batch();
|
final batch = db.batch();
|
||||||
for (final it in items) {
|
for (final it in items) {
|
||||||
batch.insert(
|
batch.insert('kanji', {
|
||||||
'kanji',
|
|
||||||
{
|
|
||||||
'id': it.id,
|
'id': it.id,
|
||||||
'level': it.level,
|
'level': it.level,
|
||||||
'characters': it.characters,
|
'characters': it.characters,
|
||||||
'meanings': it.meanings.join('|'),
|
'meanings': it.meanings.join('|'),
|
||||||
'onyomi': it.onyomi.join('|'),
|
'onyomi': it.onyomi.join('|'),
|
||||||
'kunyomi': it.kunyomi.join('|'),
|
'kunyomi': it.kunyomi.join('|'),
|
||||||
},
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
await batch.commit(noResult: true);
|
||||||
}
|
}
|
||||||
@@ -123,7 +131,8 @@ class DeckRepository with ChangeNotifier {
|
|||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
final rows = await db.query('kanji');
|
final rows = await db.query('kanji');
|
||||||
final kanjiItems = rows
|
final kanjiItems = rows
|
||||||
.map((r) => KanjiItem(
|
.map(
|
||||||
|
(r) => KanjiItem(
|
||||||
id: r['id'] as int,
|
id: r['id'] as int,
|
||||||
level: r['level'] as int? ?? 0,
|
level: r['level'] as int? ?? 0,
|
||||||
characters: r['characters'] as String,
|
characters: r['characters'] as String,
|
||||||
@@ -139,7 +148,8 @@ class DeckRepository with ChangeNotifier {
|
|||||||
.split('|')
|
.split('|')
|
||||||
.where((s) => s.isNotEmpty)
|
.where((s) => s.isNotEmpty)
|
||||||
.toList(),
|
.toList(),
|
||||||
))
|
),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
for (final item in kanjiItems) {
|
for (final item in kanjiItems) {
|
||||||
@@ -155,11 +165,17 @@ class DeckRepository with ChangeNotifier {
|
|||||||
|
|
||||||
Future<List<SrsItem>> getSrsItems(int kanjiId) async {
|
Future<List<SrsItem>> getSrsItems(int kanjiId) async {
|
||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
final rows = await db.query('srs_items', where: 'kanjiId = ?', whereArgs: [kanjiId]);
|
final rows = await db.query(
|
||||||
|
'srs_items',
|
||||||
|
where: 'kanjiId = ?',
|
||||||
|
whereArgs: [kanjiId],
|
||||||
|
);
|
||||||
return rows.map((r) {
|
return rows.map((r) {
|
||||||
return SrsItem(
|
return SrsItem(
|
||||||
kanjiId: r['kanjiId'] as int,
|
kanjiId: r['kanjiId'] as int,
|
||||||
quizMode: QuizMode.values.firstWhere((e) => e.toString() == r['quizMode'] as String),
|
quizMode: QuizMode.values.firstWhere(
|
||||||
|
(e) => e.toString() == r['quizMode'] as String,
|
||||||
|
),
|
||||||
readingType: r['readingType'] as String?,
|
readingType: r['readingType'] as String?,
|
||||||
srsStage: r['srsStage'] as int,
|
srsStage: r['srsStage'] as int,
|
||||||
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
||||||
@@ -182,17 +198,13 @@ class DeckRepository with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> insertSrsItem(SrsItem item) async {
|
Future<void> insertSrsItem(SrsItem item) async {
|
||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
await db.insert(
|
await db.insert('srs_items', {
|
||||||
'srs_items',
|
|
||||||
{
|
|
||||||
'kanjiId': item.kanjiId,
|
'kanjiId': item.kanjiId,
|
||||||
'quizMode': item.quizMode.toString(),
|
'quizMode': item.quizMode.toString(),
|
||||||
'readingType': item.readingType,
|
'readingType': item.readingType,
|
||||||
'srsStage': item.srsStage,
|
'srsStage': item.srsStage,
|
||||||
'lastAsked': item.lastAsked.toIso8601String(),
|
'lastAsked': item.lastAsked.toIso8601String(),
|
||||||
},
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<KanjiItem>> fetchAndCacheFromWk([String? apiKey]) async {
|
Future<List<KanjiItem>> fetchAndCacheFromWk([String? apiKey]) async {
|
||||||
@@ -200,8 +212,9 @@ class DeckRepository with ChangeNotifier {
|
|||||||
if (key == null) throw Exception('API key not set');
|
if (key == null) throw Exception('API key not set');
|
||||||
|
|
||||||
final client = WkClient(key);
|
final client = WkClient(key);
|
||||||
final assignments =
|
final assignments = await client.fetchAllAssignments(
|
||||||
await client.fetchAllAssignments(subjectTypes: ['kanji']);
|
subjectTypes: ['kanji'],
|
||||||
|
);
|
||||||
|
|
||||||
final unlocked = <int>{};
|
final unlocked = <int>{};
|
||||||
for (final a in assignments) {
|
for (final a in assignments) {
|
||||||
@@ -220,10 +233,12 @@ class DeckRepository with ChangeNotifier {
|
|||||||
|
|
||||||
final subjects = await client.fetchSubjectsByIds(unlocked.toList());
|
final subjects = await client.fetchSubjectsByIds(unlocked.toList());
|
||||||
final items = subjects
|
final items = subjects
|
||||||
.where((s) =>
|
.where(
|
||||||
|
(s) =>
|
||||||
s['object'] == 'kanji' ||
|
s['object'] == 'kanji' ||
|
||||||
(s['data'] != null &&
|
(s['data'] != null &&
|
||||||
(s['data'] as Map)['object_type'] == 'kanji'))
|
(s['data'] as Map)['object_type'] == 'kanji'),
|
||||||
|
)
|
||||||
.map((s) => KanjiItem.fromSubject(s))
|
.map((s) => KanjiItem.fromSubject(s))
|
||||||
.where((k) => k.characters.isNotEmpty && k.meanings.isNotEmpty)
|
.where((k) => k.characters.isNotEmpty && k.meanings.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -234,11 +249,17 @@ class DeckRepository with ChangeNotifier {
|
|||||||
|
|
||||||
Future<List<VocabSrsItem>> getVocabSrsItems(int vocabId) async {
|
Future<List<VocabSrsItem>> getVocabSrsItems(int vocabId) async {
|
||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
final rows = await db.query('srs_vocab_items', where: 'vocabId = ?', whereArgs: [vocabId]);
|
final rows = await db.query(
|
||||||
|
'srs_vocab_items',
|
||||||
|
where: 'vocabId = ?',
|
||||||
|
whereArgs: [vocabId],
|
||||||
|
);
|
||||||
return rows.map((r) {
|
return rows.map((r) {
|
||||||
return VocabSrsItem(
|
return VocabSrsItem(
|
||||||
vocabId: r['vocabId'] as int,
|
vocabId: r['vocabId'] as int,
|
||||||
quizMode: VocabQuizMode.values.firstWhere((e) => e.toString() == r['quizMode'] as String),
|
quizMode: VocabQuizMode.values.firstWhere(
|
||||||
|
(e) => e.toString() == r['quizMode'] as String,
|
||||||
|
),
|
||||||
srsStage: r['srsStage'] as int,
|
srsStage: r['srsStage'] as int,
|
||||||
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
||||||
);
|
);
|
||||||
@@ -260,16 +281,12 @@ class DeckRepository with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> insertVocabSrsItem(VocabSrsItem item) async {
|
Future<void> insertVocabSrsItem(VocabSrsItem item) async {
|
||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
await db.insert(
|
await db.insert('srs_vocab_items', {
|
||||||
'srs_vocab_items',
|
|
||||||
{
|
|
||||||
'vocabId': item.vocabId,
|
'vocabId': item.vocabId,
|
||||||
'quizMode': item.quizMode.toString(),
|
'quizMode': item.quizMode.toString(),
|
||||||
'srsStage': item.srsStage,
|
'srsStage': item.srsStage,
|
||||||
'lastAsked': item.lastAsked.toIso8601String(),
|
'lastAsked': item.lastAsked.toIso8601String(),
|
||||||
},
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveVocabulary(List<VocabularyItem> items) async {
|
Future<void> saveVocabulary(List<VocabularyItem> items) async {
|
||||||
@@ -279,18 +296,14 @@ class DeckRepository with ChangeNotifier {
|
|||||||
final audios = it.pronunciationAudios
|
final audios = it.pronunciationAudios
|
||||||
.map((a) => {'url': a.url, 'gender': a.gender})
|
.map((a) => {'url': a.url, 'gender': a.gender})
|
||||||
.toList();
|
.toList();
|
||||||
batch.insert(
|
batch.insert('vocabulary', {
|
||||||
'vocabulary',
|
|
||||||
{
|
|
||||||
'id': it.id,
|
'id': it.id,
|
||||||
'level': it.level,
|
'level': it.level,
|
||||||
'characters': it.characters,
|
'characters': it.characters,
|
||||||
'meanings': it.meanings.join('|'),
|
'meanings': it.meanings.join('|'),
|
||||||
'readings': it.readings.join('|'),
|
'readings': it.readings.join('|'),
|
||||||
'pronunciation_audios': jsonEncode(audios),
|
'pronunciation_audios': jsonEncode(audios),
|
||||||
},
|
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
await batch.commit(noResult: true);
|
||||||
}
|
}
|
||||||
@@ -298,18 +311,19 @@ class DeckRepository with ChangeNotifier {
|
|||||||
Future<List<VocabularyItem>> loadVocabulary() async {
|
Future<List<VocabularyItem>> loadVocabulary() async {
|
||||||
final db = await _openDb();
|
final db = await _openDb();
|
||||||
final rows = await db.query('vocabulary');
|
final rows = await db.query('vocabulary');
|
||||||
final vocabItems = rows
|
final vocabItems = rows.map((r) {
|
||||||
.map((r) {
|
|
||||||
final audiosRaw = r['pronunciation_audios'] as String?;
|
final audiosRaw = r['pronunciation_audios'] as String?;
|
||||||
final List<PronunciationAudio> audios = [];
|
final List<PronunciationAudio> audios = [];
|
||||||
if (audiosRaw != null && audiosRaw.isNotEmpty) {
|
if (audiosRaw != null && audiosRaw.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
final decoded = jsonDecode(audiosRaw) as List;
|
final decoded = jsonDecode(audiosRaw) as List;
|
||||||
for (final audioData in decoded) {
|
for (final audioData in decoded) {
|
||||||
audios.add(PronunciationAudio(
|
audios.add(
|
||||||
|
PronunciationAudio(
|
||||||
url: audioData['url'] as String,
|
url: audioData['url'] as String,
|
||||||
gender: audioData['gender'] as String,
|
gender: audioData['gender'] as String,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Error decoding, so we'll just have no audio for this item
|
// Error decoding, so we'll just have no audio for this item
|
||||||
@@ -329,8 +343,7 @@ class DeckRepository with ChangeNotifier {
|
|||||||
.toList(),
|
.toList(),
|
||||||
pronunciationAudios: audios,
|
pronunciationAudios: audios,
|
||||||
);
|
);
|
||||||
})
|
}).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
for (final item in vocabItems) {
|
for (final item in vocabItems) {
|
||||||
final srsItems = await getVocabSrsItems(item.id);
|
final srsItems = await getVocabSrsItems(item.id);
|
||||||
@@ -343,13 +356,16 @@ class DeckRepository with ChangeNotifier {
|
|||||||
return vocabItems;
|
return vocabItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<VocabularyItem>> fetchAndCacheVocabularyFromWk([String? apiKey]) async {
|
Future<List<VocabularyItem>> fetchAndCacheVocabularyFromWk([
|
||||||
|
String? apiKey,
|
||||||
|
]) async {
|
||||||
final key = apiKey ?? _apiKey;
|
final key = apiKey ?? _apiKey;
|
||||||
if (key == null) throw Exception('API key not set');
|
if (key == null) throw Exception('API key not set');
|
||||||
|
|
||||||
final client = WkClient(key);
|
final client = WkClient(key);
|
||||||
final assignments =
|
final assignments = await client.fetchAllAssignments(
|
||||||
await client.fetchAllAssignments(subjectTypes: ['vocabulary']);
|
subjectTypes: ['vocabulary'],
|
||||||
|
);
|
||||||
|
|
||||||
final unlocked = <int>{};
|
final unlocked = <int>{};
|
||||||
for (final a in assignments) {
|
for (final a in assignments) {
|
||||||
@@ -368,10 +384,12 @@ class DeckRepository with ChangeNotifier {
|
|||||||
|
|
||||||
final subjects = await client.fetchSubjectsByIds(unlocked.toList());
|
final subjects = await client.fetchSubjectsByIds(unlocked.toList());
|
||||||
final items = subjects
|
final items = subjects
|
||||||
.where((s) =>
|
.where(
|
||||||
|
(s) =>
|
||||||
s['object'] == 'vocabulary' ||
|
s['object'] == 'vocabulary' ||
|
||||||
(s['data'] != null &&
|
(s['data'] != null &&
|
||||||
(s['data'] as Map)['object_type'] == 'vocabulary'))
|
(s['data'] as Map)['object_type'] == 'vocabulary'),
|
||||||
|
)
|
||||||
.map((s) => VocabularyItem.fromSubject(s))
|
.map((s) => VocabularyItem.fromSubject(s))
|
||||||
.where((k) => k.characters.isNotEmpty && k.meanings.isNotEmpty)
|
.where((k) => k.characters.isNotEmpty && k.meanings.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
|
|||||||
26
pubspec.yaml
26
pubspec.yaml
@@ -2,27 +2,27 @@ name: hirameki_srs
|
|||||||
description: A simple and effective Spaced Repetition System (SRS) app for learning Japanese kanji and vocabulary.
|
description: A simple and effective Spaced Repetition System (SRS) app for learning Japanese kanji and vocabulary.
|
||||||
version: 0.1.0+1
|
version: 0.1.0+1
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: ">=3.9.0 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
audioplayers: any
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
shared_preferences: ^2.5.3
|
http: any
|
||||||
sqflite: ^2.4.2
|
path: any
|
||||||
path_provider: ^2.1.5
|
path_provider: any
|
||||||
path: ^1.9.1
|
provider: any
|
||||||
provider: ^6.1.5+1
|
shared_preferences: any
|
||||||
http: ^1.5.0
|
sqflite: any
|
||||||
audioplayers: ^6.0.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
mockito: ^5.5.0
|
mockito: any
|
||||||
test: ^1.26.2
|
test: any
|
||||||
build_runner: ^2.4.10
|
build_runner: any
|
||||||
flutter_launcher_icons: ^0.14.4
|
flutter_launcher_icons: any
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: any
|
||||||
|
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
android: true
|
android: true
|
||||||
|
|||||||
Reference in New Issue
Block a user