some build stuff and quick fix

This commit is contained in:
Rene Kievits
2025-10-29 02:47:41 +01:00
parent d2070cd6b8
commit 6785bb9133
8 changed files with 202 additions and 182 deletions

3
.gitignore vendored
View File

@@ -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

View File

@@ -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",
)
} }
} }
} }

View File

@@ -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

View File

@@ -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") {

View File

@@ -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(),

View File

@@ -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(),
),
);
}
}

View File

@@ -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,
{ 'level': it.level,
'id': it.id, 'characters': it.characters,
'level': it.level, 'meanings': it.meanings.join('|'),
'characters': it.characters, 'onyomi': it.onyomi.join('|'),
'meanings': it.meanings.join('|'), 'kunyomi': it.kunyomi.join('|'),
'onyomi': it.onyomi.join('|'), }, conflictAlgorithm: ConflictAlgorithm.replace);
'kunyomi': it.kunyomi.join('|'),
},
conflictAlgorithm: ConflictAlgorithm.replace,
);
} }
await batch.commit(noResult: true); await batch.commit(noResult: true);
} }
@@ -123,23 +131,25 @@ 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(
id: r['id'] as int, (r) => KanjiItem(
level: r['level'] as int? ?? 0, id: r['id'] as int,
characters: r['characters'] as String, level: r['level'] as int? ?? 0,
meanings: (r['meanings'] as String) characters: r['characters'] as String,
.split('|') meanings: (r['meanings'] as String)
.where((s) => s.isNotEmpty) .split('|')
.toList(), .where((s) => s.isNotEmpty)
onyomi: (r['onyomi'] as String) .toList(),
.split('|') onyomi: (r['onyomi'] as String)
.where((s) => s.isNotEmpty) .split('|')
.toList(), .where((s) => s.isNotEmpty)
kunyomi: (r['kunyomi'] as String) .toList(),
.split('|') kunyomi: (r['kunyomi'] as String)
.where((s) => s.isNotEmpty) .split('|')
.toList(), .where((s) => s.isNotEmpty)
)) .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,
{ 'quizMode': item.quizMode.toString(),
'kanjiId': item.kanjiId, 'readingType': item.readingType,
'quizMode': item.quizMode.toString(), 'srsStage': item.srsStage,
'readingType': item.readingType, 'lastAsked': item.lastAsked.toIso8601String(),
'srsStage': item.srsStage, }, conflictAlgorithm: ConflictAlgorithm.replace);
'lastAsked': item.lastAsked.toIso8601String(),
},
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['object'] == 'kanji' || (s) =>
(s['data'] != null && s['object'] == 'kanji' ||
(s['data'] as Map)['object_type'] == 'kanji')) (s['data'] != null &&
(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,
{ 'quizMode': item.quizMode.toString(),
'vocabId': item.vocabId, 'srsStage': item.srsStage,
'quizMode': item.quizMode.toString(), 'lastAsked': item.lastAsked.toIso8601String(),
'srsStage': item.srsStage, }, conflictAlgorithm: ConflictAlgorithm.replace);
'lastAsked': item.lastAsked.toIso8601String(),
},
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,
{ 'level': it.level,
'id': it.id, 'characters': it.characters,
'level': it.level, 'meanings': it.meanings.join('|'),
'characters': it.characters, 'readings': it.readings.join('|'),
'meanings': it.meanings.join('|'), 'pronunciation_audios': jsonEncode(audios),
'readings': it.readings.join('|'), }, conflictAlgorithm: ConflictAlgorithm.replace);
'pronunciation_audios': jsonEncode(audios),
},
conflictAlgorithm: ConflictAlgorithm.replace,
);
} }
await batch.commit(noResult: true); await batch.commit(noResult: true);
} }
@@ -298,39 +311,39 @@ 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(
audios.add(PronunciationAudio( PronunciationAudio(
url: audioData['url'] as String, url: audioData['url'] as String,
gender: audioData['gender'] as String, gender: audioData['gender'] as String,
)); ),
} );
} catch (e) {
// Error decoding, so we'll just have no audio for this item
}
} }
return VocabularyItem( } catch (e) {
id: r['id'] as int, // Error decoding, so we'll just have no audio for this item
level: r['level'] as int? ?? 0, }
characters: r['characters'] as String, }
meanings: (r['meanings'] as String) return VocabularyItem(
.split('|') id: r['id'] as int,
.where((s) => s.isNotEmpty) level: r['level'] as int? ?? 0,
.toList(), characters: r['characters'] as String,
readings: (r['readings'] as String) meanings: (r['meanings'] as String)
.split('|') .split('|')
.where((s) => s.isNotEmpty) .where((s) => s.isNotEmpty)
.toList(), .toList(),
pronunciationAudios: audios, readings: (r['readings'] as String)
); .split('|')
}) .where((s) => s.isNotEmpty)
.toList(); .toList(),
pronunciationAudios: audios,
);
}).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['object'] == 'vocabulary' || (s) =>
(s['data'] != null && s['object'] == 'vocabulary' ||
(s['data'] as Map)['object_type'] == 'vocabulary')) (s['data'] != null &&
(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();
@@ -379,4 +397,4 @@ class DeckRepository with ChangeNotifier {
await saveVocabulary(items); await saveVocabulary(items);
return items; return items;
} }
} }

View File

@@ -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