Merge branch 'develop'
This commit is contained in:
commit
77d374ea17
266 changed files with 2340 additions and 1301 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## <a id="unreleased"></a>[Unreleased]
|
||||
|
||||
## <a id="v1.8.4"></a>[v1.8.4] - 2023-03-17
|
||||
|
||||
### Added
|
||||
|
||||
- TV: improved support for Licenses
|
||||
|
||||
### Fixed
|
||||
|
||||
- Viewer: playing video from app content provider
|
||||
- Search: using the query bar yields a black screen
|
||||
|
||||
## <a id="v1.8.3"></a>[v1.8.3] - 2023-03-13
|
||||
|
||||
### Added
|
||||
|
|
5
fastlane/metadata/android/en-US/changelogs/95.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/95.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
In v1.8.4:
|
||||
- view items in full-screen when selecting them
|
||||
- watch videos using picture-in-picture
|
||||
- navigate with TalkBack
|
||||
Full changelog available on GitHub
|
5
fastlane/metadata/android/en-US/changelogs/9501.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/9501.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
In v1.8.4:
|
||||
- view items in full-screen when selecting them
|
||||
- watch videos using picture-in-picture
|
||||
- navigate with TalkBack
|
||||
Full changelog available on GitHub
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/vaults/vaults.dart';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/metadata/address.dart';
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:io';
|
|||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/db/db_metadata.dart';
|
||||
import 'package:aves/model/db/db_metadata_sqflite_upgrade.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/metadata/address.dart';
|
||||
|
|
|
@ -2,51 +2,42 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/geo/countries.dart';
|
||||
import 'package:aves/model/entry_cache.dart';
|
||||
import 'package:aves/model/entry_dirs.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/entry/cache.dart';
|
||||
import 'package:aves/model/entry/dirs.dart';
|
||||
import 'package:aves/model/metadata/address.dart';
|
||||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/metadata/trash.dart';
|
||||
import 'package:aves/model/multipage.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/trash.dart';
|
||||
import 'package:aves/model/video/metadata.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/service_policy.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/services/geocoding_service.dart';
|
||||
import 'package:aves/services/metadata/svg_metadata_service.dart';
|
||||
import 'package:aves/theme/format.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/change_notifier.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:aves/utils/time_utils.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:country_code/country_code.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
enum EntryDataType { basic, aspectRatio, catalog, address, references }
|
||||
|
||||
class EntryOrigins {
|
||||
static const int mediaStoreContent = 0;
|
||||
static const int unknownContent = 1;
|
||||
static const int file = 2;
|
||||
static const int vault = 3;
|
||||
}
|
||||
|
||||
class AvesEntry {
|
||||
// `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode
|
||||
class AvesEntry with AvesEntryBase {
|
||||
@override
|
||||
int id;
|
||||
|
||||
@override
|
||||
String uri;
|
||||
|
||||
@override
|
||||
int? pageId;
|
||||
|
||||
@override
|
||||
int? sizeBytes;
|
||||
|
||||
String? _path, _filename, _extension, _sourceTitle;
|
||||
EntryDir? _directory;
|
||||
int? pageId, contentId;
|
||||
int? contentId;
|
||||
final String sourceMimeType;
|
||||
int width, height, sourceRotationDegrees;
|
||||
int? sizeBytes, dateAddedSecs, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis;
|
||||
int? dateAddedSecs, _dateModifiedSecs, sourceDateTakenMillis, _durationMillis;
|
||||
bool trashed;
|
||||
int origin;
|
||||
|
||||
|
@ -57,7 +48,11 @@ class AvesEntry {
|
|||
|
||||
List<AvesEntry>? burstEntries;
|
||||
|
||||
final AChangeNotifier visualChangeNotifier = AChangeNotifier(), metadataChangeNotifier = AChangeNotifier(), addressChangeNotifier = AChangeNotifier();
|
||||
@override
|
||||
final AChangeNotifier visualChangeNotifier = AChangeNotifier();
|
||||
|
||||
final AChangeNotifier metadataChangeNotifier = AChangeNotifier();
|
||||
final AChangeNotifier addressChangeNotifier = AChangeNotifier();
|
||||
|
||||
AvesEntry({
|
||||
required int? id,
|
||||
|
@ -243,140 +238,8 @@ class AvesEntry {
|
|||
// so we use the one found during cataloguing if possible
|
||||
String get mimeType => _catalogMetadata?.mimeType ?? sourceMimeType;
|
||||
|
||||
String get mimeTypeAnySubtype => mimeType.replaceAll(RegExp('/.*'), '/*');
|
||||
|
||||
bool get isFavourite => favourites.isFavourite(this);
|
||||
|
||||
bool get isSvg => mimeType == MimeTypes.svg;
|
||||
|
||||
// guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels)
|
||||
bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(mimeType) || isRaw;
|
||||
|
||||
// Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported"
|
||||
// but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below,
|
||||
// and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested.
|
||||
bool get _supportedByBitmapRegionDecoder =>
|
||||
[
|
||||
MimeTypes.heic,
|
||||
MimeTypes.heif,
|
||||
MimeTypes.jpeg,
|
||||
MimeTypes.png,
|
||||
MimeTypes.webp,
|
||||
MimeTypes.arw,
|
||||
MimeTypes.cr2,
|
||||
MimeTypes.nef,
|
||||
MimeTypes.nrw,
|
||||
MimeTypes.orf,
|
||||
MimeTypes.pef,
|
||||
MimeTypes.raf,
|
||||
MimeTypes.rw2,
|
||||
MimeTypes.srw,
|
||||
].contains(mimeType) &&
|
||||
!isAnimated;
|
||||
|
||||
bool get supportTiling => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;
|
||||
|
||||
bool get useTiles => supportTiling && (width > 4096 || height > 4096);
|
||||
|
||||
bool get isRaw => MimeTypes.rawImages.contains(mimeType);
|
||||
|
||||
bool get isImage => MimeTypes.isImage(mimeType);
|
||||
|
||||
bool get isVideo => MimeTypes.isVideo(mimeType);
|
||||
|
||||
bool get isCatalogued => _catalogMetadata != null;
|
||||
|
||||
bool get isAnimated => _catalogMetadata?.isAnimated ?? false;
|
||||
|
||||
bool get isGeotiff => _catalogMetadata?.isGeotiff ?? false;
|
||||
|
||||
bool get is360 => _catalogMetadata?.is360 ?? false;
|
||||
|
||||
bool get isMediaStoreContent => uri.startsWith('content://media/');
|
||||
|
||||
bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains);
|
||||
|
||||
bool get isVaultContent => path?.startsWith(androidFileUtils.vaultRoot) ?? false;
|
||||
|
||||
bool get canEdit => !settings.isReadOnly && path != null && !trashed && (isMediaStoreContent || isVaultContent);
|
||||
|
||||
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
|
||||
|
||||
bool get canEditLocation => canEdit && (canEditExif || mimeType == MimeTypes.mp4);
|
||||
|
||||
bool get canEditTitleDescription => canEdit && canEditXmp;
|
||||
|
||||
bool get canEditRating => canEdit && canEditXmp;
|
||||
|
||||
bool get canEditTags => canEdit && canEditXmp;
|
||||
|
||||
bool get canRotate => canEdit && (canEditExif || mimeType == MimeTypes.mp4);
|
||||
|
||||
bool get canFlip => canEdit && canEditExif;
|
||||
|
||||
bool get canEditExif => MimeTypes.canEditExif(mimeType);
|
||||
|
||||
bool get canEditIptc => MimeTypes.canEditIptc(mimeType);
|
||||
|
||||
bool get canEditXmp => MimeTypes.canEditXmp(mimeType);
|
||||
|
||||
bool get canRemoveMetadata => MimeTypes.canRemoveMetadata(mimeType);
|
||||
|
||||
// Media Store size/rotation is inaccurate, e.g. a portrait FHD video is rotated according to its metadata,
|
||||
// so it should be registered as width=1920, height=1080, orientation=90,
|
||||
// but is incorrectly registered as width=1080, height=1920, orientation=0.
|
||||
// Double-checking the width/height during loading or cataloguing is the proper solution, but it would take space and time.
|
||||
// Comparing width and height can help with the portrait FHD video example,
|
||||
// but it fails for a portrait screenshot rotated, which is landscape with width=1080, height=1920, orientation=90
|
||||
bool get isRotated => rotationDegrees % 180 == 90;
|
||||
|
||||
static const ratioSeparator = '\u2236';
|
||||
static const resolutionSeparator = ' \u00D7 ';
|
||||
|
||||
bool get isSized => width > 0 && height > 0;
|
||||
|
||||
String get resolutionText {
|
||||
final ws = width;
|
||||
final hs = height;
|
||||
return isRotated ? '$hs$resolutionSeparator$ws' : '$ws$resolutionSeparator$hs';
|
||||
}
|
||||
|
||||
String get aspectRatioText {
|
||||
if (width > 0 && height > 0) {
|
||||
final gcd = width.gcd(height);
|
||||
final w = width ~/ gcd;
|
||||
final h = height ~/ gcd;
|
||||
return isRotated ? '$h$ratioSeparator$w' : '$w$ratioSeparator$h';
|
||||
} else {
|
||||
return '?$ratioSeparator?';
|
||||
}
|
||||
}
|
||||
|
||||
double get displayAspectRatio {
|
||||
if (width == 0 || height == 0) return 1;
|
||||
return isRotated ? height / width : width / height;
|
||||
}
|
||||
|
||||
Size get displaySize {
|
||||
final w = width.toDouble();
|
||||
final h = height.toDouble();
|
||||
return isRotated ? Size(h, w) : Size(w, h);
|
||||
}
|
||||
|
||||
Size videoDisplaySize(double sar) {
|
||||
final size = displaySize;
|
||||
if (sar != 1) {
|
||||
final dar = displayAspectRatio * sar;
|
||||
final w = size.width;
|
||||
final h = size.height;
|
||||
if (w >= h) return Size(w, w / dar);
|
||||
if (h > w) return Size(h * dar, h);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
int get megaPixels => (width * height / 1000000).round();
|
||||
|
||||
DateTime? _bestDate;
|
||||
|
||||
DateTime? get bestDate {
|
||||
|
@ -386,6 +249,7 @@ class AvesEntry {
|
|||
|
||||
int get rating => _catalogMetadata?.rating ?? 0;
|
||||
|
||||
@override
|
||||
int get rotationDegrees => _catalogMetadata?.rotationDegrees ?? sourceRotationDegrees;
|
||||
|
||||
set rotationDegrees(int rotationDegrees) {
|
||||
|
@ -397,6 +261,27 @@ class AvesEntry {
|
|||
|
||||
set isFlipped(bool isFlipped) => _catalogMetadata?.isFlipped = isFlipped;
|
||||
|
||||
// Media Store size/rotation is inaccurate, e.g. a portrait FHD video is rotated according to its metadata,
|
||||
// so it should be registered as width=1920, height=1080, orientation=90,
|
||||
// but is incorrectly registered as width=1080, height=1920, orientation=0.
|
||||
// Double-checking the width/height during loading or cataloguing is the proper solution, but it would take space and time.
|
||||
// Comparing width and height can help with the portrait FHD video example,
|
||||
// but it fails for a portrait screenshot rotated, which is landscape with width=1080, height=1920, orientation=90
|
||||
bool get isRotated => rotationDegrees % 180 == 90;
|
||||
|
||||
@override
|
||||
double get displayAspectRatio {
|
||||
if (width == 0 || height == 0) return 1;
|
||||
return isRotated ? height / width : width / height;
|
||||
}
|
||||
|
||||
@override
|
||||
Size get displaySize {
|
||||
final w = width.toDouble();
|
||||
final h = height.toDouble();
|
||||
return isRotated ? Size(h, w) : Size(w, h);
|
||||
}
|
||||
|
||||
String? get sourceTitle => _sourceTitle;
|
||||
|
||||
set sourceTitle(String? sourceTitle) {
|
||||
|
@ -423,6 +308,7 @@ class AvesEntry {
|
|||
return d == null ? null : DateTime(d.year, d.month, d.day);
|
||||
}
|
||||
|
||||
@override
|
||||
int? get durationMillis => _durationMillis;
|
||||
|
||||
set durationMillis(int? durationMillis) {
|
||||
|
@ -459,8 +345,6 @@ class AvesEntry {
|
|||
// derived from Google reverse geocoding addresses
|
||||
bool get hasFineAddress => _addressDetails?.place?.isNotEmpty == true || (_addressDetails?.countryName?.length ?? 0) > 3;
|
||||
|
||||
LatLng? get latLng => hasGps ? LatLng(_catalogMetadata!.latitude!, _catalogMetadata!.longitude!) : null;
|
||||
|
||||
Set<String>? _tags;
|
||||
|
||||
Set<String> get tags {
|
||||
|
@ -504,53 +388,6 @@ class AvesEntry {
|
|||
addressDetails = null;
|
||||
}
|
||||
|
||||
Future<void> catalog({required bool background, required bool force, required bool persist}) async {
|
||||
if (isCatalogued && !force) return;
|
||||
if (isSvg) {
|
||||
// vector image sizing is not essential, so we should not spend time for it during loading
|
||||
// but it is useful anyway (for aspect ratios etc.) so we size them during cataloguing
|
||||
final size = await SvgMetadataService.getSize(this);
|
||||
if (size != null) {
|
||||
final fields = {
|
||||
'width': size.width.ceil(),
|
||||
'height': size.height.ceil(),
|
||||
};
|
||||
await applyNewFields(fields, persist: persist);
|
||||
}
|
||||
catalogMetadata = CatalogMetadata(id: id);
|
||||
} else {
|
||||
// pre-processing
|
||||
if (isVideo && (!isSized || durationMillis == 0)) {
|
||||
// exotic video that is not sized during loading
|
||||
final fields = await VideoMetadataFormatter.getLoadingMetadata(this);
|
||||
await applyNewFields(fields, persist: persist);
|
||||
}
|
||||
|
||||
// cataloguing on platform
|
||||
catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background);
|
||||
|
||||
// post-processing
|
||||
if (isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) {
|
||||
catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this);
|
||||
}
|
||||
if (isGeotiff && !hasGps) {
|
||||
final info = await metadataFetchService.getGeoTiffInfo(this);
|
||||
if (info != null) {
|
||||
final center = MappedGeoTiff(
|
||||
info: info,
|
||||
entry: this,
|
||||
).center;
|
||||
if (center != null) {
|
||||
catalogMetadata = catalogMetadata?.copyWith(
|
||||
latitude: center.latitude,
|
||||
longitude: center.longitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddressDetails? get addressDetails => _addressDetails;
|
||||
|
||||
set addressDetails(AddressDetails? newAddress) {
|
||||
|
@ -558,79 +395,6 @@ class AvesEntry {
|
|||
addressChangeNotifier.notify();
|
||||
}
|
||||
|
||||
Future<void> locate({required bool background, required bool force, required Locale geocoderLocale}) async {
|
||||
if (hasGps) {
|
||||
await _locateCountry(force: force);
|
||||
if (await availability.canLocatePlaces) {
|
||||
await locatePlace(background: background, force: force, geocoderLocale: geocoderLocale);
|
||||
}
|
||||
} else {
|
||||
addressDetails = null;
|
||||
}
|
||||
}
|
||||
|
||||
// quick reverse geocoding to find the country, using an offline asset
|
||||
Future<void> _locateCountry({required bool force}) async {
|
||||
if (!hasGps || (hasAddress && !force)) return;
|
||||
final countryCode = await countryTopology.countryCode(latLng!);
|
||||
setCountry(countryCode);
|
||||
}
|
||||
|
||||
void setCountry(CountryCode? countryCode) {
|
||||
if (hasFineAddress || countryCode == null) return;
|
||||
addressDetails = AddressDetails(
|
||||
id: id,
|
||||
countryCode: countryCode.alpha2,
|
||||
countryName: countryCode.alpha3,
|
||||
);
|
||||
}
|
||||
|
||||
// full reverse geocoding, requiring Play Services and some connectivity
|
||||
Future<void> locatePlace({required bool background, required bool force, required Locale geocoderLocale}) async {
|
||||
if (!hasGps || (hasFineAddress && !force)) return;
|
||||
try {
|
||||
Future<List<Address>> call() => GeocodingService.getAddress(latLng!, geocoderLocale);
|
||||
final addresses = await (background
|
||||
? servicePolicy.call(
|
||||
call,
|
||||
priority: ServiceCallPriority.getLocation,
|
||||
)
|
||||
: call());
|
||||
if (addresses.isNotEmpty) {
|
||||
final address = addresses.first;
|
||||
final cc = address.countryCode?.toUpperCase();
|
||||
final cn = address.countryName;
|
||||
final aa = address.adminArea;
|
||||
addressDetails = AddressDetails(
|
||||
id: id,
|
||||
countryCode: cc,
|
||||
countryName: cn,
|
||||
adminArea: aa,
|
||||
// if country & admin fields are null, it is likely the ocean,
|
||||
// which is identified by `featureName` but we default to the address line anyway
|
||||
locality: address.locality ?? (cc == null && cn == null && aa == null ? address.addressLine : null),
|
||||
);
|
||||
}
|
||||
} catch (error, stack) {
|
||||
debugPrint('$runtimeType locate failed with path=$path coordinates=$latLng error=$error\n$stack');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> findAddressLine({required Locale geocoderLocale}) async {
|
||||
if (!hasGps) return null;
|
||||
|
||||
try {
|
||||
final addresses = await GeocodingService.getAddress(latLng!, geocoderLocale);
|
||||
if (addresses.isNotEmpty) {
|
||||
final address = addresses.first;
|
||||
return address.addressLine;
|
||||
}
|
||||
} catch (error, stack) {
|
||||
debugPrint('$runtimeType findAddressLine failed with path=$path coordinates=$latLng error=$error\n$stack');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String get shortAddress {
|
||||
// `admin area` examples: Seoul, Geneva, null
|
||||
// `locality` examples: Mapo-gu, Geneva, Annecy
|
||||
|
@ -732,107 +496,4 @@ class AvesEntry {
|
|||
visualChangeNotifier.notify();
|
||||
}
|
||||
}
|
||||
|
||||
// favourites
|
||||
|
||||
Future<void> toggleFavourite() async {
|
||||
if (isFavourite) {
|
||||
await removeFromFavourites();
|
||||
} else {
|
||||
await addToFavourites();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addToFavourites() async {
|
||||
if (!isFavourite) {
|
||||
await favourites.add({this});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeFromFavourites() async {
|
||||
if (isFavourite) {
|
||||
await favourites.removeEntries({this});
|
||||
}
|
||||
}
|
||||
|
||||
// multipage
|
||||
|
||||
static final _burstFilenamePattern = RegExp(r'^(\d{8}_\d{6})_(\d+)$');
|
||||
|
||||
bool get isMultiPage => (_catalogMetadata?.isMultiPage ?? false) || isBurst;
|
||||
|
||||
bool get isBurst => burstEntries?.isNotEmpty == true;
|
||||
|
||||
// for backward compatibility
|
||||
bool get _isMotionPhotoLegacy => isMultiPage && !isBurst && mimeType == MimeTypes.jpeg;
|
||||
|
||||
bool get isMotionPhoto => (_catalogMetadata?.isMotionPhoto ?? false) || _isMotionPhotoLegacy;
|
||||
|
||||
String? get burstKey {
|
||||
if (filenameWithoutExtension != null) {
|
||||
final match = _burstFilenamePattern.firstMatch(filenameWithoutExtension!);
|
||||
if (match != null) {
|
||||
return '$directory/${match.group(1)}';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<MultiPageInfo?> getMultiPageInfo() async {
|
||||
if (isBurst) {
|
||||
return MultiPageInfo(
|
||||
mainEntry: this,
|
||||
pages: burstEntries!
|
||||
.mapIndexed((index, entry) => SinglePageInfo(
|
||||
index: index,
|
||||
pageId: entry.id,
|
||||
isDefault: index == 0,
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
width: entry.width,
|
||||
height: entry.height,
|
||||
rotationDegrees: entry.rotationDegrees,
|
||||
durationMillis: entry.durationMillis,
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
} else {
|
||||
return await metadataFetchService.getMultiPageInfo(this);
|
||||
}
|
||||
}
|
||||
|
||||
// sort
|
||||
|
||||
// compare by:
|
||||
// 1) title ascending
|
||||
// 2) extension ascending
|
||||
static int compareByName(AvesEntry a, AvesEntry b) {
|
||||
final c = compareAsciiUpperCaseNatural(a.bestTitle ?? '', b.bestTitle ?? '');
|
||||
return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? '');
|
||||
}
|
||||
|
||||
// compare by:
|
||||
// 1) date descending
|
||||
// 2) name descending
|
||||
static int compareByDate(AvesEntry a, AvesEntry b) {
|
||||
var c = (b.bestDate ?? epoch).compareTo(a.bestDate ?? epoch);
|
||||
if (c != 0) return c;
|
||||
return compareByName(b, a);
|
||||
}
|
||||
|
||||
// compare by:
|
||||
// 1) rating descending
|
||||
// 2) date descending
|
||||
static int compareByRating(AvesEntry a, AvesEntry b) {
|
||||
final c = b.rating.compareTo(a.rating);
|
||||
return c != 0 ? c : compareByDate(a, b);
|
||||
}
|
||||
|
||||
// compare by:
|
||||
// 1) size descending
|
||||
// 2) date descending
|
||||
static int compareBySize(AvesEntry a, AvesEntry b) {
|
||||
final c = (b.sizeBytes ?? 0).compareTo(a.sizeBytes ?? 0);
|
||||
return c != 0 ? c : compareByDate(a, b);
|
||||
}
|
||||
}
|
56
lib/model/entry/extensions/catalog.dart
Normal file
56
lib/model/entry/extensions/catalog.dart
Normal file
|
@ -0,0 +1,56 @@
|
|||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/video/metadata.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/services/metadata/svg_metadata_service.dart';
|
||||
|
||||
extension ExtraAvesEntryCatalog on AvesEntry {
|
||||
Future<void> catalog({required bool background, required bool force, required bool persist}) async {
|
||||
if (isCatalogued && !force) return;
|
||||
if (isSvg) {
|
||||
// vector image sizing is not essential, so we should not spend time for it during loading
|
||||
// but it is useful anyway (for aspect ratios etc.) so we size them during cataloguing
|
||||
final size = await SvgMetadataService.getSize(this);
|
||||
if (size != null) {
|
||||
final fields = {
|
||||
'width': size.width.ceil(),
|
||||
'height': size.height.ceil(),
|
||||
};
|
||||
await applyNewFields(fields, persist: persist);
|
||||
}
|
||||
catalogMetadata = CatalogMetadata(id: id);
|
||||
} else {
|
||||
// pre-processing
|
||||
if (isVideo && (!isSized || durationMillis == 0)) {
|
||||
// exotic video that is not sized during loading
|
||||
final fields = await VideoMetadataFormatter.getLoadingMetadata(this);
|
||||
await applyNewFields(fields, persist: persist);
|
||||
}
|
||||
|
||||
// cataloguing on platform
|
||||
catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background);
|
||||
|
||||
// post-processing
|
||||
if (isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) {
|
||||
catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this);
|
||||
}
|
||||
if (isGeotiff && !hasGps) {
|
||||
final info = await metadataFetchService.getGeoTiffInfo(this);
|
||||
if (info != null) {
|
||||
final center = MappedGeoTiff(
|
||||
info: info,
|
||||
entry: this,
|
||||
).center;
|
||||
if (center != null) {
|
||||
catalogMetadata = catalogMetadata?.copyWith(
|
||||
latitude: center.latitude,
|
||||
longitude: center.longitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
lib/model/entry/extensions/favourites.dart
Normal file
26
lib/model/entry/extensions/favourites.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
|
||||
extension ExtraAvesEntryFav on AvesEntry {
|
||||
bool get isFavourite => favourites.isFavourite(this);
|
||||
|
||||
Future<void> toggleFavourite() async {
|
||||
if (isFavourite) {
|
||||
await removeFromFavourites();
|
||||
} else {
|
||||
await addToFavourites();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addToFavourites() async {
|
||||
if (!isFavourite) {
|
||||
await favourites.add({this});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeFromFavourites() async {
|
||||
if (isFavourite) {
|
||||
await favourites.removeEntries({this});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ import 'dart:math';
|
|||
import 'package:aves/image_providers/region_provider.dart';
|
||||
import 'package:aves/image_providers/thumbnail_provider.dart';
|
||||
import 'package:aves/image_providers/uri_image_provider.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry_cache.dart';
|
||||
import 'package:aves/model/entry/cache.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
|
@ -1,8 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/video/keys.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/video/metadata.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
|
@ -10,6 +11,7 @@ import 'package:aves/services/metadata/svg_metadata_service.dart';
|
|||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -79,27 +81,27 @@ extension ExtraAvesEntryInfo on AvesEntry {
|
|||
|
||||
if (mediaInfo.containsKey(Keys.streams)) {
|
||||
String getTypeText(Map stream) {
|
||||
final type = stream[Keys.streamType] ?? StreamTypes.unknown;
|
||||
final type = stream[Keys.streamType] ?? MediaStreamTypes.unknown;
|
||||
switch (type) {
|
||||
case StreamTypes.attachment:
|
||||
case MediaStreamTypes.attachment:
|
||||
return 'Attachment';
|
||||
case StreamTypes.audio:
|
||||
case MediaStreamTypes.audio:
|
||||
return 'Audio';
|
||||
case StreamTypes.metadata:
|
||||
case MediaStreamTypes.metadata:
|
||||
return 'Metadata';
|
||||
case StreamTypes.subtitle:
|
||||
case StreamTypes.timedText:
|
||||
case MediaStreamTypes.subtitle:
|
||||
case MediaStreamTypes.timedText:
|
||||
return 'Text';
|
||||
case StreamTypes.video:
|
||||
case MediaStreamTypes.video:
|
||||
return stream.containsKey(Keys.fpsDen) ? 'Video' : 'Image';
|
||||
case StreamTypes.unknown:
|
||||
case MediaStreamTypes.unknown:
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
final allStreams = (mediaInfo[Keys.streams] as List).cast<Map>();
|
||||
final attachmentStreams = allStreams.where((stream) => stream[Keys.streamType] == StreamTypes.attachment).toList();
|
||||
final attachmentStreams = allStreams.where((stream) => stream[Keys.streamType] == MediaStreamTypes.attachment).toList();
|
||||
final knownStreams = allStreams.whereNot(attachmentStreams.contains);
|
||||
|
||||
// display known streams as separate directories (e.g. video, audio, subs)
|
89
lib/model/entry/extensions/location.dart
Normal file
89
lib/model/entry/extensions/location.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/geo/countries.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/metadata/address.dart';
|
||||
import 'package:aves/services/common/service_policy.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/services/geocoding_service.dart';
|
||||
import 'package:country_code/country_code.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
extension ExtraAvesEntryLocation on AvesEntry {
|
||||
LatLng? get latLng => hasGps ? LatLng(catalogMetadata!.latitude!, catalogMetadata!.longitude!) : null;
|
||||
|
||||
Future<void> locate({required bool background, required bool force, required Locale geocoderLocale}) async {
|
||||
if (hasGps) {
|
||||
await _locateCountry(force: force);
|
||||
if (await availability.canLocatePlaces) {
|
||||
await locatePlace(background: background, force: force, geocoderLocale: geocoderLocale);
|
||||
}
|
||||
} else {
|
||||
addressDetails = null;
|
||||
}
|
||||
}
|
||||
|
||||
// quick reverse geocoding to find the country, using an offline asset
|
||||
Future<void> _locateCountry({required bool force}) async {
|
||||
if (!hasGps || (hasAddress && !force)) return;
|
||||
final countryCode = await countryTopology.countryCode(latLng!);
|
||||
setCountry(countryCode);
|
||||
}
|
||||
|
||||
void setCountry(CountryCode? countryCode) {
|
||||
if (hasFineAddress || countryCode == null) return;
|
||||
addressDetails = AddressDetails(
|
||||
id: id,
|
||||
countryCode: countryCode.alpha2,
|
||||
countryName: countryCode.alpha3,
|
||||
);
|
||||
}
|
||||
|
||||
// full reverse geocoding, requiring Play Services and some connectivity
|
||||
Future<void> locatePlace({required bool background, required bool force, required Locale geocoderLocale}) async {
|
||||
if (!hasGps || (hasFineAddress && !force)) return;
|
||||
try {
|
||||
Future<List<Address>> call() => GeocodingService.getAddress(latLng!, geocoderLocale);
|
||||
final addresses = await (background
|
||||
? servicePolicy.call(
|
||||
call,
|
||||
priority: ServiceCallPriority.getLocation,
|
||||
)
|
||||
: call());
|
||||
if (addresses.isNotEmpty) {
|
||||
final address = addresses.first;
|
||||
final cc = address.countryCode?.toUpperCase();
|
||||
final cn = address.countryName;
|
||||
final aa = address.adminArea;
|
||||
addressDetails = AddressDetails(
|
||||
id: id,
|
||||
countryCode: cc,
|
||||
countryName: cn,
|
||||
adminArea: aa,
|
||||
// if country & admin fields are null, it is likely the ocean,
|
||||
// which is identified by `featureName` but we default to the address line anyway
|
||||
locality: address.locality ?? (cc == null && cn == null && aa == null ? address.addressLine : null),
|
||||
);
|
||||
}
|
||||
} catch (error, stack) {
|
||||
debugPrint('$runtimeType locate failed with path=$path coordinates=$latLng error=$error\n$stack');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> findAddressLine({required Locale geocoderLocale}) async {
|
||||
if (!hasGps) return null;
|
||||
|
||||
try {
|
||||
final addresses = await GeocodingService.getAddress(latLng!, geocoderLocale);
|
||||
if (addresses.isNotEmpty) {
|
||||
final address = addresses.first;
|
||||
return address.addressLine;
|
||||
}
|
||||
} catch (error, stack) {
|
||||
debugPrint('$runtimeType findAddressLine failed with path=$path coordinates=$latLng error=$error\n$stack');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/catalog.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/metadata/date_modifier.dart';
|
||||
import 'package:aves/model/metadata/enums/date_field_source.dart';
|
||||
import 'package:aves/model/metadata/enums/enums.dart';
|
53
lib/model/entry/extensions/multipage.dart
Normal file
53
lib/model/entry/extensions/multipage.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/multipage.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
extension ExtraAvesEntryMultipage on AvesEntry {
|
||||
static final _burstFilenamePattern = RegExp(r'^(\d{8}_\d{6})_(\d+)$');
|
||||
|
||||
bool get isMultiPage => (catalogMetadata?.isMultiPage ?? false) || isBurst;
|
||||
|
||||
bool get isBurst => burstEntries?.isNotEmpty == true;
|
||||
|
||||
// for backward compatibility
|
||||
bool get _isMotionPhotoLegacy => isMultiPage && !isBurst && mimeType == MimeTypes.jpeg;
|
||||
|
||||
bool get isMotionPhoto => (catalogMetadata?.isMotionPhoto ?? false) || _isMotionPhotoLegacy;
|
||||
|
||||
String? get burstKey {
|
||||
if (filenameWithoutExtension != null) {
|
||||
final match = _burstFilenamePattern.firstMatch(filenameWithoutExtension!);
|
||||
if (match != null) {
|
||||
return '$directory/${match.group(1)}';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<MultiPageInfo?> getMultiPageInfo() async {
|
||||
if (isBurst) {
|
||||
return MultiPageInfo(
|
||||
mainEntry: this,
|
||||
pages: burstEntries!
|
||||
.mapIndexed((index, entry) => SinglePageInfo(
|
||||
index: index,
|
||||
pageId: entry.id,
|
||||
isDefault: index == 0,
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
width: entry.width,
|
||||
height: entry.height,
|
||||
rotationDegrees: entry.rotationDegrees,
|
||||
durationMillis: entry.durationMillis,
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
} else {
|
||||
return await metadataFetchService.getMultiPageInfo(this);
|
||||
}
|
||||
}
|
||||
}
|
119
lib/model/entry/extensions/props.dart
Normal file
119
lib/model/entry/extensions/props.dart
Normal file
|
@ -0,0 +1,119 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
|
||||
extension ExtraAvesEntryProps on AvesEntry {
|
||||
String get mimeTypeAnySubtype => mimeType.replaceAll(RegExp('/.*'), '/*');
|
||||
|
||||
bool get isSvg => mimeType == MimeTypes.svg;
|
||||
|
||||
// guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels)
|
||||
bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(mimeType) || isRaw;
|
||||
|
||||
// Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported"
|
||||
// but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below,
|
||||
// and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested.
|
||||
bool get _supportedByBitmapRegionDecoder =>
|
||||
[
|
||||
MimeTypes.heic,
|
||||
MimeTypes.heif,
|
||||
MimeTypes.jpeg,
|
||||
MimeTypes.png,
|
||||
MimeTypes.webp,
|
||||
MimeTypes.arw,
|
||||
MimeTypes.cr2,
|
||||
MimeTypes.nef,
|
||||
MimeTypes.nrw,
|
||||
MimeTypes.orf,
|
||||
MimeTypes.pef,
|
||||
MimeTypes.raf,
|
||||
MimeTypes.rw2,
|
||||
MimeTypes.srw,
|
||||
].contains(mimeType) &&
|
||||
!isAnimated;
|
||||
|
||||
bool get supportTiling => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;
|
||||
|
||||
bool get useTiles => supportTiling && (width > 4096 || height > 4096);
|
||||
|
||||
bool get isRaw => MimeTypes.rawImages.contains(mimeType);
|
||||
|
||||
bool get isImage => MimeTypes.isImage(mimeType);
|
||||
|
||||
bool get isVideo => MimeTypes.isVideo(mimeType);
|
||||
|
||||
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
|
||||
|
||||
bool get isGeotiff => catalogMetadata?.isGeotiff ?? false;
|
||||
|
||||
bool get is360 => catalogMetadata?.is360 ?? false;
|
||||
|
||||
bool get isMediaStoreContent => uri.startsWith('content://media/');
|
||||
|
||||
bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains);
|
||||
|
||||
bool get isVaultContent => path?.startsWith(androidFileUtils.vaultRoot) ?? false;
|
||||
|
||||
bool get canEdit => !settings.isReadOnly && path != null && !trashed && (isMediaStoreContent || isVaultContent);
|
||||
|
||||
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
|
||||
|
||||
bool get canEditLocation => canEdit && (canEditExif || mimeType == MimeTypes.mp4);
|
||||
|
||||
bool get canEditTitleDescription => canEdit && canEditXmp;
|
||||
|
||||
bool get canEditRating => canEdit && canEditXmp;
|
||||
|
||||
bool get canEditTags => canEdit && canEditXmp;
|
||||
|
||||
bool get canRotate => canEdit && (canEditExif || mimeType == MimeTypes.mp4);
|
||||
|
||||
bool get canFlip => canEdit && canEditExif;
|
||||
|
||||
bool get canEditExif => MimeTypes.canEditExif(mimeType);
|
||||
|
||||
bool get canEditIptc => MimeTypes.canEditIptc(mimeType);
|
||||
|
||||
bool get canEditXmp => MimeTypes.canEditXmp(mimeType);
|
||||
|
||||
bool get canRemoveMetadata => MimeTypes.canRemoveMetadata(mimeType);
|
||||
|
||||
static const ratioSeparator = '\u2236';
|
||||
static const resolutionSeparator = ' \u00D7 ';
|
||||
|
||||
bool get isSized => width > 0 && height > 0;
|
||||
|
||||
String get resolutionText {
|
||||
final ws = width;
|
||||
final hs = height;
|
||||
return isRotated ? '$hs$resolutionSeparator$ws' : '$ws$resolutionSeparator$hs';
|
||||
}
|
||||
|
||||
String get aspectRatioText {
|
||||
if (width > 0 && height > 0) {
|
||||
final gcd = width.gcd(height);
|
||||
final w = width ~/ gcd;
|
||||
final h = height ~/ gcd;
|
||||
return isRotated ? '$h$ratioSeparator$w' : '$w$ratioSeparator$h';
|
||||
} else {
|
||||
return '?$ratioSeparator?';
|
||||
}
|
||||
}
|
||||
|
||||
Size videoDisplaySize(double sar) {
|
||||
final size = displaySize;
|
||||
if (sar != 1) {
|
||||
final dar = displayAspectRatio * sar;
|
||||
final w = size.width;
|
||||
final h = size.height;
|
||||
if (w >= h) return Size(w, w / dar);
|
||||
if (h > w) return Size(h * dar, h);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
int get megaPixels => (width * height / 1000000).round();
|
||||
}
|
6
lib/model/entry/origins.dart
Normal file
6
lib/model/entry/origins.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
class EntryOrigins {
|
||||
static const int mediaStoreContent = 0;
|
||||
static const int unknownContent = 1;
|
||||
static const int file = 2;
|
||||
static const int vault = 3;
|
||||
}
|
38
lib/model/entry/sort.dart
Normal file
38
lib/model/entry/sort.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/utils/time_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class AvesEntrySort {
|
||||
// compare by:
|
||||
// 1) title ascending
|
||||
// 2) extension ascending
|
||||
static int compareByName(AvesEntry a, AvesEntry b) {
|
||||
final c = compareAsciiUpperCaseNatural(a.bestTitle ?? '', b.bestTitle ?? '');
|
||||
return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? '');
|
||||
}
|
||||
|
||||
// compare by:
|
||||
// 1) date descending
|
||||
// 2) name descending
|
||||
static int compareByDate(AvesEntry a, AvesEntry b) {
|
||||
var c = (b.bestDate ?? epoch).compareTo(a.bestDate ?? epoch);
|
||||
if (c != 0) return c;
|
||||
return compareByName(b, a);
|
||||
}
|
||||
|
||||
// compare by:
|
||||
// 1) rating descending
|
||||
// 2) date descending
|
||||
static int compareByRating(AvesEntry a, AvesEntry b) {
|
||||
final c = b.rating.compareTo(a.rating);
|
||||
return c != 0 ? c : compareByDate(a, b);
|
||||
}
|
||||
|
||||
// compare by:
|
||||
// 1) size descending
|
||||
// 2) date descending
|
||||
static int compareBySize(AvesEntry a, AvesEntry b) {
|
||||
final c = (b.sizeBytes ?? 0).compareTo(a.sizeBytes ?? 0);
|
||||
return c != 0 ? c : compareByDate(a, b);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/l10n/l10n.dart';
|
||||
import 'package:aves/model/entry/extensions/location.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/settings/enums/coordinate_format.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/aspect_ratio.dart';
|
||||
import 'package:aves/model/filters/coordinate.dart';
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/catalog.dart';
|
||||
import 'package:aves/model/entry/extensions/location.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
|
|
|
@ -2,8 +2,8 @@ import 'dart:async';
|
|||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry_images.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/images.dart';
|
||||
import 'package:aves/ref/geotiff.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/utils/change_notifier.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class Query extends ChangeNotifier {
|
||||
|
|
|
@ -1,21 +1,7 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraAccessibilityAnimations on AccessibilityAnimations {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case AccessibilityAnimations.system:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case AccessibilityAnimations.disabled:
|
||||
return context.l10n.accessibilityAnimationsRemove;
|
||||
case AccessibilityAnimations.enabled:
|
||||
return context.l10n.accessibilityAnimationsKeep;
|
||||
}
|
||||
}
|
||||
|
||||
bool get animate {
|
||||
switch (this) {
|
||||
case AccessibilityAnimations.system:
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraAccessibilityTimeout on AccessibilityTimeout {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case AccessibilityTimeout.system:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case AccessibilityTimeout.s1:
|
||||
return context.l10n.timeSeconds(1);
|
||||
case AccessibilityTimeout.s3:
|
||||
return context.l10n.timeSeconds(3);
|
||||
case AccessibilityTimeout.s5:
|
||||
return context.l10n.timeSeconds(5);
|
||||
case AccessibilityTimeout.s10:
|
||||
return context.l10n.timeSeconds(10);
|
||||
case AccessibilityTimeout.s30:
|
||||
return context.l10n.timeSeconds(30);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +1,9 @@
|
|||
import 'package:aves/l10n/l10n.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraCoordinateFormat on CoordinateFormat {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case CoordinateFormat.dms:
|
||||
return context.l10n.coordinateFormatDms;
|
||||
case CoordinateFormat.decimal:
|
||||
return context.l10n.coordinateFormatDecimal;
|
||||
}
|
||||
}
|
||||
|
||||
static const _separator = ', ';
|
||||
|
||||
String format(AppLocalizations l10n, LatLng latLng, {bool minuteSecondPadding = false, int dmsSecondDecimals = 2}) {
|
||||
|
|
|
@ -1,23 +1,10 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraDisplayRefreshRateMode on DisplayRefreshRateMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case DisplayRefreshRateMode.auto:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case DisplayRefreshRateMode.highest:
|
||||
return context.l10n.displayRefreshRatePreferHighest;
|
||||
case DisplayRefreshRateMode.lowest:
|
||||
return context.l10n.displayRefreshRatePreferLowest;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> apply() async {
|
||||
if (!await windowService.isActivity()) return;
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraEntryBackground on EntryBackground {
|
||||
bool get isColor {
|
||||
switch (this) {
|
||||
|
|
|
@ -1,20 +1,8 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraHomePageSetting on HomePageSetting {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case HomePageSetting.collection:
|
||||
return context.l10n.drawerCollectionAll;
|
||||
case HomePageSetting.albums:
|
||||
return context.l10n.drawerAlbumPage;
|
||||
}
|
||||
}
|
||||
|
||||
String get routeName {
|
||||
switch (this) {
|
||||
case HomePageSetting.collection:
|
||||
|
|
278
lib/model/settings/enums/l10n.dart
Normal file
278
lib/model/settings/enums/l10n.dart
Normal file
|
@ -0,0 +1,278 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension ExtraAccessibilityAnimationsName on AccessibilityAnimations {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case AccessibilityAnimations.system:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case AccessibilityAnimations.disabled:
|
||||
return context.l10n.accessibilityAnimationsRemove;
|
||||
case AccessibilityAnimations.enabled:
|
||||
return context.l10n.accessibilityAnimationsKeep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraAccessibilityTimeoutName on AccessibilityTimeout {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case AccessibilityTimeout.system:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case AccessibilityTimeout.s1:
|
||||
return context.l10n.timeSeconds(1);
|
||||
case AccessibilityTimeout.s3:
|
||||
return context.l10n.timeSeconds(3);
|
||||
case AccessibilityTimeout.s5:
|
||||
return context.l10n.timeSeconds(5);
|
||||
case AccessibilityTimeout.s10:
|
||||
return context.l10n.timeSeconds(10);
|
||||
case AccessibilityTimeout.s30:
|
||||
return context.l10n.timeSeconds(30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraAvesThemeBrightnessName on AvesThemeBrightness {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case AvesThemeBrightness.system:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case AvesThemeBrightness.light:
|
||||
return context.l10n.themeBrightnessLight;
|
||||
case AvesThemeBrightness.dark:
|
||||
return context.l10n.themeBrightnessDark;
|
||||
case AvesThemeBrightness.black:
|
||||
return context.l10n.themeBrightnessBlack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraCoordinateFormatName on CoordinateFormat {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case CoordinateFormat.dms:
|
||||
return context.l10n.coordinateFormatDms;
|
||||
case CoordinateFormat.decimal:
|
||||
return context.l10n.coordinateFormatDecimal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraDisplayRefreshRateModeName on DisplayRefreshRateMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case DisplayRefreshRateMode.auto:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case DisplayRefreshRateMode.highest:
|
||||
return context.l10n.displayRefreshRatePreferHighest;
|
||||
case DisplayRefreshRateMode.lowest:
|
||||
return context.l10n.displayRefreshRatePreferLowest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraEntryMapStyleName on EntryMapStyle {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case EntryMapStyle.googleNormal:
|
||||
return context.l10n.mapStyleGoogleNormal;
|
||||
case EntryMapStyle.googleHybrid:
|
||||
return context.l10n.mapStyleGoogleHybrid;
|
||||
case EntryMapStyle.googleTerrain:
|
||||
return context.l10n.mapStyleGoogleTerrain;
|
||||
case EntryMapStyle.hmsNormal:
|
||||
return context.l10n.mapStyleHuaweiNormal;
|
||||
case EntryMapStyle.hmsTerrain:
|
||||
return context.l10n.mapStyleHuaweiTerrain;
|
||||
case EntryMapStyle.osmHot:
|
||||
return context.l10n.mapStyleOsmHot;
|
||||
case EntryMapStyle.stamenToner:
|
||||
return context.l10n.mapStyleStamenToner;
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
return context.l10n.mapStyleStamenWatercolor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraHomePageSettingName on HomePageSetting {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case HomePageSetting.collection:
|
||||
return context.l10n.drawerCollectionAll;
|
||||
case HomePageSetting.albums:
|
||||
return context.l10n.drawerAlbumPage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraKeepScreenOnName on KeepScreenOn {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case KeepScreenOn.never:
|
||||
return context.l10n.keepScreenOnNever;
|
||||
case KeepScreenOn.videoPlayback:
|
||||
return context.l10n.keepScreenOnVideoPlayback;
|
||||
case KeepScreenOn.viewerOnly:
|
||||
return context.l10n.keepScreenOnViewerOnly;
|
||||
case KeepScreenOn.always:
|
||||
return context.l10n.keepScreenOnAlways;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraSlideshowVideoPlaybackName on SlideshowVideoPlayback {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case SlideshowVideoPlayback.skip:
|
||||
return context.l10n.videoPlaybackSkip;
|
||||
case SlideshowVideoPlayback.playMuted:
|
||||
return context.l10n.videoPlaybackMuted;
|
||||
case SlideshowVideoPlayback.playWithSound:
|
||||
return context.l10n.videoPlaybackWithSound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraSubtitlePositionName on SubtitlePosition {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case SubtitlePosition.top:
|
||||
return context.l10n.subtitlePositionTop;
|
||||
case SubtitlePosition.bottom:
|
||||
return context.l10n.subtitlePositionBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraThumbnailOverlayLocationIconName on ThumbnailOverlayLocationIcon {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ThumbnailOverlayLocationIcon.located:
|
||||
return context.l10n.filterLocatedLabel;
|
||||
case ThumbnailOverlayLocationIcon.unlocated:
|
||||
return context.l10n.filterNoLocationLabel;
|
||||
case ThumbnailOverlayLocationIcon.none:
|
||||
return context.l10n.settingsDisabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraThumbnailOverlayTagIconName on ThumbnailOverlayTagIcon {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ThumbnailOverlayTagIcon.tagged:
|
||||
return context.l10n.filterTaggedLabel;
|
||||
case ThumbnailOverlayTagIcon.untagged:
|
||||
return context.l10n.filterNoTagLabel;
|
||||
case ThumbnailOverlayTagIcon.none:
|
||||
return context.l10n.settingsDisabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraUnitSystemName on UnitSystem {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case UnitSystem.metric:
|
||||
return context.l10n.unitSystemMetric;
|
||||
case UnitSystem.imperial:
|
||||
return context.l10n.unitSystemImperial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraVideoAutoPlayModeName on VideoAutoPlayMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoAutoPlayMode.disabled:
|
||||
return context.l10n.settingsDisabled;
|
||||
case VideoAutoPlayMode.playMuted:
|
||||
return context.l10n.videoPlaybackMuted;
|
||||
case VideoAutoPlayMode.playWithSound:
|
||||
return context.l10n.videoPlaybackWithSound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraVideoBackgroundModeName on VideoBackgroundMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoBackgroundMode.disabled:
|
||||
return context.l10n.settingsDisabled;
|
||||
case VideoBackgroundMode.pip:
|
||||
return context.l10n.settingsVideoEnablePip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraVideoControlsName on VideoControls {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoControls.play:
|
||||
return context.l10n.videoControlsPlay;
|
||||
case VideoControls.playSeek:
|
||||
return context.l10n.videoControlsPlaySeek;
|
||||
case VideoControls.playOutside:
|
||||
return context.l10n.videoControlsPlayOutside;
|
||||
case VideoControls.none:
|
||||
return context.l10n.videoControlsNone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraVideoLoopModeName on VideoLoopMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoLoopMode.never:
|
||||
return context.l10n.videoLoopModeNever;
|
||||
case VideoLoopMode.shortOnly:
|
||||
return context.l10n.videoLoopModeShortOnly;
|
||||
case VideoLoopMode.always:
|
||||
return context.l10n.videoLoopModeAlways;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraViewerTransitionName on ViewerTransition {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ViewerTransition.slide:
|
||||
return context.l10n.viewerTransitionSlide;
|
||||
case ViewerTransition.parallax:
|
||||
return context.l10n.viewerTransitionParallax;
|
||||
case ViewerTransition.fade:
|
||||
return context.l10n.viewerTransitionFade;
|
||||
case ViewerTransition.zoomIn:
|
||||
return context.l10n.viewerTransitionZoomIn;
|
||||
case ViewerTransition.none:
|
||||
return context.l10n.viewerTransitionNone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraWidgetDisplayedItemName on WidgetDisplayedItem {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case WidgetDisplayedItem.random:
|
||||
return context.l10n.widgetDisplayedItemRandom;
|
||||
case WidgetDisplayedItem.mostRecent:
|
||||
return context.l10n.widgetDisplayedItemMostRecent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraWidgetOpenPageName on WidgetOpenPage {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case WidgetOpenPage.home:
|
||||
return context.l10n.widgetOpenPageHome;
|
||||
case WidgetOpenPage.collection:
|
||||
return context.l10n.widgetOpenPageCollection;
|
||||
case WidgetOpenPage.viewer:
|
||||
return context.l10n.widgetOpenPageViewer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension ExtraEntryMapStyle on EntryMapStyle {
|
||||
static bool isHeavy(EntryMapStyle? style) {
|
||||
|
@ -16,27 +14,6 @@ extension ExtraEntryMapStyle on EntryMapStyle {
|
|||
}
|
||||
}
|
||||
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case EntryMapStyle.googleNormal:
|
||||
return context.l10n.mapStyleGoogleNormal;
|
||||
case EntryMapStyle.googleHybrid:
|
||||
return context.l10n.mapStyleGoogleHybrid;
|
||||
case EntryMapStyle.googleTerrain:
|
||||
return context.l10n.mapStyleGoogleTerrain;
|
||||
case EntryMapStyle.hmsNormal:
|
||||
return context.l10n.mapStyleHuaweiNormal;
|
||||
case EntryMapStyle.hmsTerrain:
|
||||
return context.l10n.mapStyleHuaweiTerrain;
|
||||
case EntryMapStyle.osmHot:
|
||||
return context.l10n.mapStyleOsmHot;
|
||||
case EntryMapStyle.stamenToner:
|
||||
return context.l10n.mapStyleStamenToner;
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
return context.l10n.mapStyleStamenWatercolor;
|
||||
}
|
||||
}
|
||||
|
||||
bool get needMobileService {
|
||||
switch (this) {
|
||||
case EntryMapStyle.osmHot:
|
||||
|
|
|
@ -1,23 +1,7 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraKeepScreenOn on KeepScreenOn {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case KeepScreenOn.never:
|
||||
return context.l10n.keepScreenOnNever;
|
||||
case KeepScreenOn.videoPlayback:
|
||||
return context.l10n.keepScreenOnVideoPlayback;
|
||||
case KeepScreenOn.viewerOnly:
|
||||
return context.l10n.keepScreenOnViewerOnly;
|
||||
case KeepScreenOn.always:
|
||||
return context.l10n.keepScreenOnAlways;
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
windowService.keepScreenOn(this == KeepScreenOn.always);
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraSlideshowVideoPlayback on SlideshowVideoPlayback {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case SlideshowVideoPlayback.skip:
|
||||
return context.l10n.videoPlaybackSkip;
|
||||
case SlideshowVideoPlayback.playMuted:
|
||||
return context.l10n.videoPlaybackMuted;
|
||||
case SlideshowVideoPlayback.playWithSound:
|
||||
return context.l10n.videoPlaybackWithSound;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,7 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraSubtitlePosition on SubtitlePosition {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case SubtitlePosition.top:
|
||||
return context.l10n.subtitlePositionTop;
|
||||
case SubtitlePosition.bottom:
|
||||
return context.l10n.subtitlePositionBottom;
|
||||
}
|
||||
}
|
||||
|
||||
TextAlignVertical toTextAlignVertical() {
|
||||
switch (this) {
|
||||
case SubtitlePosition.top:
|
||||
|
|
|
@ -1,22 +1,7 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraAvesThemeBrightness on AvesThemeBrightness {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case AvesThemeBrightness.system:
|
||||
return context.l10n.settingsSystemDefault;
|
||||
case AvesThemeBrightness.light:
|
||||
return context.l10n.themeBrightnessLight;
|
||||
case AvesThemeBrightness.dark:
|
||||
return context.l10n.themeBrightnessDark;
|
||||
case AvesThemeBrightness.black:
|
||||
return context.l10n.themeBrightnessBlack;
|
||||
}
|
||||
}
|
||||
|
||||
ThemeMode get appThemeMode {
|
||||
switch (this) {
|
||||
case AvesThemeBrightness.system:
|
||||
|
|
|
@ -1,21 +1,8 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension ExtraThumbnailOverlayLocationIcon on ThumbnailOverlayLocationIcon {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ThumbnailOverlayLocationIcon.located:
|
||||
return context.l10n.filterLocatedLabel;
|
||||
case ThumbnailOverlayLocationIcon.unlocated:
|
||||
return context.l10n.filterNoLocationLabel;
|
||||
case ThumbnailOverlayLocationIcon.none:
|
||||
return context.l10n.settingsDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
IconData getIcon(BuildContext context) {
|
||||
switch (this) {
|
||||
case ThumbnailOverlayLocationIcon.unlocated:
|
||||
|
|
|
@ -1,21 +1,8 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension ExtraThumbnailOverlayTagIcon on ThumbnailOverlayTagIcon {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ThumbnailOverlayTagIcon.tagged:
|
||||
return context.l10n.filterTaggedLabel;
|
||||
case ThumbnailOverlayTagIcon.untagged:
|
||||
return context.l10n.filterNoTagLabel;
|
||||
case ThumbnailOverlayTagIcon.none:
|
||||
return context.l10n.settingsDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
IconData getIcon(BuildContext context) {
|
||||
switch (this) {
|
||||
case ThumbnailOverlayTagIcon.tagged:
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraUnitSystem on UnitSystem {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case UnitSystem.metric:
|
||||
return context.l10n.unitSystemMetric;
|
||||
case UnitSystem.imperial:
|
||||
return context.l10n.unitSystemImperial;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraVideoAutoPlayMode on VideoAutoPlayMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoAutoPlayMode.disabled:
|
||||
return context.l10n.settingsDisabled;
|
||||
case VideoAutoPlayMode.playMuted:
|
||||
return context.l10n.videoPlaybackMuted;
|
||||
case VideoAutoPlayMode.playWithSound:
|
||||
return context.l10n.videoPlaybackWithSound;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraVideoBackgroundMode on VideoBackgroundMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoBackgroundMode.disabled:
|
||||
return context.l10n.settingsDisabled;
|
||||
case VideoBackgroundMode.pip:
|
||||
return context.l10n.settingsVideoEnablePip;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraVideoControls on VideoControls {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoControls.play:
|
||||
return context.l10n.videoControlsPlay;
|
||||
case VideoControls.playSeek:
|
||||
return context.l10n.videoControlsPlaySeek;
|
||||
case VideoControls.playOutside:
|
||||
return context.l10n.videoControlsPlayOutside;
|
||||
case VideoControls.none:
|
||||
return context.l10n.videoControlsNone;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +1,13 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
|
||||
extension ExtraVideoLoopMode on VideoLoopMode {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case VideoLoopMode.never:
|
||||
return context.l10n.videoLoopModeNever;
|
||||
case VideoLoopMode.shortOnly:
|
||||
return context.l10n.videoLoopModeShortOnly;
|
||||
case VideoLoopMode.always:
|
||||
return context.l10n.videoLoopModeAlways;
|
||||
}
|
||||
}
|
||||
|
||||
static const shortVideoThreshold = Duration(seconds: 30);
|
||||
|
||||
bool shouldLoop(AvesEntry entry) {
|
||||
bool shouldLoop(int? durationMillis) {
|
||||
switch (this) {
|
||||
case VideoLoopMode.never:
|
||||
return false;
|
||||
case VideoLoopMode.shortOnly:
|
||||
final durationMillis = entry.durationMillis;
|
||||
return durationMillis != null ? durationMillis < shortVideoThreshold.inMilliseconds : false;
|
||||
case VideoLoopMode.always:
|
||||
return true;
|
||||
|
|
|
@ -1,25 +1,8 @@
|
|||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/widgets/viewer/controls/controller.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraViewerTransition on ViewerTransition {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case ViewerTransition.slide:
|
||||
return context.l10n.viewerTransitionSlide;
|
||||
case ViewerTransition.parallax:
|
||||
return context.l10n.viewerTransitionParallax;
|
||||
case ViewerTransition.fade:
|
||||
return context.l10n.viewerTransitionFade;
|
||||
case ViewerTransition.zoomIn:
|
||||
return context.l10n.viewerTransitionZoomIn;
|
||||
case ViewerTransition.none:
|
||||
return context.l10n.viewerTransitionNone;
|
||||
}
|
||||
}
|
||||
|
||||
TransitionBuilder builder(PageController pageController, int index) {
|
||||
switch (this) {
|
||||
case ViewerTransition.slide:
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension ExtraWidgetDisplayedItem on WidgetDisplayedItem {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case WidgetDisplayedItem.random:
|
||||
return context.l10n.widgetDisplayedItemRandom;
|
||||
case WidgetDisplayedItem.mostRecent:
|
||||
return context.l10n.widgetDisplayedItemMostRecent;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension ExtraWidgetOpenPage on WidgetOpenPage {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case WidgetOpenPage.home:
|
||||
return context.l10n.widgetOpenPageHome;
|
||||
case WidgetOpenPage.collection:
|
||||
return context.l10n.widgetOpenPageCollection;
|
||||
case WidgetOpenPage.viewer:
|
||||
return context.l10n.widgetOpenPageViewer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension ExtraWidgetShape on WidgetShape {
|
||||
Path path(Size widgetSize, double devicePixelRatio) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import 'package:aves/model/settings/enums/enums.dart';
|
|||
import 'package:aves/model/settings/enums/map_style.dart';
|
||||
import 'package:aves/model/source/enums/enums.dart';
|
||||
import 'package:aves/services/accessibility_service.dart';
|
||||
import 'package:aves/services/common/optional_event_channel.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/aves_app.dart';
|
||||
import 'package:aves/widgets/common/search/page.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
|
|
|
@ -2,7 +2,9 @@ import 'dart:async';
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/model/entry/sort.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/favourite.dart';
|
||||
|
@ -13,17 +15,16 @@ import 'package:aves/model/filters/rating.dart';
|
|||
import 'package:aves/model/filters/trash.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums/enums.dart';
|
||||
import 'package:aves/model/source/events.dart';
|
||||
import 'package:aves/model/source/location/location.dart';
|
||||
import 'package:aves/model/source/section_keys.dart';
|
||||
import 'package:aves/model/source/tag.dart';
|
||||
import 'package:aves/utils/change_notifier.dart';
|
||||
import 'package:aves/utils/collection_utils.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'enums/enums.dart';
|
||||
|
||||
class CollectionLens with ChangeNotifier {
|
||||
final CollectionSource source;
|
||||
final Set<CollectionFilter> filters;
|
||||
|
@ -190,7 +191,7 @@ class CollectionLens with ChangeNotifier {
|
|||
final byBurstKey = groupBy<AvesEntry, String?>(_filteredSortedEntries, (entry) => entry.burstKey).whereNotNullKey();
|
||||
byBurstKey.forEach((burstKey, entries) {
|
||||
if (entries.length > 1) {
|
||||
entries.sort(AvesEntry.compareByName);
|
||||
entries.sort(AvesEntrySort.compareByName);
|
||||
final mainEntry = entries.first;
|
||||
final burstEntry = mainEntry.copyWith(burstEntries: entries);
|
||||
|
||||
|
@ -209,16 +210,16 @@ class CollectionLens with ChangeNotifier {
|
|||
|
||||
switch (sortFactor) {
|
||||
case EntrySortFactor.date:
|
||||
_filteredSortedEntries.sort(AvesEntry.compareByDate);
|
||||
_filteredSortedEntries.sort(AvesEntrySort.compareByDate);
|
||||
break;
|
||||
case EntrySortFactor.name:
|
||||
_filteredSortedEntries.sort(AvesEntry.compareByName);
|
||||
_filteredSortedEntries.sort(AvesEntrySort.compareByName);
|
||||
break;
|
||||
case EntrySortFactor.rating:
|
||||
_filteredSortedEntries.sort(AvesEntry.compareByRating);
|
||||
_filteredSortedEntries.sort(AvesEntrySort.compareByRating);
|
||||
break;
|
||||
case EntrySortFactor.size:
|
||||
_filteredSortedEntries.sort(AvesEntry.compareBySize);
|
||||
_filteredSortedEntries.sort(AvesEntrySort.compareBySize);
|
||||
break;
|
||||
}
|
||||
if (sortReverse) {
|
||||
|
|
|
@ -2,7 +2,10 @@ import 'dart:async';
|
|||
|
||||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/catalog.dart';
|
||||
import 'package:aves/model/entry/extensions/location.dart';
|
||||
import 'package:aves/model/entry/sort.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
|
@ -105,7 +108,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
|||
|
||||
@override
|
||||
List<AvesEntry> get sortedEntriesByDate {
|
||||
_sortedEntriesByDate ??= List.unmodifiable(visibleEntries.toList()..sort(AvesEntry.compareByDate));
|
||||
_sortedEntriesByDate ??= List.unmodifiable(visibleEntries.toList()..sort(AvesEntrySort.compareByDate));
|
||||
return _sortedEntriesByDate!;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:aves/model/source/enums/enums.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
extension ExtraEntrySortFactor on EntrySortFactor {
|
||||
String getName(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
@immutable
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/utils/collection_utils.dart';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/geo/countries.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/location.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/metadata/address.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/utils/collection_utils.dart';
|
||||
|
|
|
@ -2,7 +2,8 @@ import 'dart:async';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/origins.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/analysis_controller.dart';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/catalog.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/source/analysis_controller.dart';
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/video/channel_layouts.dart';
|
||||
import 'package:aves/model/video/codecs.dart';
|
||||
import 'package:aves/model/video/keys.dart';
|
||||
import 'package:aves/model/video/profiles/aac.dart';
|
||||
import 'package:aves/model/video/profiles/h264.dart';
|
||||
import 'package:aves/model/video/profiles/hevc.dart';
|
||||
|
@ -17,6 +16,7 @@ import 'package:aves/utils/math_utils.dart';
|
|||
import 'package:aves/utils/string_utils.dart';
|
||||
import 'package:aves/utils/time_utils.dart';
|
||||
import 'package:aves/widgets/viewer/video/fijkplayer.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fijkplayer/fijkplayer.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -230,7 +230,7 @@ class VideoMetadataFormatter {
|
|||
}
|
||||
break;
|
||||
case Keys.codecPixelFormat:
|
||||
if (streamType == StreamTypes.video) {
|
||||
if (streamType == MediaStreamTypes.video) {
|
||||
// this is just a short name used by FFmpeg
|
||||
// user-friendly descriptions for related enums are defined in libavutil/pixfmt.h
|
||||
save('Pixel Format', (value as String).toUpperCase());
|
||||
|
@ -425,13 +425,3 @@ class VideoMetadataFormatter {
|
|||
return '${(size / divider / divider).toStringAsFixed(round)} M$unit';
|
||||
}
|
||||
}
|
||||
|
||||
class StreamTypes {
|
||||
static const attachment = 'attachment';
|
||||
static const audio = 'audio';
|
||||
static const metadata = 'metadata';
|
||||
static const subtitle = 'subtitle';
|
||||
static const timedText = 'timedtext';
|
||||
static const unknown = 'unknown';
|
||||
static const video = 'video';
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/metadata/enums/enums.dart';
|
||||
import 'package:aves/services/common/image_op_events.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/output_buffer.dart';
|
||||
import 'package:aves/services/common/service_policy.dart';
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/services/common/optional_event_channel.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||
import 'package:aves_video/aves_video.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -12,6 +13,7 @@ abstract class MediaSessionService {
|
|||
Stream<MediaCommandEvent> get mediaCommands;
|
||||
|
||||
Future<void> update({
|
||||
required AvesEntry entry,
|
||||
required AvesVideoController controller,
|
||||
required bool canSkipToNext,
|
||||
required bool canSkipToPrevious,
|
||||
|
@ -43,11 +45,11 @@ class PlatformMediaSessionService implements MediaSessionService, Disposable {
|
|||
|
||||
@override
|
||||
Future<void> update({
|
||||
required AvesEntry entry,
|
||||
required AvesVideoController controller,
|
||||
required bool canSkipToNext,
|
||||
required bool canSkipToPrevious,
|
||||
}) async {
|
||||
final entry = controller.entry;
|
||||
try {
|
||||
await _platformObject.invokeMethod('update', <String, dynamic>{
|
||||
'uri': entry.uri,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:streams_channel/streams_channel.dart';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/metadata/date_modifier.dart';
|
||||
import 'package:aves/model/metadata/enums/enums.dart';
|
||||
import 'package:aves/model/metadata/enums/metadata_type.dart';
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/metadata/fields.dart';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/string_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/app_flavor.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/sort.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
|
@ -80,7 +81,7 @@ Future<AvesEntry?> _getWidgetEntry(int widgetId, bool reuseEntry) async {
|
|||
entries.shuffle();
|
||||
break;
|
||||
case WidgetDisplayedItem.mostRecent:
|
||||
entries.sort(AvesEntry.compareByDate);
|
||||
entries.sort(AvesEntrySort.compareByDate);
|
||||
break;
|
||||
}
|
||||
final entry = entries.firstOrNull;
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/ref/brand_colors.dart';
|
|||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/utils/dependencies.dart';
|
||||
import 'package:aves/widgets/about/title.dart';
|
||||
import 'package:aves/widgets/about/tv_license_page.dart';
|
||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
|
@ -87,7 +88,7 @@ class _LicensesState extends State<Licenses> {
|
|||
// as of Flutter v1.22.4, `cardColor` is used as a background color by `LicensePage`
|
||||
cardColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: const LicensePage(),
|
||||
child: settings.useTvLayout ? const TvLicensePage() : const LicensePage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
357
lib/widgets/about/tv_license_page.dart
Normal file
357
lib/widgets/about/tv_license_page.dart
Normal file
|
@ -0,0 +1,357 @@
|
|||
import 'package:aves/widgets/common/basic/scaffold.dart';
|
||||
import 'package:aves/widgets/common/behaviour/intents.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
// as of Flutter v3.7.7, `LicensePage` is not designed for Android TV
|
||||
// and gets rejected from Google Play review:
|
||||
// ```
|
||||
// Your app’s text is cut off at the edge of the screen.
|
||||
// Apps should not display any text or functionality that is partially cut off by the edges of the screen.
|
||||
// For example, your app (version code 94) in the "Show All Licenses" section text is cut off from the bottom of the screen.
|
||||
// ```
|
||||
class TvLicensePage extends StatefulWidget {
|
||||
const TvLicensePage({super.key});
|
||||
|
||||
@override
|
||||
State<TvLicensePage> createState() => _TvLicensePageState();
|
||||
}
|
||||
|
||||
class _TvLicensePageState extends State<TvLicensePage> {
|
||||
final FocusNode _railFocusNode = FocusNode();
|
||||
final ScrollController _detailsScrollController = ScrollController();
|
||||
final ValueNotifier<int> _railIndexNotifier = ValueNotifier(0);
|
||||
|
||||
final Future<_LicenseData> licenses = LicenseRegistry.licenses
|
||||
.fold<_LicenseData>(
|
||||
_LicenseData(),
|
||||
(prev, license) => prev..addLicense(license),
|
||||
)
|
||||
.then((licenseData) => licenseData..sortPackages());
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_railIndexNotifier.dispose();
|
||||
_railFocusNode.dispose();
|
||||
_detailsScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AvesScaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(MaterialLocalizations.of(context).licensesPageTitle),
|
||||
),
|
||||
body: ValueListenableBuilder<int>(
|
||||
valueListenable: _railIndexNotifier,
|
||||
builder: (context, selectedIndex, child) {
|
||||
return FutureBuilder<_LicenseData>(
|
||||
future: licenses,
|
||||
builder: (context, snapshot) {
|
||||
final data = snapshot.data;
|
||||
if (data == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
final packages = data.packages;
|
||||
final rail = Focus(
|
||||
focusNode: _railFocusNode,
|
||||
skipTraversal: true,
|
||||
canRequestFocus: false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints.loose(const Size.fromWidth(300)),
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final packageName = packages[index];
|
||||
final bindings = data.packageLicenseBindings[packageName]!;
|
||||
final isSelected = index == selectedIndex;
|
||||
return Ink(
|
||||
color: isSelected ? Theme.of(context).highlightColor : Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
title: Text(packageName),
|
||||
subtitle: Text(MaterialLocalizations.of(context).licensesPackageDetailText(bindings.length)),
|
||||
selected: isSelected,
|
||||
onTap: () => _railIndexNotifier.value = index,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: packages.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final packageName = packages[selectedIndex];
|
||||
final bindings = data.packageLicenseBindings[packageName]!;
|
||||
|
||||
return SafeArea(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
rail,
|
||||
Expanded(
|
||||
child: FocusableActionDetector(
|
||||
shortcuts: const {
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
|
||||
},
|
||||
actions: {
|
||||
ScrollIntent: ScrollControllerAction(scrollController: _detailsScrollController),
|
||||
},
|
||||
child: KeyedSubtree(
|
||||
key: Key(packageName),
|
||||
child: _PackageLicensePage(
|
||||
packageName: packageName,
|
||||
licenseEntries: bindings.map((i) => data.licenses[i]).toList(growable: false),
|
||||
scrollController: _detailsScrollController,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// adapted from Flutter `_LicenseData` in `/material/about.dart`
|
||||
class _LicenseData {
|
||||
final List<LicenseEntry> licenses = <LicenseEntry>[];
|
||||
final Map<String, List<int>> packageLicenseBindings = <String, List<int>>{};
|
||||
final List<String> packages = <String>[];
|
||||
|
||||
// Special treatment for the first package since it should be the package
|
||||
// for delivered application.
|
||||
String? firstPackage;
|
||||
|
||||
void addLicense(LicenseEntry entry) {
|
||||
// Before the license can be added, we must first record the packages to
|
||||
// which it belongs.
|
||||
for (final String package in entry.packages) {
|
||||
_addPackage(package);
|
||||
// Bind this license to the package using the next index value. This
|
||||
// creates a contract that this license must be inserted at this same
|
||||
// index value.
|
||||
packageLicenseBindings[package]!.add(licenses.length);
|
||||
}
|
||||
licenses.add(entry); // Completion of the contract above.
|
||||
}
|
||||
|
||||
/// Add a package and initialize package license binding. This is a no-op if
|
||||
/// the package has been seen before.
|
||||
void _addPackage(String package) {
|
||||
if (!packageLicenseBindings.containsKey(package)) {
|
||||
packageLicenseBindings[package] = <int>[];
|
||||
firstPackage ??= package;
|
||||
packages.add(package);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sort the packages using some comparison method, or by the default manner,
|
||||
/// which is to put the application package first, followed by every other
|
||||
/// package in case-insensitive alphabetical order.
|
||||
void sortPackages([int Function(String a, String b)? compare]) {
|
||||
packages.sort(compare ??
|
||||
(a, b) {
|
||||
// Based on how LicenseRegistry currently behaves, the first package
|
||||
// returned is the end user application license. This should be
|
||||
// presented first in the list. So here we make sure that first package
|
||||
// remains at the front regardless of alphabetical sorting.
|
||||
if (a == firstPackage) {
|
||||
return -1;
|
||||
}
|
||||
if (b == firstPackage) {
|
||||
return 1;
|
||||
}
|
||||
return a.toLowerCase().compareTo(b.toLowerCase());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// adapted from Flutter `_PackageLicensePage` in `/material/about.dart`
|
||||
class _PackageLicensePage extends StatefulWidget {
|
||||
const _PackageLicensePage({
|
||||
required this.packageName,
|
||||
required this.licenseEntries,
|
||||
required this.scrollController,
|
||||
});
|
||||
|
||||
final String packageName;
|
||||
final List<LicenseEntry> licenseEntries;
|
||||
final ScrollController? scrollController;
|
||||
|
||||
@override
|
||||
_PackageLicensePageState createState() => _PackageLicensePageState();
|
||||
}
|
||||
|
||||
class _PackageLicensePageState extends State<_PackageLicensePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initLicenses();
|
||||
}
|
||||
|
||||
final List<Widget> _licenses = <Widget>[];
|
||||
bool _loaded = false;
|
||||
|
||||
Future<void> _initLicenses() async {
|
||||
for (final LicenseEntry license in widget.licenseEntries) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final List<LicenseParagraph> paragraphs = await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
|
||||
license.paragraphs.toList,
|
||||
Priority.animation,
|
||||
debugLabel: 'License',
|
||||
);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_licenses.add(const Padding(
|
||||
padding: EdgeInsets.all(18.0),
|
||||
child: Divider(),
|
||||
));
|
||||
for (final LicenseParagraph paragraph in paragraphs) {
|
||||
if (paragraph.indent == LicenseParagraph.centeredIndent) {
|
||||
_licenses.add(Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Text(
|
||||
paragraph.text,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
));
|
||||
} else {
|
||||
_licenses.add(Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
|
||||
child: Text(paragraph.text),
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
setState(() {
|
||||
_loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final String title = widget.packageName;
|
||||
final String subtitle = localizations.licensesPackageDetailText(widget.licenseEntries.length);
|
||||
const double pad = 24;
|
||||
const EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad);
|
||||
final List<Widget> listWidgets = <Widget>[
|
||||
..._licenses,
|
||||
if (!_loaded)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
final Widget page;
|
||||
if (widget.scrollController == null) {
|
||||
page = Scaffold(
|
||||
appBar: AppBar(
|
||||
title: _PackageLicensePageTitle(
|
||||
title,
|
||||
subtitle,
|
||||
theme.primaryTextTheme,
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Material(
|
||||
color: theme.cardColor,
|
||||
elevation: 4.0,
|
||||
child: Container(
|
||||
constraints: BoxConstraints.loose(const Size.fromWidth(600.0)),
|
||||
child: Localizations.override(
|
||||
locale: const Locale('en', 'US'),
|
||||
context: context,
|
||||
child: ScrollConfiguration(
|
||||
// A Scrollbar is built-in below.
|
||||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
child: Scrollbar(
|
||||
child: ListView(padding: padding, children: listWidgets),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
page = CustomScrollView(
|
||||
controller: widget.scrollController,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
backgroundColor: theme.cardColor,
|
||||
title: _PackageLicensePageTitle(title, subtitle, theme.textTheme),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: padding,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => Localizations.override(
|
||||
locale: const Locale('en', 'US'),
|
||||
context: context,
|
||||
child: listWidgets[index],
|
||||
),
|
||||
childCount: listWidgets.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return DefaultTextStyle(
|
||||
style: theme.textTheme.bodySmall!,
|
||||
child: page,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PackageLicensePageTitle extends StatelessWidget {
|
||||
const _PackageLicensePageTitle(
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.theme,
|
||||
);
|
||||
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final TextTheme theme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color? color = Theme.of(context).appBarTheme.foregroundColor;
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(title, style: theme.titleLarge?.copyWith(color: color)),
|
||||
Text(subtitle, style: theme.titleSmall?.copyWith(color: color)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import 'package:aves/model/source/collection_lens.dart';
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/media_store_source.dart';
|
||||
import 'package:aves/services/accessibility_service.dart';
|
||||
import 'package:aves/services/common/optional_event_channel.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/actions/entry_set_actions.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/query.dart';
|
||||
import 'package:aves/model/filters/trash.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/favourite.dart';
|
||||
import 'package:aves/model/filters/mime.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/query.dart';
|
||||
import 'package:aves/model/filters/trash.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/filters/rating.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
|
|
|
@ -4,8 +4,10 @@ import 'package:aves/app_mode.dart';
|
|||
import 'package:aves/model/actions/entry_set_actions.dart';
|
||||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry_metadata_edition.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||
import 'package:aves/model/entry/extensions/metadata_edition.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/metadata/date_modifier.dart';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/model/covers.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/section_keys.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums/enums.dart';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/location.dart';
|
||||
import 'package:aves/model/settings/enums/coordinate_format.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/format.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/section_keys.dart';
|
||||
import 'package:aves/widgets/collection/grid/headers/any.dart';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/enums/enums.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/widgets/common/basic/query_bar.dart';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/model/actions/entry_actions.dart';
|
||||
import 'package:aves/model/actions/share_actions.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart';
|
||||
import 'package:aves/widgets/common/action_controls/quick_choosers/share_chooser.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:aves/theme/icons.dart';
|
|||
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||
import 'package:aves_video/aves_video.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MuteToggler extends StatelessWidget {
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:aves/theme/icons.dart';
|
|||
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||
import 'package:aves_video/aves_video.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry_metadata_edition.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/metadata_edition.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/placeholder.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
|
|
|
@ -3,7 +3,9 @@ import 'dart:io';
|
|||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/trash.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/collection_utils.dart';
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue