reviewed collection model to work with source + lenses
This commit is contained in:
parent
382d509a31
commit
83f49902b9
20 changed files with 361 additions and 197 deletions
|
@ -164,7 +164,7 @@ class SliverTransitionGridTileLayout extends SliverGridLayout {
|
||||||
|
|
||||||
if (t != 0) {
|
if (t != 0) {
|
||||||
final index = childCount - 1;
|
final index = childCount - 1;
|
||||||
var maxScrollOffset = lerpDouble(_getScrollOffset(index, floor), _getScrollOffset(index, ceil), t) + current.mainAxisStride;
|
final maxScrollOffset = lerpDouble(_getScrollOffset(index, floor), _getScrollOffset(index, ceil), t) + current.mainAxisStride;
|
||||||
return maxScrollOffset;
|
return maxScrollOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
39
lib/model/collection_filters.dart
Normal file
39
lib/model/collection_filters.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
|
||||||
|
abstract class CollectionFilter {
|
||||||
|
const CollectionFilter();
|
||||||
|
|
||||||
|
bool filter(ImageEntry entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumFilter extends CollectionFilter {
|
||||||
|
final String album;
|
||||||
|
|
||||||
|
const AlbumFilter(this.album);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool filter(ImageEntry entry) => entry.directory == album;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagFilter extends CollectionFilter {
|
||||||
|
final String tag;
|
||||||
|
|
||||||
|
const TagFilter(this.tag);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool filter(ImageEntry entry) => entry.xmpSubjects.contains(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoFilter extends CollectionFilter {
|
||||||
|
@override
|
||||||
|
bool filter(ImageEntry entry) => entry.isVideo;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MetadataFilter extends CollectionFilter {
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
const MetadataFilter(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool filter(ImageEntry entry) => entry.search(value);
|
||||||
|
}
|
157
lib/model/collection_lens.dart
Normal file
157
lib/model/collection_lens.dart
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:aves/model/collection_filters.dart';
|
||||||
|
import 'package:aves/model/collection_source.dart';
|
||||||
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class CollectionLens with ChangeNotifier {
|
||||||
|
final CollectionSource source;
|
||||||
|
final List<CollectionFilter> filters;
|
||||||
|
GroupFactor groupFactor;
|
||||||
|
SortFactor sortFactor;
|
||||||
|
|
||||||
|
List<ImageEntry> _filteredEntries;
|
||||||
|
List<StreamSubscription> _subscriptions = [];
|
||||||
|
|
||||||
|
Map<dynamic, List<ImageEntry>> sections = Map.unmodifiable({});
|
||||||
|
|
||||||
|
CollectionLens({
|
||||||
|
@required this.source,
|
||||||
|
List<CollectionFilter> filters,
|
||||||
|
GroupFactor groupFactor,
|
||||||
|
SortFactor sortFactor,
|
||||||
|
}) : this.filters = filters ?? [],
|
||||||
|
this.groupFactor = groupFactor ?? GroupFactor.month,
|
||||||
|
this.sortFactor = sortFactor ?? SortFactor.date {
|
||||||
|
_subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => onSourceChanged()));
|
||||||
|
_subscriptions.add(source.eventBus.on<EntryRemovedEvent>().listen((e) => onSourceChanged()));
|
||||||
|
_subscriptions.add(source.eventBus.on<MetadataChangedEvent>().listen((e) => onMetadataChanged()));
|
||||||
|
onSourceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CollectionLens.empty() {
|
||||||
|
return CollectionLens(
|
||||||
|
source: CollectionSource(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CollectionLens.from(CollectionLens lens, CollectionFilter filter) {
|
||||||
|
return CollectionLens(
|
||||||
|
source: lens.source,
|
||||||
|
filters: [...lens.filters, filter],
|
||||||
|
groupFactor: lens.groupFactor,
|
||||||
|
sortFactor: lens.sortFactor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_subscriptions
|
||||||
|
..forEach((sub) => sub.cancel())
|
||||||
|
..clear();
|
||||||
|
_subscriptions = null;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isEmpty => _filteredEntries.isEmpty;
|
||||||
|
|
||||||
|
int get imageCount => _filteredEntries.where((entry) => !entry.isVideo).length;
|
||||||
|
|
||||||
|
int get videoCount => _filteredEntries.where((entry) => entry.isVideo).length;
|
||||||
|
|
||||||
|
List<ImageEntry> get sortedEntries => List.unmodifiable(sections.entries.expand((e) => e.value));
|
||||||
|
|
||||||
|
void sort(SortFactor sortFactor) {
|
||||||
|
this.sortFactor = sortFactor;
|
||||||
|
updateSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
void group(GroupFactor groupFactor) {
|
||||||
|
this.groupFactor = groupFactor;
|
||||||
|
updateSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateSections() {
|
||||||
|
_applySort();
|
||||||
|
switch (sortFactor) {
|
||||||
|
case SortFactor.date:
|
||||||
|
switch (groupFactor) {
|
||||||
|
case GroupFactor.album:
|
||||||
|
sections = Map.unmodifiable(groupBy(_filteredEntries, (entry) => entry.directory));
|
||||||
|
break;
|
||||||
|
case GroupFactor.month:
|
||||||
|
sections = Map.unmodifiable(groupBy(_filteredEntries, (entry) => entry.monthTaken));
|
||||||
|
break;
|
||||||
|
case GroupFactor.day:
|
||||||
|
sections = Map.unmodifiable(groupBy(_filteredEntries, (entry) => entry.dayTaken));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SortFactor.size:
|
||||||
|
sections = Map.unmodifiable(Map.fromEntries([
|
||||||
|
MapEntry(null, _filteredEntries),
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
case SortFactor.name:
|
||||||
|
final byAlbum = groupBy(_filteredEntries, (ImageEntry entry) => entry.directory);
|
||||||
|
final albums = byAlbum.keys.toSet();
|
||||||
|
final compare = (a, b) {
|
||||||
|
final ua = CollectionSource.getUniqueAlbumName(a, albums);
|
||||||
|
final ub = CollectionSource.getUniqueAlbumName(b, albums);
|
||||||
|
return compareAsciiUpperCase(ua, ub);
|
||||||
|
};
|
||||||
|
sections = Map.unmodifiable(SplayTreeMap.from(byAlbum, compare));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applySort() {
|
||||||
|
switch (sortFactor) {
|
||||||
|
case SortFactor.date:
|
||||||
|
_filteredEntries.sort((a, b) => b.bestDate.compareTo(a.bestDate));
|
||||||
|
break;
|
||||||
|
case SortFactor.size:
|
||||||
|
_filteredEntries.sort((a, b) => b.sizeBytes.compareTo(a.sizeBytes));
|
||||||
|
break;
|
||||||
|
case SortFactor.name:
|
||||||
|
_filteredEntries.sort((a, b) => compareAsciiUpperCase(a.title, b.title));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// void add(ImageEntry entry) => _rawEntries.add(entry);
|
||||||
|
//
|
||||||
|
// Future<bool> delete(ImageEntry entry) async {
|
||||||
|
// final success = await ImageFileService.delete(entry);
|
||||||
|
// if (success) {
|
||||||
|
// _rawEntries.remove(entry);
|
||||||
|
// updateSections();
|
||||||
|
// }
|
||||||
|
// return success;
|
||||||
|
// }
|
||||||
|
|
||||||
|
void onSourceChanged() {
|
||||||
|
_applyFilters();
|
||||||
|
updateSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyFilters() {
|
||||||
|
final rawEntries = source.entries;
|
||||||
|
_filteredEntries = List.of(filters.isEmpty ? rawEntries : rawEntries.where((entry) => filters.fold(true, (prev, filter) => prev && filter.filter(entry))));
|
||||||
|
updateSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMetadataChanged() {
|
||||||
|
_applyFilters();
|
||||||
|
// metadata dates impact sorting and grouping
|
||||||
|
updateSections();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SortFactor { date, size, name }
|
||||||
|
|
||||||
|
enum GroupFactor { album, month, day }
|
|
@ -1,131 +1,30 @@
|
||||||
import 'dart:collection';
|
|
||||||
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/image_file_service.dart';
|
import 'package:aves/model/image_file_service.dart';
|
||||||
import 'package:aves/model/image_metadata.dart';
|
import 'package:aves/model/image_metadata.dart';
|
||||||
import 'package:aves/model/metadata_db.dart';
|
import 'package:aves/model/metadata_db.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
class ImageCollection with ChangeNotifier {
|
class CollectionSource {
|
||||||
final List<ImageEntry> _rawEntries;
|
final List<ImageEntry> _rawEntries;
|
||||||
Map<dynamic, List<ImageEntry>> sections = Map.unmodifiable({});
|
final EventBus _eventBus = EventBus();
|
||||||
GroupFactor groupFactor = GroupFactor.month;
|
|
||||||
SortFactor sortFactor = SortFactor.date;
|
|
||||||
List<String> sortedAlbums = List.unmodifiable(const Iterable.empty());
|
List<String> sortedAlbums = List.unmodifiable(const Iterable.empty());
|
||||||
List<String> sortedTags = List.unmodifiable(const Iterable.empty());
|
List<String> sortedTags = List.unmodifiable(const Iterable.empty());
|
||||||
|
|
||||||
ImageCollection({
|
List<ImageEntry> get entries => List.unmodifiable(_rawEntries);
|
||||||
@required List<ImageEntry> entries,
|
|
||||||
this.groupFactor,
|
|
||||||
this.sortFactor,
|
|
||||||
}) : _rawEntries = entries {
|
|
||||||
if (_rawEntries.isNotEmpty) updateSections();
|
|
||||||
}
|
|
||||||
|
|
||||||
int get imageCount => _rawEntries.where((entry) => !entry.isVideo).length;
|
EventBus get eventBus => _eventBus;
|
||||||
|
|
||||||
int get videoCount => _rawEntries.where((entry) => entry.isVideo).length;
|
|
||||||
|
|
||||||
int get albumCount => sortedAlbums.length;
|
int get albumCount => sortedAlbums.length;
|
||||||
|
|
||||||
int get tagCount => sortedTags.length;
|
int get tagCount => sortedTags.length;
|
||||||
|
|
||||||
List<ImageEntry> get sortedEntries => List.unmodifiable(sections.entries.expand((e) => e.value));
|
CollectionSource({
|
||||||
|
List<ImageEntry> entries,
|
||||||
void sort(SortFactor sortFactor) {
|
}) : _rawEntries = entries ?? [];
|
||||||
this.sortFactor = sortFactor;
|
|
||||||
updateSections();
|
|
||||||
}
|
|
||||||
|
|
||||||
void group(GroupFactor groupFactor) {
|
|
||||||
this.groupFactor = groupFactor;
|
|
||||||
updateSections();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateSections() {
|
|
||||||
_applySort();
|
|
||||||
switch (sortFactor) {
|
|
||||||
case SortFactor.date:
|
|
||||||
switch (groupFactor) {
|
|
||||||
case GroupFactor.album:
|
|
||||||
sections = Map.unmodifiable(groupBy(_rawEntries, (entry) => entry.directory));
|
|
||||||
break;
|
|
||||||
case GroupFactor.month:
|
|
||||||
sections = Map.unmodifiable(groupBy(_rawEntries, (entry) => entry.monthTaken));
|
|
||||||
break;
|
|
||||||
case GroupFactor.day:
|
|
||||||
sections = Map.unmodifiable(groupBy(_rawEntries, (entry) => entry.dayTaken));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SortFactor.size:
|
|
||||||
sections = Map.unmodifiable(Map.fromEntries([
|
|
||||||
MapEntry(null, _rawEntries),
|
|
||||||
]));
|
|
||||||
break;
|
|
||||||
case SortFactor.name:
|
|
||||||
final byAlbum = groupBy(_rawEntries, (ImageEntry entry) => entry.directory);
|
|
||||||
final albums = byAlbum.keys.toSet();
|
|
||||||
final compare = (a, b) {
|
|
||||||
final ua = getUniqueAlbumName(a, albums);
|
|
||||||
final ub = getUniqueAlbumName(b, albums);
|
|
||||||
return compareAsciiUpperCase(ua, ub);
|
|
||||||
};
|
|
||||||
sections = Map.unmodifiable(SplayTreeMap.from(byAlbum, compare));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _applySort() {
|
|
||||||
switch (sortFactor) {
|
|
||||||
case SortFactor.date:
|
|
||||||
_rawEntries.sort((a, b) => b.bestDate.compareTo(a.bestDate));
|
|
||||||
break;
|
|
||||||
case SortFactor.size:
|
|
||||||
_rawEntries.sort((a, b) => b.sizeBytes.compareTo(a.sizeBytes));
|
|
||||||
break;
|
|
||||||
case SortFactor.name:
|
|
||||||
_rawEntries.sort((a, b) => compareAsciiUpperCase(a.title, b.title));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void add(ImageEntry entry) => _rawEntries.add(entry);
|
|
||||||
|
|
||||||
Future<bool> delete(ImageEntry entry) async {
|
|
||||||
final success = await ImageFileService.delete(entry);
|
|
||||||
if (success) {
|
|
||||||
_rawEntries.remove(entry);
|
|
||||||
updateSections();
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateAlbums() {
|
|
||||||
final albums = _rawEntries.map((entry) => entry.directory).toSet();
|
|
||||||
final sorted = albums.toList()
|
|
||||||
..sort((a, b) {
|
|
||||||
final ua = getUniqueAlbumName(a, albums);
|
|
||||||
final ub = getUniqueAlbumName(b, albums);
|
|
||||||
return compareAsciiUpperCase(ua, ub);
|
|
||||||
});
|
|
||||||
sortedAlbums = List.unmodifiable(sorted);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateTags() {
|
|
||||||
final tags = _rawEntries.expand((entry) => entry.xmpSubjects).toSet();
|
|
||||||
final sorted = tags.toList()..sort(compareAsciiUpperCase);
|
|
||||||
sortedTags = List.unmodifiable(sorted);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMetadataChanged() {
|
|
||||||
// metadata dates impact sorting and grouping
|
|
||||||
updateSections();
|
|
||||||
updateTags();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadCatalogMetadata() async {
|
Future<void> loadCatalogMetadata() async {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
@ -171,6 +70,11 @@ class ImageCollection with ChangeNotifier {
|
||||||
debugPrint('$runtimeType catalogEntries complete in ${stopwatch.elapsed.inSeconds}s with ${newMetadata.length} new entries');
|
debugPrint('$runtimeType catalogEntries complete in ${stopwatch.elapsed.inSeconds}s with ${newMetadata.length} new entries');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onMetadataChanged() {
|
||||||
|
updateTags();
|
||||||
|
eventBus.fire(MetadataChangedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> locateEntries() async {
|
Future<void> locateEntries() async {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
final unlocatedEntries = _rawEntries.where((entry) => entry.hasGps && !entry.isLocated).toList();
|
final unlocatedEntries = _rawEntries.where((entry) => entry.hasGps && !entry.isLocated).toList();
|
||||||
|
@ -189,15 +93,43 @@ class ImageCollection with ChangeNotifier {
|
||||||
debugPrint('$runtimeType locateEntries complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
debugPrint('$runtimeType locateEntries complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageCollection filter(bool Function(ImageEntry) filter) {
|
void updateAlbums() {
|
||||||
return ImageCollection(
|
final albums = _rawEntries.map((entry) => entry.directory).toSet();
|
||||||
entries: _rawEntries.where(filter).toList(),
|
final sorted = albums.toList()
|
||||||
groupFactor: groupFactor,
|
..sort((a, b) {
|
||||||
sortFactor: sortFactor,
|
final ua = getUniqueAlbumName(a, albums);
|
||||||
);
|
final ub = getUniqueAlbumName(b, albums);
|
||||||
|
return compareAsciiUpperCase(ua, ub);
|
||||||
|
});
|
||||||
|
sortedAlbums = List.unmodifiable(sorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getUniqueAlbumName(String album, Iterable<String> albums) {
|
void updateTags() {
|
||||||
|
final tags = _rawEntries.expand((entry) => entry.xmpSubjects).toSet();
|
||||||
|
final sorted = tags.toList()..sort(compareAsciiUpperCase);
|
||||||
|
sortedTags = List.unmodifiable(sorted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(ImageEntry entry) {
|
||||||
|
_rawEntries.add(entry);
|
||||||
|
eventBus.fire(EntryAddedEvent(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addAll(Iterable<ImageEntry> entries) {
|
||||||
|
_rawEntries.addAll(entries);
|
||||||
|
eventBus.fire(const EntryAddedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> delete(ImageEntry entry) async {
|
||||||
|
final success = await ImageFileService.delete(entry);
|
||||||
|
if (success) {
|
||||||
|
_rawEntries.remove(entry);
|
||||||
|
eventBus.fire(EntryRemovedEvent(entry));
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getUniqueAlbumName(String album, Iterable<String> albums) {
|
||||||
final otherAlbums = albums.where((item) => item != album);
|
final otherAlbums = albums.where((item) => item != album);
|
||||||
final parts = album.split(separator);
|
final parts = album.split(separator);
|
||||||
int partCount = 0;
|
int partCount = 0;
|
||||||
|
@ -209,6 +141,16 @@ class ImageCollection with ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SortFactor { date, size, name }
|
class MetadataChangedEvent {}
|
||||||
|
|
||||||
enum GroupFactor { album, month, day }
|
class EntryAddedEvent {
|
||||||
|
final ImageEntry entry;
|
||||||
|
|
||||||
|
const EntryAddedEvent([this.entry]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntryRemovedEvent {
|
||||||
|
final ImageEntry entry;
|
||||||
|
|
||||||
|
const EntryRemovedEvent(this.entry);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_filters.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
|
import 'package:aves/model/collection_source.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/utils/color_utils.dart';
|
import 'package:aves/utils/color_utils.dart';
|
||||||
import 'package:aves/widgets/album/filtered_collection_page.dart';
|
import 'package:aves/widgets/album/filtered_collection_page.dart';
|
||||||
|
@ -16,10 +17,11 @@ class AllCollectionDrawer extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final collection = Provider.of<ImageCollection>(context);
|
final collection = Provider.of<CollectionLens>(context);
|
||||||
final tags = collection.sortedTags;
|
final source = collection.source;
|
||||||
|
final tags = source.sortedTags;
|
||||||
final regularAlbums = [], appAlbums = [], specialAlbums = [];
|
final regularAlbums = [], appAlbums = [], specialAlbums = [];
|
||||||
for (var album in collection.sortedAlbums) {
|
for (var album in source.sortedAlbums) {
|
||||||
switch (androidFileUtils.getAlbumType(album)) {
|
switch (androidFileUtils.getAlbumType(album)) {
|
||||||
case AlbumType.Default:
|
case AlbumType.Default:
|
||||||
regularAlbums.add(album);
|
regularAlbums.add(album);
|
||||||
|
@ -37,13 +39,13 @@ class AllCollectionDrawer extends StatelessWidget {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
leading: const Icon(OMIcons.videoLibrary),
|
leading: const Icon(OMIcons.videoLibrary),
|
||||||
title: 'Videos',
|
title: 'Videos',
|
||||||
filter: (entry) => entry.isVideo,
|
filter: VideoFilter(),
|
||||||
);
|
);
|
||||||
final buildAlbumEntry = (album) => _FilteredCollectionNavTile(
|
final buildAlbumEntry = (album) => _FilteredCollectionNavTile(
|
||||||
collection: collection,
|
collection: collection,
|
||||||
leading: IconUtils.getAlbumIcon(context, album) ?? const Icon(OMIcons.photoAlbum),
|
leading: IconUtils.getAlbumIcon(context, album) ?? const Icon(OMIcons.photoAlbum),
|
||||||
title: collection.getUniqueAlbumName(album, collection.sortedAlbums),
|
title: CollectionSource.getUniqueAlbumName(album, source.sortedAlbums),
|
||||||
filter: (entry) => entry.directory == album,
|
filter: AlbumFilter(album),
|
||||||
);
|
);
|
||||||
final buildTagEntry = (tag) => _FilteredCollectionNavTile(
|
final buildTagEntry = (tag) => _FilteredCollectionNavTile(
|
||||||
collection: collection,
|
collection: collection,
|
||||||
|
@ -52,7 +54,7 @@ class AllCollectionDrawer extends StatelessWidget {
|
||||||
color: stringToColor(tag),
|
color: stringToColor(tag),
|
||||||
),
|
),
|
||||||
title: tag,
|
title: tag,
|
||||||
filter: (entry) => entry.xmpSubjects.contains(tag),
|
filter: TagFilter(tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Drawer(
|
return Drawer(
|
||||||
|
@ -109,12 +111,12 @@ class AllCollectionDrawer extends StatelessWidget {
|
||||||
Row(children: [
|
Row(children: [
|
||||||
const Icon(OMIcons.photoAlbum),
|
const Icon(OMIcons.photoAlbum),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text('${collection.albumCount}'),
|
Text('${source.albumCount}'),
|
||||||
]),
|
]),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
const Icon(OMIcons.label),
|
const Icon(OMIcons.label),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text('${collection.tagCount}'),
|
Text('${source.tagCount}'),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -147,10 +149,10 @@ class AllCollectionDrawer extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FilteredCollectionNavTile extends StatelessWidget {
|
class _FilteredCollectionNavTile extends StatelessWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final Widget leading;
|
final Widget leading;
|
||||||
final String title;
|
final String title;
|
||||||
final bool Function(ImageEntry) filter;
|
final CollectionFilter filter;
|
||||||
|
|
||||||
const _FilteredCollectionNavTile({
|
const _FilteredCollectionNavTile({
|
||||||
@required this.collection,
|
@required this.collection,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/settings.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
import 'package:aves/widgets/album/search_delegate.dart';
|
import 'package:aves/widgets/album/search_delegate.dart';
|
||||||
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
||||||
|
@ -31,7 +31,7 @@ class _AllCollectionAppBar extends SliverAppBar {
|
||||||
static List<Widget> _buildActions() {
|
static List<Widget> _buildActions() {
|
||||||
return [
|
return [
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) => Consumer<ImageCollection>(
|
builder: (context) => Consumer<CollectionLens>(
|
||||||
builder: (context, collection, child) => IconButton(
|
builder: (context, collection, child) => IconButton(
|
||||||
icon: Icon(OMIcons.search),
|
icon: Icon(OMIcons.search),
|
||||||
onPressed: () => showSearch(
|
onPressed: () => showSearch(
|
||||||
|
@ -42,7 +42,7 @@ class _AllCollectionAppBar extends SliverAppBar {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) => Consumer<ImageCollection>(
|
builder: (context) => Consumer<CollectionLens>(
|
||||||
builder: (context, collection, child) => PopupMenuButton<AlbumAction>(
|
builder: (context, collection, child) => PopupMenuButton<AlbumAction>(
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
|
@ -85,7 +85,7 @@ class _AllCollectionAppBar extends SliverAppBar {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _onActionSelected(BuildContext context, ImageCollection collection, AlbumAction action) {
|
static void _onActionSelected(BuildContext context, CollectionLens collection, AlbumAction action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case AlbumAction.debug:
|
case AlbumAction.debug:
|
||||||
_goToDebug(context, collection);
|
_goToDebug(context, collection);
|
||||||
|
@ -117,7 +117,7 @@ class _AllCollectionAppBar extends SliverAppBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future _goToDebug(BuildContext context, ImageCollection collection) {
|
static Future _goToDebug(BuildContext context, CollectionLens collection) {
|
||||||
return Navigator.push(
|
return Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
|
import 'package:aves/model/collection_source.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/album/sections.dart';
|
import 'package:aves/widgets/album/sections.dart';
|
||||||
import 'package:aves/widgets/album/thumbnail.dart';
|
import 'package:aves/widgets/album/thumbnail.dart';
|
||||||
|
@ -10,7 +11,7 @@ import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class SectionSliver extends StatelessWidget {
|
class SectionSliver extends StatelessWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final dynamic sectionKey;
|
final dynamic sectionKey;
|
||||||
final int columnCount;
|
final int columnCount;
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ class ThumbnailMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SectionHeader extends StatelessWidget {
|
class SectionHeader extends StatelessWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final Map<dynamic, List<ImageEntry>> sections;
|
final Map<dynamic, List<ImageEntry>> sections;
|
||||||
final dynamic sectionKey;
|
final dynamic sectionKey;
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ class SectionHeader extends StatelessWidget {
|
||||||
child: albumIcon,
|
child: albumIcon,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var title = collection.getUniqueAlbumName(sectionKey as String, sections.keys.cast<String>());
|
final title = CollectionSource.getUniqueAlbumName(sectionKey as String, sections.keys.cast<String>());
|
||||||
return TitleSectionHeader(
|
return TitleSectionHeader(
|
||||||
key: ValueKey(title),
|
key: ValueKey(title),
|
||||||
leading: albumIcon,
|
leading: albumIcon,
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_filters.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class FilteredCollectionPage extends StatelessWidget {
|
class FilteredCollectionPage extends StatelessWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final bool Function(ImageEntry) filter;
|
final CollectionFilter filter;
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
FilteredCollectionPage({Key key, ImageCollection collection, this.filter, this.title})
|
FilteredCollectionPage({Key key, CollectionLens collection, this.filter, this.title})
|
||||||
: this.collection = collection.filter(filter),
|
: this.collection = CollectionLens.from(collection, filter),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: ChangeNotifierProvider<ImageCollection>.value(
|
body: ChangeNotifierProvider<CollectionLens>.value(
|
||||||
value: collection,
|
value: collection,
|
||||||
child: ThumbnailCollection(
|
child: ThumbnailCollection(
|
||||||
appBar: SliverAppBar(
|
appBar: SliverAppBar(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_filters.dart';
|
||||||
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
@ -7,7 +8,7 @@ import 'package:outline_material_icons/outline_material_icons.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
|
|
||||||
ImageSearchDelegate(this.collection);
|
ImageSearchDelegate(this.collection);
|
||||||
|
|
||||||
|
@ -54,19 +55,17 @@ class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
||||||
showSuggestions(context);
|
showSuggestions(context);
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
final lowerQuery = query.toLowerCase();
|
|
||||||
final matches = collection.sortedEntries.where((entry) => entry.search(lowerQuery)).toList();
|
|
||||||
if (matches.isEmpty) {
|
|
||||||
return _EmptyContent();
|
|
||||||
}
|
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: ChangeNotifierProvider<ImageCollection>.value(
|
child: ChangeNotifierProvider<CollectionLens>.value(
|
||||||
value: ImageCollection(
|
value: CollectionLens(
|
||||||
entries: matches,
|
source: collection.source,
|
||||||
|
filters: [MetadataFilter(query.toLowerCase())],
|
||||||
groupFactor: collection.groupFactor,
|
groupFactor: collection.groupFactor,
|
||||||
sortFactor: collection.sortFactor,
|
sortFactor: collection.sortFactor,
|
||||||
),
|
),
|
||||||
child: ThumbnailCollection(),
|
child: ThumbnailCollection(
|
||||||
|
emptyBuilder: (context) => _EmptyContent(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +75,8 @@ class _EmptyContent extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const color = Color(0xFF607D8B);
|
const color = Color(0xFF607D8B);
|
||||||
return Center(
|
return Align(
|
||||||
|
alignment: const FractionalOffset(.5, .4),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: const [
|
children: const [
|
||||||
|
|
|
@ -10,7 +10,7 @@ class Thumbnail extends StatelessWidget {
|
||||||
final double extent;
|
final double extent;
|
||||||
|
|
||||||
static final Color borderColor = Colors.grey.shade700;
|
static final Color borderColor = Colors.grey.shade700;
|
||||||
static const double borderWidth = .5;
|
static const double borderWidth = .5;
|
||||||
|
|
||||||
const Thumbnail({
|
const Thumbnail({
|
||||||
Key key,
|
Key key,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/widgets/album/collection_scaling.dart';
|
import 'package:aves/widgets/album/collection_scaling.dart';
|
||||||
import 'package:aves/widgets/album/collection_section.dart';
|
import 'package:aves/widgets/album/collection_section.dart';
|
||||||
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
||||||
|
@ -7,6 +7,8 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ThumbnailCollection extends StatelessWidget {
|
class ThumbnailCollection extends StatelessWidget {
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
|
final WidgetBuilder emptyBuilder;
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
final ValueNotifier<int> _columnCountNotifier = ValueNotifier(4);
|
final ValueNotifier<int> _columnCountNotifier = ValueNotifier(4);
|
||||||
final GlobalKey _scrollableKey = GlobalKey();
|
final GlobalKey _scrollableKey = GlobalKey();
|
||||||
|
@ -14,11 +16,12 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
ThumbnailCollection({
|
ThumbnailCollection({
|
||||||
Key key,
|
Key key,
|
||||||
this.appBar,
|
this.appBar,
|
||||||
|
this.emptyBuilder,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final collection = Provider.of<ImageCollection>(context);
|
final collection = Provider.of<CollectionLens>(context);
|
||||||
final sections = collection.sections;
|
final sections = collection.sections;
|
||||||
final sectionKeys = sections.keys.toList();
|
final sectionKeys = sections.keys.toList();
|
||||||
|
|
||||||
|
@ -56,6 +59,12 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
if (appBar != null) appBar,
|
if (appBar != null) appBar,
|
||||||
|
if (collection.isEmpty && emptyBuilder != null)
|
||||||
|
SliverFillViewport(
|
||||||
|
delegate: SliverChildListDelegate(
|
||||||
|
[emptyBuilder(context)],
|
||||||
|
),
|
||||||
|
),
|
||||||
...sectionKeys.map((sectionKey) => SectionSliver(
|
...sectionKeys.map((sectionKey) => SectionSliver(
|
||||||
collection: collection,
|
collection: collection,
|
||||||
sectionKey: sectionKey,
|
sectionKey: sectionKey,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
|
import 'package:aves/model/collection_source.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/image_file_service.dart';
|
import 'package:aves/model/image_file_service.dart';
|
||||||
import 'package:aves/model/metadata_db.dart';
|
import 'package:aves/model/metadata_db.dart';
|
||||||
|
@ -18,7 +19,7 @@ class MediaStoreCollectionProvider extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvider> {
|
class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvider> {
|
||||||
Future<ImageCollection> collectionFuture;
|
Future<CollectionLens> collectionFuture;
|
||||||
|
|
||||||
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
|
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
|
||||||
|
|
||||||
|
@ -28,11 +29,14 @@ class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvi
|
||||||
collectionFuture = _create();
|
collectionFuture = _create();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ImageCollection> _create() async {
|
Future<CollectionLens> _create() async {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
final mediaStoreCollection = ImageCollection(entries: []);
|
final mediaStoreSource = CollectionSource();
|
||||||
mediaStoreCollection.groupFactor = settings.collectionGroupFactor;
|
final mediaStoreBaseLens = CollectionLens(
|
||||||
mediaStoreCollection.sortFactor = settings.collectionSortFactor;
|
source: mediaStoreSource,
|
||||||
|
groupFactor: settings.collectionGroupFactor,
|
||||||
|
sortFactor: settings.collectionSortFactor,
|
||||||
|
);
|
||||||
|
|
||||||
await metadataDb.init(); // <20ms
|
await metadataDb.init(); // <20ms
|
||||||
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
|
final currentTimeZone = await FlutterNativeTimezone.getLocalTimezone(); // <20ms
|
||||||
|
@ -44,17 +48,18 @@ class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvi
|
||||||
settings.catalogTimeZone = currentTimeZone;
|
settings.catalogTimeZone = currentTimeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final allEntries = List<ImageEntry>();
|
||||||
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
eventChannel.receiveBroadcastStream().cast<Map>().listen(
|
||||||
(entryMap) => mediaStoreCollection.add(ImageEntry.fromMap(entryMap)),
|
(entryMap) => allEntries.add(ImageEntry.fromMap(entryMap)),
|
||||||
onDone: () async {
|
onDone: () async {
|
||||||
debugPrint('$runtimeType stream complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
debugPrint('$runtimeType stream complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
mediaStoreCollection.updateSections(); // <50ms
|
mediaStoreSource.addAll(allEntries);
|
||||||
// TODO reduce setup time until here
|
// TODO reduce setup time until here
|
||||||
mediaStoreCollection.updateAlbums(); // <50ms
|
mediaStoreSource.updateAlbums(); // <50ms
|
||||||
await mediaStoreCollection.loadCatalogMetadata(); // 650ms
|
await mediaStoreSource.loadCatalogMetadata(); // 650ms
|
||||||
await mediaStoreCollection.catalogEntries(); // <50ms
|
await mediaStoreSource.catalogEntries(); // <50ms
|
||||||
await mediaStoreCollection.loadAddresses(); // 350ms
|
await mediaStoreSource.loadAddresses(); // 350ms
|
||||||
await mediaStoreCollection.locateEntries(); // <50ms
|
await mediaStoreSource.locateEntries(); // <50ms
|
||||||
debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}');
|
debugPrint('$runtimeType setup end, elapsed=${stopwatch.elapsed}');
|
||||||
},
|
},
|
||||||
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
|
onError: (error) => debugPrint('$runtimeType mediastore stream error=$error'),
|
||||||
|
@ -63,16 +68,16 @@ class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvi
|
||||||
// TODO split image fetch AND/OR cache fetch across sessions
|
// TODO split image fetch AND/OR cache fetch across sessions
|
||||||
await ImageFileService.getImageEntries(); // 460ms
|
await ImageFileService.getImageEntries(); // 460ms
|
||||||
|
|
||||||
return mediaStoreCollection;
|
return mediaStoreBaseLens;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: collectionFuture,
|
future: collectionFuture,
|
||||||
builder: (futureContext, AsyncSnapshot<ImageCollection> snapshot) {
|
builder: (futureContext, AsyncSnapshot<CollectionLens> snapshot) {
|
||||||
final collection = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : ImageCollection(entries: []);
|
final collection = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : CollectionLens.empty();
|
||||||
return ChangeNotifierProvider<ImageCollection>.value(
|
return ChangeNotifierProvider<CollectionLens>.value(
|
||||||
value: collection,
|
value: collection,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/utils/android_app_service.dart';
|
import 'package:aves/utils/android_app_service.dart';
|
||||||
import 'package:flushbar/flushbar.dart';
|
import 'package:flushbar/flushbar.dart';
|
||||||
|
@ -12,7 +12,7 @@ import 'package:printing/printing.dart';
|
||||||
enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share }
|
enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share }
|
||||||
|
|
||||||
class FullscreenActionDelegate {
|
class FullscreenActionDelegate {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final VoidCallback showInfo;
|
final VoidCallback showInfo;
|
||||||
|
|
||||||
FullscreenActionDelegate({
|
FullscreenActionDelegate({
|
||||||
|
@ -109,7 +109,7 @@ class FullscreenActionDelegate {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (confirmed == null || !confirmed) return;
|
if (confirmed == null || !confirmed) return;
|
||||||
if (!await collection.delete(entry)) {
|
if (!await collection.source.delete(entry)) {
|
||||||
_showFeedback(context, 'Failed');
|
_showFeedback(context, 'Failed');
|
||||||
} else if (collection.sortedEntries.isEmpty) {
|
} else if (collection.sortedEntries.isEmpty) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart';
|
import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart';
|
||||||
|
@ -19,7 +19,7 @@ import 'package:tuple/tuple.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class FullscreenPage extends AnimatedWidget {
|
class FullscreenPage extends AnimatedWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final String initialUri;
|
final String initialUri;
|
||||||
|
|
||||||
const FullscreenPage({
|
const FullscreenPage({
|
||||||
|
@ -44,7 +44,7 @@ class FullscreenPage extends AnimatedWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FullscreenBody extends StatefulWidget {
|
class FullscreenBody extends StatefulWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final String initialUri;
|
final String initialUri;
|
||||||
|
|
||||||
const FullscreenBody({
|
const FullscreenBody({
|
||||||
|
@ -69,7 +69,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
FullscreenActionDelegate _actionDelegate;
|
FullscreenActionDelegate _actionDelegate;
|
||||||
final List<Tuple2<String, VideoPlayerController>> _videoControllers = [];
|
final List<Tuple2<String, VideoPlayerController>> _videoControllers = [];
|
||||||
|
|
||||||
ImageCollection get collection => widget.collection;
|
CollectionLens get collection => widget.collection;
|
||||||
|
|
||||||
List<ImageEntry> get entries => widget.collection.sortedEntries;
|
List<ImageEntry> get entries => widget.collection.sortedEntries;
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
}
|
}
|
||||||
|
|
||||||
class FullscreenVerticalPageView extends StatefulWidget {
|
class FullscreenVerticalPageView extends StatefulWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
final List<Tuple2<String, VideoPlayerController>> videoControllers;
|
final List<Tuple2<String, VideoPlayerController>> videoControllers;
|
||||||
final PageController horizontalPager, verticalPager;
|
final PageController horizontalPager, verticalPager;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/fullscreen/video.dart';
|
import 'package:aves/widgets/fullscreen/video.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -9,7 +9,7 @@ import 'package:tuple/tuple.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class ImagePage extends StatefulWidget {
|
class ImagePage extends StatefulWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final PageController pageController;
|
final PageController pageController;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final ValueChanged<int> onPageChanged;
|
final ValueChanged<int> onPageChanged;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/widgets/common/coma_divider.dart';
|
import 'package:aves/widgets/common/coma_divider.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
@ -13,7 +13,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class InfoPage extends StatefulWidget {
|
class InfoPage extends StatefulWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
final ValueNotifier<bool> visibleNotifier;
|
final ValueNotifier<bool> visibleNotifier;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/collection_filters.dart';
|
||||||
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/utils/color_utils.dart';
|
import 'package:aves/utils/color_utils.dart';
|
||||||
import 'package:aves/widgets/album/filtered_collection_page.dart';
|
import 'package:aves/widgets/album/filtered_collection_page.dart';
|
||||||
|
@ -6,7 +7,7 @@ import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class XmpTagSectionSliver extends AnimatedWidget {
|
class XmpTagSectionSliver extends AnimatedWidget {
|
||||||
final ImageCollection collection;
|
final CollectionLens collection;
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
|
|
||||||
static const double buttonBorderWidth = 2;
|
static const double buttonBorderWidth = 2;
|
||||||
|
@ -56,7 +57,7 @@ class XmpTagSectionSliver extends AnimatedWidget {
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => FilteredCollectionPage(
|
builder: (context) => FilteredCollectionPage(
|
||||||
collection: collection,
|
collection: collection,
|
||||||
filter: (entry) => entry.xmpSubjects.contains(tag),
|
filter: TagFilter(tag),
|
||||||
title: tag,
|
title: tag,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -80,6 +80,13 @@ packages:
|
||||||
url: "git://github.com/deckerst/flutter-draggable-scrollbar.git"
|
url: "git://github.com/deckerst/flutter-draggable-scrollbar.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.4"
|
version: "0.0.4"
|
||||||
|
event_bus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: event_bus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flushbar:
|
flushbar:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -21,6 +21,7 @@ dependencies:
|
||||||
draggable_scrollbar:
|
draggable_scrollbar:
|
||||||
git:
|
git:
|
||||||
url: git://github.com/deckerst/flutter-draggable-scrollbar.git
|
url: git://github.com/deckerst/flutter-draggable-scrollbar.git
|
||||||
|
event_bus:
|
||||||
flushbar:
|
flushbar:
|
||||||
flutter_native_timezone:
|
flutter_native_timezone:
|
||||||
flutter_staggered_grid_view:
|
flutter_staggered_grid_view:
|
||||||
|
|
Loading…
Reference in a new issue