Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2025-03-16 19:44:30 +01:00
commit 160544926c
30 changed files with 814 additions and 508 deletions

@ -1 +1 @@
Subproject commit 09de023485e95e6d1225c2baa44b8feb85e0d45f
Subproject commit c23637390482d4cf9598c3ce3f2be31aa7332daf

View file

@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
## <a id="v1.12.7"></a>[v1.12.7] - 2025-03-16
### Added
- handle launch error to report and export DB
### Changed
- DB post-upgrade sanitization
- upgraded Flutter to stable v3.29.2
## <a id="v1.12.6"></a>[v1.12.6] - 2025-03-11
### Fixed

1
android/.gitignore vendored
View file

@ -6,6 +6,7 @@ gradle-wrapper.jar
/local.properties
GeneratedPluginRegistrant.java
.cxx/
.kotlin/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore

View file

@ -331,7 +331,7 @@
android:value="2" />
<!--
Screenshot driver scenario is not supported by Impeller: "Compressed screenshots not supported for Impeller".
As of Flutter v3.29.0, switching pages with alpha transition yields artifacts when Impeller is enabled.
As of Flutter v3.29.2, switching pages with alpha transition yields artifacts when Impeller is enabled.
-->
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"

View file

@ -541,6 +541,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// fallback to MP4 `loci` box for location
if (!metadataMap.contains(KEY_LATITUDE) || !metadataMap.contains(KEY_LONGITUDE)) {
try {
Mp4ParserHelper.getUserDataBox(context, mimeType, uri)?.let { userDataBox ->
Path.getPath<LocationInformationBox>(userDataBox, LocationInformationBox.TYPE)?.let { locationBox ->
if (!locationBox.isParsed) {
@ -550,6 +551,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
metadataMap[KEY_LONGITUDE] = locationBox.longitude
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get Location Information box by MP4 parser for mimeType=$mimeType uri=$uri", e)
}
}
}

View file

@ -49,6 +49,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
"requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() }
"createFile" -> ioScope.launch { createFile() }
"openFile" -> ioScope.launch { openFile() }
"copyFile" -> ioScope.launch { copyFile() }
"edit" -> edit()
"pickCollectionFilters" -> pickCollectionFilters()
else -> endOfStream()
@ -181,6 +182,49 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
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() {
val uri = args["uri"] as String?
val mimeType = args["mimeType"] as String? // optional

View file

@ -0,0 +1,4 @@
In v1.12.7:
- play more kinds of motion photos
- enjoy the app in Galician
Full changelog available on GitHub

View file

@ -0,0 +1,4 @@
In v1.12.7:
- play more kinds of motion photos
- enjoy the app in Galician
Full changelog available on GitHub

View file

@ -12,6 +12,8 @@ import 'package:aves/model/viewer/video_playback.dart';
abstract class LocalMediaDb {
int get nextId;
Future<String> get path;
Future<void> init();
Future<int> dbFileSize();

View 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;
}
}
}

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:aves/model/covers.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/dynamic_albums.dart';
import 'package:aves/model/entry/entry.dart';
@ -20,18 +21,19 @@ import 'package:sqflite/sqflite.dart';
class SqfliteLocalMediaDb implements LocalMediaDb {
late Database _db;
@override
Future<String> get path async => pContext.join(await getDatabasesPath(), 'metadata.db');
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 entryTable = SqfliteLocalMediaDbSchema.entryTable;
static const dateTakenTable = SqfliteLocalMediaDbSchema.dateTakenTable;
static const metadataTable = SqfliteLocalMediaDbSchema.metadataTable;
static const addressTable = SqfliteLocalMediaDbSchema.addressTable;
static const favouriteTable = SqfliteLocalMediaDbSchema.favouriteTable;
static const coverTable = SqfliteLocalMediaDbSchema.coverTable;
static const dynamicAlbumTable = SqfliteLocalMediaDbSchema.dynamicAlbumTable;
static const vaultTable = SqfliteLocalMediaDbSchema.vaultTable;
static const trashTable = SqfliteLocalMediaDbSchema.trashTable;
static const videoPlaybackTable = SqfliteLocalMediaDbSchema.videoPlaybackTable;
static const _entryInsertSliceMaxCount = 10000; // number of entries
static const _queryCursorBufferSize = 1000; // number of rows
@ -44,78 +46,7 @@ class SqfliteLocalMediaDb implements LocalMediaDb {
Future<void> init() async {
_db = await openDatabase(
await path,
onCreate: (db, version) async {
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'
')');
},
onCreate: (db, version) => SqfliteLocalMediaDbSchema.createLatestVersion(db),
onUpgrade: LocalMediaDbUpgrader.upgradeDb,
version: 15,
);

View 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');
}
}
}

View file

@ -1,22 +1,23 @@
import 'dart:ui';
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:flutter/foundation.dart';
import 'package:sqflite/sqflite.dart';
class LocalMediaDbUpgrader {
static const entryTable = SqfliteLocalMediaDb.entryTable;
static const dateTakenTable = SqfliteLocalMediaDb.dateTakenTable;
static const metadataTable = SqfliteLocalMediaDb.metadataTable;
static const addressTable = SqfliteLocalMediaDb.addressTable;
static const favouriteTable = SqfliteLocalMediaDb.favouriteTable;
static const coverTable = SqfliteLocalMediaDb.coverTable;
static const dynamicAlbumTable = SqfliteLocalMediaDb.dynamicAlbumTable;
static const vaultTable = SqfliteLocalMediaDb.vaultTable;
static const trashTable = SqfliteLocalMediaDb.trashTable;
static const videoPlaybackTable = SqfliteLocalMediaDb.videoPlaybackTable;
static const entryTable = SqfliteLocalMediaDbSchema.entryTable;
static const dateTakenTable = SqfliteLocalMediaDbSchema.dateTakenTable;
static const metadataTable = SqfliteLocalMediaDbSchema.metadataTable;
static const addressTable = SqfliteLocalMediaDbSchema.addressTable;
static const favouriteTable = SqfliteLocalMediaDbSchema.favouriteTable;
static const coverTable = SqfliteLocalMediaDbSchema.coverTable;
static const dynamicAlbumTable = SqfliteLocalMediaDbSchema.dynamicAlbumTable;
static const vaultTable = SqfliteLocalMediaDbSchema.vaultTable;
static const trashTable = SqfliteLocalMediaDbSchema.trashTable;
static const videoPlaybackTable = SqfliteLocalMediaDbSchema.videoPlaybackTable;
// warning: "ALTER TABLE ... RENAME COLUMN ..." is not supported
// on SQLite <3.25.0, bundled on older Android devices
@ -55,14 +56,30 @@ class LocalMediaDbUpgrader {
}
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 {
debugPrint('upgrading DB from v1');
// rename column 'orientationDegrees' to 'sourceRotationDegrees'
await db.transaction((txn) async {
const newEntryTable = '${entryTable}TEMP';
await db.execute('CREATE TABLE $newEntryTable('
await db.execute('CREATE TABLE $newEntryTable ('
'contentId INTEGER PRIMARY KEY'
', uri TEXT'
', path TEXT'
@ -76,17 +93,15 @@ class LocalMediaDbUpgrader {
', sourceDateTakenMillis INTEGER'
', durationMillis INTEGER'
')');
await db.rawInsert('INSERT INTO $newEntryTable(contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis)'
await db.rawInsert('INSERT INTO $newEntryTable (contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis)'
' SELECT contentId,uri,path,sourceMimeType,width,height,orientationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis'
' FROM $entryTable;');
await db.execute('DROP TABLE $entryTable;');
await db.execute('ALTER TABLE $newEntryTable RENAME TO $entryTable;');
});
// rename column 'videoRotation' to 'rotationDegrees'
await db.transaction((txn) async {
const newMetadataTable = '${metadataTable}TEMP';
await db.execute('CREATE TABLE $newMetadataTable('
await db.execute('CREATE TABLE $newMetadataTable ('
'contentId INTEGER PRIMARY KEY'
', mimeType TEXT'
', dateMillis INTEGER'
@ -97,13 +112,12 @@ class LocalMediaDbUpgrader {
', latitude REAL'
', longitude REAL'
')');
await db.rawInsert('INSERT INTO $newMetadataTable(contentId,mimeType,dateMillis,isAnimated,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude)'
await db.rawInsert('INSERT INTO $newMetadataTable (contentId,mimeType,dateMillis,isAnimated,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude)'
' SELECT contentId,mimeType,dateMillis,isAnimated,videoRotation,xmpSubjects,xmpTitleDescription,latitude,longitude'
' FROM $metadataTable;');
await db.rawInsert('UPDATE $newMetadataTable SET rotationDegrees = NULL WHERE rotationDegrees = 0;');
await db.execute('DROP TABLE $metadataTable;');
await db.execute('ALTER TABLE $newMetadataTable RENAME TO $metadataTable;');
});
// new column 'isFlipped'
await db.execute('ALTER TABLE $metadataTable ADD COLUMN isFlipped INTEGER;');
@ -111,10 +125,10 @@ class LocalMediaDbUpgrader {
static Future<void> _upgradeFrom2(Database db) async {
debugPrint('upgrading DB from v2');
// merge columns 'isAnimated' and 'isFlipped' into 'flags'
await db.transaction((txn) async {
const newMetadataTable = '${metadataTable}TEMP';
await db.execute('CREATE TABLE $newMetadataTable('
await db.execute('CREATE TABLE $newMetadataTable ('
'contentId INTEGER PRIMARY KEY'
', mimeType TEXT'
', dateMillis INTEGER'
@ -125,17 +139,16 @@ class LocalMediaDbUpgrader {
', latitude REAL'
', longitude REAL'
')');
await db.rawInsert('INSERT INTO $newMetadataTable(contentId,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude)'
await db.rawInsert('INSERT INTO $newMetadataTable (contentId,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude)'
' SELECT contentId,mimeType,dateMillis,ifnull(isAnimated,0)+ifnull(isFlipped,0)*2,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude'
' FROM $metadataTable;');
await db.execute('DROP TABLE $metadataTable;');
await db.execute('ALTER TABLE $newMetadataTable RENAME TO $metadataTable;');
});
}
static Future<void> _upgradeFrom3(Database db) async {
debugPrint('upgrading DB from v3');
await db.execute('CREATE TABLE $coverTable('
await db.execute('CREATE TABLE $coverTable ('
'filter TEXT PRIMARY KEY'
', contentId INTEGER'
')');
@ -143,7 +156,7 @@ class LocalMediaDbUpgrader {
static Future<void> _upgradeFrom4(Database db) async {
debugPrint('upgrading DB from v4');
await db.execute('CREATE TABLE $videoPlaybackTable('
await db.execute('CREATE TABLE $videoPlaybackTable ('
'contentId INTEGER PRIMARY KEY'
', resumeTimeMillis INTEGER'
')');
@ -160,7 +173,7 @@ class LocalMediaDbUpgrader {
// new column `trashed`
await db.transaction((txn) async {
const newEntryTable = '${entryTable}TEMP';
await db.execute('CREATE TABLE $newEntryTable('
await db.execute('CREATE TABLE $newEntryTable ('
'id INTEGER PRIMARY KEY'
', contentId INTEGER'
', uri TEXT'
@ -176,7 +189,7 @@ class LocalMediaDbUpgrader {
', durationMillis INTEGER'
', trashed INTEGER DEFAULT 0'
')');
await db.rawInsert('INSERT INTO $newEntryTable(id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis)'
await db.rawInsert('INSERT INTO $newEntryTable (id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis)'
' SELECT contentId,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis'
' FROM $entryTable;');
await db.execute('DROP TABLE $entryTable;');
@ -186,11 +199,11 @@ class LocalMediaDbUpgrader {
// rename column `contentId` to `id`
await db.transaction((txn) async {
const newDateTakenTable = '${dateTakenTable}TEMP';
await db.execute('CREATE TABLE $newDateTakenTable('
await db.execute('CREATE TABLE $newDateTakenTable ('
'id INTEGER PRIMARY KEY'
', dateMillis INTEGER'
')');
await db.rawInsert('INSERT INTO $newDateTakenTable(id,dateMillis)'
await db.rawInsert('INSERT INTO $newDateTakenTable (id,dateMillis)'
' SELECT contentId,dateMillis'
' FROM $dateTakenTable;');
await db.execute('DROP TABLE $dateTakenTable;');
@ -200,7 +213,7 @@ class LocalMediaDbUpgrader {
// rename column `contentId` to `id`
await db.transaction((txn) async {
const newMetadataTable = '${metadataTable}TEMP';
await db.execute('CREATE TABLE $newMetadataTable('
await db.execute('CREATE TABLE $newMetadataTable ('
'id INTEGER PRIMARY KEY'
', mimeType TEXT'
', dateMillis INTEGER'
@ -212,7 +225,7 @@ class LocalMediaDbUpgrader {
', longitude REAL'
', rating INTEGER'
')');
await db.rawInsert('INSERT INTO $newMetadataTable(id,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude,rating)'
await db.rawInsert('INSERT INTO $newMetadataTable (id,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude,rating)'
' SELECT contentId,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude,rating'
' FROM $metadataTable;');
await db.execute('DROP TABLE $metadataTable;');
@ -222,7 +235,7 @@ class LocalMediaDbUpgrader {
// rename column `contentId` to `id`
await db.transaction((txn) async {
const newAddressTable = '${addressTable}TEMP';
await db.execute('CREATE TABLE $newAddressTable('
await db.execute('CREATE TABLE $newAddressTable ('
'id INTEGER PRIMARY KEY'
', addressLine TEXT'
', countryCode TEXT'
@ -230,7 +243,7 @@ class LocalMediaDbUpgrader {
', adminArea TEXT'
', locality TEXT'
')');
await db.rawInsert('INSERT INTO $newAddressTable(id,addressLine,countryCode,countryName,adminArea,locality)'
await db.rawInsert('INSERT INTO $newAddressTable (id,addressLine,countryCode,countryName,adminArea,locality)'
' SELECT contentId,addressLine,countryCode,countryName,adminArea,locality'
' FROM $addressTable;');
await db.execute('DROP TABLE $addressTable;');
@ -240,11 +253,11 @@ class LocalMediaDbUpgrader {
// rename column `contentId` to `id`
await db.transaction((txn) async {
const newVideoPlaybackTable = '${videoPlaybackTable}TEMP';
await db.execute('CREATE TABLE $newVideoPlaybackTable('
await db.execute('CREATE TABLE $newVideoPlaybackTable ('
'id INTEGER PRIMARY KEY'
', resumeTimeMillis INTEGER'
')');
await db.rawInsert('INSERT INTO $newVideoPlaybackTable(id,resumeTimeMillis)'
await db.rawInsert('INSERT INTO $newVideoPlaybackTable (id,resumeTimeMillis)'
' SELECT contentId,resumeTimeMillis'
' FROM $videoPlaybackTable;');
await db.execute('DROP TABLE $videoPlaybackTable;');
@ -255,10 +268,10 @@ class LocalMediaDbUpgrader {
// remove column `path`
await db.transaction((txn) async {
const newFavouriteTable = '${favouriteTable}TEMP';
await db.execute('CREATE TABLE $newFavouriteTable('
await db.execute('CREATE TABLE $newFavouriteTable ('
'id INTEGER PRIMARY KEY'
')');
await db.rawInsert('INSERT INTO $newFavouriteTable(id)'
await db.rawInsert('INSERT INTO $newFavouriteTable (id)'
' SELECT contentId'
' FROM $favouriteTable;');
await db.execute('DROP TABLE $favouriteTable;');
@ -268,11 +281,11 @@ class LocalMediaDbUpgrader {
// rename column `contentId` to `entryId`
await db.transaction((txn) async {
const newCoverTable = '${coverTable}TEMP';
await db.execute('CREATE TABLE $newCoverTable('
await db.execute('CREATE TABLE $newCoverTable ('
'filter TEXT PRIMARY KEY'
', entryId INTEGER'
')');
await db.rawInsert('INSERT INTO $newCoverTable(filter,entryId)'
await db.rawInsert('INSERT INTO $newCoverTable (filter,entryId)'
' SELECT filter,contentId'
' FROM $coverTable;');
await db.execute('DROP TABLE $coverTable;');
@ -280,7 +293,7 @@ class LocalMediaDbUpgrader {
});
// new table
await db.execute('CREATE TABLE $trashTable('
await db.execute('CREATE TABLE $trashTable ('
'id INTEGER PRIMARY KEY'
', path TEXT'
', dateMillis INTEGER'
@ -299,7 +312,7 @@ class LocalMediaDbUpgrader {
// new column `dateAddedSecs`
await db.transaction((txn) async {
const newEntryTable = '${entryTable}TEMP';
await db.execute('CREATE TABLE $newEntryTable('
await db.execute('CREATE TABLE $newEntryTable ('
'id INTEGER PRIMARY KEY'
', contentId INTEGER'
', uri TEXT'
@ -316,7 +329,7 @@ class LocalMediaDbUpgrader {
', durationMillis INTEGER'
', trashed INTEGER DEFAULT 0'
')');
await db.rawInsert('INSERT INTO $newEntryTable(id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis,trashed)'
await db.rawInsert('INSERT INTO $newEntryTable (id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis,trashed)'
' SELECT id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis,trashed'
' FROM $entryTable;');
await db.execute('DROP TABLE $entryTable;');
@ -326,7 +339,7 @@ class LocalMediaDbUpgrader {
// rename column `xmpTitleDescription` to `xmpTitle`
await db.transaction((txn) async {
const newMetadataTable = '${metadataTable}TEMP';
await db.execute('CREATE TABLE $newMetadataTable('
await db.execute('CREATE TABLE $newMetadataTable ('
'id INTEGER PRIMARY KEY'
', mimeType TEXT'
', dateMillis INTEGER'
@ -338,7 +351,7 @@ class LocalMediaDbUpgrader {
', longitude REAL'
', rating INTEGER'
')');
await db.rawInsert('INSERT INTO $newMetadataTable(id,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitle,latitude,longitude,rating)'
await db.rawInsert('INSERT INTO $newMetadataTable (id,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitle,latitude,longitude,rating)'
' SELECT id,mimeType,dateMillis,flags,rotationDegrees,xmpSubjects,xmpTitleDescription,latitude,longitude,rating'
' FROM $metadataTable;');
await db.execute('DROP TABLE $metadataTable;');
@ -383,7 +396,7 @@ class LocalMediaDbUpgrader {
await db.execute('ALTER TABLE $entryTable ADD COLUMN origin INTEGER DEFAULT 0;');
await db.execute('CREATE TABLE $vaultTable('
await db.execute('CREATE TABLE $vaultTable ('
'name TEXT PRIMARY KEY'
', autoLock INTEGER'
', useBin INTEGER'
@ -394,7 +407,7 @@ class LocalMediaDbUpgrader {
static Future<void> _upgradeFrom11(Database db) async {
debugPrint('upgrading DB from v11');
await db.execute('CREATE TABLE $dynamicAlbumTable('
await db.execute('CREATE TABLE $dynamicAlbumTable ('
'name TEXT PRIMARY KEY'
', filter TEXT'
')');
@ -423,9 +436,8 @@ class LocalMediaDbUpgrader {
}
// convert `color` column type from value number to JSON string
await db.transaction((txn) async {
const newCoverTable = '${coverTable}TEMP';
await db.execute('CREATE TABLE $newCoverTable('
await db.execute('CREATE TABLE $newCoverTable ('
'filter TEXT PRIMARY KEY'
', entryId INTEGER'
', packageName TEXT'
@ -447,16 +459,15 @@ class LocalMediaDbUpgrader {
await db.execute('DROP TABLE $coverTable;');
await db.execute('ALTER TABLE $newCoverTable RENAME TO $coverTable;');
});
}
static Future<void> _upgradeFrom13(Database db) async {
debugPrint('upgrading DB from v13');
if (db.tableExists(entryTable)) {
// rename column 'dateModifiedSecs' to 'dateModifiedMillis'
await db.transaction((txn) async {
const newEntryTable = '${entryTable}TEMP';
await db.execute('CREATE TABLE $newEntryTable('
await db.execute('CREATE TABLE $newEntryTable ('
'id INTEGER PRIMARY KEY'
', contentId INTEGER'
', uri TEXT'
@ -474,30 +485,24 @@ class LocalMediaDbUpgrader {
', trashed INTEGER DEFAULT 0'
', origin INTEGER DEFAULT 0'
')');
await db.rawInsert('INSERT INTO $newEntryTable(id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateAddedSecs,dateModifiedMillis,sourceDateTakenMillis,durationMillis,trashed,origin)'
await db.rawInsert('INSERT INTO $newEntryTable (id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateAddedSecs,dateModifiedMillis,sourceDateTakenMillis,durationMillis,trashed,origin)'
' SELECT id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateAddedSecs,dateModifiedSecs*1000,sourceDateTakenMillis,durationMillis,trashed,origin'
' FROM $entryTable;');
await db.execute('DROP TABLE $entryTable;');
await db.execute('ALTER TABLE $newEntryTable RENAME TO $entryTable;');
});
}
}
static Future<void> _upgradeFrom14(Database db) async {
debugPrint('upgrading DB from v14');
// no schema changes, but v1.12.4 may have corrupted the DB, so we sanitize it
// clear rebuildable tables
await db.delete(dateTakenTable, where: '1');
await db.delete(metadataTable, where: '1');
await db.delete(addressTable, 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]);
// no schema changes, but v1.12.4 may have corrupted the DB,
// so we clear rebuildable tables
final tables = [dateTakenTable, metadataTable, addressTable, trashTable, videoPlaybackTable];
await Future.forEach(tables, (table) async {
if (db.tableExists(table)) {
await db.delete(table, where: '1');
}
});
}
}

View file

@ -108,8 +108,9 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
return _fromMap(jsonMap);
}
debugPrint('failed to parse filter from json=$jsonString');
} catch (error, stack) {
debugPrint('failed to parse filter from json=$jsonString error=$error\n$stack');
} catch (error) {
// no need for stack
debugPrint('failed to parse filter from json=$jsonString error=$error');
}
return null;
}

View file

@ -75,6 +75,7 @@ class MimeTypes {
static const json = 'application/json';
static const plainText = 'text/plain';
static const sqlite3 = 'application/vnd.sqlite3';
// JB2, JPC, JPX?
static const octetStream = 'application/octet-stream';

View file

@ -53,6 +53,9 @@ abstract class StorageService {
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
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 {
@ -369,4 +372,29 @@ class PlatformStorageService implements StorageService {
}
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;
}
}

View file

@ -36,21 +36,11 @@ class BugReport extends StatefulWidget {
State<BugReport> createState() => _BugReportState();
}
class _BugReportState extends State<BugReport> with FeedbackMixin {
late Future<String> _infoLoader;
class _BugReportState extends State<BugReport> {
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
Widget build(BuildContext context) {
final l10n = context.l10n;
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
return ExpansionPanelList(
expansionCallback: (index, isExpanded) {
@ -66,10 +56,40 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
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),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -109,15 +129,9 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
},
),
_buildStep(3, l10n.aboutBugReportInstruction, l10n.aboutBugReportButton, _goToGithub),
const SizedBox(height: 16),
const SizedBox(height: 8),
],
),
),
isExpanded: _showInstructions,
canTapOnHeader: true,
backgroundColor: Colors.transparent,
),
],
);
}

View file

@ -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/viewer_entry_provider.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_rail.dart';
import 'package:aves/widgets/welcome_page.dart';

View file

@ -626,7 +626,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
void _onQueryFocusRequest() => _queryBarFocusNode.requestFocus();
void _updateStatusBarHeight() {
if (!context.mounted) {
if (!mounted) {
return;
}
_statusBarHeight = MediaQuery.paddingOf(context).top;

View 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);
}
}
},
),
),
],
),
],
),
],
),
),
),
],
),
);
}
}

View file

@ -7,9 +7,9 @@ import 'package:aves/model/app/permissions.dart';
import 'package:aves/model/app_inventory.dart';
import 'package:aves/model/entry/entry.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/filters.dart';
import 'package:aves/model/filters/covered/location.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/settings.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/filter_grids/albums_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/search/search_delegate.dart';
import 'package:aves/widgets/settings/home_widget_settings_page.dart';
@ -68,6 +69,7 @@ class _HomePageState extends State<HomePage> {
String? _initialExplorerPath;
(LatLng, double?)? _initialLocationZoom;
List<String>? _secureUris;
(Object, StackTrace)? _setupError;
static const allowedShortcutRoutes = [
AlbumListPage.routeName,
@ -85,9 +87,17 @@ class _HomePageState extends State<HomePage> {
}
@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 {
try {
final stopwatch = Stopwatch()..start();
if (await windowService.isActivity()) {
// do not check whether permission was granted, because some app stores
@ -262,6 +272,10 @@ class _HomePageState extends State<HomePage> {
await _getRedirectRoute(appMode),
(route) => false,
));
} catch (error, stack) {
debugPrint('failed to setup app with error=$error\n$stack');
_setupError = (error, stack);
}
}
Future<void> _initViewerEssentials() async {

View file

@ -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/places_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/page_nav_tile.dart';
import 'package:aves/widgets/navigation/drawer/tile.dart';

View file

@ -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/places_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:flutter/material.dart';

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:math';
import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/multipage.dart';
import 'package:aves/model/entry/extensions/props.dart';
@ -227,8 +228,9 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
static const _pipRatioMin = Rational(18, 43);
Future<void> updatePictureInPicture(BuildContext context) async {
if (context.mounted) {
if (settings.videoBackgroundMode == VideoBackgroundMode.pip) {
if (!device.supportPictureInPicture) return;
if (context.mounted && settings.videoBackgroundMode == VideoBackgroundMode.pip) {
final playingController = context.read<VideoConductor>().getPlayingController();
if (playingController != null) {
final entrySize = playingController.entry.displaySize;
@ -263,7 +265,6 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
}
}
}
}
debugPrint('Cancelling picture-in-picture');
await Floating().cancelOnLeavePiP();

View file

@ -14,7 +14,7 @@ extension ExtraSwipeAction on SwipeAction {
case SwipeAction.brightness:
return AvesApp.screenBrightness?.application ?? Future.value(1);
case SwipeAction.volume:
return VolumeController().getVolume();
return VolumeController.instance.getVolume();
}
}
@ -23,7 +23,8 @@ extension ExtraSwipeAction on SwipeAction {
case SwipeAction.brightness:
await AvesApp.screenBrightness?.setApplicationScreenBrightness(value);
case SwipeAction.volume:
VolumeController().setVolume(value, showSystemUI: false);
VolumeController.instance.showSystemUI = false;
await VolumeController.instance.setVolume(value);
}
}
}

View file

@ -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/identity/aves_logo.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/scheduler.dart';
import 'package:flutter/services.dart';

View file

@ -37,10 +37,10 @@ packages:
dependency: transitive
description:
name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.7.0"
async:
dependency: transitive
description:
@ -567,10 +567,10 @@ packages:
dependency: transitive
description:
name: google_maps_flutter_web
sha256: a9fd5356d46f54744ced1ebedbbf212f3d2cb71e95d79b1d08690c1ec33dc584
sha256: bbeb93807a34bfeebdb7ace506bd2bc400a3915dc96736254fea721eb264caa0
url: "https://pub.dev"
source: hosted
version: "0.5.10+1"
version: "0.5.11"
gpx:
dependency: "direct main"
description:
@ -791,8 +791,8 @@ packages:
dependency: "direct overridden"
description:
path: media_kit
ref: "4d8c634c28d439384aab40b9d2edff83077f37c9"
resolved-ref: "4d8c634c28d439384aab40b9d2edff83077f37c9"
ref: d2145a50f68394096845915a28874341fbf5b3fe
resolved-ref: d2145a50f68394096845915a28874341fbf5b3fe
url: "https://github.com/media-kit/media-kit.git"
source: git
version: "1.1.11"
@ -808,8 +808,8 @@ packages:
dependency: "direct overridden"
description:
path: media_kit_video
ref: "4d8c634c28d439384aab40b9d2edff83077f37c9"
resolved-ref: "4d8c634c28d439384aab40b9d2edff83077f37c9"
ref: d2145a50f68394096845915a28874341fbf5b3fe
resolved-ref: d2145a50f68394096845915a28874341fbf5b3fe
url: "https://github.com/media-kit/media-kit.git"
source: git
version: "1.2.5"
@ -898,10 +898,10 @@ packages:
dependency: transitive
description:
name: package_config
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.2.0"
package_info_plus:
dependency: "direct main"
description:
@ -1219,18 +1219,18 @@ packages:
dependency: "direct main"
description:
name: screen_brightness
sha256: "99b898dae860ebe55fc872d8e300c6eafff3ee4ccb09301b90adb3f241f29874"
sha256: eca7bd9d2c3c688bcad14855361cab7097839400b6b4a56f62b7ae511c709958
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
screen_brightness_android:
dependency: transitive
description:
name: screen_brightness_android
sha256: ff9141bed547db02233e7dd88f990ab01973a0c8a8c04ddb855c7b072f33409a
sha256: "6ba1b5812f66c64e9e4892be2d36ecd34210f4e0da8bdec6a2ea34f1aa42683e"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
screen_brightness_ios:
dependency: transitive
description:
@ -1673,10 +1673,10 @@ packages:
dependency: "direct main"
description:
name: volume_controller
sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e
sha256: "30863a51338db47fe16f92902b1a6c4ee5e15c9287b46573d7c2eb6be1f197d2"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
version: "3.3.1"
wakelock_plus:
dependency: transitive
description:
@ -1745,10 +1745,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
url: "https://pub.dev"
source: hosted
version: "5.11.0"
version: "5.12.0"
win32_registry:
dependency: transitive
description:
@ -1791,4 +1791,4 @@ packages:
version: "3.1.3"
sdks:
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.29.1"
flutter: ">=3.29.2"

View file

@ -7,13 +7,13 @@ repository: https://github.com/deckerst/aves
# - play changelog: /whatsnew/whatsnew-en-US
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
version: 1.12.6+146
version: 1.12.7+147
publish_to: none
environment:
# this project bundles Flutter SDK via `flutter_wrapper`
# cf https://github.com/passsy/flutter_wrapper
flutter: 3.29.1
flutter: 3.29.2
sdk: ">=3.6.0 <4.0.0" # incoherent dartfmt from 3.7.0
workspace:
- plugins/aves_magnifier
@ -134,12 +134,12 @@ dependency_overrides:
media_kit:
git:
url: https://github.com/media-kit/media-kit.git
ref: 4d8c634c28d439384aab40b9d2edff83077f37c9
ref: d2145a50f68394096845915a28874341fbf5b3fe
path: media_kit
media_kit_video:
git:
url: https://github.com/media-kit/media-kit.git
ref: 4d8c634c28d439384aab40b9d2edff83077f37c9
ref: d2145a50f68394096845915a28874341fbf5b3fe
path: media_kit_video
dev_dependencies:

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
In v1.12.6:
In v1.12.7:
- play more kinds of motion photos
- enjoy the app in Galician
Full changelog available on GitHub