#97 search: recently added filter
This commit is contained in:
parent
0cce0c1e11
commit
503f93ed17
26 changed files with 206 additions and 58 deletions
|
@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- Collection / Info: edit description via Exif / IPTC / XMP
|
- Collection / Info: edit description via Exif / IPTC / XMP
|
||||||
- Info: read XMP from HEIC on Android >=11
|
- Info: read XMP from HEIC on Android >=11
|
||||||
- Collection: support HEIC motion photos on Android >=11
|
- Collection: support HEIC motion photos on Android >=11
|
||||||
|
- Search: `recently added` filter
|
||||||
- Dutch translation (thanks Martijn Fabrie, Koen Koppens)
|
- Dutch translation (thanks Martijn Fabrie, Koen Koppens)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -1171,7 +1171,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
private const val KEY_LATITUDE = "latitude"
|
private const val KEY_LATITUDE = "latitude"
|
||||||
private const val KEY_LONGITUDE = "longitude"
|
private const val KEY_LONGITUDE = "longitude"
|
||||||
private const val KEY_XMP_SUBJECTS = "xmpSubjects"
|
private const val KEY_XMP_SUBJECTS = "xmpSubjects"
|
||||||
private const val KEY_XMP_TITLE = "xmpTitleDescription"
|
private const val KEY_XMP_TITLE = "xmpTitle"
|
||||||
private const val KEY_RATING = "rating"
|
private const val KEY_RATING = "rating"
|
||||||
|
|
||||||
private const val MASK_IS_ANIMATED = 1 shl 0
|
private const val MASK_IS_ANIMATED = 1 shl 0
|
||||||
|
|
|
@ -125,6 +125,7 @@
|
||||||
"filterLocationEmptyLabel": "Unlocated",
|
"filterLocationEmptyLabel": "Unlocated",
|
||||||
"filterTagEmptyLabel": "Untagged",
|
"filterTagEmptyLabel": "Untagged",
|
||||||
"filterOnThisDayLabel": "On this day",
|
"filterOnThisDayLabel": "On this day",
|
||||||
|
"filterRecentlyAddedLabel": "Recently added",
|
||||||
"filterRatingUnratedLabel": "Unrated",
|
"filterRatingUnratedLabel": "Unrated",
|
||||||
"filterRatingRejectedLabel": "Rejected",
|
"filterRatingRejectedLabel": "Rejected",
|
||||||
"filterTypeAnimatedLabel": "Animated",
|
"filterTypeAnimatedLabel": "Animated",
|
||||||
|
|
|
@ -10,6 +10,8 @@ import 'package:aves/model/video_playback.dart';
|
||||||
abstract class MetadataDb {
|
abstract class MetadataDb {
|
||||||
int get nextId;
|
int get nextId;
|
||||||
|
|
||||||
|
int get timestampSecs;
|
||||||
|
|
||||||
Future<void> init();
|
Future<void> init();
|
||||||
|
|
||||||
Future<int> dbFileSize();
|
Future<int> dbFileSize();
|
||||||
|
|
|
@ -34,6 +34,9 @@ class SqfliteMetadataDb implements MetadataDb {
|
||||||
@override
|
@override
|
||||||
int get nextId => ++_lastId;
|
int get nextId => ++_lastId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get timestampSecs => DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_db = await openDatabase(
|
_db = await openDatabase(
|
||||||
|
@ -50,6 +53,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
||||||
', sourceRotationDegrees INTEGER'
|
', sourceRotationDegrees INTEGER'
|
||||||
', sizeBytes INTEGER'
|
', sizeBytes INTEGER'
|
||||||
', title TEXT'
|
', title TEXT'
|
||||||
|
', dateAddedSecs INTEGER DEFAULT (strftime(\'%s\',\'now\'))'
|
||||||
', dateModifiedSecs INTEGER'
|
', dateModifiedSecs INTEGER'
|
||||||
', sourceDateTakenMillis INTEGER'
|
', sourceDateTakenMillis INTEGER'
|
||||||
', durationMillis INTEGER'
|
', durationMillis INTEGER'
|
||||||
|
@ -66,7 +70,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
||||||
', flags INTEGER'
|
', flags INTEGER'
|
||||||
', rotationDegrees INTEGER'
|
', rotationDegrees INTEGER'
|
||||||
', xmpSubjects TEXT'
|
', xmpSubjects TEXT'
|
||||||
', xmpTitleDescription TEXT'
|
', xmpTitle TEXT'
|
||||||
', latitude REAL'
|
', latitude REAL'
|
||||||
', longitude REAL'
|
', longitude REAL'
|
||||||
', rating INTEGER'
|
', rating INTEGER'
|
||||||
|
@ -99,7 +103,7 @@ class SqfliteMetadataDb implements MetadataDb {
|
||||||
')');
|
')');
|
||||||
},
|
},
|
||||||
onUpgrade: MetadataDbUpgrader.upgradeDb,
|
onUpgrade: MetadataDbUpgrader.upgradeDb,
|
||||||
version: 8,
|
version: 9,
|
||||||
);
|
);
|
||||||
|
|
||||||
final maxIdRows = await _db.rawQuery('SELECT max(id) AS maxId FROM $entryTable');
|
final maxIdRows = await _db.rawQuery('SELECT max(id) AS maxId FROM $entryTable');
|
||||||
|
|
|
@ -38,6 +38,9 @@ class MetadataDbUpgrader {
|
||||||
case 7:
|
case 7:
|
||||||
await _upgradeFrom7(db);
|
await _upgradeFrom7(db);
|
||||||
break;
|
break;
|
||||||
|
case 8:
|
||||||
|
await _upgradeFrom8(db);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
oldVersion++;
|
oldVersion++;
|
||||||
}
|
}
|
||||||
|
@ -278,4 +281,57 @@ class MetadataDbUpgrader {
|
||||||
await db.execute('ALTER TABLE $coverTable ADD COLUMN packageName TEXT;');
|
await db.execute('ALTER TABLE $coverTable ADD COLUMN packageName TEXT;');
|
||||||
await db.execute('ALTER TABLE $coverTable ADD COLUMN color INTEGER;');
|
await db.execute('ALTER TABLE $coverTable ADD COLUMN color INTEGER;');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> _upgradeFrom8(Database db) async {
|
||||||
|
debugPrint('upgrading DB from v8');
|
||||||
|
|
||||||
|
// new column `dateAddedSecs`
|
||||||
|
await db.transaction((txn) async {
|
||||||
|
const newEntryTable = '${entryTable}TEMP';
|
||||||
|
await db.execute('CREATE TABLE $newEntryTable('
|
||||||
|
'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\'))'
|
||||||
|
', dateModifiedSecs INTEGER'
|
||||||
|
', sourceDateTakenMillis INTEGER'
|
||||||
|
', 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)'
|
||||||
|
' SELECT id,contentId,uri,path,sourceMimeType,width,height,sourceRotationDegrees,sizeBytes,title,dateModifiedSecs,sourceDateTakenMillis,durationMillis,trashed'
|
||||||
|
' FROM $entryTable;');
|
||||||
|
await db.execute('DROP TABLE $entryTable;');
|
||||||
|
await db.execute('ALTER TABLE $newEntryTable RENAME TO $entryTable;');
|
||||||
|
});
|
||||||
|
|
||||||
|
// rename column `xmpTitleDescription` to `xmpTitle`
|
||||||
|
await db.transaction((txn) async {
|
||||||
|
const newMetadataTable = '${metadataTable}TEMP';
|
||||||
|
await db.execute('CREATE TABLE $newMetadataTable('
|
||||||
|
'id INTEGER PRIMARY KEY'
|
||||||
|
', mimeType TEXT'
|
||||||
|
', dateMillis INTEGER'
|
||||||
|
', flags INTEGER'
|
||||||
|
', rotationDegrees INTEGER'
|
||||||
|
', xmpSubjects TEXT'
|
||||||
|
', xmpTitle TEXT'
|
||||||
|
', latitude REAL'
|
||||||
|
', longitude REAL'
|
||||||
|
', rating INTEGER'
|
||||||
|
')');
|
||||||
|
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;');
|
||||||
|
await db.execute('ALTER TABLE $newMetadataTable RENAME TO $metadataTable;');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ class AvesEntry {
|
||||||
int? pageId, contentId;
|
int? pageId, contentId;
|
||||||
final String sourceMimeType;
|
final String sourceMimeType;
|
||||||
int width, height, sourceRotationDegrees;
|
int width, height, sourceRotationDegrees;
|
||||||
int? sizeBytes, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis;
|
int? sizeBytes, dateAddedSecs, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis;
|
||||||
bool trashed;
|
bool trashed;
|
||||||
|
|
||||||
int? _catalogDateMillis;
|
int? _catalogDateMillis;
|
||||||
|
@ -61,6 +61,7 @@ class AvesEntry {
|
||||||
required this.sourceRotationDegrees,
|
required this.sourceRotationDegrees,
|
||||||
required this.sizeBytes,
|
required this.sizeBytes,
|
||||||
required String? sourceTitle,
|
required String? sourceTitle,
|
||||||
|
required this.dateAddedSecs,
|
||||||
required int? dateModifiedSecs,
|
required int? dateModifiedSecs,
|
||||||
required this.sourceDateTakenMillis,
|
required this.sourceDateTakenMillis,
|
||||||
required int? durationMillis,
|
required int? durationMillis,
|
||||||
|
@ -83,6 +84,7 @@ class AvesEntry {
|
||||||
String? path,
|
String? path,
|
||||||
int? contentId,
|
int? contentId,
|
||||||
String? title,
|
String? title,
|
||||||
|
int? dateAddedSecs,
|
||||||
int? dateModifiedSecs,
|
int? dateModifiedSecs,
|
||||||
List<AvesEntry>? burstEntries,
|
List<AvesEntry>? burstEntries,
|
||||||
}) {
|
}) {
|
||||||
|
@ -99,6 +101,7 @@ class AvesEntry {
|
||||||
sourceRotationDegrees: sourceRotationDegrees,
|
sourceRotationDegrees: sourceRotationDegrees,
|
||||||
sizeBytes: sizeBytes,
|
sizeBytes: sizeBytes,
|
||||||
sourceTitle: title ?? sourceTitle,
|
sourceTitle: title ?? sourceTitle,
|
||||||
|
dateAddedSecs: dateAddedSecs ?? this.dateAddedSecs,
|
||||||
dateModifiedSecs: dateModifiedSecs ?? this.dateModifiedSecs,
|
dateModifiedSecs: dateModifiedSecs ?? this.dateModifiedSecs,
|
||||||
sourceDateTakenMillis: sourceDateTakenMillis,
|
sourceDateTakenMillis: sourceDateTakenMillis,
|
||||||
durationMillis: durationMillis,
|
durationMillis: durationMillis,
|
||||||
|
@ -126,6 +129,7 @@ class AvesEntry {
|
||||||
sourceRotationDegrees: map['sourceRotationDegrees'] as int? ?? 0,
|
sourceRotationDegrees: map['sourceRotationDegrees'] as int? ?? 0,
|
||||||
sizeBytes: map['sizeBytes'] as int?,
|
sizeBytes: map['sizeBytes'] as int?,
|
||||||
sourceTitle: map['title'] as String?,
|
sourceTitle: map['title'] as String?,
|
||||||
|
dateAddedSecs: map['dateAddedSecs'] as int?,
|
||||||
dateModifiedSecs: map['dateModifiedSecs'] as int?,
|
dateModifiedSecs: map['dateModifiedSecs'] as int?,
|
||||||
sourceDateTakenMillis: map['sourceDateTakenMillis'] as int?,
|
sourceDateTakenMillis: map['sourceDateTakenMillis'] as int?,
|
||||||
durationMillis: map['durationMillis'] as int?,
|
durationMillis: map['durationMillis'] as int?,
|
||||||
|
@ -153,6 +157,23 @@ class AvesEntry {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toPlatformEntryMap() {
|
||||||
|
return {
|
||||||
|
'uri': uri,
|
||||||
|
'path': path,
|
||||||
|
'pageId': pageId,
|
||||||
|
'mimeType': mimeType,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'rotationDegrees': rotationDegrees,
|
||||||
|
'isFlipped': isFlipped,
|
||||||
|
'dateModifiedSecs': dateModifiedSecs,
|
||||||
|
'sizeBytes': sizeBytes,
|
||||||
|
'trashed': trashed,
|
||||||
|
'trashPath': trashDetails?.path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
imageChangeNotifier.dispose();
|
imageChangeNotifier.dispose();
|
||||||
metadataChangeNotifier.dispose();
|
metadataChangeNotifier.dispose();
|
||||||
|
@ -464,7 +485,7 @@ class AvesEntry {
|
||||||
String? _bestTitle;
|
String? _bestTitle;
|
||||||
|
|
||||||
String? get bestTitle {
|
String? get bestTitle {
|
||||||
_bestTitle ??= _catalogMetadata?.xmpTitleDescription?.isNotEmpty == true ? _catalogMetadata!.xmpTitleDescription : (filenameWithoutExtension ?? sourceTitle);
|
_bestTitle ??= _catalogMetadata?.xmpTitle?.isNotEmpty == true ? _catalogMetadata!.xmpTitle : (filenameWithoutExtension ?? sourceTitle);
|
||||||
return _bestTitle;
|
return _bestTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/path.dart';
|
import 'package:aves/model/filters/path.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/rating.dart';
|
import 'package:aves/model/filters/rating.dart';
|
||||||
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/filters/trash.dart';
|
import 'package:aves/model/filters/trash.dart';
|
||||||
import 'package:aves/model/filters/type.dart';
|
import 'package:aves/model/filters/type.dart';
|
||||||
|
@ -68,6 +69,8 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
||||||
return QueryFilter.fromMap(jsonMap);
|
return QueryFilter.fromMap(jsonMap);
|
||||||
case RatingFilter.type:
|
case RatingFilter.type:
|
||||||
return RatingFilter.fromMap(jsonMap);
|
return RatingFilter.fromMap(jsonMap);
|
||||||
|
case RecentlyAddedFilter.type:
|
||||||
|
return RecentlyAddedFilter.instance;
|
||||||
case TagFilter.type:
|
case TagFilter.type:
|
||||||
return TagFilter.fromMap(jsonMap);
|
return TagFilter.fromMap(jsonMap);
|
||||||
case TypeFilter.type:
|
case TypeFilter.type:
|
||||||
|
|
48
lib/model/filters/recent.dart
Normal file
48
lib/model/filters/recent.dart
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RecentlyAddedFilter extends CollectionFilter {
|
||||||
|
static const type = 'recently_added';
|
||||||
|
|
||||||
|
static final instance = RecentlyAddedFilter._private();
|
||||||
|
|
||||||
|
static late int nowSecs;
|
||||||
|
|
||||||
|
static void updateNow() {
|
||||||
|
nowSecs = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _dayInSecs = 24 * 60 * 60;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
|
||||||
|
RecentlyAddedFilter._private() {
|
||||||
|
updateNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
'type': type,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
EntryFilter get test => (entry) => (nowSecs - (entry.dateAddedSecs ?? 0)) < _dayInSecs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get universalLabel => type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getLabel(BuildContext context) => context.l10n.filterRecentlyAddedLabel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.recent, size: size);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get category => type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get key => type;
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ class CatalogMetadata {
|
||||||
final bool isAnimated, isGeotiff, is360, isMultiPage, isMotionPhoto;
|
final bool isAnimated, isGeotiff, is360, isMultiPage, isMotionPhoto;
|
||||||
bool isFlipped;
|
bool isFlipped;
|
||||||
int? rotationDegrees;
|
int? rotationDegrees;
|
||||||
final String? mimeType, xmpSubjects, xmpTitleDescription;
|
final String? mimeType, xmpSubjects, xmpTitle;
|
||||||
double? latitude, longitude;
|
double? latitude, longitude;
|
||||||
Address? address;
|
Address? address;
|
||||||
int rating;
|
int rating;
|
||||||
|
@ -32,7 +32,7 @@ class CatalogMetadata {
|
||||||
this.isMotionPhoto = false,
|
this.isMotionPhoto = false,
|
||||||
this.rotationDegrees,
|
this.rotationDegrees,
|
||||||
this.xmpSubjects,
|
this.xmpSubjects,
|
||||||
this.xmpTitleDescription,
|
this.xmpTitle,
|
||||||
double? latitude,
|
double? latitude,
|
||||||
double? longitude,
|
double? longitude,
|
||||||
this.rating = 0,
|
this.rating = 0,
|
||||||
|
@ -72,7 +72,7 @@ class CatalogMetadata {
|
||||||
isMotionPhoto: isMotionPhoto,
|
isMotionPhoto: isMotionPhoto,
|
||||||
rotationDegrees: rotationDegrees ?? this.rotationDegrees,
|
rotationDegrees: rotationDegrees ?? this.rotationDegrees,
|
||||||
xmpSubjects: xmpSubjects,
|
xmpSubjects: xmpSubjects,
|
||||||
xmpTitleDescription: xmpTitleDescription,
|
xmpTitle: xmpTitle,
|
||||||
latitude: latitude ?? this.latitude,
|
latitude: latitude ?? this.latitude,
|
||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
rating: rating,
|
rating: rating,
|
||||||
|
@ -94,7 +94,7 @@ class CatalogMetadata {
|
||||||
// `rotationDegrees` should default to `sourceRotationDegrees`, not 0
|
// `rotationDegrees` should default to `sourceRotationDegrees`, not 0
|
||||||
rotationDegrees: map['rotationDegrees'],
|
rotationDegrees: map['rotationDegrees'],
|
||||||
xmpSubjects: map['xmpSubjects'] ?? '',
|
xmpSubjects: map['xmpSubjects'] ?? '',
|
||||||
xmpTitleDescription: map['xmpTitleDescription'] ?? '',
|
xmpTitle: map['xmpTitle'] ?? '',
|
||||||
latitude: map['latitude'],
|
latitude: map['latitude'],
|
||||||
longitude: map['longitude'],
|
longitude: map['longitude'],
|
||||||
rating: map['rating'] ?? 0,
|
rating: map['rating'] ?? 0,
|
||||||
|
@ -108,12 +108,12 @@ class CatalogMetadata {
|
||||||
'flags': (isAnimated ? _isAnimatedMask : 0) | (isFlipped ? _isFlippedMask : 0) | (isGeotiff ? _isGeotiffMask : 0) | (is360 ? _is360Mask : 0) | (isMultiPage ? _isMultiPageMask : 0) | (isMotionPhoto ? _isMotionPhotoMask : 0),
|
'flags': (isAnimated ? _isAnimatedMask : 0) | (isFlipped ? _isFlippedMask : 0) | (isGeotiff ? _isGeotiffMask : 0) | (is360 ? _is360Mask : 0) | (isMultiPage ? _isMultiPageMask : 0) | (isMotionPhoto ? _isMotionPhotoMask : 0),
|
||||||
'rotationDegrees': rotationDegrees,
|
'rotationDegrees': rotationDegrees,
|
||||||
'xmpSubjects': xmpSubjects,
|
'xmpSubjects': xmpSubjects,
|
||||||
'xmpTitleDescription': xmpTitleDescription,
|
'xmpTitle': xmpTitle,
|
||||||
'latitude': latitude,
|
'latitude': latitude,
|
||||||
'longitude': longitude,
|
'longitude': longitude,
|
||||||
'rating': rating,
|
'rating': rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '$runtimeType#${shortHash(this)}{id=$id, mimeType=$mimeType, dateMillis=$dateMillis, isAnimated=$isAnimated, isFlipped=$isFlipped, isGeotiff=$isGeotiff, is360=$is360, isMultiPage=$isMultiPage, isMotionPhoto=$isMotionPhoto, rotationDegrees=$rotationDegrees, xmpSubjects=$xmpSubjects, xmpTitleDescription=$xmpTitleDescription, latitude=$latitude, longitude=$longitude, rating=$rating}';
|
String toString() => '$runtimeType#${shortHash(this)}{id=$id, mimeType=$mimeType, dateMillis=$dateMillis, isAnimated=$isAnimated, isFlipped=$isFlipped, isGeotiff=$isGeotiff, is360=$is360, isMultiPage=$isMultiPage, isMotionPhoto=$isMotionPhoto, rotationDegrees=$rotationDegrees, xmpSubjects=$xmpSubjects, xmpTitle=$xmpTitle, latitude=$latitude, longitude=$longitude, rating=$rating}';
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,7 @@ class MultiPageInfo {
|
||||||
sourceRotationDegrees: pageInfo.rotationDegrees ?? mainEntry.sourceRotationDegrees,
|
sourceRotationDegrees: pageInfo.rotationDegrees ?? mainEntry.sourceRotationDegrees,
|
||||||
sizeBytes: mainEntry.sizeBytes,
|
sizeBytes: mainEntry.sizeBytes,
|
||||||
sourceTitle: mainEntry.sourceTitle,
|
sourceTitle: mainEntry.sourceTitle,
|
||||||
|
dateAddedSecs: mainEntry.dateAddedSecs,
|
||||||
dateModifiedSecs: mainEntry.dateModifiedSecs,
|
dateModifiedSecs: mainEntry.dateModifiedSecs,
|
||||||
sourceDateTakenMillis: mainEntry.sourceDateTakenMillis,
|
sourceDateTakenMillis: mainEntry.sourceDateTakenMillis,
|
||||||
durationMillis: pageInfo.durationMillis ?? mainEntry.durationMillis,
|
durationMillis: pageInfo.durationMillis ?? mainEntry.durationMillis,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:aves/model/actions/entry_actions.dart';
|
||||||
import 'package:aves/model/actions/entry_set_actions.dart';
|
import 'package:aves/model/actions/entry_set_actions.dart';
|
||||||
import 'package:aves/model/filters/favourite.dart';
|
import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/naming_pattern.dart';
|
import 'package:aves/model/naming_pattern.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums.dart';
|
||||||
|
@ -41,6 +42,7 @@ class SettingsDefaults {
|
||||||
null,
|
null,
|
||||||
MimeFilter.video,
|
MimeFilter.video,
|
||||||
FavouriteFilter.instance,
|
FavouriteFilter.instance,
|
||||||
|
RecentlyAddedFilter.instance,
|
||||||
];
|
];
|
||||||
static const drawerPageBookmarks = [
|
static const drawerPageBookmarks = [
|
||||||
AlbumListPage.routeName,
|
AlbumListPage.routeName,
|
||||||
|
|
|
@ -294,6 +294,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
||||||
contentId: newFields['contentId'] as int?,
|
contentId: newFields['contentId'] as int?,
|
||||||
// title can change when moved files are automatically renamed to avoid conflict
|
// title can change when moved files are automatically renamed to avoid conflict
|
||||||
title: newFields['title'] as String?,
|
title: newFields['title'] as String?,
|
||||||
|
dateAddedSecs: metadataDb.timestampSecs,
|
||||||
dateModifiedSecs: newFields['dateModifiedSecs'] as int?,
|
dateModifiedSecs: newFields['dateModifiedSecs'] as int?,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -158,7 +158,14 @@ class MediaStoreSource extends CollectionSource {
|
||||||
// when discovering modified entry with known content ID,
|
// when discovering modified entry with known content ID,
|
||||||
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
||||||
final contentId = entry.contentId;
|
final contentId = entry.contentId;
|
||||||
entry.id = (knownContentIds.contains(contentId) ? knownLiveEntries.firstWhereOrNull((entry) => entry.contentId == contentId)?.id : null) ?? metadataDb.nextId;
|
final existingEntry = knownContentIds.contains(contentId) ? knownLiveEntries.firstWhereOrNull((entry) => entry.contentId == contentId) : null;
|
||||||
|
if (existingEntry != null) {
|
||||||
|
entry.id = existingEntry.id;
|
||||||
|
entry.dateAddedSecs = existingEntry.dateAddedSecs;
|
||||||
|
} else {
|
||||||
|
entry.id = metadataDb.nextId;
|
||||||
|
entry.dateAddedSecs = metadataDb.timestampSecs;
|
||||||
|
}
|
||||||
|
|
||||||
pendingNewEntries.add(entry);
|
pendingNewEntries.add(entry);
|
||||||
if (pendingNewEntries.length >= refreshCount) {
|
if (pendingNewEntries.length >= refreshCount) {
|
||||||
|
@ -243,7 +250,13 @@ class MediaStoreSource extends CollectionSource {
|
||||||
final newPath = sourceEntry.path;
|
final newPath = sourceEntry.path;
|
||||||
final volume = newPath != null ? androidFileUtils.getStorageVolume(newPath) : null;
|
final volume = newPath != null ? androidFileUtils.getStorageVolume(newPath) : null;
|
||||||
if (volume != null) {
|
if (volume != null) {
|
||||||
sourceEntry.id = existingEntry?.id ?? metadataDb.nextId;
|
if (existingEntry != null) {
|
||||||
|
sourceEntry.id = existingEntry.id;
|
||||||
|
sourceEntry.dateAddedSecs = existingEntry.dateAddedSecs;
|
||||||
|
} else {
|
||||||
|
sourceEntry.id = metadataDb.nextId;
|
||||||
|
sourceEntry.dateAddedSecs = metadataDb.timestampSecs;
|
||||||
|
}
|
||||||
newEntries.add(sourceEntry);
|
newEntries.add(sourceEntry);
|
||||||
final existingDirectory = existingEntry?.directory;
|
final existingDirectory = existingEntry?.directory;
|
||||||
if (existingDirectory != null) {
|
if (existingDirectory != null) {
|
||||||
|
|
|
@ -52,23 +52,6 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
static const _platform = MethodChannel('deckers.thibault/aves/media_edit');
|
static const _platform = MethodChannel('deckers.thibault/aves/media_edit');
|
||||||
static final _opStream = StreamsChannel('deckers.thibault/aves/media_op_stream');
|
static final _opStream = StreamsChannel('deckers.thibault/aves/media_op_stream');
|
||||||
|
|
||||||
static Map<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
|
||||||
return {
|
|
||||||
'uri': entry.uri,
|
|
||||||
'path': entry.path,
|
|
||||||
'pageId': entry.pageId,
|
|
||||||
'mimeType': entry.mimeType,
|
|
||||||
'width': entry.width,
|
|
||||||
'height': entry.height,
|
|
||||||
'rotationDegrees': entry.rotationDegrees,
|
|
||||||
'isFlipped': entry.isFlipped,
|
|
||||||
'dateModifiedSecs': entry.dateModifiedSecs,
|
|
||||||
'sizeBytes': entry.sizeBytes,
|
|
||||||
'trashed': entry.trashed,
|
|
||||||
'trashPath': entry.trashDetails?.path,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get newOpId => DateTime.now().millisecondsSinceEpoch.toString();
|
String get newOpId => DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
|
||||||
|
@ -93,7 +76,7 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'delete',
|
'op': 'delete',
|
||||||
'id': opId,
|
'id': opId,
|
||||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
'entries': entries.map((entry) => entry.toPlatformEntryMap()).toList(),
|
||||||
})
|
})
|
||||||
.where((event) => event is Map)
|
.where((event) => event is Map)
|
||||||
.map((event) => ImageOpEvent.fromMap(event as Map));
|
.map((event) => ImageOpEvent.fromMap(event as Map));
|
||||||
|
@ -115,7 +98,7 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'move',
|
'op': 'move',
|
||||||
'id': opId,
|
'id': opId,
|
||||||
'entriesByDestination': entriesByDestination.map((destination, entries) => MapEntry(destination, entries.map(_toPlatformEntryMap).toList())),
|
'entriesByDestination': entriesByDestination.map((destination, entries) => MapEntry(destination, entries.map((entry) => entry.toPlatformEntryMap()).toList())),
|
||||||
'copy': copy,
|
'copy': copy,
|
||||||
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
||||||
})
|
})
|
||||||
|
@ -138,7 +121,7 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
return _opStream
|
return _opStream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'export',
|
'op': 'export',
|
||||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
'entries': entries.map((entry) => entry.toPlatformEntryMap()).toList(),
|
||||||
'mimeType': options.mimeType,
|
'mimeType': options.mimeType,
|
||||||
'width': options.width,
|
'width': options.width,
|
||||||
'height': options.height,
|
'height': options.height,
|
||||||
|
@ -163,7 +146,7 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'rename',
|
'op': 'rename',
|
||||||
'id': opId,
|
'id': opId,
|
||||||
'entriesToNewName': entriesToNewName.map((key, value) => MapEntry(_toPlatformEntryMap(key), value)),
|
'entriesToNewName': entriesToNewName.map((entry, name) => MapEntry(entry.toPlatformEntryMap(), name)),
|
||||||
})
|
})
|
||||||
.where((event) => event is Map)
|
.where((event) => event is Map)
|
||||||
.map((event) => MoveOpEvent.fromMap(event as Map));
|
.map((event) => MoveOpEvent.fromMap(event as Map));
|
||||||
|
|
|
@ -25,27 +25,12 @@ abstract class MetadataEditService {
|
||||||
class PlatformMetadataEditService implements MetadataEditService {
|
class PlatformMetadataEditService implements MetadataEditService {
|
||||||
static const _platform = MethodChannel('deckers.thibault/aves/metadata_edit');
|
static const _platform = MethodChannel('deckers.thibault/aves/metadata_edit');
|
||||||
|
|
||||||
static Map<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
|
||||||
return {
|
|
||||||
'uri': entry.uri,
|
|
||||||
'path': entry.path,
|
|
||||||
'pageId': entry.pageId,
|
|
||||||
'mimeType': entry.mimeType,
|
|
||||||
'width': entry.width,
|
|
||||||
'height': entry.height,
|
|
||||||
'rotationDegrees': entry.rotationDegrees,
|
|
||||||
'isFlipped': entry.isFlipped,
|
|
||||||
'dateModifiedSecs': entry.dateModifiedSecs,
|
|
||||||
'sizeBytes': entry.sizeBytes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> rotate(AvesEntry entry, {required bool clockwise}) async {
|
Future<Map<String, dynamic>> rotate(AvesEntry entry, {required bool clockwise}) async {
|
||||||
try {
|
try {
|
||||||
// returns map with: 'rotationDegrees' 'isFlipped'
|
// returns map with: 'rotationDegrees' 'isFlipped'
|
||||||
final result = await _platform.invokeMethod('rotate', <String, dynamic>{
|
final result = await _platform.invokeMethod('rotate', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': entry.toPlatformEntryMap(),
|
||||||
'clockwise': clockwise,
|
'clockwise': clockwise,
|
||||||
});
|
});
|
||||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||||
|
@ -62,7 +47,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
try {
|
try {
|
||||||
// returns map with: 'rotationDegrees' 'isFlipped'
|
// returns map with: 'rotationDegrees' 'isFlipped'
|
||||||
final result = await _platform.invokeMethod('flip', <String, dynamic>{
|
final result = await _platform.invokeMethod('flip', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': entry.toPlatformEntryMap(),
|
||||||
});
|
});
|
||||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -77,7 +62,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
Future<Map<String, dynamic>> editExifDate(AvesEntry entry, DateModifier modifier) async {
|
Future<Map<String, dynamic>> editExifDate(AvesEntry entry, DateModifier modifier) async {
|
||||||
try {
|
try {
|
||||||
final result = await _platform.invokeMethod('editDate', <String, dynamic>{
|
final result = await _platform.invokeMethod('editDate', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': entry.toPlatformEntryMap(),
|
||||||
'dateMillis': modifier.setDateTime?.millisecondsSinceEpoch,
|
'dateMillis': modifier.setDateTime?.millisecondsSinceEpoch,
|
||||||
'shiftMinutes': modifier.shiftMinutes,
|
'shiftMinutes': modifier.shiftMinutes,
|
||||||
'fields': modifier.fields.where((v) => v.type == MetadataType.exif).map((v) => v.exifInterfaceTag).whereNotNull().toList(),
|
'fields': modifier.fields.where((v) => v.type == MetadataType.exif).map((v) => v.exifInterfaceTag).whereNotNull().toList(),
|
||||||
|
@ -99,7 +84,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _platform.invokeMethod('editMetadata', <String, dynamic>{
|
final result = await _platform.invokeMethod('editMetadata', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': entry.toPlatformEntryMap(),
|
||||||
'metadata': metadata.map((type, value) => MapEntry(_toPlatformMetadataType(type), value)),
|
'metadata': metadata.map((type, value) => MapEntry(_toPlatformMetadataType(type), value)),
|
||||||
'autoCorrectTrailerOffset': autoCorrectTrailerOffset,
|
'autoCorrectTrailerOffset': autoCorrectTrailerOffset,
|
||||||
});
|
});
|
||||||
|
@ -116,7 +101,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
Future<Map<String, dynamic>> removeTrailerVideo(AvesEntry entry) async {
|
Future<Map<String, dynamic>> removeTrailerVideo(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await _platform.invokeMethod('removeTrailerVideo', <String, dynamic>{
|
final result = await _platform.invokeMethod('removeTrailerVideo', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': entry.toPlatformEntryMap(),
|
||||||
});
|
});
|
||||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -131,7 +116,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
Future<Map<String, dynamic>> removeTypes(AvesEntry entry, Set<MetadataType> types) async {
|
Future<Map<String, dynamic>> removeTypes(AvesEntry entry, Set<MetadataType> types) async {
|
||||||
try {
|
try {
|
||||||
final result = await _platform.invokeMethod('removeTypes', <String, dynamic>{
|
final result = await _platform.invokeMethod('removeTypes', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': entry.toPlatformEntryMap(),
|
||||||
'types': types.map(_toPlatformMetadataType).toList(),
|
'types': types.map(_toPlatformMetadataType).toList(),
|
||||||
});
|
});
|
||||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||||
|
|
|
@ -77,7 +77,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
// 'latitude': latitude (double)
|
// 'latitude': latitude (double)
|
||||||
// 'longitude': longitude (double)
|
// 'longitude': longitude (double)
|
||||||
// 'xmpSubjects': ';' separated XMP subjects (string)
|
// 'xmpSubjects': ';' separated XMP subjects (string)
|
||||||
// 'xmpTitleDescription': XMP title (string)
|
// 'xmpTitle': XMP title (string)
|
||||||
final result = await _platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
|
final result = await _platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
|
|
|
@ -35,6 +35,7 @@ class AIcons {
|
||||||
static const IconData ratingRejected = MdiIcons.starMinusOutline;
|
static const IconData ratingRejected = MdiIcons.starMinusOutline;
|
||||||
static const IconData ratingUnrated = MdiIcons.starOffOutline;
|
static const IconData ratingUnrated = MdiIcons.starOffOutline;
|
||||||
static const IconData raw = Icons.raw_on_outlined;
|
static const IconData raw = Icons.raw_on_outlined;
|
||||||
|
static const IconData recent = Icons.today_outlined;
|
||||||
static const IconData shooting = Icons.camera_outlined;
|
static const IconData shooting = Icons.camera_outlined;
|
||||||
static const IconData removableStorage = Icons.sd_storage_outlined;
|
static const IconData removableStorage = Icons.sd_storage_outlined;
|
||||||
static const IconData sensorControlEnabled = Icons.explore_outlined;
|
static const IconData sensorControlEnabled = Icons.explore_outlined;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/app_flavor.dart';
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/l10n/l10n.dart';
|
import 'package:aves/l10n/l10n.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/settings/defaults.dart';
|
import 'package:aves/model/settings/defaults.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart';
|
import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart';
|
||||||
|
@ -275,9 +276,11 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case AppLifecycleState.resumed:
|
||||||
|
RecentlyAddedFilter.updateNow();
|
||||||
|
break;
|
||||||
case AppLifecycleState.paused:
|
case AppLifecycleState.paused:
|
||||||
case AppLifecycleState.detached:
|
case AppLifecycleState.detached:
|
||||||
case AppLifecycleState.resumed:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/filters/location.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/rating.dart';
|
import 'package:aves/model/filters/rating.dart';
|
||||||
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/filters/type.dart';
|
import 'package:aves/model/filters/type.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
@ -130,6 +131,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
|
||||||
Widget _buildDateFilters(BuildContext context, _ContainQuery containQuery) {
|
Widget _buildDateFilters(BuildContext context, _ContainQuery containQuery) {
|
||||||
final filters = [
|
final filters = [
|
||||||
DateFilter.onThisDay,
|
DateFilter.onThisDay,
|
||||||
|
RecentlyAddedFilter.instance,
|
||||||
..._monthFilters,
|
..._monthFilters,
|
||||||
].where((f) => containQuery(f.getLabel(context))).toList();
|
].where((f) => containQuery(f.getLabel(context))).toList();
|
||||||
return _buildFilterRow(
|
return _buildFilterRow(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
|
@ -31,6 +32,7 @@ class _NavigationDrawerEditorPageState extends State<NavigationDrawerEditorPage>
|
||||||
|
|
||||||
static final Set<CollectionFilter?> _typeOptions = {
|
static final Set<CollectionFilter?> _typeOptions = {
|
||||||
null,
|
null,
|
||||||
|
RecentlyAddedFilter.instance,
|
||||||
...CollectionSearchDelegate.typeFilters,
|
...CollectionSearchDelegate.typeFilters,
|
||||||
};
|
};
|
||||||
static const Set<String> _pageOptions = {
|
static const Set<String> _pageOptions = {
|
||||||
|
|
|
@ -94,6 +94,7 @@ class _DbTabState extends State<DbTab> {
|
||||||
'sourceRotationDegrees': '${data.sourceRotationDegrees}',
|
'sourceRotationDegrees': '${data.sourceRotationDegrees}',
|
||||||
'sizeBytes': '${data.sizeBytes}',
|
'sizeBytes': '${data.sizeBytes}',
|
||||||
'sourceTitle': data.sourceTitle ?? '',
|
'sourceTitle': data.sourceTitle ?? '',
|
||||||
|
'dateAddedSecs': '${data.dateAddedSecs}',
|
||||||
'dateModifiedSecs': '${data.dateModifiedSecs}',
|
'dateModifiedSecs': '${data.dateModifiedSecs}',
|
||||||
'sourceDateTakenMillis': '${data.sourceDateTakenMillis}',
|
'sourceDateTakenMillis': '${data.sourceDateTakenMillis}',
|
||||||
'durationMillis': '${data.durationMillis}',
|
'durationMillis': '${data.durationMillis}',
|
||||||
|
@ -126,7 +127,7 @@ class _DbTabState extends State<DbTab> {
|
||||||
'latitude': '${data.latitude}',
|
'latitude': '${data.latitude}',
|
||||||
'longitude': '${data.longitude}',
|
'longitude': '${data.longitude}',
|
||||||
'xmpSubjects': data.xmpSubjects ?? '',
|
'xmpSubjects': data.xmpSubjects ?? '',
|
||||||
'xmpTitleDescription': data.xmpTitleDescription ?? '',
|
'xmpTitle': data.xmpTitle ?? '',
|
||||||
'rating': '${data.rating}',
|
'rating': '${data.rating}',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -98,6 +98,7 @@ class ViewerDebugPage extends StatelessWidget {
|
||||||
InfoRowGroup(
|
InfoRowGroup(
|
||||||
info: {
|
info: {
|
||||||
'catalogDateMillis': toDateValue(entry.catalogDateMillis),
|
'catalogDateMillis': toDateValue(entry.catalogDateMillis),
|
||||||
|
'dateAddedSecs': toDateValue(entry.dateAddedSecs, factor: 1000),
|
||||||
'dateModifiedSecs': toDateValue(entry.dateModifiedSecs, factor: 1000),
|
'dateModifiedSecs': toDateValue(entry.dateModifiedSecs, factor: 1000),
|
||||||
'sourceDateTakenMillis': toDateValue(entry.sourceDateTakenMillis),
|
'sourceDateTakenMillis': toDateValue(entry.sourceDateTakenMillis),
|
||||||
'bestDate': '${entry.bestDate}',
|
'bestDate': '${entry.bestDate}',
|
||||||
|
|
|
@ -38,6 +38,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
|
||||||
sourceRotationDegrees: 0,
|
sourceRotationDegrees: 0,
|
||||||
sizeBytes: 42,
|
sizeBytes: 42,
|
||||||
sourceTitle: filenameWithoutExtension,
|
sourceTitle: filenameWithoutExtension,
|
||||||
|
dateAddedSecs: date,
|
||||||
dateModifiedSecs: date,
|
dateModifiedSecs: date,
|
||||||
sourceDateTakenMillis: date,
|
sourceDateTakenMillis: date,
|
||||||
durationMillis: null,
|
durationMillis: null,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/path.dart';
|
import 'package:aves/model/filters/path.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
import 'package:aves/model/filters/rating.dart';
|
import 'package:aves/model/filters/rating.dart';
|
||||||
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/filters/type.dart';
|
import 'package:aves/model/filters/type.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
@ -61,6 +62,9 @@ void main() {
|
||||||
const rating = RatingFilter(3);
|
const rating = RatingFilter(3);
|
||||||
expect(rating, jsonRoundTrip(rating));
|
expect(rating, jsonRoundTrip(rating));
|
||||||
|
|
||||||
|
final recent = RecentlyAddedFilter.instance;
|
||||||
|
expect(recent, jsonRoundTrip(recent));
|
||||||
|
|
||||||
final tag = TagFilter('some tag');
|
final tag = TagFilter('some tag');
|
||||||
expect(tag, jsonRoundTrip(tag));
|
expect(tag, jsonRoundTrip(tag));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"de": [
|
"de": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
|
@ -8,18 +9,21 @@
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems"
|
"settingsConfirmationAfterMoveToBinItems"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"id": [
|
"id": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
|
@ -27,6 +31,7 @@
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
|
@ -34,6 +39,7 @@
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
|
@ -41,12 +47,14 @@
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
|
@ -55,6 +63,7 @@
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
|
@ -63,6 +72,7 @@
|
||||||
"ru": [
|
"ru": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
"filterOnThisDayLabel",
|
"filterOnThisDayLabel",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
|
@ -77,6 +87,7 @@
|
||||||
"slideshowActionShowInCollection",
|
"slideshowActionShowInCollection",
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
"filterOnThisDayLabel",
|
"filterOnThisDayLabel",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"slideshowVideoPlaybackSkip",
|
"slideshowVideoPlaybackSkip",
|
||||||
"slideshowVideoPlaybackMuted",
|
"slideshowVideoPlaybackMuted",
|
||||||
"slideshowVideoPlaybackWithSound",
|
"slideshowVideoPlaybackWithSound",
|
||||||
|
@ -110,6 +121,7 @@
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
"entryInfoActionEditDescription",
|
"entryInfoActionEditDescription",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
"editEntryDescriptionDialogTitle",
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
|
|
Loading…
Reference in a new issue