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/profile
|
||||
/android/app/release
|
||||
|
||||
*.jks
|
||||
gradle.properties
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
plugins {
|
||||
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.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.untitled1"
|
||||
namespace = "com.crylia.hirameki"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
@@ -20,17 +20,31 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.crylia.wanikani_kanji_srs"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
applicationId = "com.crylia.hirameki_srs"
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
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 {
|
||||
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">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:label="Hirameki SRS">
|
||||
android:label="Hirameki SRS"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<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 {
|
||||
repositories {
|
||||
google()
|
||||
@@ -5,18 +13,17 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
// Optional: custom build directory (keep this if you really need a shared build folder)
|
||||
val newBuildDir = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||
rootProject.layout.buildDirectory.set(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
val newSubprojectBuildDir = newBuildDir.dir(name)
|
||||
layout.buildDirectory.set(newSubprojectBuildDir)
|
||||
}
|
||||
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
|
||||
@@ -20,7 +20,7 @@ class WkApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'WaniKani SRS',
|
||||
title: 'Hirameki SRS',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData.dark(useMaterial3: true),
|
||||
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:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import '../models/kanji_item.dart';
|
||||
import '../api/wk_client.dart';
|
||||
|
||||
class DeckRepository with ChangeNotifier {
|
||||
class DeckRepository {
|
||||
Database? _db;
|
||||
String? _apiKey;
|
||||
|
||||
@@ -28,39 +27,50 @@ class DeckRepository with ChangeNotifier {
|
||||
version: 7,
|
||||
onCreate: (db, version) async {
|
||||
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(
|
||||
'''CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT)''');
|
||||
'''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))''');
|
||||
'''CREATE TABLE srs_items (kanjiId INTEGER, quizMode TEXT, readingType TEXT, srsStage INTEGER, lastAsked TEXT, PRIMARY KEY (kanjiId, quizMode, readingType))''',
|
||||
);
|
||||
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(
|
||||
'''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 {
|
||||
if (oldVersion < 2) {
|
||||
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) {
|
||||
// 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))''');
|
||||
'''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.
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
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(
|
||||
'''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) {
|
||||
try {
|
||||
await db.execute('ALTER TABLE vocabulary ADD COLUMN pronunciation_audios TEXT');
|
||||
await db.execute(
|
||||
'ALTER TABLE vocabulary ADD COLUMN pronunciation_audios TEXT',
|
||||
);
|
||||
} catch (_) {
|
||||
// Ignore error, column might already exist
|
||||
}
|
||||
@@ -81,17 +91,19 @@ class DeckRepository with ChangeNotifier {
|
||||
|
||||
Future<void> saveApiKey(String apiKey) async {
|
||||
final db = await _openDb();
|
||||
await db.insert(
|
||||
'settings',
|
||||
{'key': 'apiKey', 'value': apiKey},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
await db.insert('settings', {
|
||||
'key': 'apiKey',
|
||||
'value': apiKey,
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<String?> loadApiKey() async {
|
||||
final db = await _openDb();
|
||||
final rows =
|
||||
await db.query('settings', where: 'key = ?', whereArgs: ['apiKey']);
|
||||
final rows = await db.query(
|
||||
'settings',
|
||||
where: 'key = ?',
|
||||
whereArgs: ['apiKey'],
|
||||
);
|
||||
if (rows.isNotEmpty) {
|
||||
_apiKey = rows.first['value'] as String;
|
||||
return _apiKey;
|
||||
@@ -103,18 +115,14 @@ class DeckRepository with ChangeNotifier {
|
||||
final db = await _openDb();
|
||||
final batch = db.batch();
|
||||
for (final it in items) {
|
||||
batch.insert(
|
||||
'kanji',
|
||||
{
|
||||
'id': it.id,
|
||||
'level': it.level,
|
||||
'characters': it.characters,
|
||||
'meanings': it.meanings.join('|'),
|
||||
'onyomi': it.onyomi.join('|'),
|
||||
'kunyomi': it.kunyomi.join('|'),
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
batch.insert('kanji', {
|
||||
'id': it.id,
|
||||
'level': it.level,
|
||||
'characters': it.characters,
|
||||
'meanings': it.meanings.join('|'),
|
||||
'onyomi': it.onyomi.join('|'),
|
||||
'kunyomi': it.kunyomi.join('|'),
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
}
|
||||
@@ -123,23 +131,25 @@ class DeckRepository with ChangeNotifier {
|
||||
final db = await _openDb();
|
||||
final rows = await db.query('kanji');
|
||||
final kanjiItems = rows
|
||||
.map((r) => KanjiItem(
|
||||
id: r['id'] as int,
|
||||
level: r['level'] as int? ?? 0,
|
||||
characters: r['characters'] as String,
|
||||
meanings: (r['meanings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
onyomi: (r['onyomi'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
kunyomi: (r['kunyomi'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
))
|
||||
.map(
|
||||
(r) => KanjiItem(
|
||||
id: r['id'] as int,
|
||||
level: r['level'] as int? ?? 0,
|
||||
characters: r['characters'] as String,
|
||||
meanings: (r['meanings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
onyomi: (r['onyomi'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
kunyomi: (r['kunyomi'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
for (final item in kanjiItems) {
|
||||
@@ -155,11 +165,17 @@ class DeckRepository with ChangeNotifier {
|
||||
|
||||
Future<List<SrsItem>> getSrsItems(int kanjiId) async {
|
||||
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 SrsItem(
|
||||
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?,
|
||||
srsStage: r['srsStage'] as int,
|
||||
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
||||
@@ -182,17 +198,13 @@ class DeckRepository with ChangeNotifier {
|
||||
|
||||
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,
|
||||
);
|
||||
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 {
|
||||
@@ -200,8 +212,9 @@ class DeckRepository with ChangeNotifier {
|
||||
if (key == null) throw Exception('API key not set');
|
||||
|
||||
final client = WkClient(key);
|
||||
final assignments =
|
||||
await client.fetchAllAssignments(subjectTypes: ['kanji']);
|
||||
final assignments = await client.fetchAllAssignments(
|
||||
subjectTypes: ['kanji'],
|
||||
);
|
||||
|
||||
final unlocked = <int>{};
|
||||
for (final a in assignments) {
|
||||
@@ -220,10 +233,12 @@ class DeckRepository with ChangeNotifier {
|
||||
|
||||
final subjects = await client.fetchSubjectsByIds(unlocked.toList());
|
||||
final items = subjects
|
||||
.where((s) =>
|
||||
s['object'] == 'kanji' ||
|
||||
(s['data'] != null &&
|
||||
(s['data'] as Map)['object_type'] == 'kanji'))
|
||||
.where(
|
||||
(s) =>
|
||||
s['object'] == 'kanji' ||
|
||||
(s['data'] != null &&
|
||||
(s['data'] as Map)['object_type'] == 'kanji'),
|
||||
)
|
||||
.map((s) => KanjiItem.fromSubject(s))
|
||||
.where((k) => k.characters.isNotEmpty && k.meanings.isNotEmpty)
|
||||
.toList();
|
||||
@@ -234,11 +249,17 @@ class DeckRepository with ChangeNotifier {
|
||||
|
||||
Future<List<VocabSrsItem>> getVocabSrsItems(int vocabId) async {
|
||||
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 VocabSrsItem(
|
||||
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,
|
||||
lastAsked: DateTime.parse(r['lastAsked'] as String),
|
||||
);
|
||||
@@ -260,16 +281,12 @@ class DeckRepository with ChangeNotifier {
|
||||
|
||||
Future<void> insertVocabSrsItem(VocabSrsItem item) async {
|
||||
final db = await _openDb();
|
||||
await db.insert(
|
||||
'srs_vocab_items',
|
||||
{
|
||||
'vocabId': item.vocabId,
|
||||
'quizMode': item.quizMode.toString(),
|
||||
'srsStage': item.srsStage,
|
||||
'lastAsked': item.lastAsked.toIso8601String(),
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
await db.insert('srs_vocab_items', {
|
||||
'vocabId': item.vocabId,
|
||||
'quizMode': item.quizMode.toString(),
|
||||
'srsStage': item.srsStage,
|
||||
'lastAsked': item.lastAsked.toIso8601String(),
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<void> saveVocabulary(List<VocabularyItem> items) async {
|
||||
@@ -279,18 +296,14 @@ class DeckRepository with ChangeNotifier {
|
||||
final audios = it.pronunciationAudios
|
||||
.map((a) => {'url': a.url, 'gender': a.gender})
|
||||
.toList();
|
||||
batch.insert(
|
||||
'vocabulary',
|
||||
{
|
||||
'id': it.id,
|
||||
'level': it.level,
|
||||
'characters': it.characters,
|
||||
'meanings': it.meanings.join('|'),
|
||||
'readings': it.readings.join('|'),
|
||||
'pronunciation_audios': jsonEncode(audios),
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
batch.insert('vocabulary', {
|
||||
'id': it.id,
|
||||
'level': it.level,
|
||||
'characters': it.characters,
|
||||
'meanings': it.meanings.join('|'),
|
||||
'readings': it.readings.join('|'),
|
||||
'pronunciation_audios': jsonEncode(audios),
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
}
|
||||
@@ -298,39 +311,39 @@ class DeckRepository with ChangeNotifier {
|
||||
Future<List<VocabularyItem>> loadVocabulary() async {
|
||||
final db = await _openDb();
|
||||
final rows = await db.query('vocabulary');
|
||||
final vocabItems = rows
|
||||
.map((r) {
|
||||
final audiosRaw = r['pronunciation_audios'] as String?;
|
||||
final List<PronunciationAudio> audios = [];
|
||||
if (audiosRaw != null && audiosRaw.isNotEmpty) {
|
||||
try {
|
||||
final decoded = jsonDecode(audiosRaw) as List;
|
||||
for (final audioData in decoded) {
|
||||
audios.add(PronunciationAudio(
|
||||
url: audioData['url'] as String,
|
||||
gender: audioData['gender'] as String,
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
// Error decoding, so we'll just have no audio for this item
|
||||
}
|
||||
final vocabItems = rows.map((r) {
|
||||
final audiosRaw = r['pronunciation_audios'] as String?;
|
||||
final List<PronunciationAudio> audios = [];
|
||||
if (audiosRaw != null && audiosRaw.isNotEmpty) {
|
||||
try {
|
||||
final decoded = jsonDecode(audiosRaw) as List;
|
||||
for (final audioData in decoded) {
|
||||
audios.add(
|
||||
PronunciationAudio(
|
||||
url: audioData['url'] as String,
|
||||
gender: audioData['gender'] as String,
|
||||
),
|
||||
);
|
||||
}
|
||||
return VocabularyItem(
|
||||
id: r['id'] as int,
|
||||
level: r['level'] as int? ?? 0,
|
||||
characters: r['characters'] as String,
|
||||
meanings: (r['meanings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
readings: (r['readings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
pronunciationAudios: audios,
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
} catch (e) {
|
||||
// Error decoding, so we'll just have no audio for this item
|
||||
}
|
||||
}
|
||||
return VocabularyItem(
|
||||
id: r['id'] as int,
|
||||
level: r['level'] as int? ?? 0,
|
||||
characters: r['characters'] as String,
|
||||
meanings: (r['meanings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
readings: (r['readings'] as String)
|
||||
.split('|')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList(),
|
||||
pronunciationAudios: audios,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
for (final item in vocabItems) {
|
||||
final srsItems = await getVocabSrsItems(item.id);
|
||||
@@ -343,13 +356,16 @@ class DeckRepository with ChangeNotifier {
|
||||
return vocabItems;
|
||||
}
|
||||
|
||||
Future<List<VocabularyItem>> fetchAndCacheVocabularyFromWk([String? apiKey]) async {
|
||||
Future<List<VocabularyItem>> fetchAndCacheVocabularyFromWk([
|
||||
String? apiKey,
|
||||
]) async {
|
||||
final key = apiKey ?? _apiKey;
|
||||
if (key == null) throw Exception('API key not set');
|
||||
|
||||
final client = WkClient(key);
|
||||
final assignments =
|
||||
await client.fetchAllAssignments(subjectTypes: ['vocabulary']);
|
||||
final assignments = await client.fetchAllAssignments(
|
||||
subjectTypes: ['vocabulary'],
|
||||
);
|
||||
|
||||
final unlocked = <int>{};
|
||||
for (final a in assignments) {
|
||||
@@ -368,10 +384,12 @@ class DeckRepository with ChangeNotifier {
|
||||
|
||||
final subjects = await client.fetchSubjectsByIds(unlocked.toList());
|
||||
final items = subjects
|
||||
.where((s) =>
|
||||
s['object'] == 'vocabulary' ||
|
||||
(s['data'] != null &&
|
||||
(s['data'] as Map)['object_type'] == 'vocabulary'))
|
||||
.where(
|
||||
(s) =>
|
||||
s['object'] == 'vocabulary' ||
|
||||
(s['data'] != null &&
|
||||
(s['data'] as Map)['object_type'] == 'vocabulary'),
|
||||
)
|
||||
.map((s) => VocabularyItem.fromSubject(s))
|
||||
.where((k) => k.characters.isNotEmpty && k.meanings.isNotEmpty)
|
||||
.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.
|
||||
version: 0.1.0+1
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
sdk: ">=3.9.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
audioplayers: any
|
||||
flutter:
|
||||
sdk: flutter
|
||||
shared_preferences: ^2.5.3
|
||||
sqflite: ^2.4.2
|
||||
path_provider: ^2.1.5
|
||||
path: ^1.9.1
|
||||
provider: ^6.1.5+1
|
||||
http: ^1.5.0
|
||||
audioplayers: ^6.0.0
|
||||
http: any
|
||||
path: any
|
||||
path_provider: any
|
||||
provider: any
|
||||
shared_preferences: any
|
||||
sqflite: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
mockito: ^5.5.0
|
||||
test: ^1.26.2
|
||||
build_runner: ^2.4.10
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
flutter_lints: ^6.0.0
|
||||
mockito: any
|
||||
test: any
|
||||
build_runner: any
|
||||
flutter_launcher_icons: any
|
||||
flutter_lints: any
|
||||
|
||||
flutter_icons:
|
||||
android: true
|
||||
|
||||
Reference in New Issue
Block a user