#97 search: recently added filter

This commit is contained in:
Thibault Deckers 2022-08-24 10:53:59 +02:00
parent 0cce0c1e11
commit 503f93ed17
26 changed files with 206 additions and 58 deletions

View file

@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file.
- Collection / Info: edit description via Exif / IPTC / XMP
- Info: read XMP from HEIC on Android >=11
- Collection: support HEIC motion photos on Android >=11
- Search: `recently added` filter
- Dutch translation (thanks Martijn Fabrie, Koen Koppens)
### Changed

View file

@ -1171,7 +1171,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private const val KEY_LATITUDE = "latitude"
private const val KEY_LONGITUDE = "longitude"
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 MASK_IS_ANIMATED = 1 shl 0

View file

@ -125,6 +125,7 @@
"filterLocationEmptyLabel": "Unlocated",
"filterTagEmptyLabel": "Untagged",
"filterOnThisDayLabel": "On this day",
"filterRecentlyAddedLabel": "Recently added",
"filterRatingUnratedLabel": "Unrated",
"filterRatingRejectedLabel": "Rejected",
"filterTypeAnimatedLabel": "Animated",

View file

@ -10,6 +10,8 @@ import 'package:aves/model/video_playback.dart';
abstract class MetadataDb {
int get nextId;
int get timestampSecs;
Future<void> init();
Future<int> dbFileSize();

View file

@ -34,6 +34,9 @@ class SqfliteMetadataDb implements MetadataDb {
@override
int get nextId => ++_lastId;
@override
int get timestampSecs => DateTime.now().millisecondsSinceEpoch ~/ 1000;
@override
Future<void> init() async {
_db = await openDatabase(
@ -50,6 +53,7 @@ class SqfliteMetadataDb implements MetadataDb {
', sourceRotationDegrees INTEGER'
', sizeBytes INTEGER'
', title TEXT'
', dateAddedSecs INTEGER DEFAULT (strftime(\'%s\',\'now\'))'
', dateModifiedSecs INTEGER'
', sourceDateTakenMillis INTEGER'
', durationMillis INTEGER'
@ -66,7 +70,7 @@ class SqfliteMetadataDb implements MetadataDb {
', flags INTEGER'
', rotationDegrees INTEGER'
', xmpSubjects TEXT'
', xmpTitleDescription TEXT'
', xmpTitle TEXT'
', latitude REAL'
', longitude REAL'
', rating INTEGER'
@ -99,7 +103,7 @@ class SqfliteMetadataDb implements MetadataDb {
')');
},
onUpgrade: MetadataDbUpgrader.upgradeDb,
version: 8,
version: 9,
);
final maxIdRows = await _db.rawQuery('SELECT max(id) AS maxId FROM $entryTable');

View file

@ -38,6 +38,9 @@ class MetadataDbUpgrader {
case 7:
await _upgradeFrom7(db);
break;
case 8:
await _upgradeFrom8(db);
break;
}
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 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;');
});
}
}

View file

@ -37,7 +37,7 @@ class AvesEntry {
int? pageId, contentId;
final String sourceMimeType;
int width, height, sourceRotationDegrees;
int? sizeBytes, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis;
int? sizeBytes, dateAddedSecs, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis;
bool trashed;
int? _catalogDateMillis;
@ -61,6 +61,7 @@ class AvesEntry {
required this.sourceRotationDegrees,
required this.sizeBytes,
required String? sourceTitle,
required this.dateAddedSecs,
required int? dateModifiedSecs,
required this.sourceDateTakenMillis,
required int? durationMillis,
@ -83,6 +84,7 @@ class AvesEntry {
String? path,
int? contentId,
String? title,
int? dateAddedSecs,
int? dateModifiedSecs,
List<AvesEntry>? burstEntries,
}) {
@ -99,6 +101,7 @@ class AvesEntry {
sourceRotationDegrees: sourceRotationDegrees,
sizeBytes: sizeBytes,
sourceTitle: title ?? sourceTitle,
dateAddedSecs: dateAddedSecs ?? this.dateAddedSecs,
dateModifiedSecs: dateModifiedSecs ?? this.dateModifiedSecs,
sourceDateTakenMillis: sourceDateTakenMillis,
durationMillis: durationMillis,
@ -126,6 +129,7 @@ class AvesEntry {
sourceRotationDegrees: map['sourceRotationDegrees'] as int? ?? 0,
sizeBytes: map['sizeBytes'] as int?,
sourceTitle: map['title'] as String?,
dateAddedSecs: map['dateAddedSecs'] as int?,
dateModifiedSecs: map['dateModifiedSecs'] as int?,
sourceDateTakenMillis: map['sourceDateTakenMillis'] 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() {
imageChangeNotifier.dispose();
metadataChangeNotifier.dispose();
@ -464,7 +485,7 @@ class AvesEntry {
String? _bestTitle;
String? get bestTitle {
_bestTitle ??= _catalogMetadata?.xmpTitleDescription?.isNotEmpty == true ? _catalogMetadata!.xmpTitleDescription : (filenameWithoutExtension ?? sourceTitle);
_bestTitle ??= _catalogMetadata?.xmpTitle?.isNotEmpty == true ? _catalogMetadata!.xmpTitle : (filenameWithoutExtension ?? sourceTitle);
return _bestTitle;
}

View file

@ -11,6 +11,7 @@ import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/path.dart';
import 'package:aves/model/filters/query.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/trash.dart';
import 'package:aves/model/filters/type.dart';
@ -68,6 +69,8 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
return QueryFilter.fromMap(jsonMap);
case RatingFilter.type:
return RatingFilter.fromMap(jsonMap);
case RecentlyAddedFilter.type:
return RecentlyAddedFilter.instance;
case TagFilter.type:
return TagFilter.fromMap(jsonMap);
case TypeFilter.type:

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

View file

@ -7,7 +7,7 @@ class CatalogMetadata {
final bool isAnimated, isGeotiff, is360, isMultiPage, isMotionPhoto;
bool isFlipped;
int? rotationDegrees;
final String? mimeType, xmpSubjects, xmpTitleDescription;
final String? mimeType, xmpSubjects, xmpTitle;
double? latitude, longitude;
Address? address;
int rating;
@ -32,7 +32,7 @@ class CatalogMetadata {
this.isMotionPhoto = false,
this.rotationDegrees,
this.xmpSubjects,
this.xmpTitleDescription,
this.xmpTitle,
double? latitude,
double? longitude,
this.rating = 0,
@ -72,7 +72,7 @@ class CatalogMetadata {
isMotionPhoto: isMotionPhoto,
rotationDegrees: rotationDegrees ?? this.rotationDegrees,
xmpSubjects: xmpSubjects,
xmpTitleDescription: xmpTitleDescription,
xmpTitle: xmpTitle,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
rating: rating,
@ -94,7 +94,7 @@ class CatalogMetadata {
// `rotationDegrees` should default to `sourceRotationDegrees`, not 0
rotationDegrees: map['rotationDegrees'],
xmpSubjects: map['xmpSubjects'] ?? '',
xmpTitleDescription: map['xmpTitleDescription'] ?? '',
xmpTitle: map['xmpTitle'] ?? '',
latitude: map['latitude'],
longitude: map['longitude'],
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),
'rotationDegrees': rotationDegrees,
'xmpSubjects': xmpSubjects,
'xmpTitleDescription': xmpTitleDescription,
'xmpTitle': xmpTitle,
'latitude': latitude,
'longitude': longitude,
'rating': rating,
};
@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}';
}

View file

@ -102,6 +102,7 @@ class MultiPageInfo {
sourceRotationDegrees: pageInfo.rotationDegrees ?? mainEntry.sourceRotationDegrees,
sizeBytes: mainEntry.sizeBytes,
sourceTitle: mainEntry.sourceTitle,
dateAddedSecs: mainEntry.dateAddedSecs,
dateModifiedSecs: mainEntry.dateModifiedSecs,
sourceDateTakenMillis: mainEntry.sourceDateTakenMillis,
durationMillis: pageInfo.durationMillis ?? mainEntry.durationMillis,

View file

@ -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/filters/favourite.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/settings/enums/enums.dart';
import 'package:aves/model/source/enums.dart';
@ -41,6 +42,7 @@ class SettingsDefaults {
null,
MimeFilter.video,
FavouriteFilter.instance,
RecentlyAddedFilter.instance,
];
static const drawerPageBookmarks = [
AlbumListPage.routeName,

View file

@ -294,6 +294,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
contentId: newFields['contentId'] as int?,
// title can change when moved files are automatically renamed to avoid conflict
title: newFields['title'] as String?,
dateAddedSecs: metadataDb.timestampSecs,
dateModifiedSecs: newFields['dateModifiedSecs'] as int?,
));
} else {

View file

@ -158,7 +158,14 @@ class MediaStoreSource extends CollectionSource {
// when discovering modified entry with known content ID,
// reuse known entry ID to overwrite it while preserving favourites, etc.
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);
if (pendingNewEntries.length >= refreshCount) {
@ -243,7 +250,13 @@ class MediaStoreSource extends CollectionSource {
final newPath = sourceEntry.path;
final volume = newPath != null ? androidFileUtils.getStorageVolume(newPath) : 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);
final existingDirectory = existingEntry?.directory;
if (existingDirectory != null) {

View file

@ -52,23 +52,6 @@ class PlatformMediaEditService implements MediaEditService {
static const _platform = MethodChannel('deckers.thibault/aves/media_edit');
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
String get newOpId => DateTime.now().millisecondsSinceEpoch.toString();
@ -93,7 +76,7 @@ class PlatformMediaEditService implements MediaEditService {
.receiveBroadcastStream(<String, dynamic>{
'op': 'delete',
'id': opId,
'entries': entries.map(_toPlatformEntryMap).toList(),
'entries': entries.map((entry) => entry.toPlatformEntryMap()).toList(),
})
.where((event) => event is Map)
.map((event) => ImageOpEvent.fromMap(event as Map));
@ -115,7 +98,7 @@ class PlatformMediaEditService implements MediaEditService {
.receiveBroadcastStream(<String, dynamic>{
'op': 'move',
'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,
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
})
@ -138,7 +121,7 @@ class PlatformMediaEditService implements MediaEditService {
return _opStream
.receiveBroadcastStream(<String, dynamic>{
'op': 'export',
'entries': entries.map(_toPlatformEntryMap).toList(),
'entries': entries.map((entry) => entry.toPlatformEntryMap()).toList(),
'mimeType': options.mimeType,
'width': options.width,
'height': options.height,
@ -163,7 +146,7 @@ class PlatformMediaEditService implements MediaEditService {
.receiveBroadcastStream(<String, dynamic>{
'op': 'rename',
'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)
.map((event) => MoveOpEvent.fromMap(event as Map));

View file

@ -25,27 +25,12 @@ abstract class MetadataEditService {
class PlatformMetadataEditService implements MetadataEditService {
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
Future<Map<String, dynamic>> rotate(AvesEntry entry, {required bool clockwise}) async {
try {
// returns map with: 'rotationDegrees' 'isFlipped'
final result = await _platform.invokeMethod('rotate', <String, dynamic>{
'entry': _toPlatformEntryMap(entry),
'entry': entry.toPlatformEntryMap(),
'clockwise': clockwise,
});
if (result != null) return (result as Map).cast<String, dynamic>();
@ -62,7 +47,7 @@ class PlatformMetadataEditService implements MetadataEditService {
try {
// returns map with: 'rotationDegrees' 'isFlipped'
final result = await _platform.invokeMethod('flip', <String, dynamic>{
'entry': _toPlatformEntryMap(entry),
'entry': entry.toPlatformEntryMap(),
});
if (result != null) return (result as Map).cast<String, dynamic>();
} on PlatformException catch (e, stack) {
@ -77,7 +62,7 @@ class PlatformMetadataEditService implements MetadataEditService {
Future<Map<String, dynamic>> editExifDate(AvesEntry entry, DateModifier modifier) async {
try {
final result = await _platform.invokeMethod('editDate', <String, dynamic>{
'entry': _toPlatformEntryMap(entry),
'entry': entry.toPlatformEntryMap(),
'dateMillis': modifier.setDateTime?.millisecondsSinceEpoch,
'shiftMinutes': modifier.shiftMinutes,
'fields': modifier.fields.where((v) => v.type == MetadataType.exif).map((v) => v.exifInterfaceTag).whereNotNull().toList(),
@ -99,7 +84,7 @@ class PlatformMetadataEditService implements MetadataEditService {
}) async {
try {
final result = await _platform.invokeMethod('editMetadata', <String, dynamic>{
'entry': _toPlatformEntryMap(entry),
'entry': entry.toPlatformEntryMap(),
'metadata': metadata.map((type, value) => MapEntry(_toPlatformMetadataType(type), value)),
'autoCorrectTrailerOffset': autoCorrectTrailerOffset,
});
@ -116,7 +101,7 @@ class PlatformMetadataEditService implements MetadataEditService {
Future<Map<String, dynamic>> removeTrailerVideo(AvesEntry entry) async {
try {
final result = await _platform.invokeMethod('removeTrailerVideo', <String, dynamic>{
'entry': _toPlatformEntryMap(entry),
'entry': entry.toPlatformEntryMap(),
});
if (result != null) return (result as Map).cast<String, dynamic>();
} on PlatformException catch (e, stack) {
@ -131,7 +116,7 @@ class PlatformMetadataEditService implements MetadataEditService {
Future<Map<String, dynamic>> removeTypes(AvesEntry entry, Set<MetadataType> types) async {
try {
final result = await _platform.invokeMethod('removeTypes', <String, dynamic>{
'entry': _toPlatformEntryMap(entry),
'entry': entry.toPlatformEntryMap(),
'types': types.map(_toPlatformMetadataType).toList(),
});
if (result != null) return (result as Map).cast<String, dynamic>();

View file

@ -77,7 +77,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
// 'latitude': latitude (double)
// 'longitude': longitude (double)
// 'xmpSubjects': ';' separated XMP subjects (string)
// 'xmpTitleDescription': XMP title (string)
// 'xmpTitle': XMP title (string)
final result = await _platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
'mimeType': entry.mimeType,
'uri': entry.uri,

View file

@ -35,6 +35,7 @@ class AIcons {
static const IconData ratingRejected = MdiIcons.starMinusOutline;
static const IconData ratingUnrated = MdiIcons.starOffOutline;
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 removableStorage = Icons.sd_storage_outlined;
static const IconData sensorControlEnabled = Icons.explore_outlined;

View file

@ -5,6 +5,7 @@ import 'package:aves/app_flavor.dart';
import 'package:aves/app_mode.dart';
import 'package:aves/l10n/l10n.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/enums/accessibility_animations.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;
case AppLifecycleState.resumed:
RecentlyAddedFilter.updateNow();
break;
case AppLifecycleState.paused:
case AppLifecycleState.detached:
case AppLifecycleState.resumed:
break;
}
}

View file

@ -6,6 +6,7 @@ import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/query.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/type.dart';
import 'package:aves/model/settings/settings.dart';
@ -130,6 +131,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
Widget _buildDateFilters(BuildContext context, _ContainQuery containQuery) {
final filters = [
DateFilter.onThisDay,
RecentlyAddedFilter.instance,
..._monthFilters,
].where((f) => containQuery(f.getLabel(context))).toList();
return _buildFilterRow(

View file

@ -1,4 +1,5 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/recent.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
@ -31,6 +32,7 @@ class _NavigationDrawerEditorPageState extends State<NavigationDrawerEditorPage>
static final Set<CollectionFilter?> _typeOptions = {
null,
RecentlyAddedFilter.instance,
...CollectionSearchDelegate.typeFilters,
};
static const Set<String> _pageOptions = {

View file

@ -94,6 +94,7 @@ class _DbTabState extends State<DbTab> {
'sourceRotationDegrees': '${data.sourceRotationDegrees}',
'sizeBytes': '${data.sizeBytes}',
'sourceTitle': data.sourceTitle ?? '',
'dateAddedSecs': '${data.dateAddedSecs}',
'dateModifiedSecs': '${data.dateModifiedSecs}',
'sourceDateTakenMillis': '${data.sourceDateTakenMillis}',
'durationMillis': '${data.durationMillis}',
@ -126,7 +127,7 @@ class _DbTabState extends State<DbTab> {
'latitude': '${data.latitude}',
'longitude': '${data.longitude}',
'xmpSubjects': data.xmpSubjects ?? '',
'xmpTitleDescription': data.xmpTitleDescription ?? '',
'xmpTitle': data.xmpTitle ?? '',
'rating': '${data.rating}',
},
),

View file

@ -98,6 +98,7 @@ class ViewerDebugPage extends StatelessWidget {
InfoRowGroup(
info: {
'catalogDateMillis': toDateValue(entry.catalogDateMillis),
'dateAddedSecs': toDateValue(entry.dateAddedSecs, factor: 1000),
'dateModifiedSecs': toDateValue(entry.dateModifiedSecs, factor: 1000),
'sourceDateTakenMillis': toDateValue(entry.sourceDateTakenMillis),
'bestDate': '${entry.bestDate}',

View file

@ -38,6 +38,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
sourceRotationDegrees: 0,
sizeBytes: 42,
sourceTitle: filenameWithoutExtension,
dateAddedSecs: date,
dateModifiedSecs: date,
sourceDateTakenMillis: date,
durationMillis: null,

View file

@ -8,6 +8,7 @@ import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/path.dart';
import 'package:aves/model/filters/query.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/type.dart';
import 'package:aves/services/common/services.dart';
@ -61,6 +62,9 @@ void main() {
const rating = RatingFilter(3);
expect(rating, jsonRoundTrip(rating));
final recent = RecentlyAddedFilter.instance;
expect(recent, jsonRoundTrip(recent));
final tag = TagFilter('some tag');
expect(tag, jsonRoundTrip(tag));

View file

@ -1,6 +1,7 @@
{
"de": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext"
@ -8,18 +9,21 @@
"es": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems"
],
"fr": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsViewerGestureSideTapNext"
],
"id": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext"
@ -27,6 +31,7 @@
"it": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext"
@ -34,6 +39,7 @@
"ja": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext"
@ -41,12 +47,14 @@
"ko": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsViewerGestureSideTapNext"
],
"nl": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext",
@ -55,6 +63,7 @@
"pt": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext"
@ -63,6 +72,7 @@
"ru": [
"entryInfoActionEditDescription",
"filterOnThisDayLabel",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext",
@ -77,6 +87,7 @@
"slideshowActionShowInCollection",
"entryInfoActionEditDescription",
"filterOnThisDayLabel",
"filterRecentlyAddedLabel",
"slideshowVideoPlaybackSkip",
"slideshowVideoPlaybackMuted",
"slideshowVideoPlaybackWithSound",
@ -110,6 +121,7 @@
"zh": [
"entryInfoActionEditDescription",
"filterRecentlyAddedLabel",
"editEntryDescriptionDialogTitle",
"settingsConfirmationAfterMoveToBinItems",
"settingsViewerGestureSideTapNext"