mirror of
https://github.com/h7x4/Jisho-Study-Tool.git
synced 2024-12-22 13:57:29 +01:00
Migrate history to SQLite + more
- remove all sembast code - setup database migration system - setup data import export system - remove sembast object tests - make everything ready for implementing "saved lists" feature
This commit is contained in:
parent
cad62f2b8b
commit
d2a3de4823
@ -1,5 +1,9 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="app.jishostudytool.jisho_study_tool">
|
package="app.jishostudytool.jisho_study_tool">
|
||||||
|
<application
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
>
|
||||||
|
</application>
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="app.jishostudytool.jisho_study_tool">
|
package="app.jishostudytool.jisho_study_tool">
|
||||||
<application
|
<application
|
||||||
android:label="Jisho Study Tool"
|
android:label="Jisho Study Tool"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/launcher_icon">
|
android:icon="@mipmap/launcher_icon"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
>
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
64
assets/migrations/0001_initial.sql
Normal file
64
assets/migrations/0001_initial.sql
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
CREATE TABLE "JST_SavedList" (
|
||||||
|
"name" TEXT PRIMARY KEY NOT NULL,
|
||||||
|
"nextList" TEXT REFERENCES "JST_SavedList"("name")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX "JST_SavedList_byNextList" ON "JST_SavedList"("nextList");
|
||||||
|
|
||||||
|
CREATE TABLE "JST_SavedListEntry" (
|
||||||
|
"listName" TEXT NOT NULL REFERENCES "JST_SavedList"("name") ON DELETE CASCADE,
|
||||||
|
"entryText" TEXT NOT NULL,
|
||||||
|
"isKanji" BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
"lastModified" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"nextEntry" TEXT NOT NULL,
|
||||||
|
PRIMARY KEY ("listName", "entryText", "isKanji"),
|
||||||
|
FOREIGN KEY ("listName", "nextEntry") REFERENCES "JST_SavedListEntry"("listName", "entryText"),
|
||||||
|
CHECK ((NOT "isKanji") OR ("nextEntry" <> 0))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX "JST_SavedListEntry_byListName" ON "JST_SavedListEntry"("listName");
|
||||||
|
|
||||||
|
-- CREATE VIEW "JST_SavedListEntry_sortedByLists" AS
|
||||||
|
-- WITH RECURSIVE "JST_SavedListEntry_sorted"("next") AS (
|
||||||
|
-- -- Initial SELECT
|
||||||
|
-- UNION ALL
|
||||||
|
-- SELECT * FROM ""
|
||||||
|
-- -- Recursive Select
|
||||||
|
-- )
|
||||||
|
-- SELECT * FROM "JST_SavedListEntry_sorted";
|
||||||
|
|
||||||
|
CREATE TABLE "JST_HistoryEntry" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "JST_HistoryEntryKanji" (
|
||||||
|
"entryId" INTEGER NOT NULL REFERENCES "JST_HistoryEntry"("id") ON DELETE CASCADE,
|
||||||
|
"kanji" CHAR(1) NOT NULL,
|
||||||
|
PRIMARY KEY ("entryId", "kanji")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "JST_HistoryEntryWord" (
|
||||||
|
"entryId" INTEGER NOT NULL REFERENCES "JST_HistoryEntry"("id") ON DELETE CASCADE,
|
||||||
|
"searchword" TEXT NOT NULL,
|
||||||
|
"language" CHAR(1) CHECK ("language" IN ("e", "j")),
|
||||||
|
PRIMARY KEY ("entryId", "searchword")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "JST_HistoryEntryTimestamp" (
|
||||||
|
"entryId" INTEGER NOT NULL REFERENCES "JST_HistoryEntry"("id") ON DELETE CASCADE,
|
||||||
|
-- Here, I'm using INTEGER insted of TIMESTAMP or DATETIME, because it seems to be
|
||||||
|
-- the easiest way to deal with global and local timeconversion between dart and
|
||||||
|
-- SQLite.
|
||||||
|
"timestamp" INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY ("entryId", "timestamp")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX "JST_HistoryEntryTimestamp_byTimestamp" ON "JST_HistoryEntryTimestamp"("timestamp");
|
||||||
|
|
||||||
|
CREATE VIEW "JST_HistoryEntry_orderedByTimestamp" AS
|
||||||
|
SELECT * FROM "JST_HistoryEntryTimestamp"
|
||||||
|
LEFT JOIN "JST_HistoryEntryWord" USING ("entryId")
|
||||||
|
LEFT JOIN "JST_HistoryEntryKanji" USING ("entryId")
|
||||||
|
GROUP BY "entryId"
|
||||||
|
ORDER BY MAX("timestamp") DESC;
|
@ -1,51 +1,64 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
|
||||||
import '../../models/history/search.dart';
|
import '../../models/history/history_entry.dart';
|
||||||
import '../../routing/routes.dart';
|
import '../../routing/routes.dart';
|
||||||
import '../../services/datetime.dart';
|
import '../../services/datetime.dart';
|
||||||
|
import '../../services/snackbar.dart';
|
||||||
import '../../settings.dart';
|
import '../../settings.dart';
|
||||||
|
import '../common/loading.dart';
|
||||||
import 'kanji_box.dart';
|
import 'kanji_box.dart';
|
||||||
|
|
||||||
class SearchItem extends StatelessWidget {
|
class HistoryEntryItem extends StatelessWidget {
|
||||||
final Search search;
|
final HistoryEntry entry;
|
||||||
// final Widget search;
|
|
||||||
final int objectKey;
|
final int objectKey;
|
||||||
final void Function()? onDelete;
|
final void Function()? onDelete;
|
||||||
final void Function()? onFavourite;
|
final void Function()? onFavourite;
|
||||||
|
|
||||||
const SearchItem({
|
const HistoryEntryItem({
|
||||||
required this.search,
|
required this.entry,
|
||||||
required this.objectKey,
|
required this.objectKey,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
this.onFavourite,
|
this.onFavourite,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
Widget get _child => (search.isKanji)
|
Widget get _child => (entry.isKanji)
|
||||||
? KanjiBox(kanji: search.kanjiQuery!.kanji)
|
? KanjiBox(kanji: entry.kanji!)
|
||||||
: Text(search.wordQuery!.query);
|
: Text(entry.word!);
|
||||||
|
|
||||||
void Function() _onTap(context) => search.isKanji
|
void Function() _onTap(context) => entry.isKanji
|
||||||
? () => Navigator.pushNamed(
|
? () => Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
Routes.kanjiSearch,
|
Routes.kanjiSearch,
|
||||||
arguments: search.kanjiQuery!.kanji,
|
arguments: entry.kanji,
|
||||||
)
|
)
|
||||||
: () => Navigator.pushNamed(
|
: () => Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
Routes.search,
|
Routes.search,
|
||||||
arguments: search.wordQuery!.query,
|
arguments: entry.word,
|
||||||
);
|
);
|
||||||
|
|
||||||
MaterialPageRoute get timestamps => MaterialPageRoute(
|
MaterialPageRoute get timestamps => MaterialPageRoute(
|
||||||
builder: (context) => Scaffold(
|
builder: (context) => Scaffold(
|
||||||
appBar: AppBar(title: const Text('Last searched')),
|
appBar: AppBar(title: const Text('Last searched')),
|
||||||
body: ListView(
|
body: FutureBuilder<List<DateTime>>(
|
||||||
children: [
|
future: entry.timestamps,
|
||||||
for (final ts in search.timestamps.reversed)
|
builder: (context, snapshot) {
|
||||||
ListTile(title: Text('${formatDate(ts)} ${formatTime(ts)}'))
|
// TODO: provide proper error handling
|
||||||
],
|
if (snapshot.hasError)
|
||||||
|
return ErrorWidget(snapshot.error!);
|
||||||
|
if (!snapshot.hasData) return const LoadingScreen();
|
||||||
|
return ListView(
|
||||||
|
children: snapshot.data!
|
||||||
|
.map(
|
||||||
|
(ts) => ListTile(
|
||||||
|
title: Text('${formatDate(ts)} ${formatTime(ts)}'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -60,18 +73,15 @@ class SearchItem extends StatelessWidget {
|
|||||||
backgroundColor: Colors.yellow,
|
backgroundColor: Colors.yellow,
|
||||||
icon: Icons.star,
|
icon: Icons.star,
|
||||||
onPressed: (_) {
|
onPressed: (_) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showSnackbar(context, 'TODO: implement favourites');
|
||||||
const SnackBar(content: Text('TODO: implement favourites')),
|
|
||||||
);
|
|
||||||
onFavourite?.call();
|
onFavourite?.call();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
icon: Icons.delete,
|
icon: Icons.delete,
|
||||||
onPressed: (_) {
|
onPressed: (_) async {
|
||||||
final Database db = GetIt.instance.get<Database>();
|
await entry.delete();
|
||||||
Search.store.record(objectKey).delete(db);
|
|
||||||
onDelete?.call();
|
onDelete?.call();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -93,7 +103,7 @@ class SearchItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Text(formatTime(search.timestamp)),
|
child: Text(formatTime(entry.lastTimestamp)),
|
||||||
),
|
),
|
||||||
DefaultTextStyle.merge(
|
DefaultTextStyle.merge(
|
||||||
style: japaneseFont.textStyle,
|
style: japaneseFont.textStyle,
|
20
lib/data/archive_format.dart
Normal file
20
lib/data/archive_format.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
// Example file Structure:
|
||||||
|
// jisho_data_22.01.01_1
|
||||||
|
// - history.json
|
||||||
|
// - saved/
|
||||||
|
// - lista.json
|
||||||
|
// - listb.json
|
||||||
|
|
||||||
|
extension ArchiveFormat on Directory {
|
||||||
|
// TODO: make the export dir dependent on date
|
||||||
|
Directory get exportDirectory {
|
||||||
|
final dir = Directory(uri.resolve('export').path);
|
||||||
|
dir.createSync(recursive: true);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
File get historyFile => File(uri.resolve('history.json').path);
|
||||||
|
Directory get savedLists => Directory(uri.resolve('savedLists').path);
|
||||||
|
}
|
127
lib/data/database.dart
Normal file
127
lib/data/database.dart
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
|
export 'package:sqflite/sqlite_api.dart';
|
||||||
|
|
||||||
|
Database db() => GetIt.instance.get<Database>();
|
||||||
|
|
||||||
|
Future<Directory> _databaseDir() async {
|
||||||
|
final Directory appDocDir = await getApplicationDocumentsDirectory();
|
||||||
|
if (!appDocDir.existsSync()) appDocDir.createSync(recursive: true);
|
||||||
|
return appDocDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> databasePath() async {
|
||||||
|
return join((await _databaseDir()).path, 'jisho.sqlite');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> migrate(Database db, int oldVersion, int newVersion) async {
|
||||||
|
final String assetManifest =
|
||||||
|
await rootBundle.loadString('AssetManifest.json');
|
||||||
|
|
||||||
|
final List<String> migrations =
|
||||||
|
(jsonDecode(assetManifest) as Map<String, Object?>)
|
||||||
|
.keys
|
||||||
|
.where(
|
||||||
|
(assetPath) =>
|
||||||
|
assetPath.contains(RegExp(r'migrations\/\d{4}.*\.sql')),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
migrations.sort();
|
||||||
|
|
||||||
|
for (int i = oldVersion + 1; i <= newVersion; i++) {
|
||||||
|
log(
|
||||||
|
'Migrating database from v$i to v${i + 1} with File(${migrations[i - 1]})',
|
||||||
|
);
|
||||||
|
final migrationContent = await rootBundle.loadString(migrations[i - 1], cache: false);
|
||||||
|
|
||||||
|
migrationContent
|
||||||
|
.split(';')
|
||||||
|
.map(
|
||||||
|
(s) => s
|
||||||
|
.split('\n')
|
||||||
|
.where((l) => !l.startsWith(RegExp(r'\s*--')))
|
||||||
|
.join('\n')
|
||||||
|
.trim(),
|
||||||
|
)
|
||||||
|
.where((s) => s != '')
|
||||||
|
.forEach(db.execute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setupDatabase() async {
|
||||||
|
databaseFactory.debugSetLogLevel(sqfliteLogLevelSql);
|
||||||
|
final Database database = await openDatabase(
|
||||||
|
await databasePath(),
|
||||||
|
version: 1,
|
||||||
|
onCreate: (db, version) => migrate(db, 0, version),
|
||||||
|
onUpgrade: migrate,
|
||||||
|
onOpen: (db) => Future.wait([
|
||||||
|
db.execute('PRAGMA foreign_keys=ON')
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
GetIt.instance.registerSingleton<Database>(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> resetDatabase() async {
|
||||||
|
await db().close();
|
||||||
|
File(await databasePath()).deleteSync();
|
||||||
|
GetIt.instance.unregister<Database>();
|
||||||
|
await setupDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TableNames {
|
||||||
|
/// Attributes:
|
||||||
|
/// - id INTEGER
|
||||||
|
static const String historyEntry = 'JST_HistoryEntry';
|
||||||
|
|
||||||
|
/// Attributes:
|
||||||
|
/// - entryId INTEGER
|
||||||
|
/// - kanji CHAR(1)
|
||||||
|
static const String historyEntryKanji = 'JST_HistoryEntryKanji';
|
||||||
|
|
||||||
|
/// Attributes:
|
||||||
|
/// - entryId INTEGER
|
||||||
|
/// - timestamp INTEGER
|
||||||
|
static const String historyEntryTimestamp = 'JST_HistoryEntryTimestamp';
|
||||||
|
|
||||||
|
/// Attributes:
|
||||||
|
/// - entryId INTEGER
|
||||||
|
/// - searchword TEXT
|
||||||
|
/// - language CHAR(1)?
|
||||||
|
static const String historyEntryWord = 'JST_HistoryEntryWord';
|
||||||
|
|
||||||
|
/// Attributes:
|
||||||
|
/// - name TEXT
|
||||||
|
/// - nextList TEXT
|
||||||
|
static const String savedList = 'JST_SavedList';
|
||||||
|
|
||||||
|
/// Attributes:
|
||||||
|
/// - listName TEXT
|
||||||
|
/// - entryText TEXT
|
||||||
|
/// - isKanji BOOLEAN
|
||||||
|
/// - lastModified TIMESTAMP
|
||||||
|
/// - nextEntry TEXT
|
||||||
|
static const String savedListEntry = 'JST_SavedListEntry';
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// VIEWS //
|
||||||
|
///////////
|
||||||
|
|
||||||
|
/// Attributes:
|
||||||
|
/// - entryId INTEGER
|
||||||
|
/// - timestamp INTEGER
|
||||||
|
/// - searchword TEXT?
|
||||||
|
/// - kanji CHAR(1)?
|
||||||
|
/// - language CHAR(1)?
|
||||||
|
static const String historyEntryOrderedByTimestamp =
|
||||||
|
'JST_HistoryEntry_orderedByTimestamp';
|
||||||
|
}
|
45
lib/data/export.dart
Normal file
45
lib/data/export.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../models/history/history_entry.dart';
|
||||||
|
import 'database.dart';
|
||||||
|
|
||||||
|
Future<Directory> exportDirectory() async {
|
||||||
|
final basedir = (await getExternalStorageDirectory())!;
|
||||||
|
// TODO: fix path
|
||||||
|
final dir = Directory(basedir.uri.resolve('export').path);
|
||||||
|
dir.createSync(recursive: true);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path to which the data was saved.
|
||||||
|
Future<String> exportData() async {
|
||||||
|
final dir = await exportDirectory();
|
||||||
|
final savedDir = Directory.fromUri(dir.uri.resolve('saved'));
|
||||||
|
savedDir.createSync();
|
||||||
|
|
||||||
|
await Future.wait([
|
||||||
|
exportHistoryTo(dir),
|
||||||
|
exportSavedListsTo(savedDir),
|
||||||
|
]);
|
||||||
|
return dir.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> exportHistoryTo(Directory dir) async {
|
||||||
|
final file = File(dir.uri.resolve('history.json').path);
|
||||||
|
file.createSync();
|
||||||
|
final query = await db().query(TableNames.historyEntryOrderedByTimestamp);
|
||||||
|
final List<HistoryEntry> entries =
|
||||||
|
query.map((e) => HistoryEntry.fromDBMap(e)).toList();
|
||||||
|
final List<Map<String, Object?>> jsonEntries =
|
||||||
|
await Future.wait(entries.map((he) async => he.toJson()));
|
||||||
|
file.writeAsStringSync(jsonEncode(jsonEntries));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> exportSavedListsTo(Directory dir) async {
|
||||||
|
// TODO:
|
||||||
|
// final query = db().query(TableNames.savedList);
|
||||||
|
print('TODO: implement exportSavedLists');
|
||||||
|
}
|
25
lib/data/import.dart
Normal file
25
lib/data/import.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import '../models/history/history_entry.dart';
|
||||||
|
import 'archive_format.dart';
|
||||||
|
|
||||||
|
Future<void> importData(Directory dir) async {
|
||||||
|
await Future.wait([
|
||||||
|
importHistoryFrom(dir.historyFile),
|
||||||
|
importSavedListsFrom(dir.savedLists),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> importHistoryFrom(File file) async {
|
||||||
|
final String content = file.readAsStringSync();
|
||||||
|
await HistoryEntry.insertJsonEntries(
|
||||||
|
(jsonDecode(content) as List)
|
||||||
|
.map((h) => h as Map<String, Object?>)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> importSavedListsFrom(Directory savedListsDir) async {
|
||||||
|
print('TODO: Implement importSavedLists');
|
||||||
|
}
|
@ -2,8 +2,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'bloc/theme/theme_bloc.dart';
|
import 'bloc/theme/theme_bloc.dart';
|
||||||
|
import 'data/database.dart';
|
||||||
import 'routing/router.dart';
|
import 'routing/router.dart';
|
||||||
import 'services/database.dart';
|
|
||||||
import 'services/licenses.dart';
|
import 'services/licenses.dart';
|
||||||
import 'services/preferences.dart';
|
import 'services/preferences.dart';
|
||||||
import 'settings.dart';
|
import 'settings.dart';
|
||||||
|
358
lib/models/history/history_entry.dart
Normal file
358
lib/models/history/history_entry.dart
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
import '../../data/database.dart';
|
||||||
|
|
||||||
|
export 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
class HistoryEntry {
|
||||||
|
int id;
|
||||||
|
final String? kanji;
|
||||||
|
final String? word;
|
||||||
|
final DateTime lastTimestamp;
|
||||||
|
|
||||||
|
/// Whether this item is a kanji search or a word search
|
||||||
|
bool get isKanji => word == null;
|
||||||
|
|
||||||
|
HistoryEntry.withKanji({
|
||||||
|
required this.id,
|
||||||
|
required this.kanji,
|
||||||
|
required this.lastTimestamp,
|
||||||
|
}) : word = null;
|
||||||
|
|
||||||
|
HistoryEntry.withWord({
|
||||||
|
required this.id,
|
||||||
|
required this.word,
|
||||||
|
required this.lastTimestamp,
|
||||||
|
}) : kanji = null;
|
||||||
|
|
||||||
|
/// Reconstruct a HistoryEntry object with data from the database
|
||||||
|
/// This is specifically intended for the historyEntryOrderedByTimestamp
|
||||||
|
/// view, but it can also be used with custom searches as long as it
|
||||||
|
/// contains the following attributes:
|
||||||
|
///
|
||||||
|
/// - entryId
|
||||||
|
/// - timestamp
|
||||||
|
/// - searchword?
|
||||||
|
/// - kanji?
|
||||||
|
factory HistoryEntry.fromDBMap(Map<String, Object?> dbObject) =>
|
||||||
|
dbObject['searchword'] != null
|
||||||
|
? HistoryEntry.withWord(
|
||||||
|
id: dbObject['entryId']! as int,
|
||||||
|
word: dbObject['searchword']! as String,
|
||||||
|
lastTimestamp: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
dbObject['timestamp']! as int,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: HistoryEntry.withKanji(
|
||||||
|
id: dbObject['entryId']! as int,
|
||||||
|
kanji: dbObject['kanji']! as String,
|
||||||
|
lastTimestamp: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
dbObject['timestamp']! as int,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: There is a lot in common with
|
||||||
|
// insertKanji,
|
||||||
|
// insertWord,
|
||||||
|
// insertJsonEntry,
|
||||||
|
// insertJsonEntries,
|
||||||
|
// The commonalities should be factored into a helper function
|
||||||
|
|
||||||
|
/// Insert a kanji history entry into the database.
|
||||||
|
/// If it already exists, only a timestamp will be added
|
||||||
|
static Future<HistoryEntry> insertKanji({
|
||||||
|
required String kanji,
|
||||||
|
}) =>
|
||||||
|
db().transaction((txn) async {
|
||||||
|
final DateTime timestamp = DateTime.now();
|
||||||
|
late final int id;
|
||||||
|
|
||||||
|
final existingEntry = await txn.query(
|
||||||
|
TableNames.historyEntryKanji,
|
||||||
|
where: 'kanji = ?',
|
||||||
|
whereArgs: [kanji],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingEntry.isNotEmpty) {
|
||||||
|
// Retrieve entry record id, and add a timestamp.
|
||||||
|
id = existingEntry.first['entryId']! as int;
|
||||||
|
await txn.insert(TableNames.historyEntryTimestamp, {
|
||||||
|
'entryId': id,
|
||||||
|
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create new record, and add a timestamp.
|
||||||
|
id = await txn.insert(
|
||||||
|
TableNames.historyEntry,
|
||||||
|
{},
|
||||||
|
nullColumnHack: 'id',
|
||||||
|
);
|
||||||
|
final Batch b = txn.batch();
|
||||||
|
|
||||||
|
b.insert(TableNames.historyEntryTimestamp, {
|
||||||
|
'entryId': id,
|
||||||
|
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
b.insert(TableNames.historyEntryKanji, {
|
||||||
|
'entryId': id,
|
||||||
|
'kanji': kanji,
|
||||||
|
});
|
||||||
|
await b.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return HistoryEntry.withKanji(
|
||||||
|
id: id,
|
||||||
|
kanji: kanji,
|
||||||
|
lastTimestamp: timestamp,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Insert a word history entry into the database.
|
||||||
|
/// If it already exists, only a timestamp will be added
|
||||||
|
static Future<HistoryEntry> insertWord({
|
||||||
|
required String word,
|
||||||
|
String? language,
|
||||||
|
}) =>
|
||||||
|
db().transaction((txn) async {
|
||||||
|
final DateTime timestamp = DateTime.now();
|
||||||
|
late final int id;
|
||||||
|
|
||||||
|
final existingEntry = await txn.query(
|
||||||
|
TableNames.historyEntryWord,
|
||||||
|
where: 'searchword = ?',
|
||||||
|
whereArgs: [word],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingEntry.isNotEmpty) {
|
||||||
|
// Retrieve entry record id, and add a timestamp.
|
||||||
|
id = existingEntry.first['entryId']! as int;
|
||||||
|
await txn.insert(TableNames.historyEntryTimestamp, {
|
||||||
|
'entryId': id,
|
||||||
|
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
id = await txn.insert(
|
||||||
|
TableNames.historyEntry,
|
||||||
|
{},
|
||||||
|
nullColumnHack: 'id',
|
||||||
|
);
|
||||||
|
final Batch b = txn.batch();
|
||||||
|
|
||||||
|
b.insert(TableNames.historyEntryTimestamp, {
|
||||||
|
'entryId': id,
|
||||||
|
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
b.insert(TableNames.historyEntryWord, {
|
||||||
|
'entryId': id,
|
||||||
|
'searchword': word,
|
||||||
|
'language': {
|
||||||
|
null: null,
|
||||||
|
'japanese': 'j',
|
||||||
|
'english': 'e',
|
||||||
|
}[language]
|
||||||
|
});
|
||||||
|
await b.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return HistoryEntry.withWord(
|
||||||
|
id: id,
|
||||||
|
word: word,
|
||||||
|
lastTimestamp: timestamp,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// All recorded timestamps for this specific HistoryEntry
|
||||||
|
/// sorted in descending order.
|
||||||
|
Future<List<DateTime>> get timestamps async => GetIt.instance
|
||||||
|
.get<Database>()
|
||||||
|
.query(
|
||||||
|
TableNames.historyEntryTimestamp,
|
||||||
|
where: 'entryId = ?',
|
||||||
|
whereArgs: [id],
|
||||||
|
orderBy: 'timestamp DESC',
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(timestamps) => timestamps
|
||||||
|
.map(
|
||||||
|
(t) => DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
t['timestamp']! as int,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Export to json for archival reasons
|
||||||
|
/// Combined with [insertJsonEntry], this makes up functionality for exporting
|
||||||
|
/// and importing data from the app.
|
||||||
|
Future<Map<String, Object?>> toJson() async => {
|
||||||
|
'word': word,
|
||||||
|
'kanji': kanji,
|
||||||
|
'timestamps':
|
||||||
|
(await timestamps).map((ts) => ts.millisecondsSinceEpoch).toList()
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Insert archived json entry into database if it doesn't exist there already.
|
||||||
|
/// Combined with [toJson], this makes up functionality for exporting and
|
||||||
|
/// importing data from the app.
|
||||||
|
static Future<HistoryEntry> insertJsonEntry(
|
||||||
|
Map<String, Object?> json,
|
||||||
|
) async =>
|
||||||
|
db().transaction((txn) async {
|
||||||
|
final b = txn.batch();
|
||||||
|
final bool isKanji = json['word'] == null;
|
||||||
|
final existingEntry = isKanji
|
||||||
|
? await txn.query(
|
||||||
|
TableNames.historyEntryKanji,
|
||||||
|
where: 'kanji = ?',
|
||||||
|
whereArgs: [json['kanji']! as String],
|
||||||
|
)
|
||||||
|
: await txn.query(
|
||||||
|
TableNames.historyEntryWord,
|
||||||
|
where: 'searchword = ?',
|
||||||
|
whereArgs: [json['word']! as String],
|
||||||
|
);
|
||||||
|
|
||||||
|
late final int id;
|
||||||
|
if (existingEntry.isEmpty) {
|
||||||
|
id = await txn.insert(
|
||||||
|
TableNames.historyEntry,
|
||||||
|
{},
|
||||||
|
nullColumnHack: 'id',
|
||||||
|
);
|
||||||
|
if (isKanji) {
|
||||||
|
b.insert(TableNames.historyEntryKanji, {
|
||||||
|
'entryId': id,
|
||||||
|
'kanji': json['kanji']! as String,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
b.insert(TableNames.historyEntryWord, {
|
||||||
|
'entryId': id,
|
||||||
|
'searchword': json['word']! as String,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = existingEntry.first['entryId']! as int;
|
||||||
|
}
|
||||||
|
final List<int> timestamps =
|
||||||
|
(json['timestamps']! as List).map((ts) => ts as int).toList();
|
||||||
|
for (final timestamp in timestamps) {
|
||||||
|
b.insert(
|
||||||
|
TableNames.historyEntryTimestamp,
|
||||||
|
{
|
||||||
|
'entryId': id,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
},
|
||||||
|
conflictAlgorithm: ConflictAlgorithm.ignore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await b.commit();
|
||||||
|
|
||||||
|
return isKanji
|
||||||
|
? HistoryEntry.withKanji(
|
||||||
|
id: id,
|
||||||
|
kanji: json['kanji']! as String,
|
||||||
|
lastTimestamp:
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(timestamps.reduce(max)),
|
||||||
|
)
|
||||||
|
: HistoryEntry.withWord(
|
||||||
|
id: id,
|
||||||
|
word: json['word']! as String,
|
||||||
|
lastTimestamp:
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(timestamps.reduce(max)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// An efficient implementation of [insertJsonEntry] for multiple
|
||||||
|
/// entries.
|
||||||
|
///
|
||||||
|
/// This assumes that there are no duplicates within the elements
|
||||||
|
/// in the json.
|
||||||
|
static Future<List<HistoryEntry>> insertJsonEntries(
|
||||||
|
List<Map<String, Object?>> json,
|
||||||
|
) =>
|
||||||
|
db().transaction((txn) async {
|
||||||
|
final b = txn.batch();
|
||||||
|
final List<HistoryEntry> entries = [];
|
||||||
|
for (final jsonObject in json) {
|
||||||
|
final bool isKanji = jsonObject['word'] == null;
|
||||||
|
final existingEntry = isKanji
|
||||||
|
? await txn.query(
|
||||||
|
TableNames.historyEntryKanji,
|
||||||
|
where: 'kanji = ?',
|
||||||
|
whereArgs: [jsonObject['kanji']! as String],
|
||||||
|
)
|
||||||
|
: await txn.query(
|
||||||
|
TableNames.historyEntryWord,
|
||||||
|
where: 'searchword = ?',
|
||||||
|
whereArgs: [jsonObject['word']! as String],
|
||||||
|
);
|
||||||
|
|
||||||
|
late final int id;
|
||||||
|
if (existingEntry.isEmpty) {
|
||||||
|
id = await txn.insert(
|
||||||
|
TableNames.historyEntry,
|
||||||
|
{},
|
||||||
|
nullColumnHack: 'id',
|
||||||
|
);
|
||||||
|
if (isKanji) {
|
||||||
|
b.insert(TableNames.historyEntryKanji, {
|
||||||
|
'entryId': id,
|
||||||
|
'kanji': jsonObject['kanji']! as String,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
b.insert(TableNames.historyEntryWord, {
|
||||||
|
'entryId': id,
|
||||||
|
'searchword': jsonObject['word']! as String,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = existingEntry.first['entryId']! as int;
|
||||||
|
}
|
||||||
|
final List<int> timestamps = (jsonObject['timestamps']! as List)
|
||||||
|
.map((ts) => ts as int)
|
||||||
|
.toList();
|
||||||
|
for (final timestamp in timestamps) {
|
||||||
|
b.insert(
|
||||||
|
TableNames.historyEntryTimestamp,
|
||||||
|
{
|
||||||
|
'entryId': id,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
},
|
||||||
|
conflictAlgorithm: ConflictAlgorithm.ignore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.add(
|
||||||
|
isKanji
|
||||||
|
? HistoryEntry.withKanji(
|
||||||
|
id: id,
|
||||||
|
kanji: jsonObject['kanji']! as String,
|
||||||
|
lastTimestamp: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
timestamps.reduce(max),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: HistoryEntry.withWord(
|
||||||
|
id: id,
|
||||||
|
word: jsonObject['word']! as String,
|
||||||
|
lastTimestamp: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
timestamps.reduce(max),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await b.commit();
|
||||||
|
return entries;
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<List<HistoryEntry>> get fromDB async =>
|
||||||
|
(await db().query(TableNames.historyEntryOrderedByTimestamp))
|
||||||
|
.map((e) => HistoryEntry.fromDBMap(e))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
Future<void> delete() =>
|
||||||
|
db().delete(TableNames.historyEntry, where: 'id = ?', whereArgs: [id]);
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
class KanjiQuery {
|
|
||||||
final String kanji;
|
|
||||||
|
|
||||||
KanjiQuery({
|
|
||||||
required this.kanji,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, Object?> toJson() => {'kanji': kanji};
|
|
||||||
|
|
||||||
factory KanjiQuery.fromJson(Map<String, dynamic> json) =>
|
|
||||||
KanjiQuery(kanji: json['kanji'] as String);
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:sembast/sembast.dart';
|
|
||||||
|
|
||||||
import './kanji_query.dart';
|
|
||||||
import './word_query.dart';
|
|
||||||
|
|
||||||
export 'package:get_it/get_it.dart';
|
|
||||||
export 'package:sembast/sembast.dart';
|
|
||||||
|
|
||||||
class Search {
|
|
||||||
final WordQuery? wordQuery;
|
|
||||||
final KanjiQuery? kanjiQuery;
|
|
||||||
final List<DateTime> timestamps;
|
|
||||||
|
|
||||||
Search.fromKanjiQuery({
|
|
||||||
required KanjiQuery this.kanjiQuery,
|
|
||||||
List<DateTime>? timestamps,
|
|
||||||
}) : wordQuery = null,
|
|
||||||
timestamps = timestamps ?? [DateTime.now()];
|
|
||||||
|
|
||||||
Search.fromWordQuery({
|
|
||||||
required WordQuery this.wordQuery,
|
|
||||||
List<DateTime>? timestamps,
|
|
||||||
}) : kanjiQuery = null,
|
|
||||||
timestamps = timestamps ?? [DateTime.now()];
|
|
||||||
|
|
||||||
bool get isKanji => wordQuery == null;
|
|
||||||
|
|
||||||
DateTime get timestamp => timestamps.last;
|
|
||||||
|
|
||||||
Map<String, Object?> toJson() => {
|
|
||||||
'timestamps': [for (final ts in timestamps) ts.millisecondsSinceEpoch],
|
|
||||||
'lastTimestamp': timestamps.last.millisecondsSinceEpoch,
|
|
||||||
'wordQuery': wordQuery?.toJson(),
|
|
||||||
'kanjiQuery': kanjiQuery?.toJson(),
|
|
||||||
};
|
|
||||||
|
|
||||||
factory Search.fromJson(Map<String, dynamic> json) {
|
|
||||||
final List<DateTime> timestamps = [
|
|
||||||
for (final ts in json['timestamps'] as List<dynamic>)
|
|
||||||
DateTime.fromMillisecondsSinceEpoch(ts as int)
|
|
||||||
];
|
|
||||||
|
|
||||||
return json['wordQuery'] != null
|
|
||||||
? Search.fromWordQuery(
|
|
||||||
wordQuery: WordQuery.fromJson(json['wordQuery']),
|
|
||||||
timestamps: timestamps,
|
|
||||||
)
|
|
||||||
: Search.fromKanjiQuery(
|
|
||||||
kanjiQuery: KanjiQuery.fromJson(json['kanjiQuery']),
|
|
||||||
timestamps: timestamps,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static StoreRef<int, Object?> get store => intMapStoreFactory.store('search');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addSearchToDatabase({
|
|
||||||
required String searchTerm,
|
|
||||||
required bool isKanji,
|
|
||||||
}) async {
|
|
||||||
final DateTime now = DateTime.now();
|
|
||||||
final db = GetIt.instance.get<Database>();
|
|
||||||
final Filter filter = Filter.equals(
|
|
||||||
isKanji ? 'kanjiQuery.kanji' : 'wordQuery.query',
|
|
||||||
searchTerm,
|
|
||||||
);
|
|
||||||
|
|
||||||
final RecordSnapshot<int, Object?>? previousSearch =
|
|
||||||
await Search.store.findFirst(db, finder: Finder(filter: filter));
|
|
||||||
|
|
||||||
if (previousSearch != null) {
|
|
||||||
final search =
|
|
||||||
Search.fromJson(previousSearch.value! as Map<String, Object?>);
|
|
||||||
search.timestamps.add(now);
|
|
||||||
Search.store.record(previousSearch.key).put(db, search.toJson());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Search.store.add(
|
|
||||||
db,
|
|
||||||
isKanji
|
|
||||||
? Search.fromKanjiQuery(kanjiQuery: KanjiQuery(kanji: searchTerm))
|
|
||||||
.toJson()
|
|
||||||
: Search.fromWordQuery(wordQuery: WordQuery(query: searchTerm))
|
|
||||||
.toJson(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Search> mergeSearches(List<Search> a, List<Search> b) {
|
|
||||||
final List<Search> result = [...a];
|
|
||||||
|
|
||||||
for (final Search search in b) {
|
|
||||||
late final Iterable<Search> matchingEntry;
|
|
||||||
if (search.isKanji) {
|
|
||||||
matchingEntry =
|
|
||||||
result.where((e) => e.kanjiQuery?.kanji == search.kanjiQuery!.kanji);
|
|
||||||
} else {
|
|
||||||
matchingEntry =
|
|
||||||
result.where((e) => e.wordQuery?.query == search.wordQuery!.query);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingEntry.isEmpty) {
|
|
||||||
result.add(search);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final timestamps = [...matchingEntry.first.timestamps];
|
|
||||||
matchingEntry.first.timestamps.clear();
|
|
||||||
matchingEntry.first.timestamps.addAll(
|
|
||||||
(timestamps
|
|
||||||
..addAll(search.timestamps)
|
|
||||||
..sort())
|
|
||||||
.toSet()
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
class WordQuery {
|
|
||||||
final String query;
|
|
||||||
|
|
||||||
// TODO: Link query with results that the user clicks onto.
|
|
||||||
// final List<WordResult> chosenResults;
|
|
||||||
|
|
||||||
WordQuery({
|
|
||||||
required this.query,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, Object?> toJson() => {'query': query};
|
|
||||||
|
|
||||||
factory WordQuery.fromJson(Map<String, dynamic> json) =>
|
|
||||||
WordQuery(query: json['query'] as String);
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import 'word_query.dart';
|
|
||||||
|
|
||||||
class WordResult {
|
|
||||||
final DateTime timestamp;
|
|
||||||
final String word;
|
|
||||||
final WordQuery searchString;
|
|
||||||
|
|
||||||
WordResult({
|
|
||||||
required this.timestamp,
|
|
||||||
required this.word,
|
|
||||||
required this.searchString,
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
// import 'package:objectbox/objectbox.dart';
|
|
||||||
// import 'package:unofficial_jisho_api/api.dart' as jisho;
|
|
||||||
|
|
||||||
// TODO: Rewrite for sembast
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class ExampleSentencePiece {
|
|
||||||
// int id;
|
|
||||||
// String? lifted;
|
|
||||||
// String unlifted;
|
|
||||||
|
|
||||||
// ExampleSentencePiece({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.lifted,
|
|
||||||
// required this.unlifted,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// ExampleSentencePiece.fromJishoObject(jisho.ExampleSentencePiece object)
|
|
||||||
// : id = 0,
|
|
||||||
// lifted = object.lifted,
|
|
||||||
// unlifted = object.unlifted;
|
|
||||||
// }
|
|
@ -1,58 +0,0 @@
|
|||||||
// import 'package:objectbox/objectbox.dart';
|
|
||||||
// import 'package:unofficial_jisho_api/api.dart' as jisho;
|
|
||||||
|
|
||||||
// import 'common.dart';
|
|
||||||
|
|
||||||
// TODO: Rewrite for sembast
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class ExampleResultData {
|
|
||||||
// int id;
|
|
||||||
// String kanji;
|
|
||||||
// String kana;
|
|
||||||
// String english;
|
|
||||||
// List<ExampleSentencePiece> pieces;
|
|
||||||
|
|
||||||
// ExampleResultData({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.kanji,
|
|
||||||
// required this.kana,
|
|
||||||
// required this.english,
|
|
||||||
// required this.pieces,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// ExampleResultData.fromJishoObject(jisho.ExampleResultData object)
|
|
||||||
// : id = 0,
|
|
||||||
// kanji = object.kanji,
|
|
||||||
// kana = object.kana,
|
|
||||||
// english = object.english,
|
|
||||||
// pieces = object.pieces
|
|
||||||
// .map((p) => ExampleSentencePiece.fromJishoObject(p))
|
|
||||||
// .toList();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class ExampleResults {
|
|
||||||
// int id;
|
|
||||||
// String query;
|
|
||||||
// bool found;
|
|
||||||
// String uri;
|
|
||||||
// List<ExampleResultData> results;
|
|
||||||
|
|
||||||
// ExampleResults({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.query,
|
|
||||||
// required this.found,
|
|
||||||
// required this.uri,
|
|
||||||
// required this.results,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// ExampleResults.fromJishoObject(jisho.ExampleResults object)
|
|
||||||
// : id = 0,
|
|
||||||
// query = object.query,
|
|
||||||
// found = object.found,
|
|
||||||
// uri = object.uri,
|
|
||||||
// results = object.results
|
|
||||||
// .map((r) => ExampleResultData.fromJishoObject(r))
|
|
||||||
// .toList();
|
|
||||||
// }
|
|
@ -1,129 +0,0 @@
|
|||||||
// import 'package:objectbox/objectbox.dart';
|
|
||||||
// import 'package:unofficial_jisho_api/api.dart' as jisho;
|
|
||||||
|
|
||||||
// TODO: Rewrite for sembast
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class YomiExample {
|
|
||||||
// int id;
|
|
||||||
// String example;
|
|
||||||
// String reading;
|
|
||||||
// String meaning;
|
|
||||||
|
|
||||||
// YomiExample({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.example,
|
|
||||||
// required this.reading,
|
|
||||||
// required this.meaning,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// YomiExample.fromJishoObject(jisho.YomiExample object)
|
|
||||||
// : id = 0,
|
|
||||||
// example = object.example,
|
|
||||||
// reading = object.reading,
|
|
||||||
// meaning = object.meaning;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class Radical {
|
|
||||||
// int id = 0;
|
|
||||||
// String symbol;
|
|
||||||
// List<String> forms;
|
|
||||||
// String meaning;
|
|
||||||
|
|
||||||
// Radical({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.symbol,
|
|
||||||
// required this.forms,
|
|
||||||
// required this.meaning,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Radical.fromJishoObject(jisho.Radical object)
|
|
||||||
// : symbol = object.symbol,
|
|
||||||
// forms = object.forms,
|
|
||||||
// meaning = object.meaning;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class KanjiResult {
|
|
||||||
// int id = 0;
|
|
||||||
// String query;
|
|
||||||
// bool found;
|
|
||||||
// KanjiResultData? data;
|
|
||||||
|
|
||||||
// KanjiResult({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.query,
|
|
||||||
// required this.found,
|
|
||||||
// required this.data,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// KanjiResult.fromJishoObject(jisho.KanjiResult object)
|
|
||||||
// : query = object.query,
|
|
||||||
// found = object.found,
|
|
||||||
// data = (object.data == null)
|
|
||||||
// ? null
|
|
||||||
// : KanjiResultData.fromJishoObject(object.data!);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class KanjiResultData {
|
|
||||||
// int id = 0;
|
|
||||||
// String? taughtIn;
|
|
||||||
// String? jlptLevel;
|
|
||||||
// int? newspaperFrequencyRank;
|
|
||||||
// int strokeCount;
|
|
||||||
// String meaning;
|
|
||||||
// List<String> kunyomi;
|
|
||||||
// List<String> onyomi;
|
|
||||||
// List<YomiExample> kunyomiExamples;
|
|
||||||
// List<YomiExample> onyomiExamples;
|
|
||||||
// Radical? radical;
|
|
||||||
// List<String> parts;
|
|
||||||
// String strokeOrderDiagramUri;
|
|
||||||
// String strokeOrderSvgUri;
|
|
||||||
// String strokeOrderGifUri;
|
|
||||||
// String uri;
|
|
||||||
|
|
||||||
// KanjiResultData({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.taughtIn,
|
|
||||||
// required this.jlptLevel,
|
|
||||||
// required this.newspaperFrequencyRank,
|
|
||||||
// required this.strokeCount,
|
|
||||||
// required this.meaning,
|
|
||||||
// required this.kunyomi,
|
|
||||||
// required this.onyomi,
|
|
||||||
// required this.kunyomiExamples,
|
|
||||||
// required this.onyomiExamples,
|
|
||||||
// required this.radical,
|
|
||||||
// required this.parts,
|
|
||||||
// required this.strokeOrderDiagramUri,
|
|
||||||
// required this.strokeOrderSvgUri,
|
|
||||||
// required this.strokeOrderGifUri,
|
|
||||||
// required this.uri,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// KanjiResultData.fromJishoObject(jisho.KanjiResultData object)
|
|
||||||
// : taughtIn = object.taughtIn,
|
|
||||||
// jlptLevel = object.jlptLevel,
|
|
||||||
// newspaperFrequencyRank = object.newspaperFrequencyRank,
|
|
||||||
// strokeCount = object.strokeCount,
|
|
||||||
// meaning = object.meaning,
|
|
||||||
// kunyomi = object.kunyomi,
|
|
||||||
// onyomi = object.onyomi,
|
|
||||||
// kunyomiExamples = object.kunyomiExamples
|
|
||||||
// .map((k) => YomiExample.fromJishoObject(k))
|
|
||||||
// .toList(),
|
|
||||||
// onyomiExamples = object.onyomiExamples
|
|
||||||
// .map((o) => YomiExample.fromJishoObject(o))
|
|
||||||
// .toList(),
|
|
||||||
// radical = (object.radical == null)
|
|
||||||
// ? null
|
|
||||||
// : Radical.fromJishoObject(object.radical!),
|
|
||||||
// parts = object.parts,
|
|
||||||
// strokeOrderDiagramUri = object.strokeOrderDiagramUri,
|
|
||||||
// strokeOrderSvgUri = object.strokeOrderSvgUri,
|
|
||||||
// strokeOrderGifUri = object.strokeOrderGifUri,
|
|
||||||
// uri = object.uri;
|
|
||||||
// }
|
|
@ -1,155 +0,0 @@
|
|||||||
// import 'package:objectbox/objectbox.dart';
|
|
||||||
// import 'package:unofficial_jisho_api/api.dart' as jisho;
|
|
||||||
|
|
||||||
// import 'common.dart';
|
|
||||||
|
|
||||||
// TODO: Rewrite for sembast
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class PhraseScrapeSentence {
|
|
||||||
// int id;
|
|
||||||
// String english;
|
|
||||||
// String japanese;
|
|
||||||
// List<ExampleSentencePiece> pieces;
|
|
||||||
|
|
||||||
// PhraseScrapeSentence({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.english,
|
|
||||||
// required this.japanese,
|
|
||||||
// required this.pieces,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// PhraseScrapeSentence.fromJishoObject(jisho.PhraseScrapeSentence object)
|
|
||||||
// : id = 0,
|
|
||||||
// english = object.english,
|
|
||||||
// japanese = object.japanese,
|
|
||||||
// pieces = object.pieces
|
|
||||||
// .map((p) => ExampleSentencePiece.fromJishoObject(p))
|
|
||||||
// .toList();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class PhraseScrapeMeaning {
|
|
||||||
// int id;
|
|
||||||
// List<String> seeAlsoTerms;
|
|
||||||
// List<PhraseScrapeSentence> sentences;
|
|
||||||
// String definition;
|
|
||||||
// List<String> supplemental;
|
|
||||||
// String? definitionAbstract;
|
|
||||||
// List<String> tags;
|
|
||||||
|
|
||||||
// PhraseScrapeMeaning({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.seeAlsoTerms,
|
|
||||||
// required this.sentences,
|
|
||||||
// required this.definition,
|
|
||||||
// required this.supplemental,
|
|
||||||
// required this.definitionAbstract,
|
|
||||||
// required this.tags,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// PhraseScrapeMeaning.fromJishoObject(jisho.PhraseScrapeMeaning object)
|
|
||||||
// : id = 0,
|
|
||||||
// seeAlsoTerms = object.seeAlsoTerms,
|
|
||||||
// sentences = object.sentences
|
|
||||||
// .map((s) => PhraseScrapeSentence.fromJishoObject(s))
|
|
||||||
// .toList(),
|
|
||||||
// definition = object.definition,
|
|
||||||
// supplemental = object.supplemental,
|
|
||||||
// definitionAbstract = object.definitionAbstract,
|
|
||||||
// tags = object.tags;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class KanjiKanaPair {
|
|
||||||
// int id;
|
|
||||||
// String kanji;
|
|
||||||
// String? kana;
|
|
||||||
|
|
||||||
// KanjiKanaPair({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.kanji,
|
|
||||||
// required this.kana,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// KanjiKanaPair.fromJishoObject(jisho.KanjiKanaPair object)
|
|
||||||
// : id = 0,
|
|
||||||
// kanji = object.kanji,
|
|
||||||
// kana = object.kana;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class PhrasePageScrapeResult {
|
|
||||||
// int id;
|
|
||||||
// bool found;
|
|
||||||
// String query;
|
|
||||||
// PhrasePageScrapeResultData? data;
|
|
||||||
|
|
||||||
// PhrasePageScrapeResult({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.found,
|
|
||||||
// required this.query,
|
|
||||||
// required this.data,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// PhrasePageScrapeResult.fromJishoObject(jisho.PhrasePageScrapeResult object)
|
|
||||||
// : id = 0,
|
|
||||||
// found = object.found,
|
|
||||||
// query = object.query,
|
|
||||||
// data = (object.data == null)
|
|
||||||
// ? null
|
|
||||||
// : PhrasePageScrapeResultData.fromJishoObject(object.data!);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class AudioFile {
|
|
||||||
// int id;
|
|
||||||
// String uri;
|
|
||||||
// String mimetype;
|
|
||||||
|
|
||||||
// AudioFile({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.uri,
|
|
||||||
// required this.mimetype,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// AudioFile.fromJishoObject(jisho.AudioFile object)
|
|
||||||
// : id = 0,
|
|
||||||
// uri = object.uri,
|
|
||||||
// mimetype = object.mimetype;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class PhrasePageScrapeResultData {
|
|
||||||
// int id;
|
|
||||||
// String uri;
|
|
||||||
// List<String> tags;
|
|
||||||
// List<PhraseScrapeMeaning> meanings;
|
|
||||||
// List<KanjiKanaPair> otherForms;
|
|
||||||
// List<AudioFile> audio;
|
|
||||||
// List<String> notes;
|
|
||||||
|
|
||||||
// PhrasePageScrapeResultData({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.uri,
|
|
||||||
// required this.tags,
|
|
||||||
// required this.meanings,
|
|
||||||
// required this.otherForms,
|
|
||||||
// required this.audio,
|
|
||||||
// required this.notes,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// PhrasePageScrapeResultData.fromJishoObject(
|
|
||||||
// jisho.PhrasePageScrapeResultData object,
|
|
||||||
// ) : id = 0,
|
|
||||||
// uri = object.uri,
|
|
||||||
// tags = object.tags,
|
|
||||||
// meanings = object.meanings
|
|
||||||
// .map((m) => PhraseScrapeMeaning.fromJishoObject(m))
|
|
||||||
// .toList(),
|
|
||||||
// otherForms = object.otherForms
|
|
||||||
// .map((f) => KanjiKanaPair.fromJishoObject(f))
|
|
||||||
// .toList(),
|
|
||||||
// audio = object.audio.map((a) => AudioFile.fromJishoObject(a)).toList(),
|
|
||||||
// notes = object.notes;
|
|
||||||
// }
|
|
@ -1,195 +0,0 @@
|
|||||||
// import 'package:objectbox/objectbox.dart';
|
|
||||||
// import 'package:unofficial_jisho_api/api.dart' as jisho;
|
|
||||||
|
|
||||||
// TODO: Rewrite for sembast
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class SearchResult {
|
|
||||||
// int id;
|
|
||||||
// final JishoResultMeta meta;
|
|
||||||
// final ToMany<JishoResult> data;
|
|
||||||
|
|
||||||
// SearchResult({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.meta,
|
|
||||||
// required this.data,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// SearchResult.fromJishoObject(final jisho.JishoAPIResult object)
|
|
||||||
// : id = 0,
|
|
||||||
// meta = JishoResultMeta.fromJishoObject(object.meta),
|
|
||||||
// data = ToMany<JishoResult>()
|
|
||||||
// ..addAll(
|
|
||||||
// object.data?.map((r) => JishoResult.fromJishoObject(r)) ??
|
|
||||||
// <JishoResult>[],
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class JishoResultMeta {
|
|
||||||
// int id;
|
|
||||||
// int status;
|
|
||||||
|
|
||||||
// JishoResultMeta({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.status,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// JishoResultMeta.fromJishoObject(final jisho.JishoResultMeta object)
|
|
||||||
// : id = 0,
|
|
||||||
// status = object.status;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class JishoResult {
|
|
||||||
// int id;
|
|
||||||
// JishoAttribution attribution;
|
|
||||||
// bool? is_common;
|
|
||||||
// List<JishoJapaneseWord> japanese;
|
|
||||||
// List<String> jlpt;
|
|
||||||
// List<JishoWordSense> senses;
|
|
||||||
// String slug;
|
|
||||||
// List<String> tags;
|
|
||||||
|
|
||||||
// JishoResult({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.attribution,
|
|
||||||
// required this.is_common,
|
|
||||||
// required this.japanese,
|
|
||||||
// required this.jlpt,
|
|
||||||
// required this.senses,
|
|
||||||
// required this.slug,
|
|
||||||
// required this.tags,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// JishoResult.fromJishoObject(final jisho.JishoResult object)
|
|
||||||
// : id = 0,
|
|
||||||
// attribution = JishoAttribution.fromJishoObject(object.attribution),
|
|
||||||
// is_common = object.isCommon,
|
|
||||||
// japanese = object.japanese
|
|
||||||
// .map((j) => JishoJapaneseWord.fromJishoObject(j))
|
|
||||||
// .toList(),
|
|
||||||
// jlpt = object.jlpt,
|
|
||||||
// senses = object.senses
|
|
||||||
// .map((s) => JishoWordSense.fromJishoObject(s))
|
|
||||||
// .toList(),
|
|
||||||
// slug = object.slug,
|
|
||||||
// tags = object.tags;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class JishoAttribution {
|
|
||||||
// int id;
|
|
||||||
// String? dbpedia;
|
|
||||||
// bool jmdict;
|
|
||||||
// bool jmnedict;
|
|
||||||
|
|
||||||
// JishoAttribution({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.dbpedia,
|
|
||||||
// required this.jmdict,
|
|
||||||
// required this.jmnedict,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// JishoAttribution.fromJishoObject(final jisho.JishoAttribution object)
|
|
||||||
// : id = 0,
|
|
||||||
// dbpedia = object.dbpedia,
|
|
||||||
// jmdict = object.jmdict,
|
|
||||||
// jmnedict = object.jmnedict;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class JishoJapaneseWord {
|
|
||||||
// int id;
|
|
||||||
// String? reading;
|
|
||||||
// String? word;
|
|
||||||
|
|
||||||
// JishoJapaneseWord({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.reading,
|
|
||||||
// required this.word,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// JishoJapaneseWord.fromJishoObject(final jisho.JishoJapaneseWord object)
|
|
||||||
// : id = 0,
|
|
||||||
// reading = object.reading,
|
|
||||||
// word = object.word;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class JishoWordSense {
|
|
||||||
// int id;
|
|
||||||
// List<String> antonyms;
|
|
||||||
// List<String> english_definitions;
|
|
||||||
// List<String> info;
|
|
||||||
// List<JishoSenseLink> links;
|
|
||||||
// List<String> parts_of_speech;
|
|
||||||
// List<String> restrictions;
|
|
||||||
// List<String> see_also;
|
|
||||||
// List<JishoWordSource> source;
|
|
||||||
// List<String> tags;
|
|
||||||
|
|
||||||
// JishoWordSense({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.antonyms,
|
|
||||||
// required this.english_definitions,
|
|
||||||
// required this.info,
|
|
||||||
// required this.links,
|
|
||||||
// required this.parts_of_speech,
|
|
||||||
// required this.restrictions,
|
|
||||||
// required this.see_also,
|
|
||||||
// required this.source,
|
|
||||||
// required this.tags,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// JishoWordSense.fromJishoObject(final jisho.JishoWordSense object)
|
|
||||||
// : id = 0,
|
|
||||||
// antonyms = object.antonyms,
|
|
||||||
// english_definitions = object.englishDefinitions,
|
|
||||||
// info = object.info,
|
|
||||||
// links =
|
|
||||||
// object.links.map((l) => JishoSenseLink.fromJishoObject(l)).toList(),
|
|
||||||
// parts_of_speech = object.partsOfSpeech,
|
|
||||||
// restrictions = object.restrictions,
|
|
||||||
// see_also = object.seeAlso,
|
|
||||||
// source = object.source
|
|
||||||
// .map((s) => JishoWordSource.fromJishoObject(s))
|
|
||||||
// .toList(),
|
|
||||||
// tags = object.tags;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class JishoWordSource {
|
|
||||||
// int id;
|
|
||||||
// String language;
|
|
||||||
// String? word;
|
|
||||||
|
|
||||||
// JishoWordSource({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.language,
|
|
||||||
// required this.word,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// JishoWordSource.fromJishoObject(final jisho.JishoWordSource object)
|
|
||||||
// : id = 0,
|
|
||||||
// language = object.language,
|
|
||||||
// word = object.word;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
// class JishoSenseLink {
|
|
||||||
// int id;
|
|
||||||
// String text;
|
|
||||||
// String url;
|
|
||||||
|
|
||||||
// JishoSenseLink({
|
|
||||||
// this.id = 0,
|
|
||||||
// required this.text,
|
|
||||||
// required this.url,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// JishoSenseLink.fromJishoObject(final jisho.JishoSenseLink object)
|
|
||||||
// : id = 0,
|
|
||||||
// text = object.text,
|
|
||||||
// url = object.url;
|
|
||||||
// }
|
|
@ -3,37 +3,24 @@ import 'package:flutter/material.dart';
|
|||||||
import '../components/common/loading.dart';
|
import '../components/common/loading.dart';
|
||||||
import '../components/common/opaque_box.dart';
|
import '../components/common/opaque_box.dart';
|
||||||
import '../components/history/date_divider.dart';
|
import '../components/history/date_divider.dart';
|
||||||
import '../components/history/search_item.dart';
|
import '../components/history/history_entry_item.dart';
|
||||||
import '../models/history/search.dart';
|
import '../models/history/history_entry.dart';
|
||||||
import '../services/datetime.dart';
|
import '../services/datetime.dart';
|
||||||
|
|
||||||
class HistoryView extends StatelessWidget {
|
class HistoryView extends StatelessWidget {
|
||||||
const HistoryView({Key? key}) : super(key: key);
|
const HistoryView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
Stream<Map<int, Search>> get searchStream => Search.store
|
|
||||||
.query(finder: Finder(sortOrders: [SortOrder('lastTimestamp', false)]))
|
|
||||||
.onSnapshots(_db)
|
|
||||||
.map(
|
|
||||||
(snapshot) => Map.fromEntries(
|
|
||||||
snapshot.where((snap) => snap.value != null).map(
|
|
||||||
(snap) => MapEntry(
|
|
||||||
snap.key,
|
|
||||||
Search.fromJson(snap.value! as Map<String, Object?>),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Database get _db => GetIt.instance.get<Database>();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StreamBuilder<Map<int, Search>>(
|
// TODO: Use infinite scroll pagination
|
||||||
stream: searchStream,
|
return FutureBuilder<List<HistoryEntry>>(
|
||||||
|
future: HistoryEntry.fromDB,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
// TODO: provide proper error handling
|
||||||
|
if (snapshot.hasError) return ErrorWidget(snapshot.error!);
|
||||||
if (!snapshot.hasData) return const LoadingScreen();
|
if (!snapshot.hasData) return const LoadingScreen();
|
||||||
|
|
||||||
final Map<int, Search> data = snapshot.data!;
|
final Map<int, HistoryEntry> data = snapshot.data!.asMap();
|
||||||
if (data.isEmpty)
|
if (data.isEmpty)
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Text('The history is empty.\nTry searching for something!'),
|
child: Text('The history is empty.\nTry searching for something!'),
|
||||||
@ -52,25 +39,25 @@ class HistoryView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget Function(BuildContext, int) historyEntrySeparatorWithData(
|
Widget Function(BuildContext, int) historyEntrySeparatorWithData(
|
||||||
List<Search> data,
|
List<HistoryEntry> data,
|
||||||
) =>
|
) =>
|
||||||
(context, index) {
|
(context, index) {
|
||||||
final Search search = data[index];
|
final HistoryEntry search = data[index];
|
||||||
final DateTime searchDate = search.timestamp;
|
final DateTime searchDate = search.lastTimestamp;
|
||||||
|
|
||||||
if (index == 0 || !dateIsEqual(data[index - 1].timestamp, searchDate))
|
if (index == 0 || !dateIsEqual(data[index - 1].lastTimestamp, searchDate))
|
||||||
return TextDivider(text: formatDate(roundToDay(searchDate)));
|
return TextDivider(text: formatDate(roundToDay(searchDate)));
|
||||||
|
|
||||||
return const Divider(height: 0);
|
return const Divider(height: 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
Widget Function(BuildContext, int) historyEntryWithData(
|
Widget Function(BuildContext, int) historyEntryWithData(
|
||||||
Map<int, Search> data,
|
Map<int, HistoryEntry> data,
|
||||||
) =>
|
) =>
|
||||||
(context, index) => (index == 0)
|
(context, index) => (index == 0)
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: SearchItem(
|
: HistoryEntryItem(
|
||||||
search: data.values.toList()[index - 1],
|
entry: data.values.toList()[index - 1],
|
||||||
objectKey: data.keys.toList()[index - 1],
|
objectKey: data.keys.toList()[index - 1],
|
||||||
onDelete: () => build(context),
|
onDelete: () => build(context),
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import '../../components/common/loading.dart';
|
import '../../components/common/loading.dart';
|
||||||
import '../../components/kanji/kanji_result_body.dart';
|
import '../../components/kanji/kanji_result_body.dart';
|
||||||
import '../../components/search/search_result_body.dart';
|
import '../../components/search/search_result_body.dart';
|
||||||
import '../../models/history/search.dart';
|
import '../../models/history/history_entry.dart';
|
||||||
import '../../services/jisho_api/jisho_search.dart';
|
import '../../services/jisho_api/jisho_search.dart';
|
||||||
import '../../services/jisho_api/kanji_search.dart';
|
import '../../services/jisho_api/kanji_search.dart';
|
||||||
|
|
||||||
@ -33,14 +33,16 @@ class _ResultPageState extends State<ResultPage> {
|
|||||||
? fetchKanji(widget.searchTerm)
|
? fetchKanji(widget.searchTerm)
|
||||||
: fetchJishoResults(widget.searchTerm),
|
: fetchJishoResults(widget.searchTerm),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) return const LoadingScreen();
|
// TODO: provide proper error handling
|
||||||
if (snapshot.hasError) return ErrorWidget(snapshot.error!);
|
if (snapshot.hasError) return ErrorWidget(snapshot.error!);
|
||||||
|
if (!snapshot.hasData) return const LoadingScreen();
|
||||||
|
|
||||||
if (!addedToDatabase) {
|
if (!addedToDatabase) {
|
||||||
addSearchToDatabase(
|
if (widget.isKanji) {
|
||||||
searchTerm: widget.searchTerm,
|
HistoryEntry.insertKanji(kanji: widget.searchTerm);
|
||||||
isKanji: widget.isKanji,
|
} else {
|
||||||
);
|
HistoryEntry.insertWord(word: widget.searchTerm);
|
||||||
|
}
|
||||||
addedToDatabase = true;
|
addedToDatabase = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:confirm_dialog/confirm_dialog.dart';
|
import 'package:confirm_dialog/confirm_dialog.dart';
|
||||||
@ -6,15 +5,13 @@ import 'package:file_picker/file_picker.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||||
import 'package:mdi/mdi.dart';
|
import 'package:mdi/mdi.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:sembast/sembast_io.dart';
|
|
||||||
import 'package:sembast/utils/sembast_import_export.dart';
|
|
||||||
|
|
||||||
import '../bloc/theme/theme_bloc.dart';
|
import '../bloc/theme/theme_bloc.dart';
|
||||||
import '../components/common/denshi_jisho_background.dart';
|
import '../components/common/denshi_jisho_background.dart';
|
||||||
import '../models/history/search.dart';
|
import '../data/database.dart';
|
||||||
|
import '../data/export.dart';
|
||||||
|
import '../data/import.dart';
|
||||||
import '../routing/routes.dart';
|
import '../routing/routes.dart';
|
||||||
import '../services/database.dart';
|
|
||||||
import '../services/open_webpage.dart';
|
import '../services/open_webpage.dart';
|
||||||
import '../services/snackbar.dart';
|
import '../services/snackbar.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
@ -27,22 +24,39 @@ class SettingsView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SettingsViewState extends State<SettingsView> {
|
class _SettingsViewState extends State<SettingsView> {
|
||||||
final Database db = GetIt.instance.get<Database>();
|
|
||||||
bool dataExportIsLoading = false;
|
bool dataExportIsLoading = false;
|
||||||
bool dataImportIsLoading = false;
|
bool dataImportIsLoading = false;
|
||||||
|
|
||||||
Future<void> clearHistory(context) async {
|
Future<void> clearHistory(context) async {
|
||||||
final bool userIsSure = await confirm(context);
|
final historyCount = (await db().query(
|
||||||
|
TableNames.historyEntry,
|
||||||
|
columns: ['COUNT(*) AS count'],
|
||||||
|
))[0]['count']! as int;
|
||||||
|
|
||||||
if (userIsSure) {
|
final bool userIsSure = await confirm(
|
||||||
await Search.store.delete(db);
|
context,
|
||||||
}
|
content: Text(
|
||||||
|
'Are you sure that you want to delete $historyCount entries?',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!userIsSure) return;
|
||||||
|
|
||||||
|
await db().delete(TableNames.historyEntry);
|
||||||
|
showSnackbar(context, 'Cleared history');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearAll(context) async {
|
||||||
|
final bool userIsSure = await confirm(context);
|
||||||
|
if (!userIsSure) return;
|
||||||
|
|
||||||
|
await resetDatabase();
|
||||||
|
showSnackbar(context, 'Cleared everything');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: avoid_positional_boolean_parameters
|
// ignore: avoid_positional_boolean_parameters
|
||||||
void toggleAutoTheme(bool b) {
|
void toggleAutoTheme(bool b) {
|
||||||
final bool newThemeIsDark = b
|
final bool newThemeIsDark = b
|
||||||
? WidgetsBinding.instance!.window.platformBrightness == Brightness.dark
|
? WidgetsBinding.instance.window.platformBrightness == Brightness.dark
|
||||||
: darkThemeEnabled;
|
: darkThemeEnabled;
|
||||||
|
|
||||||
BlocProvider.of<ThemeBloc>(context)
|
BlocProvider.of<ThemeBloc>(context)
|
||||||
@ -63,75 +77,19 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Can assume Android for time being
|
/// Can assume Android for time being
|
||||||
Future<void> exportData(context) async {
|
Future<void> exportHandler(context) async {
|
||||||
setState(() => dataExportIsLoading = true);
|
setState(() => dataExportIsLoading = true);
|
||||||
|
final path = await exportData();
|
||||||
final path = (await getExternalStorageDirectory())!;
|
|
||||||
final dbData = await exportDatabase(db);
|
|
||||||
final file = File('${path.path}/jisho_data.json');
|
|
||||||
file.createSync(recursive: true);
|
|
||||||
await file.writeAsString(jsonEncode(dbData));
|
|
||||||
|
|
||||||
setState(() => dataExportIsLoading = false);
|
setState(() => dataExportIsLoading = false);
|
||||||
ScaffoldMessenger.of(context)
|
showSnackbar(context, 'Data exported to $path');
|
||||||
.showSnackBar(SnackBar(content: Text('Data exported to ${file.path}')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Can assume Android for time being
|
/// Can assume Android for time being
|
||||||
Future<void> importData(context) async {
|
Future<void> importHandler(context) async {
|
||||||
setState(() => dataImportIsLoading = true);
|
setState(() => dataImportIsLoading = true);
|
||||||
|
|
||||||
final path = await FilePicker.platform.pickFiles(
|
final path = await FilePicker.platform.getDirectoryPath();
|
||||||
type: FileType.custom,
|
await importData(Directory(path!));
|
||||||
allowedExtensions: ['json'],
|
|
||||||
);
|
|
||||||
final file = File(path!.files[0].path!);
|
|
||||||
|
|
||||||
final List<Search> prevSearches = (await Search.store.find(db))
|
|
||||||
.map((e) => Search.fromJson(e.value! as Map<String, Object?>))
|
|
||||||
.toList();
|
|
||||||
late final List<Search> importedSearches;
|
|
||||||
try {
|
|
||||||
importedSearches = ((((jsonDecode(await file.readAsString())
|
|
||||||
as Map<String, Object?>)['stores']! as List)
|
|
||||||
.map((e) => e as Map)
|
|
||||||
.where((e) => e['name'] == 'search')
|
|
||||||
.first)['values'] as List)
|
|
||||||
.map((item) => Search.fromJson(item))
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint(e.toString());
|
|
||||||
showSnackbar(
|
|
||||||
context,
|
|
||||||
"Couldn't read file. Did you choose the right one?",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Search> mergedSearches =
|
|
||||||
mergeSearches(prevSearches, importedSearches);
|
|
||||||
|
|
||||||
// print(mergedSearches);
|
|
||||||
|
|
||||||
await GetIt.instance.get<Database>().close();
|
|
||||||
GetIt.instance.unregister<Database>();
|
|
||||||
|
|
||||||
final importedDb = await importDatabase(
|
|
||||||
{
|
|
||||||
'sembast_export': 1,
|
|
||||||
'version': 1,
|
|
||||||
'stores': [
|
|
||||||
{
|
|
||||||
'name': 'search',
|
|
||||||
'keys': [for (var i = 1; i <= mergedSearches.length; i++) i],
|
|
||||||
'values': mergedSearches.map((e) => e.toJson()).toList(),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
databaseFactoryIo,
|
|
||||||
await databasePath(),
|
|
||||||
);
|
|
||||||
GetIt.instance.registerSingleton<Database>(importedDb);
|
|
||||||
|
|
||||||
setState(() => dataImportIsLoading = false);
|
setState(() => dataImportIsLoading = false);
|
||||||
showSnackbar(context, 'Data imported successfully');
|
showSnackbar(context, 'Data imported successfully');
|
||||||
@ -288,7 +246,7 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
SettingsTile(
|
SettingsTile(
|
||||||
leading: const Icon(Icons.file_upload),
|
leading: const Icon(Icons.file_upload),
|
||||||
title: 'Import Data',
|
title: 'Import Data',
|
||||||
onPressed: importData,
|
onPressed: importHandler,
|
||||||
enabled: Platform.isAndroid,
|
enabled: Platform.isAndroid,
|
||||||
subtitle:
|
subtitle:
|
||||||
Platform.isAndroid ? null : 'Not available on iOS yet',
|
Platform.isAndroid ? null : 'Not available on iOS yet',
|
||||||
@ -299,6 +257,7 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
SettingsTile(
|
SettingsTile(
|
||||||
leading: const Icon(Icons.file_download),
|
leading: const Icon(Icons.file_download),
|
||||||
title: 'Export Data',
|
title: 'Export Data',
|
||||||
|
onPressed: exportHandler,
|
||||||
enabled: Platform.isAndroid,
|
enabled: Platform.isAndroid,
|
||||||
subtitle:
|
subtitle:
|
||||||
Platform.isAndroid ? null : 'Not available on iOS yet',
|
Platform.isAndroid ? null : 'Not available on iOS yet',
|
||||||
@ -318,7 +277,13 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
onPressed: (c) {},
|
onPressed: (c) {},
|
||||||
titleTextStyle: const TextStyle(color: Colors.red),
|
titleTextStyle: const TextStyle(color: Colors.red),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
)
|
),
|
||||||
|
SettingsTile(
|
||||||
|
leading: const Icon(Icons.delete),
|
||||||
|
title: 'Clear Everything',
|
||||||
|
onPressed: clearAll,
|
||||||
|
titleTextStyle: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:sembast/sembast.dart';
|
|
||||||
import 'package:sembast/sembast_io.dart';
|
|
||||||
|
|
||||||
Future<String> databasePath() async {
|
|
||||||
final Directory appDocDir = await getApplicationDocumentsDirectory();
|
|
||||||
if (!appDocDir.existsSync()) appDocDir.createSync(recursive: true);
|
|
||||||
return join(appDocDir.path, 'sembast.db');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setupDatabase() async {
|
|
||||||
final Database database =
|
|
||||||
await databaseFactoryIo.openDatabase(await databasePath());
|
|
||||||
GetIt.instance.registerSingleton<Database>(database);
|
|
||||||
}
|
|
191
pubspec.lock
191
pubspec.lock
@ -7,14 +7,14 @@ packages:
|
|||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "31.0.0"
|
version: "40.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.0"
|
version: "4.1.0"
|
||||||
animated_size_and_fade:
|
animated_size_and_fade:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -35,7 +35,7 @@ packages:
|
|||||||
name: args
|
name: args
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.1"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -49,7 +49,7 @@ packages:
|
|||||||
name: audio_session
|
name: audio_session
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.6+1"
|
version: "0.1.7"
|
||||||
bloc:
|
bloc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -91,14 +91,14 @@ packages:
|
|||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.6"
|
version: "2.0.9"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.10"
|
version: "2.1.11"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -119,7 +119,7 @@ packages:
|
|||||||
name: built_value
|
name: built_value
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.3"
|
version: "8.3.2"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -141,13 +141,6 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
cli_util:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: cli_util
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.5"
|
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -168,7 +161,7 @@ packages:
|
|||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.16.0"
|
||||||
confirm_dialog:
|
confirm_dialog:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -182,14 +175,14 @@ packages:
|
|||||||
name: convert
|
name: convert
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
coverage:
|
coverage:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: coverage
|
name: coverage
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.3.2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -203,14 +196,14 @@ packages:
|
|||||||
name: csslib
|
name: csslib
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.17.1"
|
version: "0.17.2"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.3"
|
||||||
division:
|
division:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -218,20 +211,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.0"
|
version: "0.9.0"
|
||||||
|
equatable:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.0"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.2.1"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -245,14 +245,14 @@ packages:
|
|||||||
name: file_picker
|
name: file_picker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.1"
|
version: "4.6.1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fixnum
|
name: fixnum
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -278,14 +278,14 @@ packages:
|
|||||||
name: flutter_native_splash
|
name: flutter_native_splash
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.6"
|
version: "2.2.2"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.6"
|
||||||
flutter_settings_ui:
|
flutter_settings_ui:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -299,14 +299,14 @@ packages:
|
|||||||
name: flutter_slidable
|
name: flutter_slidable
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.0"
|
||||||
flutter_svg:
|
flutter_svg:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.1.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -323,7 +323,7 @@ packages:
|
|||||||
name: frontend_server_client
|
name: frontend_server_client
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.3"
|
||||||
get_it:
|
get_it:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -379,14 +379,14 @@ packages:
|
|||||||
name: http_parser
|
name: http_parser
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.1"
|
||||||
image:
|
image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.2.0"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -400,7 +400,7 @@ packages:
|
|||||||
name: js
|
name: js
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.3"
|
version: "0.6.4"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -414,7 +414,7 @@ packages:
|
|||||||
name: just_audio
|
name: just_audio
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.21"
|
version: "0.9.24"
|
||||||
just_audio_platform_interface:
|
just_audio_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -456,7 +456,7 @@ packages:
|
|||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.1.4"
|
||||||
mdi:
|
mdi:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -505,7 +505,7 @@ packages:
|
|||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.1"
|
||||||
path_drawing:
|
path_drawing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -526,56 +526,56 @@ packages:
|
|||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.9"
|
version: "2.0.10"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.13"
|
version: "2.0.14"
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_ios
|
name: path_provider_ios
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.0.9"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.7"
|
||||||
path_provider_macos:
|
path_provider_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_macos
|
name: path_provider_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.6"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "2.0.4"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.7"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.0"
|
version: "5.0.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -610,7 +610,7 @@ packages:
|
|||||||
name: provider
|
name: provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.2"
|
version: "6.0.3"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -631,21 +631,14 @@ packages:
|
|||||||
name: rxdart
|
name: rxdart
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.27.3"
|
version: "0.27.4"
|
||||||
sembast:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: sembast
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.2.0"
|
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.4"
|
version: "4.0.5"
|
||||||
share_plus_linux:
|
share_plus_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -659,63 +652,63 @@ packages:
|
|||||||
name: share_plus_macos
|
name: share_plus_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
share_plus_platform_interface:
|
share_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: share_plus_platform_interface
|
name: share_plus_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.3"
|
||||||
share_plus_web:
|
share_plus_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: share_plus_web
|
name: share_plus_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
share_plus_windows:
|
share_plus_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: share_plus_windows
|
name: share_plus_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.13"
|
version: "2.0.15"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.11"
|
version: "2.0.12"
|
||||||
shared_preferences_ios:
|
shared_preferences_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_ios
|
name: shared_preferences_ios
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
shared_preferences_macos:
|
shared_preferences_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_macos
|
name: shared_preferences_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "2.0.4"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -729,14 +722,14 @@ packages:
|
|||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "2.0.4"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.1"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -771,7 +764,7 @@ packages:
|
|||||||
name: signature
|
name: signature
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -797,7 +790,35 @@ packages:
|
|||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.1"
|
version: "1.8.2"
|
||||||
|
sqflite:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sqflite
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2+1"
|
||||||
|
sqflite_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_common
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1+1"
|
||||||
|
sqflite_common_ffi:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sqflite_common_ffi
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
sqlite3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.7.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -846,21 +867,21 @@ packages:
|
|||||||
name: test
|
name: test
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.5"
|
version: "1.21.1"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.8"
|
version: "0.4.9"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.9"
|
version: "0.4.13"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -874,7 +895,7 @@ packages:
|
|||||||
name: typed_data
|
name: typed_data
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.1"
|
||||||
universal_io:
|
universal_io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -888,42 +909,42 @@ packages:
|
|||||||
name: unofficial_jisho_api
|
name: unofficial_jisho_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "3.0.0"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "6.1.2"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.16"
|
version: "6.0.17"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.15"
|
version: "6.0.17"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -937,14 +958,14 @@ packages:
|
|||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.9"
|
version: "2.0.11"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -958,14 +979,14 @@ packages:
|
|||||||
name: vector_math
|
name: vector_math
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.5.0"
|
version: "8.3.0"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -986,14 +1007,14 @@ packages:
|
|||||||
name: webkit_inspection_protocol
|
name: webkit_inspection_protocol
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.1.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.2"
|
version: "2.6.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1007,14 +1028,14 @@ packages:
|
|||||||
name: xml
|
name: xml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.3.1"
|
version: "6.1.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: yaml
|
name: yaml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.16.0 <3.0.0"
|
dart: ">=2.17.0 <3.0.0"
|
||||||
flutter: ">=2.10.0"
|
flutter: ">=2.11.0-0.1.pre"
|
||||||
|
@ -23,11 +23,12 @@ dependencies:
|
|||||||
mdi: ^5.0.0-nullsafety.0
|
mdi: ^5.0.0-nullsafety.0
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
path_provider: ^2.0.2
|
path_provider: ^2.0.2
|
||||||
sembast: ^3.1.1
|
|
||||||
share_plus: ^4.0.4
|
share_plus: ^4.0.4
|
||||||
test: ^1.19.5
|
|
||||||
shared_preferences: ^2.0.6
|
shared_preferences: ^2.0.6
|
||||||
signature: ^5.0.0
|
signature: ^5.0.0
|
||||||
|
sqflite: ^2.0.2
|
||||||
|
sqflite_common_ffi: ^2.1.1
|
||||||
|
test: ^1.19.5
|
||||||
unofficial_jisho_api: ^3.0.0
|
unofficial_jisho_api: ^3.0.0
|
||||||
url_launcher: ^6.0.9
|
url_launcher: ^6.0.9
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ flutter:
|
|||||||
- assets/images/components/
|
- assets/images/components/
|
||||||
- assets/images/links/
|
- assets/images/links/
|
||||||
- assets/images/logo/
|
- assets/images/logo/
|
||||||
|
- assets/migrations/
|
||||||
- assets/licenses/
|
- assets/licenses/
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,34 +1,6 @@
|
|||||||
import 'package:jisho_study_tool/models/history/kanji_query.dart';
|
import 'package:jisho_study_tool/models/history/history_entry.dart';
|
||||||
import 'package:jisho_study_tool/models/history/search.dart';
|
|
||||||
import 'package:jisho_study_tool/models/history/word_query.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('Search', () {
|
group('Search', () {});
|
||||||
final List<Search> searches = [
|
|
||||||
Search.fromKanjiQuery(kanjiQuery: KanjiQuery(kanji: '何')),
|
|
||||||
Search.fromWordQuery(wordQuery: WordQuery(query: 'テスト')),
|
|
||||||
Search.fromJson({'timestamps':[1648658269960],'lastTimestamp':1648658269960,'wordQuery':null,'kanjiQuery':{'kanji':'日'}}),
|
|
||||||
Search.fromJson({'timestamps':[1648674967535],'lastTimestamp':1648674967535,'wordQuery':{'query':'黙る'},'kanjiQuery':null}),
|
|
||||||
Search.fromJson({'timestamps':[1649079907766],'lastTimestamp':1649079907766,'wordQuery':{'query':'seal'},'kanjiQuery':null}),
|
|
||||||
Search.fromJson({'timestamps':[1649082072981],'lastTimestamp':1649082072981,'wordQuery':{'query':'感涙屋'},'kanjiQuery':null}),
|
|
||||||
Search.fromJson({'timestamps':[1644951726777,1644951732749],'lastTimestamp':1644951732749,'wordQuery':{'query':'呑める'},'kanjiQuery':null}),
|
|
||||||
];
|
|
||||||
test("mergeSearches with empty lists doesn't add data", () {
|
|
||||||
final List<Search> merged1 = mergeSearches(searches, []);
|
|
||||||
final List<Search> merged2 = mergeSearches([], searches);
|
|
||||||
for (int i = 0; i < searches.length; i++) {
|
|
||||||
expect(merged1[i], searches[i]);
|
|
||||||
expect(merged2[i], searches[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("mergeSearches with the same list doesn't add data", () {
|
|
||||||
final List<Search> merged = mergeSearches(searches, searches);
|
|
||||||
for (int i = 0; i < searches.length; i++) {
|
|
||||||
expect(merged[i], searches[i]);
|
|
||||||
}
|
|
||||||
expect(mergeSearches(searches, searches), searches);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user