#1476 launch error handling;
DB: table existence check in v13+ upgrades
This commit is contained in:
parent
cf74e75d58
commit
cb067aa1ac
16 changed files with 722 additions and 438 deletions
|
@ -4,8 +4,13 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <a id="unreleased"></a>[Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- handle launch error to report and export DB
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- DB post-upgrade sanitization
|
||||||
- upgraded Flutter to stable v3.29.2
|
- upgraded Flutter to stable v3.29.2
|
||||||
|
|
||||||
## <a id="v1.12.6"></a>[v1.12.6] - 2025-03-11
|
## <a id="v1.12.6"></a>[v1.12.6] - 2025-03-11
|
||||||
|
|
|
@ -49,6 +49,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
||||||
"requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() }
|
"requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() }
|
||||||
"createFile" -> ioScope.launch { createFile() }
|
"createFile" -> ioScope.launch { createFile() }
|
||||||
"openFile" -> ioScope.launch { openFile() }
|
"openFile" -> ioScope.launch { openFile() }
|
||||||
|
"copyFile" -> ioScope.launch { copyFile() }
|
||||||
"edit" -> edit()
|
"edit" -> edit()
|
||||||
"pickCollectionFilters" -> pickCollectionFilters()
|
"pickCollectionFilters" -> pickCollectionFilters()
|
||||||
else -> endOfStream()
|
else -> endOfStream()
|
||||||
|
@ -181,6 +182,49 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
||||||
safeStartActivityForStorageAccessResult(intent, MainActivity.OPEN_FILE_REQUEST, ::onGranted, ::onDenied)
|
safeStartActivityForStorageAccessResult(intent, MainActivity.OPEN_FILE_REQUEST, ::onGranted, ::onDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun copyFile() {
|
||||||
|
val name = args["name"] as String?
|
||||||
|
val mimeType = args["mimeType"] as String?
|
||||||
|
val sourceUri = (args["sourceUri"] as String?)?.toUri()
|
||||||
|
if (name == null || mimeType == null || sourceUri == null) {
|
||||||
|
error("copyFile-args", "missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onGranted(uri: Uri) {
|
||||||
|
ioScope.launch {
|
||||||
|
try {
|
||||||
|
StorageUtils.openInputStream(activity, sourceUri)?.use { input ->
|
||||||
|
// truncate is necessary when overwriting a longer file
|
||||||
|
activity.contentResolver.openOutputStream(uri, "wt")?.use { output ->
|
||||||
|
val buffer = ByteArray(BUFFER_SIZE)
|
||||||
|
var len: Int
|
||||||
|
while (input.read(buffer).also { len = it } != -1) {
|
||||||
|
output.write(buffer, 0, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
success(true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
error("copyFile-write", "failed to copy file from sourceUri=$sourceUri to uri=$uri", e.message)
|
||||||
|
}
|
||||||
|
endOfStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDenied() {
|
||||||
|
success(null)
|
||||||
|
endOfStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = mimeType
|
||||||
|
putExtra(Intent.EXTRA_TITLE, name)
|
||||||
|
}
|
||||||
|
safeStartActivityForStorageAccessResult(intent, MainActivity.CREATE_FILE_REQUEST, ::onGranted, ::onDenied)
|
||||||
|
}
|
||||||
|
|
||||||
private fun edit() {
|
private fun edit() {
|
||||||
val uri = args["uri"] as String?
|
val uri = args["uri"] as String?
|
||||||
val mimeType = args["mimeType"] as String? // optional
|
val mimeType = args["mimeType"] as String? // optional
|
||||||
|
|
|
@ -12,6 +12,8 @@ import 'package:aves/model/viewer/video_playback.dart';
|
||||||
abstract class LocalMediaDb {
|
abstract class LocalMediaDb {
|
||||||
int get nextId;
|
int get nextId;
|
||||||
|
|
||||||
|
Future<String> get path;
|
||||||
|
|
||||||
Future<void> init();
|
Future<void> init();
|
||||||
|
|
||||||
Future<int> dbFileSize();
|
Future<int> dbFileSize();
|
||||||
|
|
15
lib/model/db/db_extension.dart
Normal file
15
lib/model/db/db_extension.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
|
extension ExtraDatabase on Database {
|
||||||
|
// check table existence
|
||||||
|
// proper way is to select from `sqlite_master` but this meta table may be missing on some devices
|
||||||
|
// so we rely on failure check instead
|
||||||
|
bool tableExists(String table) {
|
||||||
|
try {
|
||||||
|
query(table, limit: 1);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:aves/model/covers.dart';
|
import 'package:aves/model/covers.dart';
|
||||||
import 'package:aves/model/db/db.dart';
|
import 'package:aves/model/db/db.dart';
|
||||||
|
import 'package:aves/model/db/db_sqflite_schema.dart';
|
||||||
import 'package:aves/model/db/db_sqflite_upgrade.dart';
|
import 'package:aves/model/db/db_sqflite_upgrade.dart';
|
||||||
import 'package:aves/model/dynamic_albums.dart';
|
import 'package:aves/model/dynamic_albums.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
@ -20,18 +21,19 @@ import 'package:sqflite/sqflite.dart';
|
||||||
class SqfliteLocalMediaDb implements LocalMediaDb {
|
class SqfliteLocalMediaDb implements LocalMediaDb {
|
||||||
late Database _db;
|
late Database _db;
|
||||||
|
|
||||||
|
@override
|
||||||
Future<String> get path async => pContext.join(await getDatabasesPath(), 'metadata.db');
|
Future<String> get path async => pContext.join(await getDatabasesPath(), 'metadata.db');
|
||||||
|
|
||||||
static const entryTable = 'entry';
|
static const entryTable = SqfliteLocalMediaDbSchema.entryTable;
|
||||||
static const dateTakenTable = 'dateTaken';
|
static const dateTakenTable = SqfliteLocalMediaDbSchema.dateTakenTable;
|
||||||
static const metadataTable = 'metadata';
|
static const metadataTable = SqfliteLocalMediaDbSchema.metadataTable;
|
||||||
static const addressTable = 'address';
|
static const addressTable = SqfliteLocalMediaDbSchema.addressTable;
|
||||||
static const favouriteTable = 'favourites';
|
static const favouriteTable = SqfliteLocalMediaDbSchema.favouriteTable;
|
||||||
static const coverTable = 'covers';
|
static const coverTable = SqfliteLocalMediaDbSchema.coverTable;
|
||||||
static const dynamicAlbumTable = 'dynamicAlbums';
|
static const dynamicAlbumTable = SqfliteLocalMediaDbSchema.dynamicAlbumTable;
|
||||||
static const vaultTable = 'vaults';
|
static const vaultTable = SqfliteLocalMediaDbSchema.vaultTable;
|
||||||
static const trashTable = 'trash';
|
static const trashTable = SqfliteLocalMediaDbSchema.trashTable;
|
||||||
static const videoPlaybackTable = 'videoPlayback';
|
static const videoPlaybackTable = SqfliteLocalMediaDbSchema.videoPlaybackTable;
|
||||||
|
|
||||||
static const _entryInsertSliceMaxCount = 10000; // number of entries
|
static const _entryInsertSliceMaxCount = 10000; // number of entries
|
||||||
static const _queryCursorBufferSize = 1000; // number of rows
|
static const _queryCursorBufferSize = 1000; // number of rows
|
||||||
|
@ -44,78 +46,7 @@ class SqfliteLocalMediaDb implements LocalMediaDb {
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_db = await openDatabase(
|
_db = await openDatabase(
|
||||||
await path,
|
await path,
|
||||||
onCreate: (db, version) async {
|
onCreate: (db, version) => SqfliteLocalMediaDbSchema.createLatestVersion(db),
|
||||||
await db.execute('CREATE TABLE $entryTable('
|
|
||||||
'id INTEGER PRIMARY KEY'
|
|
||||||
', contentId INTEGER'
|
|
||||||
', uri TEXT'
|
|
||||||
', path TEXT'
|
|
||||||
', sourceMimeType TEXT'
|
|
||||||
', width INTEGER'
|
|
||||||
', height INTEGER'
|
|
||||||
', sourceRotationDegrees INTEGER'
|
|
||||||
', sizeBytes INTEGER'
|
|
||||||
', title TEXT'
|
|
||||||
', dateAddedSecs INTEGER DEFAULT (strftime(\'%s\',\'now\'))'
|
|
||||||
', dateModifiedMillis INTEGER'
|
|
||||||
', sourceDateTakenMillis INTEGER'
|
|
||||||
', durationMillis INTEGER'
|
|
||||||
', trashed INTEGER DEFAULT 0'
|
|
||||||
', origin INTEGER DEFAULT 0'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $dateTakenTable('
|
|
||||||
'id INTEGER PRIMARY KEY'
|
|
||||||
', dateMillis INTEGER'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $metadataTable('
|
|
||||||
'id INTEGER PRIMARY KEY'
|
|
||||||
', mimeType TEXT'
|
|
||||||
', dateMillis INTEGER'
|
|
||||||
', flags INTEGER'
|
|
||||||
', rotationDegrees INTEGER'
|
|
||||||
', xmpSubjects TEXT'
|
|
||||||
', xmpTitle TEXT'
|
|
||||||
', latitude REAL'
|
|
||||||
', longitude REAL'
|
|
||||||
', rating INTEGER'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $addressTable('
|
|
||||||
'id INTEGER PRIMARY KEY'
|
|
||||||
', addressLine TEXT'
|
|
||||||
', countryCode TEXT'
|
|
||||||
', countryName TEXT'
|
|
||||||
', adminArea TEXT'
|
|
||||||
', locality TEXT'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $favouriteTable('
|
|
||||||
'id INTEGER PRIMARY KEY'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $coverTable('
|
|
||||||
'filter TEXT PRIMARY KEY'
|
|
||||||
', entryId INTEGER'
|
|
||||||
', packageName TEXT'
|
|
||||||
', color TEXT'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $dynamicAlbumTable('
|
|
||||||
'name TEXT PRIMARY KEY'
|
|
||||||
', filter TEXT'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $vaultTable('
|
|
||||||
'name TEXT PRIMARY KEY'
|
|
||||||
', autoLock INTEGER'
|
|
||||||
', useBin INTEGER'
|
|
||||||
', lockType TEXT'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $trashTable('
|
|
||||||
'id INTEGER PRIMARY KEY'
|
|
||||||
', path TEXT'
|
|
||||||
', dateMillis INTEGER'
|
|
||||||
')');
|
|
||||||
await db.execute('CREATE TABLE $videoPlaybackTable('
|
|
||||||
'id INTEGER PRIMARY KEY'
|
|
||||||
', resumeTimeMillis INTEGER'
|
|
||||||
')');
|
|
||||||
},
|
|
||||||
onUpgrade: LocalMediaDbUpgrader.upgradeDb,
|
onUpgrade: LocalMediaDbUpgrader.upgradeDb,
|
||||||
version: 15,
|
version: 15,
|
||||||
);
|
);
|
||||||
|
|
118
lib/model/db/db_sqflite_schema.dart
Normal file
118
lib/model/db/db_sqflite_schema.dart
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
|
class SqfliteLocalMediaDbSchema {
|
||||||
|
static const entryTable = 'entry';
|
||||||
|
static const dateTakenTable = 'dateTaken';
|
||||||
|
static const metadataTable = 'metadata';
|
||||||
|
static const addressTable = 'address';
|
||||||
|
static const favouriteTable = 'favourites';
|
||||||
|
static const coverTable = 'covers';
|
||||||
|
static const dynamicAlbumTable = 'dynamicAlbums';
|
||||||
|
static const vaultTable = 'vaults';
|
||||||
|
static const trashTable = 'trash';
|
||||||
|
static const videoPlaybackTable = 'videoPlayback';
|
||||||
|
|
||||||
|
static const allTables = [
|
||||||
|
entryTable,
|
||||||
|
dateTakenTable,
|
||||||
|
metadataTable,
|
||||||
|
addressTable,
|
||||||
|
favouriteTable,
|
||||||
|
coverTable,
|
||||||
|
dynamicAlbumTable,
|
||||||
|
vaultTable,
|
||||||
|
trashTable,
|
||||||
|
videoPlaybackTable,
|
||||||
|
];
|
||||||
|
|
||||||
|
static Future<void> createLatestVersion(Database db) async {
|
||||||
|
await Future.forEach(allTables, (table) => createTable(db, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> createTable(Database db, String table) {
|
||||||
|
switch (table) {
|
||||||
|
case entryTable:
|
||||||
|
return db.execute('CREATE TABLE $entryTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
', contentId INTEGER'
|
||||||
|
', uri TEXT'
|
||||||
|
', path TEXT'
|
||||||
|
', sourceMimeType TEXT'
|
||||||
|
', width INTEGER'
|
||||||
|
', height INTEGER'
|
||||||
|
', sourceRotationDegrees INTEGER'
|
||||||
|
', sizeBytes INTEGER'
|
||||||
|
', title TEXT'
|
||||||
|
', dateAddedSecs INTEGER DEFAULT (strftime(\'%s\',\'now\'))'
|
||||||
|
', dateModifiedMillis INTEGER'
|
||||||
|
', sourceDateTakenMillis INTEGER'
|
||||||
|
', durationMillis INTEGER'
|
||||||
|
', trashed INTEGER DEFAULT 0'
|
||||||
|
', origin INTEGER DEFAULT 0'
|
||||||
|
')');
|
||||||
|
case dateTakenTable:
|
||||||
|
return db.execute('CREATE TABLE $dateTakenTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
', dateMillis INTEGER'
|
||||||
|
')');
|
||||||
|
case metadataTable:
|
||||||
|
return db.execute('CREATE TABLE $metadataTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
', mimeType TEXT'
|
||||||
|
', dateMillis INTEGER'
|
||||||
|
', flags INTEGER'
|
||||||
|
', rotationDegrees INTEGER'
|
||||||
|
', xmpSubjects TEXT'
|
||||||
|
', xmpTitle TEXT'
|
||||||
|
', latitude REAL'
|
||||||
|
', longitude REAL'
|
||||||
|
', rating INTEGER'
|
||||||
|
')');
|
||||||
|
case addressTable:
|
||||||
|
return db.execute('CREATE TABLE $addressTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
', addressLine TEXT'
|
||||||
|
', countryCode TEXT'
|
||||||
|
', countryName TEXT'
|
||||||
|
', adminArea TEXT'
|
||||||
|
', locality TEXT'
|
||||||
|
')');
|
||||||
|
case favouriteTable:
|
||||||
|
return db.execute('CREATE TABLE $favouriteTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
')');
|
||||||
|
case coverTable:
|
||||||
|
return db.execute('CREATE TABLE $coverTable('
|
||||||
|
'filter TEXT PRIMARY KEY'
|
||||||
|
', entryId INTEGER'
|
||||||
|
', packageName TEXT'
|
||||||
|
', color TEXT'
|
||||||
|
')');
|
||||||
|
case dynamicAlbumTable:
|
||||||
|
return db.execute('CREATE TABLE $dynamicAlbumTable('
|
||||||
|
'name TEXT PRIMARY KEY'
|
||||||
|
', filter TEXT'
|
||||||
|
')');
|
||||||
|
case vaultTable:
|
||||||
|
return db.execute('CREATE TABLE $vaultTable('
|
||||||
|
'name TEXT PRIMARY KEY'
|
||||||
|
', autoLock INTEGER'
|
||||||
|
', useBin INTEGER'
|
||||||
|
', lockType TEXT'
|
||||||
|
')');
|
||||||
|
case trashTable:
|
||||||
|
return db.execute('CREATE TABLE $trashTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
', path TEXT'
|
||||||
|
', dateMillis INTEGER'
|
||||||
|
')');
|
||||||
|
case videoPlaybackTable:
|
||||||
|
return db.execute('CREATE TABLE $videoPlaybackTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
', resumeTimeMillis INTEGER'
|
||||||
|
')');
|
||||||
|
default:
|
||||||
|
throw Exception('unknown table=$table');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,23 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/model/covers.dart';
|
import 'package:aves/model/covers.dart';
|
||||||
import 'package:aves/model/db/db_sqflite.dart';
|
import 'package:aves/model/db/db_extension.dart';
|
||||||
|
import 'package:aves/model/db/db_sqflite_schema.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
class LocalMediaDbUpgrader {
|
class LocalMediaDbUpgrader {
|
||||||
static const entryTable = SqfliteLocalMediaDb.entryTable;
|
static const entryTable = SqfliteLocalMediaDbSchema.entryTable;
|
||||||
static const dateTakenTable = SqfliteLocalMediaDb.dateTakenTable;
|
static const dateTakenTable = SqfliteLocalMediaDbSchema.dateTakenTable;
|
||||||
static const metadataTable = SqfliteLocalMediaDb.metadataTable;
|
static const metadataTable = SqfliteLocalMediaDbSchema.metadataTable;
|
||||||
static const addressTable = SqfliteLocalMediaDb.addressTable;
|
static const addressTable = SqfliteLocalMediaDbSchema.addressTable;
|
||||||
static const favouriteTable = SqfliteLocalMediaDb.favouriteTable;
|
static const favouriteTable = SqfliteLocalMediaDbSchema.favouriteTable;
|
||||||
static const coverTable = SqfliteLocalMediaDb.coverTable;
|
static const coverTable = SqfliteLocalMediaDbSchema.coverTable;
|
||||||
static const dynamicAlbumTable = SqfliteLocalMediaDb.dynamicAlbumTable;
|
static const dynamicAlbumTable = SqfliteLocalMediaDbSchema.dynamicAlbumTable;
|
||||||
static const vaultTable = SqfliteLocalMediaDb.vaultTable;
|
static const vaultTable = SqfliteLocalMediaDbSchema.vaultTable;
|
||||||
static const trashTable = SqfliteLocalMediaDb.trashTable;
|
static const trashTable = SqfliteLocalMediaDbSchema.trashTable;
|
||||||
static const videoPlaybackTable = SqfliteLocalMediaDb.videoPlaybackTable;
|
static const videoPlaybackTable = SqfliteLocalMediaDbSchema.videoPlaybackTable;
|
||||||
|
|
||||||
// warning: "ALTER TABLE ... RENAME COLUMN ..." is not supported
|
// warning: "ALTER TABLE ... RENAME COLUMN ..." is not supported
|
||||||
// on SQLite <3.25.0, bundled on older Android devices
|
// on SQLite <3.25.0, bundled on older Android devices
|
||||||
|
@ -55,12 +56,28 @@ class LocalMediaDbUpgrader {
|
||||||
}
|
}
|
||||||
oldVersion++;
|
oldVersion++;
|
||||||
}
|
}
|
||||||
|
await _sanitize(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _sanitize(Database db) async {
|
||||||
|
// ensure all tables exist
|
||||||
|
await Future.forEach(SqfliteLocalMediaDbSchema.allTables, (table) async {
|
||||||
|
if (!db.tableExists(table)) {
|
||||||
|
await SqfliteLocalMediaDbSchema.createTable(db, table);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove rows referencing future entry IDs
|
||||||
|
final maxIdRows = await db.rawQuery('SELECT MAX(id) AS maxId FROM $entryTable');
|
||||||
|
final lastId = (maxIdRows.firstOrNull?['maxId'] as int?) ?? 0;
|
||||||
|
await db.delete(favouriteTable, where: 'id > ?', whereArgs: [lastId]);
|
||||||
|
await db.delete(coverTable, where: 'entryId > ?', whereArgs: [lastId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> _upgradeFrom1(Database db) async {
|
static Future<void> _upgradeFrom1(Database db) async {
|
||||||
debugPrint('upgrading DB from v1');
|
debugPrint('upgrading DB from v1');
|
||||||
|
|
||||||
// rename column 'orientationDegrees' to 'sourceRotationDegrees'
|
// rename column 'orientationDegrees' to 'sourceRotationDegrees'
|
||||||
await db.transaction((txn) async {
|
|
||||||
const newEntryTable = '${entryTable}TEMP';
|
const newEntryTable = '${entryTable}TEMP';
|
||||||
await db.execute('CREATE TABLE $newEntryTable ('
|
await db.execute('CREATE TABLE $newEntryTable ('
|
||||||
'contentId INTEGER PRIMARY KEY'
|
'contentId INTEGER PRIMARY KEY'
|
||||||
|
@ -81,10 +98,8 @@ class LocalMediaDbUpgrader {
|
||||||
' FROM $entryTable;');
|
' FROM $entryTable;');
|
||||||
await db.execute('DROP TABLE $entryTable;');
|
await db.execute('DROP TABLE $entryTable;');
|
||||||
await db.execute('ALTER TABLE $newEntryTable RENAME TO $entryTable;');
|
await db.execute('ALTER TABLE $newEntryTable RENAME TO $entryTable;');
|
||||||
});
|
|
||||||
|
|
||||||
// rename column 'videoRotation' to 'rotationDegrees'
|
// rename column 'videoRotation' to 'rotationDegrees'
|
||||||
await db.transaction((txn) async {
|
|
||||||
const newMetadataTable = '${metadataTable}TEMP';
|
const newMetadataTable = '${metadataTable}TEMP';
|
||||||
await db.execute('CREATE TABLE $newMetadataTable ('
|
await db.execute('CREATE TABLE $newMetadataTable ('
|
||||||
'contentId INTEGER PRIMARY KEY'
|
'contentId INTEGER PRIMARY KEY'
|
||||||
|
@ -103,7 +118,6 @@ class LocalMediaDbUpgrader {
|
||||||
await db.rawInsert('UPDATE $newMetadataTable SET rotationDegrees = NULL WHERE rotationDegrees = 0;');
|
await db.rawInsert('UPDATE $newMetadataTable SET rotationDegrees = NULL WHERE rotationDegrees = 0;');
|
||||||
await db.execute('DROP TABLE $metadataTable;');
|
await db.execute('DROP TABLE $metadataTable;');
|
||||||
await db.execute('ALTER TABLE $newMetadataTable RENAME TO $metadataTable;');
|
await db.execute('ALTER TABLE $newMetadataTable RENAME TO $metadataTable;');
|
||||||
});
|
|
||||||
|
|
||||||
// new column 'isFlipped'
|
// new column 'isFlipped'
|
||||||
await db.execute('ALTER TABLE $metadataTable ADD COLUMN isFlipped INTEGER;');
|
await db.execute('ALTER TABLE $metadataTable ADD COLUMN isFlipped INTEGER;');
|
||||||
|
@ -111,8 +125,8 @@ class LocalMediaDbUpgrader {
|
||||||
|
|
||||||
static Future<void> _upgradeFrom2(Database db) async {
|
static Future<void> _upgradeFrom2(Database db) async {
|
||||||
debugPrint('upgrading DB from v2');
|
debugPrint('upgrading DB from v2');
|
||||||
|
|
||||||
// merge columns 'isAnimated' and 'isFlipped' into 'flags'
|
// merge columns 'isAnimated' and 'isFlipped' into 'flags'
|
||||||
await db.transaction((txn) async {
|
|
||||||
const newMetadataTable = '${metadataTable}TEMP';
|
const newMetadataTable = '${metadataTable}TEMP';
|
||||||
await db.execute('CREATE TABLE $newMetadataTable ('
|
await db.execute('CREATE TABLE $newMetadataTable ('
|
||||||
'contentId INTEGER PRIMARY KEY'
|
'contentId INTEGER PRIMARY KEY'
|
||||||
|
@ -130,7 +144,6 @@ class LocalMediaDbUpgrader {
|
||||||
' FROM $metadataTable;');
|
' FROM $metadataTable;');
|
||||||
await db.execute('DROP TABLE $metadataTable;');
|
await db.execute('DROP TABLE $metadataTable;');
|
||||||
await db.execute('ALTER TABLE $newMetadataTable RENAME TO $metadataTable;');
|
await db.execute('ALTER TABLE $newMetadataTable RENAME TO $metadataTable;');
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> _upgradeFrom3(Database db) async {
|
static Future<void> _upgradeFrom3(Database db) async {
|
||||||
|
@ -423,7 +436,6 @@ class LocalMediaDbUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert `color` column type from value number to JSON string
|
// convert `color` column type from value number to JSON string
|
||||||
await db.transaction((txn) async {
|
|
||||||
const newCoverTable = '${coverTable}TEMP';
|
const newCoverTable = '${coverTable}TEMP';
|
||||||
await db.execute('CREATE TABLE $newCoverTable ('
|
await db.execute('CREATE TABLE $newCoverTable ('
|
||||||
'filter TEXT PRIMARY KEY'
|
'filter TEXT PRIMARY KEY'
|
||||||
|
@ -447,14 +459,13 @@ class LocalMediaDbUpgrader {
|
||||||
|
|
||||||
await db.execute('DROP TABLE $coverTable;');
|
await db.execute('DROP TABLE $coverTable;');
|
||||||
await db.execute('ALTER TABLE $newCoverTable RENAME TO $coverTable;');
|
await db.execute('ALTER TABLE $newCoverTable RENAME TO $coverTable;');
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> _upgradeFrom13(Database db) async {
|
static Future<void> _upgradeFrom13(Database db) async {
|
||||||
debugPrint('upgrading DB from v13');
|
debugPrint('upgrading DB from v13');
|
||||||
|
|
||||||
|
if (db.tableExists(entryTable)) {
|
||||||
// rename column 'dateModifiedSecs' to 'dateModifiedMillis'
|
// rename column 'dateModifiedSecs' to 'dateModifiedMillis'
|
||||||
await db.transaction((txn) async {
|
|
||||||
const newEntryTable = '${entryTable}TEMP';
|
const newEntryTable = '${entryTable}TEMP';
|
||||||
await db.execute('CREATE TABLE $newEntryTable ('
|
await db.execute('CREATE TABLE $newEntryTable ('
|
||||||
'id INTEGER PRIMARY KEY'
|
'id INTEGER PRIMARY KEY'
|
||||||
|
@ -479,25 +490,19 @@ class LocalMediaDbUpgrader {
|
||||||
' FROM $entryTable;');
|
' FROM $entryTable;');
|
||||||
await db.execute('DROP TABLE $entryTable;');
|
await db.execute('DROP TABLE $entryTable;');
|
||||||
await db.execute('ALTER TABLE $newEntryTable RENAME TO $entryTable;');
|
await db.execute('ALTER TABLE $newEntryTable RENAME TO $entryTable;');
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> _upgradeFrom14(Database db) async {
|
static Future<void> _upgradeFrom14(Database db) async {
|
||||||
debugPrint('upgrading DB from v14');
|
debugPrint('upgrading DB from v14');
|
||||||
|
|
||||||
// no schema changes, but v1.12.4 may have corrupted the DB, so we sanitize it
|
// no schema changes, but v1.12.4 may have corrupted the DB,
|
||||||
|
// so we clear rebuildable tables
|
||||||
// clear rebuildable tables
|
final tables = [dateTakenTable, metadataTable, addressTable, trashTable, videoPlaybackTable];
|
||||||
await db.delete(dateTakenTable, where: '1');
|
await Future.forEach(tables, (table) async {
|
||||||
await db.delete(metadataTable, where: '1');
|
if (db.tableExists(table)) {
|
||||||
await db.delete(addressTable, where: '1');
|
await db.delete(table, where: '1');
|
||||||
await db.delete(trashTable, where: '1');
|
}
|
||||||
await db.delete(videoPlaybackTable, where: '1');
|
});
|
||||||
|
|
||||||
// remove rows referencing future entry IDs
|
|
||||||
final maxIdRows = await db.rawQuery('SELECT MAX(id) AS maxId FROM $entryTable');
|
|
||||||
final lastId = (maxIdRows.firstOrNull?['maxId'] as int?) ?? 0;
|
|
||||||
await db.delete(favouriteTable, where: 'id > ?', whereArgs: [lastId]);
|
|
||||||
await db.delete(coverTable, where: 'entryId > ?', whereArgs: [lastId]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ class MimeTypes {
|
||||||
|
|
||||||
static const json = 'application/json';
|
static const json = 'application/json';
|
||||||
static const plainText = 'text/plain';
|
static const plainText = 'text/plain';
|
||||||
|
static const sqlite3 = 'application/vnd.sqlite3';
|
||||||
|
|
||||||
// JB2, JPC, JPX?
|
// JB2, JPC, JPX?
|
||||||
static const octetStream = 'application/octet-stream';
|
static const octetStream = 'application/octet-stream';
|
||||||
|
|
|
@ -53,6 +53,9 @@ abstract class StorageService {
|
||||||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
|
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
|
||||||
|
|
||||||
Future<Uint8List> openFile([String? mimeType]);
|
Future<Uint8List> openFile([String? mimeType]);
|
||||||
|
|
||||||
|
// return whether operation succeeded (`null` if user cancelled)
|
||||||
|
Future<bool?> copyFile(String name, String mimeType, String sourceUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformStorageService implements StorageService {
|
class PlatformStorageService implements StorageService {
|
||||||
|
@ -369,4 +372,29 @@ class PlatformStorageService implements StorageService {
|
||||||
}
|
}
|
||||||
return Uint8List(0);
|
return Uint8List(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool?> copyFile(String name, String mimeType, String sourceUri) async {
|
||||||
|
try {
|
||||||
|
final opCompleter = Completer<bool?>();
|
||||||
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
|
'op': 'copyFile',
|
||||||
|
'name': name,
|
||||||
|
'mimeType': mimeType,
|
||||||
|
'sourceUri': sourceUri,
|
||||||
|
}).listen(
|
||||||
|
(data) => opCompleter.complete(data as bool?),
|
||||||
|
onError: opCompleter.completeError,
|
||||||
|
onDone: () {
|
||||||
|
if (!opCompleter.isCompleted) opCompleter.complete(false);
|
||||||
|
},
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
|
// `await` here, so that `completeError` will be caught below
|
||||||
|
return await opCompleter.future;
|
||||||
|
} on PlatformException catch (e, stack) {
|
||||||
|
await reportService.recordError(e, stack);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,21 +36,11 @@ class BugReport extends StatefulWidget {
|
||||||
State<BugReport> createState() => _BugReportState();
|
State<BugReport> createState() => _BugReportState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BugReportState extends State<BugReport> with FeedbackMixin {
|
class _BugReportState extends State<BugReport> {
|
||||||
late Future<String> _infoLoader;
|
|
||||||
bool _showInstructions = false;
|
bool _showInstructions = false;
|
||||||
|
|
||||||
static const bugReportUrl = '${AppReference.avesGithub}/issues/new?labels=type%3Abug&template=bug_report.md';
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_infoLoader = _getInfo(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
|
||||||
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
|
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
|
||||||
return ExpansionPanelList(
|
return ExpansionPanelList(
|
||||||
expansionCallback: (index, isExpanded) {
|
expansionCallback: (index, isExpanded) {
|
||||||
|
@ -66,10 +56,40 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Text(l10n.aboutBugSectionTitle, style: AStyles.knownTitleText),
|
child: Text(context.l10n.aboutBugSectionTitle, style: AStyles.knownTitleText),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: const BugReportContent(),
|
||||||
|
isExpanded: _showInstructions,
|
||||||
|
canTapOnHeader: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BugReportContent extends StatefulWidget {
|
||||||
|
const BugReportContent({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BugReportContent> createState() => _BugReportContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BugReportContentState extends State<BugReportContent> with FeedbackMixin {
|
||||||
|
late Future<String> _infoLoader;
|
||||||
|
static const bugReportUrl = '${AppReference.avesGithub}/issues/new?labels=type%3Abug&template=bug_report.md';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_infoLoader = _getInfo(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -109,15 +129,9 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_buildStep(3, l10n.aboutBugReportInstruction, l10n.aboutBugReportButton, _goToGithub),
|
_buildStep(3, l10n.aboutBugReportInstruction, l10n.aboutBugReportButton, _goToGithub),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
isExpanded: _showInstructions,
|
|
||||||
canTapOnHeader: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/common/providers/viewer_entry_provider.dart';
|
import 'package:aves/widgets/common/providers/viewer_entry_provider.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/edit_location_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/edit_location_dialog.dart';
|
||||||
import 'package:aves/widgets/home_page.dart';
|
import 'package:aves/widgets/home/home_page.dart';
|
||||||
import 'package:aves/widgets/navigation/tv_page_transitions.dart';
|
import 'package:aves/widgets/navigation/tv_page_transitions.dart';
|
||||||
import 'package:aves/widgets/navigation/tv_rail.dart';
|
import 'package:aves/widgets/navigation/tv_rail.dart';
|
||||||
import 'package:aves/widgets/welcome_page.dart';
|
import 'package:aves/widgets/welcome_page.dart';
|
||||||
|
|
107
lib/widgets/home/home_error.dart
Normal file
107
lib/widgets/home/home_error.dart
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:aves/ref/locales.dart';
|
||||||
|
import 'package:aves/ref/mime_types.dart';
|
||||||
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:aves/widgets/about/bug_report.dart';
|
||||||
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class HomeError extends StatefulWidget {
|
||||||
|
final Object error;
|
||||||
|
final StackTrace stack;
|
||||||
|
|
||||||
|
const HomeError({
|
||||||
|
super.key,
|
||||||
|
required this.error,
|
||||||
|
required this.stack,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeError> createState() => _HomeErrorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeErrorState extends State<HomeError> with FeedbackMixin {
|
||||||
|
final ValueNotifier<String?> _expandedNotifier = ValueNotifier(null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_expandedNotifier.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildListDelegate(
|
||||||
|
[
|
||||||
|
AvesExpansionTile(
|
||||||
|
title: 'Error',
|
||||||
|
expandedNotifier: _expandedNotifier,
|
||||||
|
showHighlight: false,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: SelectableText(
|
||||||
|
'${widget.error}:\n${widget.stack}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AvesExpansionTile(
|
||||||
|
title: l10n.aboutBugSectionTitle,
|
||||||
|
expandedNotifier: _expandedNotifier,
|
||||||
|
showHighlight: false,
|
||||||
|
children: const [BugReportContent()],
|
||||||
|
),
|
||||||
|
AvesExpansionTile(
|
||||||
|
title: l10n.aboutDataUsageDatabase,
|
||||||
|
expandedNotifier: _expandedNotifier,
|
||||||
|
showHighlight: false,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
|
child: AvesOutlinedButton(
|
||||||
|
label: l10n.settingsActionExport,
|
||||||
|
onPressed: () async {
|
||||||
|
final sourcePath = await localMediaDb.path;
|
||||||
|
final success = await storageService.copyFile(
|
||||||
|
'aves-database-${DateFormat('yyyyMMdd_HHmmss', asciiLocale).format(DateTime.now())}.db',
|
||||||
|
MimeTypes.sqlite3,
|
||||||
|
Uri.file(sourcePath).toString(),
|
||||||
|
);
|
||||||
|
if (success != null) {
|
||||||
|
if (success) {
|
||||||
|
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||||
|
} else {
|
||||||
|
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,9 @@ import 'package:aves/model/app/permissions.dart';
|
||||||
import 'package:aves/model/app_inventory.dart';
|
import 'package:aves/model/app_inventory.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/catalog.dart';
|
import 'package:aves/model/entry/extensions/catalog.dart';
|
||||||
|
import 'package:aves/model/filters/covered/location.dart';
|
||||||
import 'package:aves/model/filters/covered/stored_album.dart';
|
import 'package:aves/model/filters/covered/stored_album.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/covered/location.dart';
|
|
||||||
import 'package:aves/model/settings/enums/home_page.dart';
|
import 'package:aves/model/settings/enums/home_page.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
|
@ -31,6 +31,7 @@ import 'package:aves/widgets/editor/entry_editor_page.dart';
|
||||||
import 'package:aves/widgets/explorer/explorer_page.dart';
|
import 'package:aves/widgets/explorer/explorer_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
|
import 'package:aves/widgets/home/home_error.dart';
|
||||||
import 'package:aves/widgets/map/map_page.dart';
|
import 'package:aves/widgets/map/map_page.dart';
|
||||||
import 'package:aves/widgets/search/search_delegate.dart';
|
import 'package:aves/widgets/search/search_delegate.dart';
|
||||||
import 'package:aves/widgets/settings/home_widget_settings_page.dart';
|
import 'package:aves/widgets/settings/home_widget_settings_page.dart';
|
||||||
|
@ -68,6 +69,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
String? _initialExplorerPath;
|
String? _initialExplorerPath;
|
||||||
(LatLng, double?)? _initialLocationZoom;
|
(LatLng, double?)? _initialLocationZoom;
|
||||||
List<String>? _secureUris;
|
List<String>? _secureUris;
|
||||||
|
(Object, StackTrace)? _setupError;
|
||||||
|
|
||||||
static const allowedShortcutRoutes = [
|
static const allowedShortcutRoutes = [
|
||||||
AlbumListPage.routeName,
|
AlbumListPage.routeName,
|
||||||
|
@ -85,9 +87,17 @@ class _HomePageState extends State<HomePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => const AvesScaffold();
|
Widget build(BuildContext context) => AvesScaffold(
|
||||||
|
body: _setupError != null
|
||||||
|
? HomeError(
|
||||||
|
error: _setupError!.$1,
|
||||||
|
stack: _setupError!.$2,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
Future<void> _setup() async {
|
Future<void> _setup() async {
|
||||||
|
try {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
if (await windowService.isActivity()) {
|
if (await windowService.isActivity()) {
|
||||||
// do not check whether permission was granted, because some app stores
|
// do not check whether permission was granted, because some app stores
|
||||||
|
@ -262,6 +272,10 @@ class _HomePageState extends State<HomePage> {
|
||||||
await _getRedirectRoute(appMode),
|
await _getRedirectRoute(appMode),
|
||||||
(route) => false,
|
(route) => false,
|
||||||
));
|
));
|
||||||
|
} catch (error, stack) {
|
||||||
|
debugPrint('failed to setup app with error=$error\n$stack');
|
||||||
|
_setupError = (error, stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initViewerEssentials() async {
|
Future<void> _initViewerEssentials() async {
|
|
@ -26,7 +26,7 @@ import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/places_page.dart';
|
import 'package:aves/widgets/filter_grids/places_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
import 'package:aves/widgets/home_page.dart';
|
import 'package:aves/widgets/home/home_page.dart';
|
||||||
import 'package:aves/widgets/navigation/drawer/collection_nav_tile.dart';
|
import 'package:aves/widgets/navigation/drawer/collection_nav_tile.dart';
|
||||||
import 'package:aves/widgets/navigation/drawer/page_nav_tile.dart';
|
import 'package:aves/widgets/navigation/drawer/page_nav_tile.dart';
|
||||||
import 'package:aves/widgets/navigation/drawer/tile.dart';
|
import 'package:aves/widgets/navigation/drawer/tile.dart';
|
||||||
|
|
|
@ -12,7 +12,7 @@ import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/places_page.dart';
|
import 'package:aves/widgets/filter_grids/places_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
import 'package:aves/widgets/home_page.dart';
|
import 'package:aves/widgets/home/home_page.dart';
|
||||||
import 'package:aves/widgets/settings/settings_page.dart';
|
import 'package:aves/widgets/settings/settings_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import 'package:aves/widgets/common/basic/scaffold.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_logo.dart';
|
import 'package:aves/widgets/common/identity/aves_logo.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
||||||
import 'package:aves/widgets/home_page.dart';
|
import 'package:aves/widgets/home/home_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
Loading…
Reference in a new issue