upgraded Flutter to stable v3.10.0, agp 8, dart 3, use media query aspects

This commit is contained in:
Thibault Deckers 2023-05-13 17:39:39 +02:00
parent 01d2e21369
commit 28973ec322
208 changed files with 1905 additions and 1988 deletions

@ -1 +1 @@
Subproject commit 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf Subproject commit 84a1e904f44f9b0e9c4510138010edcc653163f8

View file

@ -7,10 +7,12 @@ All notable changes to this project will be documented in this file.
### Added ### Added
- option to set the Tags page as home - option to set the Tags page as home
- support for animated PNG
### Changed ### Changed
- remember whether to show the title filter when picking albums - remember whether to show the title filter when picking albums
- upgraded Flutter to stable v3.10.0
## <a id="v1.8.6"></a>[v1.8.6] - 2023-04-30 ## <a id="v1.8.6"></a>[v1.8.6] - 2023-04-30

View file

@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
@ -46,7 +48,7 @@ if (keystorePropertiesFile.exists()) {
android { android {
namespace 'deckers.thibault.aves' namespace 'deckers.thibault.aves'
compileSdkVersion 33 compileSdk 33
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@ -60,10 +62,6 @@ android {
disable 'InvalidPackage' disable 'InvalidPackage'
} }
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
@ -174,6 +172,15 @@ android {
} }
} }
tasks.withType(KotlinCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
kotlinOptions {
jvmTarget = '1.8'
}
}
flutter { flutter {
source '../..' source '../..'
} }
@ -195,10 +202,10 @@ repositories {
} }
dependencies { dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.appcompat:appcompat:1.6.1"
implementation 'androidx.core:core-ktx:1.10.0' implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.exifinterface:exifinterface:1.3.6' implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation 'androidx.lifecycle:lifecycle-process:2.6.1' implementation 'androidx.lifecycle:lifecycle-process:2.6.1'
implementation 'androidx.media:media:1.6.0' implementation 'androidx.media:media:1.6.0'

View file

@ -8,7 +8,6 @@ This change eventually prevents building the app with Flutter v3.7.11.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="deckers.thibault.aves"
android:installLocation="auto"> android:installLocation="auto">
<uses-feature <uses-feature

View file

@ -1,7 +1,7 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.8.21' kotlin_version = '1.8.21'
agp_version = '7.4.2' agp_version = '8.0.1'
glide_version = '4.15.1' glide_version = '4.15.1'
huawei_agconnect_version = '1.8.0.300' huawei_agconnect_version = '1.8.0.300'
abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4] abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]

View file

@ -15,3 +15,6 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip

View file

@ -27,7 +27,7 @@ class AppIconImage extends ImageProvider<AppIconImageKey> {
} }
@override @override
ImageStreamCompleter loadBuffer(AppIconImageKey key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(AppIconImageKey key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode), codec: _loadAsync(key, decode),
scale: key.scale, scale: key.scale,
@ -37,7 +37,7 @@ class AppIconImage extends ImageProvider<AppIconImageKey> {
); );
} }
Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderBufferCallback decode) async { Future<ui.Codec> _loadAsync(AppIconImageKey key, ImageDecoderCallback decode) async {
try { try {
final bytes = await appService.getAppIcon(key.packageName, key.size); final bytes = await appService.getAppIcon(key.packageName, key.size);
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes.isEmpty ? kTransparentImage : bytes); final buffer = await ui.ImmutableBuffer.fromUint8List(bytes.isEmpty ? kTransparentImage : bytes);

View file

@ -18,7 +18,7 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
} }
@override @override
ImageStreamCompleter loadBuffer(RegionProviderKey key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(RegionProviderKey key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode), codec: _loadAsync(key, decode),
scale: 1.0, scale: 1.0,
@ -28,7 +28,7 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
); );
} }
Future<ui.Codec> _loadAsync(RegionProviderKey key, DecoderBufferCallback decode) async { Future<ui.Codec> _loadAsync(RegionProviderKey key, ImageDecoderCallback decode) async {
final uri = key.uri; final uri = key.uri;
final mimeType = key.mimeType; final mimeType = key.mimeType;
final pageId = key.pageId; final pageId = key.pageId;

View file

@ -19,7 +19,7 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
} }
@override @override
ImageStreamCompleter loadBuffer(ThumbnailProviderKey key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(ThumbnailProviderKey key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode), codec: _loadAsync(key, decode),
scale: 1.0, scale: 1.0,
@ -30,7 +30,7 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
); );
} }
Future<ui.Codec> _loadAsync(ThumbnailProviderKey key, DecoderBufferCallback decode) async { Future<ui.Codec> _loadAsync(ThumbnailProviderKey key, ImageDecoderCallback decode) async {
final uri = key.uri; final uri = key.uri;
final mimeType = key.mimeType; final mimeType = key.mimeType;
final pageId = key.pageId; final pageId = key.pageId;

View file

@ -32,7 +32,7 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
} }
@override @override
ImageStreamCompleter loadBuffer(UriImage key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(UriImage key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>(); final chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
@ -45,7 +45,7 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
); );
} }
Future<ui.Codec> _loadAsync(UriImage key, DecoderBufferCallback decode, StreamController<ImageChunkEvent> chunkEvents) async { Future<ui.Codec> _loadAsync(UriImage key, ImageDecoderCallback decode, StreamController<ImageChunkEvent> chunkEvents) async {
assert(key == this); assert(key == this);
try { try {

View file

@ -21,34 +21,24 @@ class MetadataDbUpgrader {
switch (oldVersion) { switch (oldVersion) {
case 1: case 1:
await _upgradeFrom1(db); await _upgradeFrom1(db);
break;
case 2: case 2:
await _upgradeFrom2(db); await _upgradeFrom2(db);
break;
case 3: case 3:
await _upgradeFrom3(db); await _upgradeFrom3(db);
break;
case 4: case 4:
await _upgradeFrom4(db); await _upgradeFrom4(db);
break;
case 5: case 5:
await _upgradeFrom5(db); await _upgradeFrom5(db);
break;
case 6: case 6:
await _upgradeFrom6(db); await _upgradeFrom6(db);
break;
case 7: case 7:
await _upgradeFrom7(db); await _upgradeFrom7(db);
break;
case 8: case 8:
await _upgradeFrom8(db); await _upgradeFrom8(db);
break;
case 9: case 9:
await _upgradeFrom9(db); await _upgradeFrom9(db);
break;
case 10: case 10:
await _upgradeFrom10(db); await _upgradeFrom10(db);
break;
} }
oldVersion++; oldVersion++;
} }

View file

@ -39,7 +39,7 @@ extension ExtraAvesEntryCatalog on AvesEntry {
if (isGeotiff && !hasGps) { if (isGeotiff && !hasGps) {
final info = await metadataFetchService.getGeoTiffInfo(this); final info = await metadataFetchService.getGeoTiffInfo(this);
if (info != null) { if (info != null) {
final center = MappedGeoTiff( final center = GeoTiffCoordinateConverter(
info: info, info: info,
entry: this, entry: this,
).center; ).center;

View file

@ -52,7 +52,6 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
case DateEditAction.copyItem: case DateEditAction.copyItem:
case DateEditAction.extractFromTitle: case DateEditAction.extractFromTitle:
editCreateDateXmp(descriptions, appliedModifier.setDateTime); editCreateDateXmp(descriptions, appliedModifier.setDateTime);
break;
case DateEditAction.shift: case DateEditAction.shift:
final xmpDate = XMP.getString(descriptions, XmpAttributes.xmpCreateDate, namespace: XmpNamespaces.xmp); final xmpDate = XMP.getString(descriptions, XmpAttributes.xmpCreateDate, namespace: XmpNamespaces.xmp);
if (xmpDate != null) { if (xmpDate != null) {
@ -65,10 +64,8 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
reportService.recordError('failed to parse XMP date=$xmpDate', null); reportService.recordError('failed to parse XMP date=$xmpDate', null);
} }
} }
break;
case DateEditAction.remove: case DateEditAction.remove:
editCreateDateXmp(descriptions, null); editCreateDateXmp(descriptions, null);
break;
} }
return true; return true;
}), }),
@ -541,10 +538,8 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
} }
} }
} on FileSystemException catch (_) {} } on FileSystemException catch (_) {}
break;
default: default:
date = await metadataFetchService.getDate(this, source.toMetadataField()!); date = await metadataFetchService.getDate(this, source.toMetadataField()!);
break;
} }
} }
return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null; return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null;

View file

@ -78,7 +78,6 @@ class AlbumFilter extends CoveredCollectionFilter {
case AlbumType.app: case AlbumType.app:
final appColor = colors.appColor(album); final appColor = colors.appColor(album);
if (appColor != null) return appColor; if (appColor != null) return appColor;
break;
case AlbumType.camera: case AlbumType.camera:
return SynchronousFuture(colors.albumCamera); return SynchronousFuture(colors.albumCamera);
case AlbumType.download: case AlbumType.download:

View file

@ -21,13 +21,10 @@ class AspectRatioFilter extends CollectionFilter {
switch (op) { switch (op) {
case QueryFilter.opEqual: case QueryFilter.opEqual:
_test = (entry) => entry.displayAspectRatio == threshold; _test = (entry) => entry.displayAspectRatio == threshold;
break;
case QueryFilter.opLower: case QueryFilter.opLower:
_test = (entry) => entry.displayAspectRatio < threshold; _test = (entry) => entry.displayAspectRatio < threshold;
break;
case QueryFilter.opGreater: case QueryFilter.opGreater:
_test = (entry) => entry.displayAspectRatio > threshold; _test = (entry) => entry.displayAspectRatio > threshold;
break;
} }
} }

View file

@ -25,13 +25,10 @@ class DateFilter extends CollectionFilter {
switch (level) { switch (level) {
case DateLevel.y: case DateLevel.y:
_test = (entry) => entry.bestDate?.isAtSameYearAs(_effectiveDate) ?? false; _test = (entry) => entry.bestDate?.isAtSameYearAs(_effectiveDate) ?? false;
break;
case DateLevel.ym: case DateLevel.ym:
_test = (entry) => entry.bestDate?.isAtSameMonthAs(_effectiveDate) ?? false; _test = (entry) => entry.bestDate?.isAtSameMonthAs(_effectiveDate) ?? false;
break;
case DateLevel.ymd: case DateLevel.ymd:
_test = (entry) => entry.bestDate?.isAtSameDayAs(_effectiveDate) ?? false; _test = (entry) => entry.bestDate?.isAtSameDayAs(_effectiveDate) ?? false;
break;
case DateLevel.md: case DateLevel.md:
final month = _effectiveDate.month; final month = _effectiveDate.month;
final day = _effectiveDate.day; final day = _effectiveDate.day;
@ -39,15 +36,12 @@ class DateFilter extends CollectionFilter {
final bestDate = entry.bestDate; final bestDate = entry.bestDate;
return bestDate != null && bestDate.month == month && bestDate.day == day; return bestDate != null && bestDate.month == month && bestDate.day == day;
}; };
break;
case DateLevel.m: case DateLevel.m:
final month = _effectiveDate.month; final month = _effectiveDate.month;
_test = (entry) => entry.bestDate?.month == month; _test = (entry) => entry.bestDate?.month == month;
break;
case DateLevel.d: case DateLevel.d:
final day = _effectiveDate.day; final day = _effectiveDate.day;
_test = (entry) => entry.bestDate?.day == day; _test = (entry) => entry.bestDate?.day == day;
break;
} }
} }

View file

@ -29,13 +29,10 @@ class LocationFilter extends CoveredCollectionFilter {
switch (level) { switch (level) {
case LocationLevel.country: case LocationLevel.country:
_test = (entry) => entry.addressDetails?.countryCode == _code; _test = (entry) => entry.addressDetails?.countryCode == _code;
break;
case LocationLevel.state: case LocationLevel.state:
_test = (entry) => entry.addressDetails?.stateCode == _code; _test = (entry) => entry.addressDetails?.stateCode == _code;
break;
case LocationLevel.place: case LocationLevel.place:
_test = (entry) => entry.addressDetails?.place == _location; _test = (entry) => entry.addressDetails?.place == _location;
break;
} }
} }
} }
@ -57,7 +54,6 @@ class LocationFilter extends CoveredCollectionFilter {
if (_code != null) { if (_code != null) {
location = _nameAndCode; location = _nameAndCode;
} }
break;
case LocationLevel.place: case LocationLevel.place:
break; break;
} }

View file

@ -26,15 +26,12 @@ class MissingFilter extends CollectionFilter {
case _date: case _date:
_test = (entry) => (entry.catalogMetadata?.dateMillis ?? 0) == 0; _test = (entry) => (entry.catalogMetadata?.dateMillis ?? 0) == 0;
_icon = AIcons.dateUndated; _icon = AIcons.dateUndated;
break;
case _fineAddress: case _fineAddress:
_test = (entry) => entry.hasGps && !entry.hasFineAddress; _test = (entry) => entry.hasGps && !entry.hasFineAddress;
_icon = AIcons.locationUnlocated; _icon = AIcons.locationUnlocated;
break;
case _title: case _title:
_test = (entry) => (entry.catalogMetadata?.xmpTitle ?? '').isEmpty; _test = (entry) => (entry.catalogMetadata?.xmpTitle ?? '').isEmpty;
_icon = AIcons.descriptionUntitled; _icon = AIcons.descriptionUntitled;
break;
} }
} }

View file

@ -28,13 +28,10 @@ class PlaceholderFilter extends CollectionFilter {
switch (placeholder) { switch (placeholder) {
case _country: case _country:
_icon = AIcons.country; _icon = AIcons.country;
break;
case _state: case _state:
_icon = AIcons.state; _icon = AIcons.state;
break;
case _place: case _place:
_icon = AIcons.place; _icon = AIcons.place;
break;
} }
} }
@ -74,7 +71,6 @@ class PlaceholderFilter extends CollectionFilter {
case _place: case _place:
return address.place; return address.place;
} }
break;
} }
return null; return null;
} }

View file

@ -117,7 +117,6 @@ class QueryFilter extends CollectionFilter {
if (op == opEqual) { if (op == opEqual) {
return (entry) => entry.contentId == valueInt; return (entry) => entry.contentId == valueInt;
} }
break;
case keyContentYear: case keyContentYear:
if (valueInt == null) return null; if (valueInt == null) return null;
switch (op) { switch (op) {
@ -128,7 +127,6 @@ class QueryFilter extends CollectionFilter {
case opGreater: case opGreater:
return (entry) => (entry.bestDate?.year ?? 0) > valueInt; return (entry) => (entry.bestDate?.year ?? 0) > valueInt;
} }
break;
case keyContentMonth: case keyContentMonth:
if (valueInt == null) return null; if (valueInt == null) return null;
switch (op) { switch (op) {
@ -139,7 +137,6 @@ class QueryFilter extends CollectionFilter {
case opGreater: case opGreater:
return (entry) => (entry.bestDate?.month ?? 0) > valueInt; return (entry) => (entry.bestDate?.month ?? 0) > valueInt;
} }
break;
case keyContentDay: case keyContentDay:
if (valueInt == null) return null; if (valueInt == null) return null;
switch (op) { switch (op) {
@ -150,7 +147,6 @@ class QueryFilter extends CollectionFilter {
case opGreater: case opGreater:
return (entry) => (entry.bestDate?.day ?? 0) > valueInt; return (entry) => (entry.bestDate?.day ?? 0) > valueInt;
} }
break;
case keyContentWidth: case keyContentWidth:
if (valueInt == null) return null; if (valueInt == null) return null;
switch (op) { switch (op) {
@ -161,7 +157,6 @@ class QueryFilter extends CollectionFilter {
case opGreater: case opGreater:
return (entry) => entry.displaySize.width > valueInt; return (entry) => entry.displaySize.width > valueInt;
} }
break;
case keyContentHeight: case keyContentHeight:
if (valueInt == null) return null; if (valueInt == null) return null;
switch (op) { switch (op) {
@ -172,7 +167,6 @@ class QueryFilter extends CollectionFilter {
case opGreater: case opGreater:
return (entry) => entry.displaySize.height > valueInt; return (entry) => entry.displaySize.height > valueInt;
} }
break;
case keyContentSize: case keyContentSize:
match = _fileSizePattern.firstMatch(valueString); match = _fileSizePattern.firstMatch(valueString);
if (match == null) return null; if (match == null) return null;
@ -187,13 +181,10 @@ class QueryFilter extends CollectionFilter {
switch (multiplierString) { switch (multiplierString) {
case 'K': case 'K':
bytes *= kilo; bytes *= kilo;
break;
case 'M': case 'M':
bytes *= mega; bytes *= mega;
break;
case 'G': case 'G':
bytes *= giga; bytes *= giga;
break;
} }
switch (op) { switch (op) {
@ -204,7 +195,6 @@ class QueryFilter extends CollectionFilter {
case opGreater: case opGreater:
return (entry) => (entry.sizeBytes ?? 0) > bytes; return (entry) => (entry.sizeBytes ?? 0) > bytes;
} }
break;
} }
return null; return null;

View file

@ -37,27 +37,21 @@ class TypeFilter extends CollectionFilter {
case _animated: case _animated:
_test = (entry) => entry.isAnimated; _test = (entry) => entry.isAnimated;
_icon = AIcons.animated; _icon = AIcons.animated;
break;
case _geotiff: case _geotiff:
_test = (entry) => entry.isGeotiff; _test = (entry) => entry.isGeotiff;
_icon = AIcons.geo; _icon = AIcons.geo;
break;
case _motionPhoto: case _motionPhoto:
_test = (entry) => entry.isMotionPhoto; _test = (entry) => entry.isMotionPhoto;
_icon = AIcons.motionPhoto; _icon = AIcons.motionPhoto;
break;
case _panorama: case _panorama:
_test = (entry) => entry.isImage && entry.is360; _test = (entry) => entry.isImage && entry.is360;
_icon = AIcons.panorama; _icon = AIcons.panorama;
break;
case _raw: case _raw:
_test = (entry) => entry.isRaw; _test = (entry) => entry.isRaw;
_icon = AIcons.raw; _icon = AIcons.raw;
break;
case _sphericalVideo: case _sphericalVideo:
_test = (entry) => entry.isVideo && entry.is360; _test = (entry) => entry.isVideo && entry.is360;
_icon = AIcons.sphericalVideo; _icon = AIcons.sphericalVideo;
break;
} }
} }

View file

@ -8,8 +8,7 @@ import 'package:aves/ref/metadata/geotiff.dart';
import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/math_utils.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/painting.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:proj4dart/proj4dart.dart' as proj4; import 'package:proj4dart/proj4dart.dart' as proj4;
@ -42,19 +41,145 @@ class GeoTiffInfo extends Equatable {
class MappedGeoTiff with MapOverlay { class MappedGeoTiff with MapOverlay {
final AvesEntry entry; final AvesEntry entry;
late LatLng? Function(Point<int> pixel) pointToLatLng;
late Point<int>? Function(Point<double> smPoint) epsg3857ToPoint;
static final mapServiceTileSize = (256 * ui.window.devicePixelRatio).round(); late final GeoTiffCoordinateConverter _converter;
static final mapServiceHelper = MapServiceHelper(mapServiceTileSize); late final int _mapServiceTileSize;
static final tileImagePaint = Paint(); late final MapServiceHelper _mapServiceHelper;
static final tileMissingPaint = Paint()
static final _tileImagePaint = Paint();
static final _tileMissingPaint = Paint()
..style = PaintingStyle.fill ..style = PaintingStyle.fill
..color = const Color(0xFF000000); ..color = const Color(0xFF000000);
MappedGeoTiff({ MappedGeoTiff({
required GeoTiffInfo info, required GeoTiffInfo info,
required this.entry, required this.entry,
required double devicePixelRatio,
}) {
_converter = GeoTiffCoordinateConverter(info: info, entry: entry);
_mapServiceTileSize = (256 * devicePixelRatio).round();
_mapServiceHelper = MapServiceHelper(_mapServiceTileSize);
}
@override
Future<MapTile?> getTile(int tx, int ty, int? zoomLevel) async {
zoomLevel ??= 0;
// global projected coordinates in meters (EPSG:3857 Spherical Mercator)
final tileTopLeft3857 = _mapServiceHelper.tileTopLeft(tx, ty, zoomLevel);
final tileBottomRight3857 = _mapServiceHelper.tileTopLeft(tx + 1, ty + 1, zoomLevel);
// image region coordinates in pixels
final tileTopLeftPx = _converter.epsg3857ToPoint(tileTopLeft3857);
final tileBottomRightPx = _converter.epsg3857ToPoint(tileBottomRight3857);
if (tileTopLeftPx == null || tileBottomRightPx == null) return null;
final tileLeft = tileTopLeftPx.x;
final tileRight = tileBottomRightPx.x;
final tileTop = tileTopLeftPx.y;
final tileBottom = tileBottomRightPx.y;
final width = entry.width;
final height = entry.height;
final regionLeft = tileLeft.clamp(0, width);
final regionRight = tileRight.clamp(0, width);
final regionTop = tileTop.clamp(0, height);
final regionBottom = tileBottom.clamp(0, height);
final regionWidth = regionRight - regionLeft;
final regionHeight = regionBottom - regionTop;
if (regionWidth == 0 || regionHeight == 0) return null;
final tileXScale = (tileRight - tileLeft) / _mapServiceTileSize;
final sampleSize = max<int>(1, highestPowerOf2(tileXScale));
final region = entry.getRegion(
sampleSize: sampleSize,
region: Rectangle(regionLeft, regionTop, regionWidth, regionHeight),
);
final imageInfoCompleter = Completer<ImageInfo?>();
final imageStream = region.resolve(ImageConfiguration.empty);
final imageStreamListener = ImageStreamListener((image, synchronousCall) {
imageInfoCompleter.complete(image);
}, onError: imageInfoCompleter.completeError);
imageStream.addListener(imageStreamListener);
ImageInfo? regionImageInfo;
try {
regionImageInfo = await imageInfoCompleter.future;
} catch (error) {
debugPrint('failed to get image for region=$region with error=$error');
}
imageStream.removeListener(imageStreamListener);
final imageOffset = Offset(
regionLeft > tileLeft ? (regionLeft - tileLeft).toDouble() : 0,
regionTop > tileTop ? (regionTop - tileTop).toDouble() : 0,
);
final tileImageScaleX = (tileRight - tileLeft) / _mapServiceTileSize;
final tileImageScaleY = (tileBottom - tileTop) / _mapServiceTileSize;
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
canvas.scale(1 / tileImageScaleX, 1 / tileImageScaleY);
if (regionImageInfo != null) {
final s = sampleSize.toDouble();
canvas.scale(s, s);
canvas.drawImage(regionImageInfo.image, imageOffset / s, _tileImagePaint);
canvas.scale(1 / s, 1 / s);
} else {
// fallback to show area
canvas.drawRect(
Rect.fromLTWH(
imageOffset.dx,
imageOffset.dy,
regionWidth.toDouble(),
regionHeight.toDouble(),
),
_tileMissingPaint,
);
}
canvas.scale(tileImageScaleX, tileImageScaleY);
final picture = recorder.endRecording();
final tileImage = await picture.toImage(_mapServiceTileSize, _mapServiceTileSize);
final byteData = await tileImage.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) return null;
return MapTile(
width: tileImage.width,
height: tileImage.height,
data: byteData.buffer.asUint8List(),
);
}
@override
String get id => entry.uri;
@override
ImageProvider get imageProvider => entry.uriImage;
@override
bool get canOverlay => center != null;
LatLng? get center => _converter.center;
@override
LatLng? get topLeft => _converter.topLeft;
@override
LatLng? get bottomRight => _converter.bottomRight;
}
class GeoTiffCoordinateConverter {
final AvesEntry entry;
late LatLng? Function(Point<int> pixel) pointToLatLng;
late Point<int>? Function(Point<double> smPoint) epsg3857ToPoint;
GeoTiffCoordinateConverter({
required GeoTiffInfo info,
required this.entry,
}) { }) {
pointToLatLng = (_) => null; pointToLatLng = (_) => null;
epsg3857ToPoint = (_) => null; epsg3857ToPoint = (_) => null;
@ -129,113 +254,13 @@ class MappedGeoTiff with MapOverlay {
}; };
} }
@override
Future<MapTile?> getTile(int tx, int ty, int? zoomLevel) async {
zoomLevel ??= 0;
// global projected coordinates in meters (EPSG:3857 Spherical Mercator)
final tileTopLeft3857 = mapServiceHelper.tileTopLeft(tx, ty, zoomLevel);
final tileBottomRight3857 = mapServiceHelper.tileTopLeft(tx + 1, ty + 1, zoomLevel);
// image region coordinates in pixels
final tileTopLeftPx = epsg3857ToPoint(tileTopLeft3857);
final tileBottomRightPx = epsg3857ToPoint(tileBottomRight3857);
if (tileTopLeftPx == null || tileBottomRightPx == null) return null;
final tileLeft = tileTopLeftPx.x;
final tileRight = tileBottomRightPx.x;
final tileTop = tileTopLeftPx.y;
final tileBottom = tileBottomRightPx.y;
final regionLeft = tileLeft.clamp(0, width);
final regionRight = tileRight.clamp(0, width);
final regionTop = tileTop.clamp(0, height);
final regionBottom = tileBottom.clamp(0, height);
final regionWidth = regionRight - regionLeft;
final regionHeight = regionBottom - regionTop;
if (regionWidth == 0 || regionHeight == 0) return null;
final tileXScale = (tileRight - tileLeft) / mapServiceTileSize;
final sampleSize = max<int>(1, highestPowerOf2(tileXScale));
final region = entry.getRegion(
sampleSize: sampleSize,
region: Rectangle(regionLeft, regionTop, regionWidth, regionHeight),
);
final imageInfoCompleter = Completer<ImageInfo?>();
final imageStream = region.resolve(ImageConfiguration.empty);
final imageStreamListener = ImageStreamListener((image, synchronousCall) {
imageInfoCompleter.complete(image);
}, onError: imageInfoCompleter.completeError);
imageStream.addListener(imageStreamListener);
ImageInfo? regionImageInfo;
try {
regionImageInfo = await imageInfoCompleter.future;
} catch (error) {
debugPrint('failed to get image for region=$region with error=$error');
}
imageStream.removeListener(imageStreamListener);
final imageOffset = Offset(
regionLeft > tileLeft ? (regionLeft - tileLeft).toDouble() : 0,
regionTop > tileTop ? (regionTop - tileTop).toDouble() : 0,
);
final tileImageScaleX = (tileRight - tileLeft) / mapServiceTileSize;
final tileImageScaleY = (tileBottom - tileTop) / mapServiceTileSize;
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
canvas.scale(1 / tileImageScaleX, 1 / tileImageScaleY);
if (regionImageInfo != null) {
final s = sampleSize.toDouble();
canvas.scale(s, s);
canvas.drawImage(regionImageInfo.image, imageOffset / s, tileImagePaint);
canvas.scale(1 / s, 1 / s);
} else {
// fallback to show area
canvas.drawRect(
Rect.fromLTWH(
imageOffset.dx,
imageOffset.dy,
regionWidth.toDouble(),
regionHeight.toDouble(),
),
tileMissingPaint,
);
}
canvas.scale(tileImageScaleX, tileImageScaleY);
final picture = recorder.endRecording();
final tileImage = await picture.toImage(mapServiceTileSize, mapServiceTileSize);
final byteData = await tileImage.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) return null;
return MapTile(
width: tileImage.width,
height: tileImage.height,
data: byteData.buffer.asUint8List(),
);
}
@override
String get id => entry.uri;
@override
ImageProvider get imageProvider => entry.uriImage;
int get width => entry.width; int get width => entry.width;
int get height => entry.height; int get height => entry.height;
@override
bool get canOverlay => center != null;
LatLng? get center => pointToLatLng(Point((width / 2).round(), (height / 2).round())); LatLng? get center => pointToLatLng(Point((width / 2).round(), (height / 2).round()));
@override
LatLng? get topLeft => pointToLatLng(const Point(0, 0)); LatLng? get topLeft => pointToLatLng(const Point(0, 0));
@override
LatLng? get bottomRight => pointToLatLng(Point(width, height)); LatLng? get bottomRight => pointToLatLng(Point(width, height));
} }

View file

@ -38,10 +38,8 @@ class NamingPattern {
if (processorOptions != null) { if (processorOptions != null) {
processors.add(DateNamingProcessor(processorOptions.trim())); processors.add(DateNamingProcessor(processorOptions.trim()));
} }
break;
case NameNamingProcessor.key: case NameNamingProcessor.key:
processors.add(const NameNamingProcessor()); processors.add(const NameNamingProcessor());
break;
case CounterNamingProcessor.key: case CounterNamingProcessor.key:
int? start, padding; int? start, padding;
_applyProcessorOptions(processorOptions, (key, value) { _applyProcessorOptions(processorOptions, (key, value) {
@ -50,18 +48,14 @@ class NamingPattern {
switch (key) { switch (key) {
case CounterNamingProcessor.optionStart: case CounterNamingProcessor.optionStart:
start = valueInt; start = valueInt;
break;
case CounterNamingProcessor.optionPadding: case CounterNamingProcessor.optionPadding:
padding = valueInt; padding = valueInt;
break;
} }
} }
}); });
processors.add(CounterNamingProcessor(start: start ?? defaultCounterStart, padding: padding ?? defaultCounterPadding)); processors.add(CounterNamingProcessor(start: start ?? defaultCounterStart, padding: padding ?? defaultCounterPadding));
break;
default: default:
debugPrint('unsupported naming processor: ${match.group(0)}'); debugPrint('unsupported naming processor: ${match.group(0)}');
break;
} }
index = end; index = end;
}); });

View file

@ -15,13 +15,10 @@ extension ExtraDisplayRefreshRateMode on DisplayRefreshRateMode {
switch (this) { switch (this) {
case DisplayRefreshRateMode.auto: case DisplayRefreshRateMode.auto:
await FlutterDisplayMode.setPreferredMode(DisplayMode.auto); await FlutterDisplayMode.setPreferredMode(DisplayMode.auto);
break;
case DisplayRefreshRateMode.highest: case DisplayRefreshRateMode.highest:
await FlutterDisplayMode.setHighRefreshRate(); await FlutterDisplayMode.setHighRefreshRate();
break;
case DisplayRefreshRateMode.lowest: case DisplayRefreshRateMode.lowest:
await FlutterDisplayMode.setLowRefreshRate(); await FlutterDisplayMode.setLowRefreshRate();
break;
} }
} }
} }

View file

@ -375,7 +375,7 @@ class Settings extends ChangeNotifier {
if (_locale != null) { if (_locale != null) {
preferredLocales.add(_locale); preferredLocales.add(_locale);
} else { } else {
preferredLocales.addAll(WidgetsBinding.instance.window.locales); preferredLocales.addAll(WidgetsBinding.instance.platformDispatcher.locales);
if (preferredLocales.isEmpty) { if (preferredLocales.isEmpty) {
// the `window` locales may be empty in a window-less service context // the `window` locales may be empty in a window-less service context
preferredLocales.addAll(_systemLocalesFallback); preferredLocales.addAll(_systemLocalesFallback);
@ -1022,7 +1022,6 @@ class Settings extends ChangeNotifier {
if (value is num) { if (value is num) {
isRotationLocked = value == 0; isRotationLocked = value == 0;
} }
break;
case platformTransitionAnimationScaleKey: case platformTransitionAnimationScaleKey:
if (value is num) { if (value is num) {
areAnimationsRemoved = value == 0; areAnimationsRemoved = value == 0;
@ -1080,7 +1079,6 @@ class Settings extends ChangeNotifier {
} else { } else {
debugPrint('failed to import key=$key, value=$newValue is not an int'); debugPrint('failed to import key=$key, value=$newValue is not an int');
} }
break;
case subtitleFontSizeKey: case subtitleFontSizeKey:
case infoMapZoomKey: case infoMapZoomKey:
if (newValue is double) { if (newValue is double) {
@ -1088,7 +1086,6 @@ class Settings extends ChangeNotifier {
} else { } else {
debugPrint('failed to import key=$key, value=$newValue is not a double'); debugPrint('failed to import key=$key, value=$newValue is not a double');
} }
break;
case isInstalledAppAccessAllowedKey: case isInstalledAppAccessAllowedKey:
case isErrorReportingAllowedKey: case isErrorReportingAllowedKey:
case enableDynamicColorKey: case enableDynamicColorKey:
@ -1144,7 +1141,6 @@ class Settings extends ChangeNotifier {
} else { } else {
debugPrint('failed to import key=$key, value=$newValue is not a bool'); debugPrint('failed to import key=$key, value=$newValue is not a bool');
} }
break;
case localeKey: case localeKey:
case displayRefreshRateModeKey: case displayRefreshRateModeKey:
case themeBrightnessKey: case themeBrightnessKey:
@ -1187,7 +1183,6 @@ class Settings extends ChangeNotifier {
} else { } else {
debugPrint('failed to import key=$key, value=$newValue is not a string'); debugPrint('failed to import key=$key, value=$newValue is not a string');
} }
break;
case drawerTypeBookmarksKey: case drawerTypeBookmarksKey:
case drawerAlbumBookmarksKey: case drawerAlbumBookmarksKey:
case drawerPageBookmarksKey: case drawerPageBookmarksKey:
@ -1203,7 +1198,6 @@ class Settings extends ChangeNotifier {
} else { } else {
debugPrint('failed to import key=$key, value=$newValue is not a list'); debugPrint('failed to import key=$key, value=$newValue is not a list');
} }
break;
} }
} }
if (oldValue != newValue) { if (oldValue != newValue) {

View file

@ -47,13 +47,10 @@ mixin AlbumMixin on SourceBase {
switch (androidFileUtils.getAlbumType(album)) { switch (androidFileUtils.getAlbumType(album)) {
case AlbumType.regular: case AlbumType.regular:
regularAlbums.add(album); regularAlbums.add(album);
break;
case AlbumType.app: case AlbumType.app:
appAlbums.add(album); appAlbums.add(album);
break;
default: default:
specialAlbums.add(album); specialAlbums.add(album);
break;
} }
} }
return Map.fromEntries([...specialAlbums, ...appAlbums, ...regularAlbums].map((album) => MapEntry( return Map.fromEntries([...specialAlbums, ...appAlbums, ...regularAlbums].map((album) => MapEntry(

View file

@ -68,10 +68,8 @@ class CollectionLens with ChangeNotifier {
case MoveType.move: case MoveType.move:
case MoveType.fromBin: case MoveType.fromBin:
refresh(); refresh();
break;
case MoveType.toBin: case MoveType.toBin:
_onEntryRemoved(e.entries); _onEntryRemoved(e.entries);
break;
} }
})); }));
_subscriptions.add(sourceEvents.on<EntryRefreshedEvent>().listen((e) => refresh())); _subscriptions.add(sourceEvents.on<EntryRefreshedEvent>().listen((e) => refresh()));
@ -213,16 +211,12 @@ class CollectionLens with ChangeNotifier {
switch (sortFactor) { switch (sortFactor) {
case EntrySortFactor.date: case EntrySortFactor.date:
_filteredSortedEntries.sort(AvesEntrySort.compareByDate); _filteredSortedEntries.sort(AvesEntrySort.compareByDate);
break;
case EntrySortFactor.name: case EntrySortFactor.name:
_filteredSortedEntries.sort(AvesEntrySort.compareByName); _filteredSortedEntries.sort(AvesEntrySort.compareByName);
break;
case EntrySortFactor.rating: case EntrySortFactor.rating:
_filteredSortedEntries.sort(AvesEntrySort.compareByRating); _filteredSortedEntries.sort(AvesEntrySort.compareByRating);
break;
case EntrySortFactor.size: case EntrySortFactor.size:
_filteredSortedEntries.sort(AvesEntrySort.compareBySize); _filteredSortedEntries.sort(AvesEntrySort.compareBySize);
break;
} }
if (sortReverse) { if (sortReverse) {
_filteredSortedEntries = _filteredSortedEntries.reversed.toList(); _filteredSortedEntries = _filteredSortedEntries.reversed.toList();
@ -240,33 +234,25 @@ class CollectionLens with ChangeNotifier {
switch (sectionFactor) { switch (sectionFactor) {
case EntryGroupFactor.album: case EntryGroupFactor.album:
sections = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); sections = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
break;
case EntryGroupFactor.month: case EntryGroupFactor.month:
sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken)); sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken));
break;
case EntryGroupFactor.day: case EntryGroupFactor.day:
sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken)); sections = groupBy<AvesEntry, EntryDateSectionKey>(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken));
break;
case EntryGroupFactor.none: case EntryGroupFactor.none:
sections = Map.fromEntries([ sections = Map.fromEntries([
MapEntry(const SectionKey(), _filteredSortedEntries), MapEntry(const SectionKey(), _filteredSortedEntries),
]); ]);
break;
} }
break;
case EntrySortFactor.name: case EntrySortFactor.name:
final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
final compare = sortReverse ? (a, b) => source.compareAlbumsByName(b.directory!, a.directory!) : (a, b) => source.compareAlbumsByName(a.directory!, b.directory!); final compare = sortReverse ? (a, b) => source.compareAlbumsByName(b.directory!, a.directory!) : (a, b) => source.compareAlbumsByName(a.directory!, b.directory!);
sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, compare); sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, compare);
break;
case EntrySortFactor.rating: case EntrySortFactor.rating:
sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
break;
case EntrySortFactor.size: case EntrySortFactor.size:
sections = Map.fromEntries([ sections = Map.fromEntries([
MapEntry(const SectionKey(), _filteredSortedEntries), MapEntry(const SectionKey(), _filteredSortedEntries),
]); ]);
break;
} }
} }
sections = Map.unmodifiable(sections); sections = Map.unmodifiable(sections);

View file

@ -221,18 +221,14 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
switch (key) { switch (key) {
case 'contentId': case 'contentId':
entry.contentId = newValue as int?; entry.contentId = newValue as int?;
break;
case 'dateModifiedSecs': case 'dateModifiedSecs':
// `dateModifiedSecs` changes when moving entries to another directory, // `dateModifiedSecs` changes when moving entries to another directory,
// but it does not change when renaming the containing directory // but it does not change when renaming the containing directory
entry.dateModifiedSecs = newValue as int?; entry.dateModifiedSecs = newValue as int?;
break;
case 'path': case 'path':
entry.path = newValue as String?; entry.path = newValue as String?;
break;
case 'title': case 'title':
entry.sourceTitle = newValue as String?; entry.sourceTitle = newValue as String?;
break;
case 'trashed': case 'trashed':
final trashed = newValue as bool; final trashed = newValue as bool;
entry.trashed = trashed; entry.trashed = trashed;
@ -243,13 +239,10 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
dateMillis: DateTime.now().millisecondsSinceEpoch, dateMillis: DateTime.now().millisecondsSinceEpoch,
) )
: null; : null;
break;
case 'uri': case 'uri':
entry.uri = newValue as String; entry.uri = newValue as String;
break;
case 'origin': case 'origin':
entry.origin = newValue as int; entry.origin = newValue as int;
break;
} }
}); });
if (entry.trashed) { if (entry.trashed) {
@ -371,16 +364,13 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
switch (moveType) { switch (moveType) {
case MoveType.copy: case MoveType.copy:
addEntries(movedEntries); addEntries(movedEntries);
break;
case MoveType.move: case MoveType.move:
case MoveType.export: case MoveType.export:
cleanEmptyAlbums(fromAlbums.whereNotNull().toSet()); cleanEmptyAlbums(fromAlbums.whereNotNull().toSet());
addDirectories(albums: destinationAlbums); addDirectories(albums: destinationAlbums);
break;
case MoveType.toBin: case MoveType.toBin:
case MoveType.fromBin: case MoveType.fromBin:
updateDerivedFilters(movedEntries); updateDerivedFilters(movedEntries);
break;
} }
invalidateAlbumFilterSummary(directories: fromAlbums); invalidateAlbumFilterSummary(directories: fromAlbums);
_invalidate(entries: movedEntries); _invalidate(entries: movedEntries);

View file

@ -210,38 +210,29 @@ class VideoMetadataFormatter {
case Keys.androidCaptureFramerate: case Keys.androidCaptureFramerate:
final captureFps = double.parse(value); final captureFps = double.parse(value);
save('Capture Frame Rate', '${roundToPrecision(captureFps, decimals: 3).toString()} FPS'); save('Capture Frame Rate', '${roundToPrecision(captureFps, decimals: 3).toString()} FPS');
break;
case Keys.androidManufacturer: case Keys.androidManufacturer:
save('Android Manufacturer', value); save('Android Manufacturer', value);
break;
case Keys.androidModel: case Keys.androidModel:
save('Android Model', value); save('Android Model', value);
break;
case Keys.androidVersion: case Keys.androidVersion:
save('Android Version', value); save('Android Version', value);
break;
case Keys.bitrate: case Keys.bitrate:
case Keys.bps: case Keys.bps:
save('Bit Rate', _formatMetric(value, 'b/s')); save('Bit Rate', _formatMetric(value, 'b/s'));
break;
case Keys.byteCount: case Keys.byteCount:
save('Size', _formatFilesize(value)); save('Size', _formatFilesize(value));
break;
case Keys.channelLayout: case Keys.channelLayout:
save('Channel Layout', _formatChannelLayout(value)); save('Channel Layout', _formatChannelLayout(value));
break;
case Keys.codecName: case Keys.codecName:
if (value != 'none') { if (value != 'none') {
save('Format', _formatCodecName(value)); save('Format', _formatCodecName(value));
} }
break;
case Keys.codecPixelFormat: case Keys.codecPixelFormat:
if (streamType == MediaStreamTypes.video) { if (streamType == MediaStreamTypes.video) {
// this is just a short name used by FFmpeg // this is just a short name used by FFmpeg
// user-friendly descriptions for related enums are defined in libavutil/pixfmt.h // user-friendly descriptions for related enums are defined in libavutil/pixfmt.h
save('Pixel Format', (value as String).toUpperCase()); save('Pixel Format', (value as String).toUpperCase());
} }
break;
case Keys.codecProfileId: case Keys.codecProfileId:
{ {
final profile = int.tryParse(value); final profile = int.tryParse(value);
@ -260,18 +251,14 @@ class VideoMetadataFormatter {
profileString = Hevc.formatProfile(profile, level); profileString = Hevc.formatProfile(profile, level);
} }
} }
break;
} }
case Codecs.aac: case Codecs.aac:
profileString = AAC.formatProfile(profile); profileString = AAC.formatProfile(profile);
break;
default: default:
profileString = profile.toString(); profileString = profile.toString();
break;
} }
save('Format Profile', profileString); save('Format Profile', profileString);
} }
break;
} }
case Keys.compatibleBrands: case Keys.compatibleBrands:
final formattedBrands = RegExp(r'.{4}').allMatches(value).map((m) { final formattedBrands = RegExp(r'.{4}').allMatches(value).map((m) {
@ -279,52 +266,37 @@ class VideoMetadataFormatter {
return _formatBrand(brand); return _formatBrand(brand);
}).join(', '); }).join(', ');
save('Compatible Brands', formattedBrands); save('Compatible Brands', formattedBrands);
break;
case Keys.creationTime: case Keys.creationTime:
save('Creation Time', _formatDate(value)); save('Creation Time', _formatDate(value));
break;
case Keys.date: case Keys.date:
if (value is String && value != '0') { if (value is String && value != '0') {
final charCount = value.length; final charCount = value.length;
save(charCount == 4 ? 'Year' : 'Date', value); save(charCount == 4 ? 'Year' : 'Date', value);
} }
break;
case Keys.duration: case Keys.duration:
save('Duration', _formatDuration(value)); save('Duration', _formatDuration(value));
break;
case Keys.durationMicros: case Keys.durationMicros:
if (value != 0) save('Duration', formatPreciseDuration(Duration(microseconds: value))); if (value != 0) save('Duration', formatPreciseDuration(Duration(microseconds: value)));
break;
case Keys.fpsDen: case Keys.fpsDen:
save('Frame Rate', '${roundToPrecision(info[Keys.fpsNum] / info[Keys.fpsDen], decimals: 3).toString()} FPS'); save('Frame Rate', '${roundToPrecision(info[Keys.fpsNum] / info[Keys.fpsDen], decimals: 3).toString()} FPS');
break;
case Keys.frameCount: case Keys.frameCount:
save('Frame Count', value); save('Frame Count', value);
break;
case Keys.height: case Keys.height:
save('Height', '$value pixels'); save('Height', '$value pixels');
break;
case Keys.language: case Keys.language:
if (value != 'und') save('Language', _formatLanguage(value)); if (value != 'und') save('Language', _formatLanguage(value));
break;
case Keys.location: case Keys.location:
save('Location', _formatLocation(value)); save('Location', _formatLocation(value));
break;
case Keys.majorBrand: case Keys.majorBrand:
save('Major Brand', _formatBrand(value)); save('Major Brand', _formatBrand(value));
break;
case Keys.mediaFormat: case Keys.mediaFormat:
save('Format', (value as String).splitMapJoin(',', onMatch: (s) => ', ', onNonMatch: _formatCodecName)); save('Format', (value as String).splitMapJoin(',', onMatch: (s) => ', ', onNonMatch: _formatCodecName));
break;
case Keys.mediaType: case Keys.mediaType:
save('Media Type', value); save('Media Type', value);
break;
case Keys.minorVersion: case Keys.minorVersion:
if (value != '0') save('Minor Version', value); if (value != '0') save('Minor Version', value);
break;
case Keys.quicktimeLocationAccuracyHorizontal: case Keys.quicktimeLocationAccuracyHorizontal:
save('QuickTime Location Horizontal Accuracy', value); save('QuickTime Location Horizontal Accuracy', value);
break;
case Keys.quicktimeCreationDate: case Keys.quicktimeCreationDate:
case Keys.quicktimeLocationIso6709: case Keys.quicktimeLocationIso6709:
case Keys.quicktimeMake: case Keys.quicktimeMake:
@ -334,37 +306,27 @@ class VideoMetadataFormatter {
break; break;
case Keys.rotate: case Keys.rotate:
save('Rotation', '$value°'); save('Rotation', '$value°');
break;
case Keys.sampleRate: case Keys.sampleRate:
save('Sample Rate', _formatMetric(value, 'Hz')); save('Sample Rate', _formatMetric(value, 'Hz'));
break;
case Keys.sarDen: case Keys.sarDen:
final sarNum = info[Keys.sarNum]; final sarNum = info[Keys.sarNum];
final sarDen = info[Keys.sarDen]; final sarDen = info[Keys.sarDen];
// skip common square pixels (1:1) // skip common square pixels (1:1)
if (sarNum != sarDen) save('SAR', '$sarNum:$sarDen'); if (sarNum != sarDen) save('SAR', '$sarNum:$sarDen');
break;
case Keys.sourceOshash: case Keys.sourceOshash:
save('Source OSHash', value); save('Source OSHash', value);
break;
case Keys.startMicros: case Keys.startMicros:
if (value != 0) save('Start', formatPreciseDuration(Duration(microseconds: value))); if (value != 0) save('Start', formatPreciseDuration(Duration(microseconds: value)));
break;
case Keys.statisticsWritingApp: case Keys.statisticsWritingApp:
save('Stats Writing App', value); save('Stats Writing App', value);
break;
case Keys.statisticsWritingDateUtc: case Keys.statisticsWritingDateUtc:
save('Stats Writing Date', _formatDate(value)); save('Stats Writing Date', _formatDate(value));
break;
case Keys.track: case Keys.track:
if (value != '0') save('Track', value); if (value != '0') save('Track', value);
break;
case Keys.width: case Keys.width:
save('Width', '$value pixels'); save('Width', '$value pixels');
break;
case Keys.xiaomiSlowMoment: case Keys.xiaomiSlowMoment:
save('Xiaomi Slow Moment', value); save('Xiaomi Slow Moment', value);
break;
default: default:
save(key.toSentenceCase(), value.toString()); save(key.toSentenceCase(), value.toString());
} }

View file

@ -28,43 +28,30 @@ class H264 {
switch (profileIndex) { switch (profileIndex) {
case profileBaseline: case profileBaseline:
profile = 'Baseline'; profile = 'Baseline';
break;
case profileConstrainedBaseline: case profileConstrainedBaseline:
profile = 'Constrained Baseline'; profile = 'Constrained Baseline';
break;
case profileMain: case profileMain:
profile = 'Main'; profile = 'Main';
break;
case profileExtended: case profileExtended:
profile = 'Extended'; profile = 'Extended';
break;
case profileHigh: case profileHigh:
profile = 'High'; profile = 'High';
break;
case profileHigh10: case profileHigh10:
profile = 'High 10'; profile = 'High 10';
break;
case profileHigh10Intra: case profileHigh10Intra:
profile = 'High 10 Intra'; profile = 'High 10 Intra';
break;
case profileHigh422: case profileHigh422:
profile = 'High 4:2:2'; profile = 'High 4:2:2';
break;
case profileHigh422Intra: case profileHigh422Intra:
profile = 'High 4:2:2 Intra'; profile = 'High 4:2:2 Intra';
break;
case profileHigh444: case profileHigh444:
profile = 'High 4:4:4'; profile = 'High 4:4:4';
break;
case profileHigh444Predictive: case profileHigh444Predictive:
profile = 'High 4:4:4 Predictive'; profile = 'High 4:4:4 Predictive';
break;
case profileHigh444Intra: case profileHigh444Intra:
profile = 'High 4:4:4 Intra'; profile = 'High 4:4:4 Intra';
break;
case profileCAVLC444: case profileCAVLC444:
profile = 'CAVLC 4:4:4'; profile = 'CAVLC 4:4:4';
break;
default: default:
return '$profileIndex'; return '$profileIndex';
} }

View file

@ -9,16 +9,12 @@ class Hevc {
switch (profileIndex) { switch (profileIndex) {
case profileMain: case profileMain:
profile = 'Main'; profile = 'Main';
break;
case profileMain10: case profileMain10:
profile = 'Main 10'; profile = 'Main 10';
break;
case profileMainStillPicture: case profileMainStillPicture:
profile = 'Main Still Picture'; profile = 'Main Still Picture';
break;
case profileRExt: case profileRExt:
profile = 'Format Range'; profile = 'Format Range';
break;
default: default:
return '$profileIndex'; return '$profileIndex';
} }

View file

@ -145,11 +145,9 @@ class Analyzer {
case AnalyzerState.stopping: case AnalyzerState.stopping:
await _stopPlatformService(); await _stopPlatformService();
_serviceStateNotifier.value = AnalyzerState.stopped; _serviceStateNotifier.value = AnalyzerState.stopped;
break;
case AnalyzerState.stopped: case AnalyzerState.stopped:
_controller?.stopSignal.value = true; _controller?.stopSignal.value = true;
_stopUpdateTimer(); _stopUpdateTimer();
break;
} }
} }

View file

@ -96,25 +96,19 @@ class PlatformMediaSessionService implements MediaSessionService, Disposable {
switch (command) { switch (command) {
case 'play': case 'play':
event = const MediaCommandEvent(MediaCommand.play); event = const MediaCommandEvent(MediaCommand.play);
break;
case 'pause': case 'pause':
event = const MediaCommandEvent(MediaCommand.pause); event = const MediaCommandEvent(MediaCommand.pause);
break;
case 'skip_to_next': case 'skip_to_next':
event = const MediaCommandEvent(MediaCommand.skipToNext); event = const MediaCommandEvent(MediaCommand.skipToNext);
break;
case 'skip_to_previous': case 'skip_to_previous':
event = const MediaCommandEvent(MediaCommand.skipToPrevious); event = const MediaCommandEvent(MediaCommand.skipToPrevious);
break;
case 'stop': case 'stop':
event = const MediaCommandEvent(MediaCommand.stop); event = const MediaCommandEvent(MediaCommand.stop);
break;
case 'seek': case 'seek':
final position = fields['position'] as int?; final position = fields['position'] as int?;
if (position != null) { if (position != null) {
event = MediaSeekCommandEvent(MediaCommand.stop, position: position); event = MediaSeekCommandEvent(MediaCommand.stop, position: position);
} }
break;
} }
if (event != null) { if (event != null) {
_streamController.add(event); _streamController.add(event);

View file

@ -80,7 +80,7 @@ class SvgMetadataService {
final docDir = Map.fromEntries([ final docDir = Map.fromEntries([
...root.attributes.where((a) => _attributes.contains(a.name.qualified)).map((a) => MapEntry(formatKey(a.name.qualified), a.value)), ...root.attributes.where((a) => _attributes.contains(a.name.qualified)).map((a) => MapEntry(formatKey(a.name.qualified), a.value)),
..._textElements.map((name) { ..._textElements.map((name) {
final value = root.getElement(name)?.text; final value = root.getElement(name)?.innerText;
return value != null ? MapEntry(formatKey(name), value) : null; return value != null ? MapEntry(formatKey(name), value) : null;
}).whereNotNull(), }).whereNotNull(),
]); ]);

View file

@ -74,15 +74,12 @@ class PlatformWindowService implements WindowService {
case Orientation.landscape: case Orientation.landscape:
// SCREEN_ORIENTATION_SENSOR_LANDSCAPE // SCREEN_ORIENTATION_SENSOR_LANDSCAPE
orientationCode = 6; orientationCode = 6;
break;
case Orientation.portrait: case Orientation.portrait:
// SCREEN_ORIENTATION_SENSOR_PORTRAIT // SCREEN_ORIENTATION_SENSOR_PORTRAIT
orientationCode = 7; orientationCode = 7;
break;
default: default:
// SCREEN_ORIENTATION_UNSPECIFIED // SCREEN_ORIENTATION_UNSPECIFIED
orientationCode = -1; orientationCode = -1;
break;
} }
try { try {
await _platform.invokeMethod('requestOrientation', <String, dynamic>{ await _platform.invokeMethod('requestOrientation', <String, dynamic>{

View file

@ -274,11 +274,9 @@ class DiffMatchPatch {
case Operation.insert: case Operation.insert:
count_insert++; count_insert++;
text_insert.write(diffs[pointer].text); text_insert.write(diffs[pointer].text);
break;
case Operation.delete: case Operation.delete:
count_delete++; count_delete++;
text_delete.write(diffs[pointer].text); text_delete.write(diffs[pointer].text);
break;
case Operation.equal: case Operation.equal:
// Upon reaching an equality, check for prior redundancies. // Upon reaching an equality, check for prior redundancies.
if (count_delete >= 1 && count_insert >= 1) { if (count_delete >= 1 && count_insert >= 1) {
@ -295,7 +293,6 @@ class DiffMatchPatch {
count_delete = 0; count_delete = 0;
text_delete.clear(); text_delete.clear();
text_insert.clear(); text_insert.clear();
break;
} }
pointer++; pointer++;
} }
@ -1013,12 +1010,10 @@ class DiffMatchPatch {
count_insert++; count_insert++;
text_insert += diffs[pointer].text; text_insert += diffs[pointer].text;
pointer++; pointer++;
break;
case Operation.delete: case Operation.delete:
count_delete++; count_delete++;
text_delete += diffs[pointer].text; text_delete += diffs[pointer].text;
pointer++; pointer++;
break;
case Operation.equal: case Operation.equal:
// Upon reaching an equality, check for prior redundancies. // Upon reaching an equality, check for prior redundancies.
if (count_delete + count_insert > 1) { if (count_delete + count_insert > 1) {
@ -1068,7 +1063,6 @@ class DiffMatchPatch {
count_delete = 0; count_delete = 0;
text_delete = ''; text_delete = '';
text_insert = ''; text_insert = '';
break;
} }
} }
if (diffs.last.text.isEmpty) { if (diffs.last.text.isEmpty) {
@ -1155,17 +1149,14 @@ class DiffMatchPatch {
html.write('<ins style="background:#e6ffe6;">'); html.write('<ins style="background:#e6ffe6;">');
html.write(text); html.write(text);
html.write('</ins>'); html.write('</ins>');
break;
case Operation.delete: case Operation.delete:
html.write('<del style="background:#ffe6e6;">'); html.write('<del style="background:#ffe6e6;">');
html.write(text); html.write(text);
html.write('</del>'); html.write('</del>');
break;
case Operation.equal: case Operation.equal:
html.write('<span>'); html.write('<span>');
html.write(text); html.write(text);
html.write('</span>'); html.write('</span>');
break;
} }
} }
return html.toString(); return html.toString();
@ -1209,16 +1200,13 @@ class DiffMatchPatch {
switch (aDiff.operation) { switch (aDiff.operation) {
case Operation.insert: case Operation.insert:
insertions += aDiff.text.length; insertions += aDiff.text.length;
break;
case Operation.delete: case Operation.delete:
deletions += aDiff.text.length; deletions += aDiff.text.length;
break;
case Operation.equal: case Operation.equal:
// A deletion and an insertion is one substitution. // A deletion and an insertion is one substitution.
levenshtein += max(insertions, deletions); levenshtein += max(insertions, deletions);
insertions = 0; insertions = 0;
deletions = 0; deletions = 0;
break;
} }
} }
levenshtein += max(insertions, deletions); levenshtein += max(insertions, deletions);
@ -1239,17 +1227,14 @@ class DiffMatchPatch {
text.write('+'); text.write('+');
text.write(Uri.encodeFull(aDiff.text)); text.write(Uri.encodeFull(aDiff.text));
text.write('\t'); text.write('\t');
break;
case Operation.delete: case Operation.delete:
text.write('-'); text.write('-');
text.write(aDiff.text.length); text.write(aDiff.text.length);
text.write('\t'); text.write('\t');
break;
case Operation.equal: case Operation.equal:
text.write('='); text.write('=');
text.write(aDiff.text.length); text.write(aDiff.text.length);
text.write('\t'); text.write('\t');
break;
} }
} }
String delta = text.toString(); String delta = text.toString();
@ -1289,7 +1274,6 @@ class DiffMatchPatch {
throw ArgumentError('Illegal escape in diff_fromDelta: $param'); throw ArgumentError('Illegal escape in diff_fromDelta: $param');
} }
diffs.add(Diff(Operation.insert, param)); diffs.add(Diff(Operation.insert, param));
break;
case '-': case '-':
// Fall through. // Fall through.
case '=': case '=':
@ -1314,7 +1298,6 @@ class DiffMatchPatch {
} else { } else {
diffs.add(Diff(Operation.delete, text)); diffs.add(Diff(Operation.delete, text));
} }
break;
default: default:
// Anything else is an error. // Anything else is an error.
throw ArgumentError('Invalid diff operation in diff_fromDelta: ${token[0]}'); throw ArgumentError('Invalid diff operation in diff_fromDelta: ${token[0]}');

View file

@ -26,7 +26,7 @@ class XMP {
static const nsXmp = XmpNamespaces.xmp; static const nsXmp = XmpNamespaces.xmp;
// for `rdf:Description` node only // for `rdf:Description` node only
static bool _hasMeaningfulChildren(XmlNode node) => node.children.any((v) => v.nodeType != XmlNodeType.TEXT || v.text.trim().isNotEmpty); static bool _hasMeaningfulChildren(XmlNode node) => node.children.any((v) => v.nodeType != XmlNodeType.TEXT || v.innerText.trim().isNotEmpty);
// for `rdf:Description` node only // for `rdf:Description` node only
static bool _hasMeaningfulAttributes(XmlNode description) { static bool _hasMeaningfulAttributes(XmlNode description) {

View file

@ -79,10 +79,8 @@ Future<AvesEntry?> _getWidgetEntry(int widgetId, bool reuseEntry) async {
switch (settings.getWidgetDisplayedItem(widgetId)) { switch (settings.getWidgetDisplayedItem(widgetId)) {
case WidgetDisplayedItem.random: case WidgetDisplayedItem.random:
entries.shuffle(); entries.shuffle();
break;
case WidgetDisplayedItem.mostRecent: case WidgetDisplayedItem.mostRecent:
entries.sort(AvesEntrySort.compareByDate); entries.sort(AvesEntrySort.compareByDate);
break;
} }
final entry = entries.firstOrNull; final entry = entries.firstOrNull;
if (entry != null) { if (entry != null) {

View file

@ -94,11 +94,12 @@ class _AppReferenceState extends State<AppReference> {
return FutureBuilder<PackageInfo>( return FutureBuilder<PackageInfo>(
future: _packageInfoLoader, future: _packageInfoLoader,
builder: (context, snapshot) { builder: (context, snapshot) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
AvesLogo( AvesLogo(
size: _appTitleStyle.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3, size: _appTitleStyle.fontSize! * textScaleFactor * 1.3,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(

View file

@ -157,7 +157,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
'Device: ${androidInfo.manufacturer} ${androidInfo.model}', 'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
'Geocoder: ${device.hasGeocoder ? 'ready' : 'not available'}', 'Geocoder: ${device.hasGeocoder ? 'ready' : 'not available'}',
'Mobile services: ${mobileServices.isServiceAvailable ? 'ready' : 'not available'}', 'Mobile services: ${mobileServices.isServiceAvailable ? 'ready' : 'not available'}',
'System locales: ${WidgetsBinding.instance.window.locales.join(', ')}', 'System locales: ${WidgetsBinding.instance.platformDispatcher.locales.join(', ')}',
'Storage volumes: ${storageVolumes.map((v) => v.path).join(', ')}', 'Storage volumes: ${storageVolumes.map((v) => v.path).join(', ')}',
'Storage grants: ${storageGrants.join(', ')}', 'Storage grants: ${storageGrants.join(', ')}',
'Error reporting: ${settings.isErrorReportingAllowed}', 'Error reporting: ${settings.isErrorReportingAllowed}',

View file

@ -1,7 +1,9 @@
import 'dart:developer' show Flow, Timeline;
import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/behaviour/intents.dart'; import 'package:aves/widgets/common/behaviour/intents.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide Flow;
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -209,10 +211,21 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
bool _loaded = false; bool _loaded = false;
Future<void> _initLicenses() async { Future<void> _initLicenses() async {
int debugFlowId = -1;
assert(() {
final Flow flow = Flow.begin();
Timeline.timeSync('_initLicenses()', () {}, flow: flow);
debugFlowId = flow.id;
return true;
}());
for (final LicenseEntry license in widget.licenseEntries) { for (final LicenseEntry license in widget.licenseEntries) {
if (!mounted) { if (!mounted) {
return; return;
} }
assert(() {
Timeline.timeSync('_initLicenses()', () {}, flow: Flow.step(debugFlowId));
return true;
}());
final List<LicenseParagraph> paragraphs = await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>( final List<LicenseParagraph> paragraphs = await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
license.paragraphs.toList, license.paragraphs.toList,
Priority.animation, Priority.animation,
@ -237,6 +250,7 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
), ),
)); ));
} else { } else {
assert(paragraph.indent >= 0);
_licenses.add(Padding( _licenses.add(Padding(
padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent), padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
child: Text(paragraph.text), child: Text(paragraph.text),
@ -248,16 +262,21 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
setState(() { setState(() {
_loaded = true; _loaded = true;
}); });
assert(() {
Timeline.timeSync('Build scheduled', () {}, flow: Flow.end(debugFlowId));
return true;
}());
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context); final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final String title = widget.packageName; final String title = widget.packageName;
final String subtitle = localizations.licensesPackageDetailText(widget.licenseEntries.length); final String subtitle = localizations.licensesPackageDetailText(widget.licenseEntries.length);
const double pad = 24; final double pad = _getGutterSize(context);
const EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad); final EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad);
final List<Widget> listWidgets = <Widget>[ final List<Widget> listWidgets = <Widget>[
..._licenses, ..._licenses,
if (!_loaded) if (!_loaded)
@ -274,9 +293,11 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
page = Scaffold( page = Scaffold(
appBar: AppBar( appBar: AppBar(
title: _PackageLicensePageTitle( title: _PackageLicensePageTitle(
title, title: title,
subtitle, subtitle: subtitle,
theme.primaryTextTheme, theme: theme.useMaterial3 ? theme.textTheme : theme.primaryTextTheme,
titleTextStyle: theme.appBarTheme.titleTextStyle,
foregroundColor: theme.appBarTheme.foregroundColor,
), ),
), ),
body: Center( body: Center(
@ -292,7 +313,11 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
// A Scrollbar is built-in below. // A Scrollbar is built-in below.
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: Scrollbar( child: Scrollbar(
child: ListView(padding: padding, children: listWidgets), child: ListView(
primary: true,
padding: padding,
children: listWidgets,
),
), ),
), ),
), ),
@ -308,7 +333,12 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
pinned: true, pinned: true,
backgroundColor: theme.cardColor, backgroundColor: theme.cardColor,
title: _PackageLicensePageTitle(title, subtitle, theme.textTheme), title: _PackageLicensePageTitle(
title: title,
subtitle: subtitle,
theme: theme.textTheme,
titleTextStyle: theme.textTheme.titleLarge,
),
), ),
SliverPadding( SliverPadding(
padding: padding, padding: padding,
@ -334,27 +364,36 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
} }
class _PackageLicensePageTitle extends StatelessWidget { class _PackageLicensePageTitle extends StatelessWidget {
const _PackageLicensePageTitle( const _PackageLicensePageTitle({
this.title, required this.title,
this.subtitle, required this.subtitle,
this.theme, required this.theme,
); this.titleTextStyle,
this.foregroundColor,
});
final String title; final String title;
final String subtitle; final String subtitle;
final TextTheme theme; final TextTheme theme;
final TextStyle? titleTextStyle;
final Color? foregroundColor;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color? color = Theme.of(context).appBarTheme.foregroundColor; final TextStyle? effectiveTitleTextStyle = titleTextStyle ?? theme.titleLarge;
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text(title, style: theme.titleLarge?.copyWith(color: color)), Text(title, style: effectiveTitleTextStyle?.copyWith(color: foregroundColor)),
Text(subtitle, style: theme.titleSmall?.copyWith(color: color)), Text(subtitle, style: theme.titleSmall?.copyWith(color: foregroundColor)),
], ],
); );
} }
} }
const int _materialGutterThreshold = 720;
const double _wideGutterSize = 24.0;
const double _narrowGutterSize = 12.0;
double _getGutterSize(BuildContext context) => MediaQuery.sizeOf(context).width >= _materialGutterThreshold ? _wideGutterSize : _narrowGutterSize;

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'dart:ui';
import 'package:aves/app_flavor.dart'; import 'package:aves/app_flavor.dart';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
@ -180,8 +179,6 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
super.initState(); super.initState();
EquatableConfig.stringify = true; EquatableConfig.stringify = true;
_appSetup = _setup(); _appSetup = _setup();
// remember screen size to use it later, when `context` and `window` are no longer reliable
_screenSize = _getScreenSize();
_shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont(); _shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont();
_dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette(); _dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette();
_subscriptions.add(_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChanged(event as String?))); _subscriptions.add(_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChanged(event as String?)));
@ -206,6 +203,9 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// remember screen size to use it later, when `context` and `window` are no longer reliable
_screenSize ??= _getScreenSize(context);
// place the settings provider above `MaterialApp` // place the settings provider above `MaterialApp`
// so it can be used during navigation transitions // so it can be used during navigation transitions
return MultiProvider( return MultiProvider(
@ -266,10 +266,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
// KEYCODE_ENTER, KEYCODE_BUTTON_A, KEYCODE_NUMPAD_ENTER // KEYCODE_ENTER, KEYCODE_BUTTON_A, KEYCODE_NUMPAD_ENTER
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
}, },
child: MediaQuery.fromWindow( child: MediaQuery(
child: Builder(
builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith( data: MediaQuery.of(context).copyWith(
// disable accessible navigation, as it impacts snack bar action timer // disable accessible navigation, as it impacts snack bar action timer
// for all users of apps registered as accessibility services, // for all users of apps registered as accessibility services,
@ -294,10 +291,6 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
supportedLocales: AvesApp.supportedLocales, supportedLocales: AvesApp.supportedLocales,
// TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906 // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906
scrollBehavior: StretchMaterialScrollBehavior(), scrollBehavior: StretchMaterialScrollBehavior(),
useInheritedMediaQuery: true,
),
);
},
), ),
), ),
); );
@ -390,7 +383,6 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
case AppMode.pickSingleMediaExternal: case AppMode.pickSingleMediaExternal:
case AppMode.pickMultipleMediaExternal: case AppMode.pickMultipleMediaExternal:
_saveTopEntries(); _saveTopEntries();
break;
case AppMode.pickCollectionFiltersExternal: case AppMode.pickCollectionFiltersExternal:
case AppMode.pickMediaInternal: case AppMode.pickMediaInternal:
case AppMode.pickFilterInternal: case AppMode.pickFilterInternal:
@ -400,10 +392,8 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
case AppMode.view: case AppMode.view:
break; break;
} }
break;
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
RecentlyAddedFilter.updateNow(); RecentlyAddedFilter.updateNow();
break;
case AppLifecycleState.paused: case AppLifecycleState.paused:
case AppLifecycleState.detached: case AppLifecycleState.detached:
break; break;
@ -421,9 +411,10 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
Widget _getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage(); Widget _getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage();
Size? _getScreenSize() { Size? _getScreenSize(BuildContext context) {
final physicalSize = window.physicalSize; final view = View.of(context);
final ratio = window.devicePixelRatio; final physicalSize = view.physicalSize;
final ratio = view.devicePixelRatio;
return physicalSize > Size.zero && ratio > 0 ? physicalSize / ratio : null; return physicalSize > Size.zero && ratio > 0 ? physicalSize / ratio : null;
} }
@ -431,7 +422,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
void _saveTopEntries() { void _saveTopEntries() {
if (!settings.initialized) return; if (!settings.initialized) return;
final screenSize = _screenSize ?? _getScreenSize(); final screenSize = _screenSize;
if (screenSize == null) return; if (screenSize == null) return;
var tileExtent = settings.getTileExtent(CollectionPage.routeName); var tileExtent = settings.getTileExtent(CollectionPage.routeName);
@ -525,10 +516,8 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
case MaxBrightness.never: case MaxBrightness.never:
case MaxBrightness.viewerOnly: case MaxBrightness.viewerOnly:
ScreenBrightness().resetScreenBrightness(); ScreenBrightness().resetScreenBrightness();
break;
case MaxBrightness.always: case MaxBrightness.always:
ScreenBrightness().setScreenBrightness(1); ScreenBrightness().setScreenBrightness(1);
break;
} }
} }
@ -586,7 +575,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
: 'debug', : 'debug',
'has_mobile_services': mobileServices.isServiceAvailable, 'has_mobile_services': mobileServices.isServiceAvailable,
'is_television': device.isTelevision, 'is_television': device.isTelevision,
'locales': WidgetsBinding.instance.window.locales.join(', '), 'locales': WidgetsBinding.instance.platformDispatcher.locales.join(', '),
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})', 'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
}); });
await reportService.log('Launch'); await reportService.log('Launch');

View file

@ -224,7 +224,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
} }
double get appBarContentHeight { double get appBarContentHeight {
final textScaleFactor = context.read<MediaQueryData>().textScaleFactor; final textScaleFactor = MediaQuery.textScaleFactorOf(context);
double height = kToolbarHeight * textScaleFactor; double height = kToolbarHeight * textScaleFactor;
if (settings.useTvLayout) { if (settings.useTvLayout) {
height += CaptionedButton.getTelevisionButtonHeight(context); height += CaptionedButton.getTelevisionButtonHeight(context);
@ -511,16 +511,13 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
queryEnabled: context.read<Query>().enabled, queryEnabled: context.read<Query>().enabled,
isMenuItem: true, isMenuItem: true,
); );
break;
case EntrySetAction.toggleFavourite: case EntrySetAction.toggleFavourite:
child = FavouriteToggler( child = FavouriteToggler(
entries: _getExpandedSelectedItems(selection), entries: _getExpandedSelectedItems(selection),
isMenuItem: true, isMenuItem: true,
); );
break;
default: default:
child = MenuRow(text: action.getText(context), icon: action.getIcon()); child = MenuRow(text: action.getText(context), icon: action.getIcon());
break;
} }
return PopupMenuItem( return PopupMenuItem(
key: _getActionKey(action), key: _getActionKey(action),
@ -598,7 +595,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
void _onQueryFocusRequest() => _queryBarFocusNode.requestFocus(); void _onQueryFocusRequest() => _queryBarFocusNode.requestFocus();
void _updateStatusBarHeight() { void _updateStatusBarHeight() {
_statusBarHeight = context.read<MediaQueryData>().padding.top; _statusBarHeight = MediaQuery.paddingOf(context).top;
_updateAppBarHeight(); _updateAppBarHeight();
} }
@ -611,16 +608,12 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
// general // general
case EntrySetAction.configureView: case EntrySetAction.configureView:
await _configureView(); await _configureView();
break;
case EntrySetAction.select: case EntrySetAction.select:
context.read<Selection<AvesEntry>>().select(); context.read<Selection<AvesEntry>>().select();
break;
case EntrySetAction.selectAll: case EntrySetAction.selectAll:
context.read<Selection<AvesEntry>>().addToSelection(collection.sortedEntries); context.read<Selection<AvesEntry>>().addToSelection(collection.sortedEntries);
break;
case EntrySetAction.selectNone: case EntrySetAction.selectNone:
context.read<Selection<AvesEntry>>().clearSelection(); context.read<Selection<AvesEntry>>().clearSelection();
break;
// browsing // browsing
case EntrySetAction.searchCollection: case EntrySetAction.searchCollection:
case EntrySetAction.toggleTitleSearch: case EntrySetAction.toggleTitleSearch:
@ -650,7 +643,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
case EntrySetAction.editTags: case EntrySetAction.editTags:
case EntrySetAction.removeMetadata: case EntrySetAction.removeMetadata:
_actionDelegate.onActionSelected(context, action); _actionDelegate.onActionSelected(context, action);
break;
} }
} }

View file

@ -46,7 +46,6 @@ import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -374,6 +373,8 @@ class _CollectionScaler extends StatelessWidget {
final tileSpacing = metrics.item1; final tileSpacing = metrics.item1;
final horizontalPadding = metrics.item2; final horizontalPadding = metrics.item2;
final brightness = Theme.of(context).brightness; final brightness = Theme.of(context).brightness;
final borderColor = DecoratedThumbnail.borderColor;
final borderWidth = DecoratedThumbnail.borderWidth(context);
return GridScaleGestureDetector<AvesEntry>( return GridScaleGestureDetector<AvesEntry>(
scrollableKey: scrollableKey, scrollableKey: scrollableKey,
tileLayout: tileLayout, tileLayout: tileLayout,
@ -385,9 +386,9 @@ class _CollectionScaler extends StatelessWidget {
tileSize: tileSize, tileSize: tileSize,
spacing: tileSpacing, spacing: tileSpacing,
horizontalPadding: horizontalPadding, horizontalPadding: horizontalPadding,
borderWidth: DecoratedThumbnail.borderWidth, borderWidth: borderWidth,
borderRadius: Radius.zero, borderRadius: Radius.zero,
color: DecoratedThumbnail.borderColor, color: borderColor,
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
), ),
child: child, child: child,
@ -404,8 +405,8 @@ class _CollectionScaler extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: ThumbnailImage.computeLoadingBackgroundColor(index * 10, brightness).withOpacity(.9), color: ThumbnailImage.computeLoadingBackgroundColor(index * 10, brightness).withOpacity(.9),
border: Border.all( border: Border.all(
color: DecoratedThumbnail.borderColor, color: borderColor,
width: DecoratedThumbnail.borderWidth, width: borderWidth,
), ),
), ),
), ),
@ -489,7 +490,6 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
} }
}); });
} }
break;
} }
} }
@ -573,7 +573,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
physics: collection.isEmpty physics: collection.isEmpty
? const NeverScrollableScrollPhysics() ? const NeverScrollableScrollPhysics()
: SloppyScrollPhysics( : SloppyScrollPhysics(
gestureSettings: context.select<MediaQueryData, DeviceGestureSettings>((mq) => mq.gestureSettings), gestureSettings: MediaQuery.gestureSettingsOf(context),
parent: const AlwaysScrollableScrollPhysics(), parent: const AlwaysScrollableScrollPhysics(),
), ),
cacheExtent: context.select<TileExtentController, double>((controller) => controller.effectiveExtentMax), cacheExtent: context.select<TileExtentController, double>((controller) => controller.effectiveExtentMax),
@ -677,7 +677,6 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
switch (collection.sectionFactor) { switch (collection.sectionFactor) {
case EntryGroupFactor.album: case EntryGroupFactor.album:
addAlbums(collection, sectionLayouts, crumbs); addAlbums(collection, sectionLayouts, crumbs);
break;
case EntryGroupFactor.month: case EntryGroupFactor.month:
case EntryGroupFactor.day: case EntryGroupFactor.day:
final firstKey = sectionLayouts.first.sectionKey; final firstKey = sectionLayouts.first.sectionKey;
@ -701,14 +700,11 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
}); });
} }
} }
break;
case EntryGroupFactor.none: case EntryGroupFactor.none:
break; break;
} }
break;
case EntrySortFactor.name: case EntrySortFactor.name:
addAlbums(collection, sectionLayouts, crumbs); addAlbums(collection, sectionLayouts, crumbs);
break;
case EntrySortFactor.rating: case EntrySortFactor.rating:
case EntrySortFactor.size: case EntrySortFactor.size:
break; break;

View file

@ -173,79 +173,55 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
// browsing // browsing
case EntrySetAction.searchCollection: case EntrySetAction.searchCollection:
_goToSearch(context); _goToSearch(context);
break;
case EntrySetAction.toggleTitleSearch: case EntrySetAction.toggleTitleSearch:
context.read<Query>().toggle(); context.read<Query>().toggle();
break;
case EntrySetAction.addShortcut: case EntrySetAction.addShortcut:
_addShortcut(context); _addShortcut(context);
break;
// browsing or selecting // browsing or selecting
case EntrySetAction.map: case EntrySetAction.map:
_goToMap(context); _goToMap(context);
break;
case EntrySetAction.slideshow: case EntrySetAction.slideshow:
_goToSlideshow(context); _goToSlideshow(context);
break;
case EntrySetAction.stats: case EntrySetAction.stats:
_goToStats(context); _goToStats(context);
break;
case EntrySetAction.rescan: case EntrySetAction.rescan:
_rescan(context); _rescan(context);
break;
// selecting // selecting
case EntrySetAction.share: case EntrySetAction.share:
_share(context); _share(context);
break;
case EntrySetAction.delete: case EntrySetAction.delete:
case EntrySetAction.emptyBin: case EntrySetAction.emptyBin:
_delete(context); _delete(context);
break;
case EntrySetAction.restore: case EntrySetAction.restore:
_move(context, moveType: MoveType.fromBin); _move(context, moveType: MoveType.fromBin);
break;
case EntrySetAction.copy: case EntrySetAction.copy:
_move(context, moveType: MoveType.copy); _move(context, moveType: MoveType.copy);
break;
case EntrySetAction.move: case EntrySetAction.move:
_move(context, moveType: MoveType.move); _move(context, moveType: MoveType.move);
break;
case EntrySetAction.rename: case EntrySetAction.rename:
_rename(context); _rename(context);
break;
case EntrySetAction.convert: case EntrySetAction.convert:
_convert(context); _convert(context);
break;
case EntrySetAction.toggleFavourite: case EntrySetAction.toggleFavourite:
_toggleFavourite(context); _toggleFavourite(context);
break;
case EntrySetAction.rotateCCW: case EntrySetAction.rotateCCW:
_rotate(context, clockwise: false); _rotate(context, clockwise: false);
break;
case EntrySetAction.rotateCW: case EntrySetAction.rotateCW:
_rotate(context, clockwise: true); _rotate(context, clockwise: true);
break;
case EntrySetAction.flip: case EntrySetAction.flip:
_flip(context); _flip(context);
break;
case EntrySetAction.editDate: case EntrySetAction.editDate:
editDate(context); editDate(context);
break;
case EntrySetAction.editLocation: case EntrySetAction.editLocation:
_editLocation(context); _editLocation(context);
break;
case EntrySetAction.editTitleDescription: case EntrySetAction.editTitleDescription:
_editTitleDescription(context); _editTitleDescription(context);
break;
case EntrySetAction.editRating: case EntrySetAction.editRating:
_editRating(context); _editRating(context);
break;
case EntrySetAction.editTags: case EntrySetAction.editTags:
_editTags(context); _editTags(context);
break;
case EntrySetAction.removeMetadata: case EntrySetAction.removeMetadata:
_removeMetadata(context); _removeMetadata(context);
break;
} }
} }

View file

@ -57,7 +57,6 @@ class CollectionSectionHeader extends StatelessWidget {
case EntryGroupFactor.none: case EntryGroupFactor.none:
break; break;
} }
break;
case EntrySortFactor.name: case EntrySortFactor.name:
return _buildAlbumHeader(context); return _buildAlbumHeader(context);
case EntrySortFactor.rating: case EntrySortFactor.rating:

View file

@ -78,7 +78,7 @@ class EntryListDetails extends StatelessWidget {
Widget _buildDateRow(BuildContext context, TextStyle style) { Widget _buildDateRow(BuildContext context, TextStyle style) {
final locale = context.l10n.localeName; final locale = context.l10n.localeName;
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat); final use24hour = MediaQuery.alwaysUse24HourFormatOf(context);
final date = entry.bestDate; final date = entry.bestDate;
final dateText = date != null ? formatDateTime(date, locale, use24hour) : AText.valueNotAvailable; final dateText = date != null ? formatDateTime(date, locale, use24hour) : AText.valueNotAvailable;

View file

@ -41,17 +41,13 @@ class InteractiveTile extends StatelessWidget {
} else { } else {
OpenViewerNotification(entry).dispatch(context); OpenViewerNotification(entry).dispatch(context);
} }
break;
case AppMode.pickSingleMediaExternal: case AppMode.pickSingleMediaExternal:
IntentService.submitPickedItems([entry.uri]); IntentService.submitPickedItems([entry.uri]);
break;
case AppMode.pickMultipleMediaExternal: case AppMode.pickMultipleMediaExternal:
final selection = context.read<Selection<AvesEntry>>(); final selection = context.read<Selection<AvesEntry>>();
selection.toggleSelection(entry); selection.toggleSelection(entry);
break;
case AppMode.pickMediaInternal: case AppMode.pickMediaInternal:
Navigator.maybeOf(context)?.pop(entry); Navigator.maybeOf(context)?.pop(entry);
break;
case AppMode.pickCollectionFiltersExternal: case AppMode.pickCollectionFiltersExternal:
case AppMode.pickFilterInternal: case AppMode.pickFilterInternal:
case AppMode.screenSaver: case AppMode.screenSaver:

View file

@ -52,7 +52,7 @@ class _EntryQueryBarState extends State<EntryQueryBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = context.select<MediaQueryData, double>((mq) => mq.textScaleFactor); final textScaleFactor = MediaQuery.textScaleFactorOf(context);
return Container( return Container(
height: EntryQueryBar.getPreferredHeight(textScaleFactor), height: EntryQueryBar.getPreferredHeight(textScaleFactor),
alignment: Alignment.topCenter, alignment: Alignment.topCenter,

View file

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart';
@ -51,10 +50,15 @@ class _MenuQuickChooserState<T> extends State<MenuQuickChooser<T>> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedRowRect.value = Rect.fromLTWH(0, window.physicalSize.height * (reversed ? 1 : -1), 0, 0);
_registerWidget(widget); _registerWidget(widget);
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
_selectedRowRect.value = Rect.fromLTWH(0, MediaQuery.sizeOf(context).height * (reversed ? 1 : -1), 0, 0);
}
@override @override
void didUpdateWidget(covariant MenuQuickChooser<T> oldWidget) { void didUpdateWidget(covariant MenuQuickChooser<T> oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);

View file

@ -33,10 +33,8 @@ class QuickChooserRouteLayout extends SingleChildLayoutDelegate {
switch (menuPosition) { switch (menuPosition) {
case PopupMenuPosition.over: case PopupMenuPosition.over:
y = triggerRect.top - childSize.height; y = triggerRect.top - childSize.height;
break;
case PopupMenuPosition.under: case PopupMenuPosition.under:
y = size.height - triggerRect.bottom; y = size.height - triggerRect.bottom;
break;
} }
double x = (triggerRect.left + (size.width - triggerRect.right) - childSize.width) / 2; double x = (triggerRect.left + (size.width - triggerRect.right) - childSize.width) / 2;
final wantedPosition = Offset(x, y); final wantedPosition = Offset(x, y);

View file

@ -309,17 +309,14 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
..remove(destinationAlbum) ..remove(destinationAlbum)
..insert(0, destinationAlbum); ..insert(0, destinationAlbum);
entriesByDestination[destinationAlbum] = entries; entriesByDestination[destinationAlbum] = entries;
break;
case MoveType.toBin: case MoveType.toBin:
entriesByDestination[AndroidFileUtils.trashDirPath] = entries; entriesByDestination[AndroidFileUtils.trashDirPath] = entries;
break;
case MoveType.fromBin: case MoveType.fromBin:
groupBy<AvesEntry, String?>(entries, (e) => e.directory).forEach((originAlbum, dirEntries) { groupBy<AvesEntry, String?>(entries, (e) => e.directory).forEach((originAlbum, dirEntries) {
if (originAlbum != null) { if (originAlbum != null) {
entriesByDestination[originAlbum] = dirEntries.toSet(); entriesByDestination[originAlbum] = dirEntries.toSet();
} }
}); });
break;
} }
await doQuickMove( await doQuickMove(

View file

@ -8,36 +8,50 @@ import 'package:flutter/material.dart';
// This overlay entry is not below a `Scaffold` (which is expected by `SnackBar` // This overlay entry is not below a `Scaffold` (which is expected by `SnackBar`
// and `SnackBarAction`), and is not dismissed the same way. // and `SnackBarAction`), and is not dismissed the same way.
// This adaptation assumes the `SnackBarBehavior.floating` behavior and no animation. // This adaptation assumes the `SnackBarBehavior.floating` behavior and no animation.
class OverlaySnackBar extends StatelessWidget { class OverlaySnackBar extends StatefulWidget {
final Widget content; final Widget content;
final Widget? action; final Widget? action;
final DismissDirection dismissDirection; final DismissDirection dismissDirection;
final VoidCallback onDismiss; final VoidCallback onDismiss;
final Clip clipBehavior;
const OverlaySnackBar({ const OverlaySnackBar({
super.key, super.key,
required this.content, required this.content,
required this.action, this.action,
required this.dismissDirection, this.dismissDirection = DismissDirection.down,
this.clipBehavior = Clip.hardEdge,
required this.onDismiss, required this.onDismiss,
}); });
@override @override
Widget build(BuildContext context) { State<OverlaySnackBar> createState() => _OverlaySnackBarState();
final theme = Theme.of(context); }
final colorScheme = theme.colorScheme;
final snackBarTheme = theme.snackBarTheme;
final isThemeDark = theme.brightness == Brightness.dark;
final buttonColor = isThemeDark ? colorScheme.primary : colorScheme.secondary;
final brightness = isThemeDark ? Brightness.light : Brightness.dark; class _OverlaySnackBarState extends State<OverlaySnackBar> {
final themeBackgroundColor = isThemeDark ? colorScheme.onSurface : Color.alphaBlend(colorScheme.onSurface.withOpacity(0.80), colorScheme.surface); @override
final inverseTheme = theme.copyWith( Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
final SnackBarThemeData snackBarTheme = theme.snackBarTheme;
final bool isThemeDark = theme.brightness == Brightness.dark;
final Color buttonColor = isThemeDark ? colorScheme.primary : colorScheme.secondary;
final SnackBarThemeData defaults = theme.useMaterial3 ? _SnackbarDefaultsM3(context) : _SnackbarDefaultsM2(context);
// SnackBar uses a theme that is the opposite brightness from
// the surrounding theme.
final Brightness brightness = isThemeDark ? Brightness.light : Brightness.dark;
// Invert the theme values for Material 2. Material 3 values are tokenized to pre-inverted values.
final ThemeData effectiveTheme = theme.useMaterial3
? theme
: theme.copyWith(
colorScheme: ColorScheme( colorScheme: ColorScheme(
primary: colorScheme.onPrimary, primary: colorScheme.onPrimary,
secondary: buttonColor, secondary: buttonColor,
surface: colorScheme.onSurface, surface: colorScheme.onSurface,
background: themeBackgroundColor, background: defaults.backgroundColor!,
error: colorScheme.onError, error: colorScheme.onError,
onPrimary: colorScheme.primary, onPrimary: colorScheme.primary,
onSecondary: colorScheme.secondary, onSecondary: colorScheme.secondary,
@ -48,26 +62,28 @@ class OverlaySnackBar extends StatelessWidget {
), ),
); );
final contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.titleMedium; final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? defaults.contentTextStyle;
final horizontalPadding = FeedbackMixin.snackBarHorizontalPadding(snackBarTheme); final horizontalPadding = FeedbackMixin.snackBarHorizontalPadding(snackBarTheme);
final padding = EdgeInsetsDirectional.only(start: horizontalPadding, end: action != null ? 0 : horizontalPadding); final padding = EdgeInsetsDirectional.only(start: horizontalPadding, end: widget.action != null ? 0 : horizontalPadding);
const singleLineVerticalPadding = 14.0; const singleLineVerticalPadding = 14.0;
final EdgeInsets margin = snackBarTheme.insetPadding ?? defaults.insetPadding!;
Widget snackBar = Padding( Widget snackBar = Padding(
padding: padding, padding: padding,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Container( child: Container(
padding: action != null ? null : const EdgeInsets.symmetric(vertical: singleLineVerticalPadding), padding: widget.action != null ? null : const EdgeInsets.symmetric(vertical: singleLineVerticalPadding),
child: DefaultTextStyle( child: DefaultTextStyle(
style: contentTextStyle!, style: contentTextStyle!,
child: content, child: widget.content,
), ),
), ),
), ),
if (action != null) if (widget.action != null)
TextButtonTheme( TextButtonTheme(
data: TextButtonThemeData( data: TextButtonThemeData(
style: TextButton.styleFrom( style: TextButton.styleFrom(
@ -75,36 +91,28 @@ class OverlaySnackBar extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: horizontalPadding), padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
), ),
), ),
child: action!, child: widget.action!,
), ),
], ],
), ),
); );
final elevation = snackBarTheme.elevation ?? 6.0; final double elevation = snackBarTheme.elevation ?? defaults.elevation!;
final backgroundColor = snackBarTheme.backgroundColor ?? inverseTheme.colorScheme.background; final Color backgroundColor = snackBarTheme.backgroundColor ?? defaults.backgroundColor!;
final shape = snackBarTheme.shape ?? const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))); final ShapeBorder? shape = snackBarTheme.shape ?? defaults.shape;
snackBar = Material( snackBar = Material(
shape: shape, shape: shape,
elevation: elevation, elevation: elevation,
color: backgroundColor, color: backgroundColor,
child: Theme( child: Theme(
data: inverseTheme, data: effectiveTheme,
child: snackBar, child: snackBar,
), ),
); );
const topMargin = 5.0;
const bottomMargin = 10.0;
const horizontalMargin = 15.0;
snackBar = Padding( snackBar = Padding(
padding: const EdgeInsets.fromLTRB( padding: margin,
horizontalMargin,
topMargin,
horizontalMargin,
bottomMargin,
),
child: snackBar, child: snackBar,
); );
@ -117,16 +125,138 @@ class OverlaySnackBar extends StatelessWidget {
snackBar = Semantics( snackBar = Semantics(
container: true, container: true,
liveRegion: true, liveRegion: true,
onDismiss: onDismiss, onDismiss: widget.onDismiss,
child: Dismissible( child: Dismissible(
key: const Key('dismissible'), key: const Key('dismissible'),
direction: dismissDirection, direction: widget.dismissDirection,
resizeDuration: null, resizeDuration: null,
onDismissed: (direction) => onDismiss(), onDismissed: (direction) => widget.onDismiss(),
child: snackBar, child: snackBar,
), ),
); );
return snackBar; final Widget snackBarTransition = snackBar;
return Hero(
tag: '<SnackBar Hero tag - ${widget.content}>',
transitionOnUserGestures: true,
child: ClipRect(
clipBehavior: widget.clipBehavior,
child: snackBarTransition,
),
);
} }
} }
// Hand coded defaults based on Material Design 2.
class _SnackbarDefaultsM2 extends SnackBarThemeData {
_SnackbarDefaultsM2(BuildContext context)
: _theme = Theme.of(context),
_colors = Theme.of(context).colorScheme,
super(elevation: 6.0);
late final ThemeData _theme;
late final ColorScheme _colors;
@override
Color get backgroundColor => _theme.brightness == Brightness.light ? Color.alphaBlend(_colors.onSurface.withOpacity(0.80), _colors.surface) : _colors.onSurface;
@override
TextStyle? get contentTextStyle => ThemeData(brightness: _theme.brightness == Brightness.light ? Brightness.dark : Brightness.light).textTheme.titleMedium;
@override
SnackBarBehavior get behavior => SnackBarBehavior.fixed;
@override
Color get actionTextColor => _colors.secondary;
@override
Color get disabledActionTextColor => _colors.onSurface.withOpacity(_theme.brightness == Brightness.light ? 0.38 : 0.3);
@override
ShapeBorder get shape => const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
),
);
@override
EdgeInsets get insetPadding => const EdgeInsets.fromLTRB(15.0, 5.0, 15.0, 10.0);
@override
bool get showCloseIcon => false;
@override
Color get closeIconColor => _colors.onSurface;
@override
double get actionOverflowThreshold => 0.25;
}
// BEGIN GENERATED TOKEN PROPERTIES - Snackbar
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// Token database version: v0_162
class _SnackbarDefaultsM3 extends SnackBarThemeData {
_SnackbarDefaultsM3(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
Color get backgroundColor => _colors.inverseSurface;
@override
Color get actionTextColor => MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return _colors.inversePrimary;
}
if (states.contains(MaterialState.pressed)) {
return _colors.inversePrimary;
}
if (states.contains(MaterialState.hovered)) {
return _colors.inversePrimary;
}
if (states.contains(MaterialState.focused)) {
return _colors.inversePrimary;
}
return _colors.inversePrimary;
});
@override
Color get disabledActionTextColor => _colors.inversePrimary;
@override
TextStyle get contentTextStyle => Theme.of(context).textTheme.bodyMedium!.copyWith(
color: _colors.onInverseSurface,
);
@override
double get elevation => 6.0;
@override
ShapeBorder get shape => const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
@override
SnackBarBehavior get behavior => SnackBarBehavior.fixed;
@override
EdgeInsets get insetPadding => const EdgeInsets.fromLTRB(15.0, 5.0, 15.0, 10.0);
@override
bool get showCloseIcon => false;
@override
Color? get closeIconColor => _colors.onInverseSurface;
@override
double get actionOverflowThreshold => 0.25;
}
// END GENERATED TOKEN PROPERTIES - Snackbar

View file

@ -35,7 +35,6 @@ mixin SizeAwareMixin {
case MoveType.copy: case MoveType.copy:
case MoveType.export: case MoveType.export:
needed = selection.fold(0, sumSize); needed = selection.fold(0, sumSize);
break;
case MoveType.move: case MoveType.move:
case MoveType.toBin: case MoveType.toBin:
case MoveType.fromBin: case MoveType.fromBin:
@ -46,7 +45,6 @@ mixin SizeAwareMixin {
// and we need at least as much space as the largest entry because individual entries are copied then deleted // and we need at least as much space as the largest entry because individual entries are copied then deleted
final largestSingle = selection.fold<int>(0, (largest, entry) => max(largest, entry.sizeBytes ?? 0)); final largestSingle = selection.fold<int>(0, (largest, entry) => max(largest, entry.sizeBytes ?? 0));
needed = max(fromOtherVolumes, largestSingle); needed = max(fromOtherVolumes, largestSingle);
break;
} }
final hasEnoughSpace = needed < free; final hasEnoughSpace = needed < free;

View file

@ -36,7 +36,6 @@ mixin VaultAwareMixin on FeedbackMixin {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
} }
break;
case VaultLockType.pattern: case VaultLockType.pattern:
final pattern = await showDialog<String>( final pattern = await showDialog<String>(
context: context, context: context,
@ -46,7 +45,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pattern != null) { if (pattern != null) {
confirmed = pattern == await securityService.readValue(details.passKey); confirmed = pattern == await securityService.readValue(details.passKey);
} }
break;
case VaultLockType.pin: case VaultLockType.pin:
final pin = await showDialog<String>( final pin = await showDialog<String>(
context: context, context: context,
@ -56,7 +54,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pin != null) { if (pin != null) {
confirmed = pin == await securityService.readValue(details.passKey); confirmed = pin == await securityService.readValue(details.passKey);
} }
break;
case VaultLockType.password: case VaultLockType.password:
final password = await showDialog<String>( final password = await showDialog<String>(
context: context, context: context,
@ -66,7 +63,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (password != null) { if (password != null) {
confirmed = password == await securityService.readValue(details.passKey); confirmed = password == await securityService.readValue(details.passKey);
} }
break;
} }
if (confirmed == null || !confirmed) return false; if (confirmed == null || !confirmed) return false;
@ -120,7 +116,6 @@ mixin VaultAwareMixin on FeedbackMixin {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
} }
break;
case VaultLockType.pattern: case VaultLockType.pattern:
final pattern = await showDialog<String>( final pattern = await showDialog<String>(
context: context, context: context,
@ -130,7 +125,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pattern != null) { if (pattern != null) {
return await securityService.writeValue(details.passKey, pattern); return await securityService.writeValue(details.passKey, pattern);
} }
break;
case VaultLockType.pin: case VaultLockType.pin:
final pin = await showDialog<String>( final pin = await showDialog<String>(
context: context, context: context,
@ -140,7 +134,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pin != null) { if (pin != null) {
return await securityService.writeValue(details.passKey, pin); return await securityService.writeValue(details.passKey, pin);
} }
break;
case VaultLockType.password: case VaultLockType.password:
final password = await showDialog<String>( final password = await showDialog<String>(
context: context, context: context,
@ -150,7 +143,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (password != null) { if (password != null) {
return await securityService.writeValue(details.passKey, password); return await securityService.writeValue(details.passKey, password);
} }
break;
} }
return false; return false;
} }

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class InteractiveAppBarTitle extends StatelessWidget { class InteractiveAppBarTitle extends StatelessWidget {
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
@ -13,6 +12,7 @@ class InteractiveAppBarTitle extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
// use a `Container` with a dummy color to make it expand // use a `Container` with a dummy color to make it expand
@ -20,7 +20,7 @@ class InteractiveAppBarTitle extends StatelessWidget {
child: Container( child: Container(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
color: Colors.transparent, color: Colors.transparent,
height: kToolbarHeight * context.select<MediaQueryData, double>((mq) => mq.textScaleFactor), height: kToolbarHeight * textScaleFactor,
child: child, child: child,
), ),
); );

View file

@ -11,10 +11,11 @@ class FontSizeIconTheme extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final iconTheme = IconTheme.of(context); final iconTheme = IconTheme.of(context);
return IconTheme( return IconTheme(
data: iconTheme.copyWith( data: iconTheme.copyWith(
size: iconTheme.size! * MediaQuery.textScaleFactorOf(context), size: iconTheme.size! * textScaleFactor,
), ),
child: child, child: child,
); );

View file

@ -22,7 +22,7 @@ class BottomGestureAreaProtector extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
height: context.select<MediaQueryData, double>((mq) => mq.systemGestureInsets.bottom), height: MediaQuery.systemGestureInsetsOf(context).bottom,
child: GestureDetector( child: GestureDetector(
// absorb vertical gestures only // absorb vertical gestures only
onVerticalDragDown: (details) {}, onVerticalDragDown: (details) {},
@ -42,7 +42,7 @@ class TopGestureAreaProtector extends StatelessWidget {
left: 0, left: 0,
top: 0, top: 0,
right: 0, right: 0,
height: context.select<MediaQueryData, double>((mq) => mq.systemGestureInsets.top), height: MediaQuery.systemGestureInsetsOf(context).top,
child: GestureDetector( child: GestureDetector(
// absorb vertical gestures only // absorb vertical gestures only
onVerticalDragDown: (details) {}, onVerticalDragDown: (details) {},
@ -64,7 +64,7 @@ class SideGestureAreaProtector extends StatelessWidget {
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: [ children: [
SizedBox( SizedBox(
width: context.select<MediaQueryData, double>((mq) => mq.systemGestureInsets.left), width: MediaQuery.systemGestureInsetsOf(context).left,
child: GestureDetector( child: GestureDetector(
// absorb horizontal gestures only // absorb horizontal gestures only
onHorizontalDragDown: (details) {}, onHorizontalDragDown: (details) {},
@ -73,7 +73,7 @@ class SideGestureAreaProtector extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
SizedBox( SizedBox(
width: context.select<MediaQueryData, double>((mq) => mq.systemGestureInsets.right), width: MediaQuery.systemGestureInsetsOf(context).right,
child: GestureDetector( child: GestureDetector(
// absorb horizontal gestures only // absorb horizontal gestures only
onHorizontalDragDown: (details) {}, onHorizontalDragDown: (details) {},

View file

@ -54,11 +54,9 @@ class ReselectableRadioListTile<T> extends StatelessWidget {
case ListTileControlAffinity.platform: case ListTileControlAffinity.platform:
leading = control; leading = control;
trailing = secondary; trailing = secondary;
break;
case ListTileControlAffinity.trailing: case ListTileControlAffinity.trailing:
leading = secondary; leading = secondary;
trailing = control; trailing = control;
break;
} }
return MergeSemantics( return MergeSemantics(
child: ListTileTheme.merge( child: ListTileTheme.merge(

View file

@ -54,7 +54,7 @@ class MarkdownContainer extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 8), margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,
border: Border.all(color: Theme.of(context).dividerColor, width: AvesBorder.curvedBorderWidth), border: Border.all(color: Theme.of(context).dividerColor, width: AvesBorder.curvedBorderWidth(context)),
borderRadius: const BorderRadius.all(Radius.circular(16)), borderRadius: const BorderRadius.all(Radius.circular(16)),
), ),
constraints: BoxConstraints(maxWidth: useTvLayout ? double.infinity : mobileMaxWidth), constraints: BoxConstraints(maxWidth: useTvLayout ? double.infinity : mobileMaxWidth),

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AvesScaffold extends StatelessWidget { class AvesScaffold extends StatelessWidget {
final PreferredSizeWidget? appBar; final PreferredSizeWidget? appBar;
@ -26,7 +25,7 @@ class AvesScaffold extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// prevent conflict between drawer drag gesture and Android navigation gestures // prevent conflict between drawer drag gesture and Android navigation gestures
final drawerEnableOpenDragGesture = context.select<MediaQueryData, bool>((mq) => mq.systemGestureInsets.horizontal == 0); final drawerEnableOpenDragGesture = MediaQuery.systemGestureInsetsOf(context).horizontal == 0;
return Scaffold( return Scaffold(
appBar: appBar, appBar: appBar,

View file

@ -31,26 +31,18 @@ class OutlinedText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// TODO TLAD [subtitles] fix background area for mixed alphabetic-ideographic text // TODO TLAD [subtitles] fix background area for mixed alphabetic-ideographic text
// as of Flutter v2.2.2, the area computed for `backgroundColor` has inconsistent height // as of Flutter v3.10.0, the area computed for `backgroundColor` has inconsistent height
// in case of mixed alphabetic-ideographic text. The painted boxes depends on the script. // in case of mixed alphabetic-ideographic text. The painted boxes depend on the script.
// Possible workarounds would be to use metrics from: // Possible workarounds would be to use metrics from:
// - `TextPainter.getBoxesForSelection` // - `TextPainter.getBoxesForSelection`
// - `Paragraph.getBoxesForRange` // - `Paragraph.getBoxesForRange`
// and paint the background at the bottom of the `Stack` // and paint the background at the bottom of the `Stack`
final hasOutline = outlineWidth > 0; final hasOutline = outlineWidth > 0;
return Stack(
children: [ Widget? outline;
if (hasOutline) if (hasOutline) {
ImageFiltered( outline = Text.rich(
imageFilter: outlineBlurSigma > 0
? ImageFilter.blur(
sigmaX: outlineBlurSigma,
sigmaY: outlineBlurSigma,
)
: ImageFilter.matrix(
Matrix4.identity().storage,
),
child: Text.rich(
TextSpan( TextSpan(
children: textSpans.map(_toStrokeSpan).toList(), children: textSpans.map(_toStrokeSpan).toList(),
), ),
@ -58,9 +50,19 @@ class OutlinedText extends StatelessWidget {
softWrap: softWrap, softWrap: softWrap,
overflow: overflow, overflow: overflow,
maxLines: maxLines, maxLines: maxLines,
);
if (outlineBlurSigma > 0) {
outline = ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: outlineBlurSigma,
sigmaY: outlineBlurSigma,
), ),
), child: outline,
Text.rich( );
}
}
final fill = Text.rich(
TextSpan( TextSpan(
children: hasOutline ? textSpans.map(_toFillSpan).toList() : textSpans, children: hasOutline ? textSpans.map(_toFillSpan).toList() : textSpans,
), ),
@ -68,7 +70,12 @@ class OutlinedText extends StatelessWidget {
softWrap: softWrap, softWrap: softWrap,
overflow: overflow, overflow: overflow,
maxLines: maxLines, maxLines: maxLines,
), );
return Stack(
children: [
if (outline != null) outline,
fill,
], ],
); );
} }
@ -89,6 +96,7 @@ class OutlinedText extends StatelessWidget {
children: span.children, children: span.children,
style: (span.style ?? const TextStyle()).copyWith( style: (span.style ?? const TextStyle()).copyWith(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
shadows: [],
), ),
); );
} }

View file

@ -46,10 +46,11 @@ class _WheelSelectorState<T> extends State<WheelSelector<T>> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
const background = Colors.transparent; const background = Colors.transparent;
final foreground = DefaultTextStyle.of(context).style.color!; final foreground = DefaultTextStyle.of(context).style.color!;
final transitionDuration = context.select<DurationsData, Duration>((v) => v.formTransition); final transitionDuration = context.select<DurationsData, Duration>((v) => v.formTransition);
final itemSize = Size.square(40 * context.select<MediaQueryData, double>((mq) => mq.textScaleFactor)); final itemSize = Size.square(40 * textScaleFactor);
return FocusableActionDetector( return FocusableActionDetector(
shortcuts: const { shortcuts: const {
@ -138,10 +139,8 @@ class _WheelSelectorState<T> extends State<WheelSelector<T>> {
switch (intent.type) { switch (intent.type) {
case _ValueAdjustmentType.up: case _ValueAdjustmentType.up:
delta = -1; delta = -1;
break;
case _ValueAdjustmentType.down: case _ValueAdjustmentType.down:
delta = 1; delta = 1;
break;
} }
final targetItem = _controller.selectedItem + delta; final targetItem = _controller.selectedItem + delta;
final duration = context.read<DurationsData>().formTransition; final duration = context.read<DurationsData>().formTransition;

View file

@ -26,20 +26,47 @@ enum _ScaleState {
} }
class _PointerPanZoomData { class _PointerPanZoomData {
_PointerPanZoomData({required this.focalPoint, required this.scale, required this.rotation}); _PointerPanZoomData.fromStartEvent(this.parent, PointerPanZoomStartEvent event)
: _position = event.position,
_pan = Offset.zero,
_scale = 1,
_rotation = 0;
Offset focalPoint; _PointerPanZoomData.fromUpdateEvent(this.parent, PointerPanZoomUpdateEvent event)
double scale; : _position = event.position,
double rotation; _pan = event.pan,
_scale = event.scale,
_rotation = event.rotation;
final EagerScaleGestureRecognizer parent;
final Offset _position;
final Offset _pan;
final double _scale;
final double _rotation;
Offset get focalPoint {
if (parent.trackpadScrollCausesScale) {
return _position;
}
return _position + _pan;
}
double get scale {
if (parent.trackpadScrollCausesScale) {
return _scale * math.exp((_pan.dx * parent.trackpadScrollToScaleFactor.dx) + (_pan.dy * parent.trackpadScrollToScaleFactor.dy));
}
return _scale;
}
double get rotation => _rotation;
@override @override
String toString() => '_PointerPanZoomData(focalPoint: $focalPoint, scale: $scale, angle: $rotation)'; String toString() => '_PointerPanZoomData(parent: $parent, _position: $_position, _pan: $_pan, _scale: $_scale, _rotation: $_rotation)';
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
bool _isFlingGesture(Velocity velocity) { bool _isFlingGesture(Velocity velocity) {
assert(velocity != null);
final double speedSquared = velocity.pixelsPerSecond.distanceSquared; final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
return speedSquared > kMinFlingVelocity * kMinFlingVelocity; return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
} }
@ -57,9 +84,7 @@ class _LineBetweenPointers {
this.pointerStartId = 0, this.pointerStartId = 0,
this.pointerEndLocation = Offset.zero, this.pointerEndLocation = Offset.zero,
this.pointerEndId = 1, this.pointerEndId = 1,
}) : assert(pointerStartLocation != null && pointerEndLocation != null), }) : assert(pointerStartId != pointerEndId);
assert(pointerStartId != null && pointerEndId != null),
assert(pointerStartId != pointerEndId);
// The location and the id of the pointer that marks the start of the line. // The location and the id of the pointer that marks the start of the line.
final Offset pointerStartLocation; final Offset pointerStartLocation;
@ -74,23 +99,22 @@ class _LineBetweenPointers {
/// ///
/// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and /// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and
/// calculates their focal point, indicated scale, and rotation. When a focal /// calculates their focal point, indicated scale, and rotation. When a focal
/// pointer is established, the recognizer calls [onStart]. As the focal point, /// point is established, the recognizer calls [onStart]. As the focal point,
/// scale, rotation change, the recognizer calls [onUpdate]. When the pointers /// scale, and rotation change, the recognizer calls [onUpdate]. When the
/// are no longer in contact with the screen, the recognizer calls [onEnd]. /// pointers are no longer in contact with the screen, the recognizer calls
/// [onEnd].
class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer { class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
/// Create a gesture recognizer for interactions intended for scaling content. /// Create a gesture recognizer for interactions intended for scaling content.
/// ///
/// {@macro flutter.gestures.GestureRecognizer.supportedDevices} /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
EagerScaleGestureRecognizer({ EagerScaleGestureRecognizer({
super.debugOwner, super.debugOwner,
@Deprecated(
'Migrate to supportedDevices. '
'This feature was deprecated after v2.3.0-1.0.pre.',
)
super.kind,
super.supportedDevices, super.supportedDevices,
super.allowedButtonsFilter,
this.dragStartBehavior = DragStartBehavior.down, this.dragStartBehavior = DragStartBehavior.down,
}) : assert(dragStartBehavior != null); this.trackpadScrollCausesScale = false,
this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor,
});
/// Determines what point is used as the starting point in all calculations /// Determines what point is used as the starting point in all calculations
/// involving this gesture. /// involving this gesture.
@ -137,6 +161,26 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
Matrix4? _lastTransform; Matrix4? _lastTransform;
/// {@template flutter.gestures.scale.trackpadScrollCausesScale}
/// Whether scrolling up/down on a trackpad should cause scaling instead of
/// panning.
///
/// Defaults to false.
/// {@endtemplate}
bool trackpadScrollCausesScale;
/// {@template flutter.gestures.scale.trackpadScrollToScaleFactor}
/// A factor to control the direction and magnitude of scale when converting
/// trackpad scrolling.
///
/// Incoming trackpad pan offsets will be divided by this factor to get scale
/// values. Increasing this offset will reduce the amount of scaling caused by
/// a fixed amount of trackpad scrolling.
///
/// Defaults to [kDefaultTrackpadScrollToScaleFactor].
/// {@endtemplate}
Offset trackpadScrollToScaleFactor;
late Offset _initialFocalPoint; late Offset _initialFocalPoint;
Offset? _currentFocalPoint; Offset? _currentFocalPoint;
late double _initialSpan; late double _initialSpan;
@ -151,6 +195,7 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
final Map<int, Offset> _pointerLocations = <int, Offset>{}; final Map<int, Offset> _pointerLocations = <int, Offset>{};
final List<int> _pointerQueue = <int>[]; // A queue to sort pointers in order of entrance final List<int> _pointerQueue = <int>[]; // A queue to sort pointers in order of entrance
final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{}; final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
VelocityTracker? _scaleVelocityTracker;
late Offset _delta; late Offset _delta;
final Map<int, _PointerPanZoomData> _pointerPanZooms = <int, _PointerPanZoomData>{}; final Map<int, _PointerPanZoomData> _pointerPanZooms = <int, _PointerPanZoomData>{};
double _initialPanZoomScaleFactor = 1; double _initialPanZoomScaleFactor = 1;
@ -271,15 +316,16 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_lastTransform = event.transform; _lastTransform = event.transform;
} else if (event is PointerPanZoomStartEvent) { } else if (event is PointerPanZoomStartEvent) {
assert(_pointerPanZooms[event.pointer] == null); assert(_pointerPanZooms[event.pointer] == null);
_pointerPanZooms[event.pointer] = _PointerPanZoomData(focalPoint: event.position, scale: 1, rotation: 0); _pointerPanZooms[event.pointer] = _PointerPanZoomData.fromStartEvent(this, event);
didChangeConfiguration = true; didChangeConfiguration = true;
shouldStartIfAccepted = true; shouldStartIfAccepted = true;
_lastTransform = event.transform;
} else if (event is PointerPanZoomUpdateEvent) { } else if (event is PointerPanZoomUpdateEvent) {
assert(_pointerPanZooms[event.pointer] != null); assert(_pointerPanZooms[event.pointer] != null);
if (!event.synthesized) { if (!event.synthesized && !trackpadScrollCausesScale) {
_velocityTrackers[event.pointer]!.addPosition(event.timeStamp, event.pan); _velocityTrackers[event.pointer]!.addPosition(event.timeStamp, event.pan);
} }
_pointerPanZooms[event.pointer] = _PointerPanZoomData(focalPoint: event.position + event.pan, scale: event.scale, rotation: event.rotation); _pointerPanZooms[event.pointer] = _PointerPanZoomData.fromUpdateEvent(this, event);
_lastTransform = event.transform; _lastTransform = event.transform;
shouldStartIfAccepted = true; shouldStartIfAccepted = true;
} else if (event is PointerPanZoomEndEvent) { } else if (event is PointerPanZoomEndEvent) {
@ -292,7 +338,7 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_update(); _update();
if (!didChangeConfiguration || _reconfigure(event.pointer)) { if (!didChangeConfiguration || _reconfigure(event.pointer)) {
_advanceStateMachine(shouldStartIfAccepted, event.kind); _advanceStateMachine(shouldStartIfAccepted, event);
} }
stopTrackingIfPointerNoLongerDown(event); stopTrackingIfPointerNoLongerDown(event);
} }
@ -403,18 +449,20 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) { if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) {
velocity = Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity); velocity = Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
} }
invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, pointerCount: _pointerCount))); invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
} else { } else {
invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(pointerCount: _pointerCount))); invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
} }
} }
_state = _ScaleState.accepted; _state = _ScaleState.accepted;
_scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
return false; return false;
} }
_scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
return true; return true;
} }
void _advanceStateMachine(bool shouldStartIfAccepted, PointerDeviceKind pointerDeviceKind) { void _advanceStateMachine(bool shouldStartIfAccepted, PointerEvent event) {
if (_state == _ScaleState.ready) { if (_state == _ScaleState.ready) {
_state = _ScaleState.possible; _state = _ScaleState.possible;
} }
@ -428,7 +476,7 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
if (_state == _ScaleState.possible) { if (_state == _ScaleState.possible) {
final double spanDelta = (_currentSpan - _initialSpan).abs(); final double spanDelta = (_currentSpan - _initialSpan).abs();
final double focalPointDelta = (_currentFocalPoint! - _initialFocalPoint).distance; final double focalPointDelta = (_currentFocalPoint! - _initialFocalPoint).distance;
if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computePanSlop(pointerDeviceKind, gestureSettings) || math.max(_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05) { if (spanDelta > computeScaleSlop(event.kind) || focalPointDelta > computePanSlop(event.kind, gestureSettings) || math.max(_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05) {
resolve(GestureDisposition.accepted); resolve(GestureDisposition.accepted);
} }
} else if (_state.index >= _ScaleState.accepted.index) { } else if (_state.index >= _ScaleState.accepted.index) {
@ -440,7 +488,9 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_dispatchOnStartCallbackIfNeeded(); _dispatchOnStartCallbackIfNeeded();
} }
if (_state == _ScaleState.started && onUpdate != null) { if (_state == _ScaleState.started) {
_scaleVelocityTracker?.addPosition(event.timeStamp, Offset(_scaleFactor, 0));
if (onUpdate != null) {
invokeCallback<void>('onUpdate', () { invokeCallback<void>('onUpdate', () {
onUpdate!(ScaleUpdateDetails( onUpdate!(ScaleUpdateDetails(
scale: _scaleFactor, scale: _scaleFactor,
@ -455,6 +505,7 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
}); });
} }
} }
}
void _dispatchOnStartCallbackIfNeeded() { void _dispatchOnStartCallbackIfNeeded() {
assert(_state == _ScaleState.started); assert(_state == _ScaleState.started);
@ -504,15 +555,12 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
switch (_state) { switch (_state) {
case _ScaleState.possible: case _ScaleState.possible:
resolve(GestureDisposition.rejected); resolve(GestureDisposition.rejected);
break;
case _ScaleState.ready: case _ScaleState.ready:
assert(false); // We should have not seen a pointer yet assert(false); // We should have not seen a pointer yet
break;
case _ScaleState.accepted: case _ScaleState.accepted:
break; break;
case _ScaleState.started: case _ScaleState.started:
assert(false); // We should be in the accepted state when user is done assert(false); // We should be in the accepted state when user is done
break;
} }
_state = _ScaleState.ready; _state = _ScaleState.ready;
} }

View file

@ -14,11 +14,9 @@ class ScrollControllerAction extends CallbackAction<ScrollIntent> {
case AxisDirection.up: case AxisDirection.up:
case AxisDirection.left: case AxisDirection.left:
factor = -1; factor = -1;
break;
case AxisDirection.down: case AxisDirection.down:
case AxisDirection.right: case AxisDirection.right:
factor = 1; factor = 1;
break;
} }
scrollController.animateTo( scrollController.animateTo(
scrollController.offset + factor * 150, scrollController.offset + factor * 150,

View file

@ -53,7 +53,7 @@ class KnownExtentScrollPhysics extends ScrollPhysics {
// Scenario 3: // Scenario 3:
// If there's no velocity and we're already at where we intend to land, // If there's no velocity and we're already at where we intend to land,
// do nothing. // do nothing.
if (velocity.abs() < tolerance.velocity && (settlingPixels - metrics.pixels).abs() < tolerance.distance) { if (velocity.abs() < toleranceFor(position).velocity && (settlingPixels - metrics.pixels).abs() < toleranceFor(position).distance) {
return null; return null;
} }
@ -66,7 +66,7 @@ class KnownExtentScrollPhysics extends ScrollPhysics {
metrics.pixels, metrics.pixels,
settlingPixels, settlingPixels,
velocity, velocity,
tolerance: tolerance, tolerance: toleranceFor(position),
); );
} }
@ -77,7 +77,7 @@ class KnownExtentScrollPhysics extends ScrollPhysics {
metrics.pixels, metrics.pixels,
settlingPixels, settlingPixels,
velocity, velocity,
tolerance.velocity * velocity.sign, toleranceFor(position).velocity * velocity.sign,
); );
} }
} }

View file

@ -16,5 +16,4 @@ class SpringyScrollPhysics extends ScrollPhysics {
parent: buildParent(ancestor), parent: buildParent(ancestor),
); );
} }
} }

View file

@ -1,26 +1,22 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AvesBorder { class AvesBorder {
static Color _borderColor(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Colors.white30 : Colors.black26; static Color _borderColor(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Colors.white30 : Colors.black26;
// directly uses `devicePixelRatio` as it never changes, to avoid visiting ancestors via `MediaQuery`
// 1 device pixel for straight lines is fine // 1 device pixel for straight lines is fine
static double get straightBorderWidth => 1 / window.devicePixelRatio; static double straightBorderWidth(BuildContext context) => 1 / View.of(context).devicePixelRatio;
// 1 device pixel for curves is too thin // 1 device pixel for curves is too thin
static double get curvedBorderWidth => window.devicePixelRatio > 2 ? 0.5 : 1.0; static double curvedBorderWidth(BuildContext context) => View.of(context).devicePixelRatio > 2 ? 0.5 : 1.0;
static BorderSide straightSide(BuildContext context, {double? width}) => BorderSide( static BorderSide straightSide(BuildContext context, {double? width}) => BorderSide(
color: _borderColor(context), color: _borderColor(context),
width: width ?? straightBorderWidth, width: width ?? straightBorderWidth(context),
); );
static BorderSide curvedSide(BuildContext context, {double? width}) => BorderSide( static BorderSide curvedSide(BuildContext context, {double? width}) => BorderSide(
color: _borderColor(context), color: _borderColor(context),
width: width ?? curvedBorderWidth, width: width ?? curvedBorderWidth(context),
); );
static Border border(BuildContext context, {double? width}) => Border.fromBorderSide(curvedSide(context, width: width)); static Border border(BuildContext context, {double? width}) => Border.fromBorderSide(curvedSide(context, width: width));

View file

@ -2,6 +2,8 @@ import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
// adapted from Flutter `_ImageState` in `/widgets/image.dart` // adapted from Flutter `_ImageState` in `/widgets/image.dart`
// and `DecorationImagePainter` in `/painting/decoration_image.dart` // and `DecorationImagePainter` in `/painting/decoration_image.dart`
@ -13,6 +15,11 @@ class TransitionImage extends StatefulWidget {
final ImageProvider image; final ImageProvider image;
final ValueListenable<double> animation; final ValueListenable<double> animation;
final BoxFit thumbnailFit, viewerFit; final BoxFit thumbnailFit, viewerFit;
final ImageFrameBuilder? frameBuilder;
final ImageLoadingBuilder? loadingBuilder;
final ImageErrorWidgetBuilder? errorBuilder;
final String? semanticLabel;
final bool excludeFromSemantics;
final double? width, height; final double? width, height;
final bool gaplessPlayback = false; final bool gaplessPlayback = false;
final Color? background; final Color? background;
@ -23,6 +30,11 @@ class TransitionImage extends StatefulWidget {
required this.animation, required this.animation,
required this.thumbnailFit, required this.thumbnailFit,
required this.viewerFit, required this.viewerFit,
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width, this.width,
this.height, this.height,
this.background, this.background,
@ -32,16 +44,33 @@ class TransitionImage extends StatefulWidget {
State<TransitionImage> createState() => _TransitionImageState(); State<TransitionImage> createState() => _TransitionImageState();
} }
class _TransitionImageState extends State<TransitionImage> { class _TransitionImageState extends State<TransitionImage> with WidgetsBindingObserver {
ImageStream? _imageStream; ImageStream? _imageStream;
ImageInfo? _imageInfo; ImageInfo? _imageInfo;
ImageChunkEvent? _loadingProgress;
bool _isListeningToStream = false; bool _isListeningToStream = false;
int? _frameNumber; int? _frameNumber;
bool _wasSynchronouslyLoaded = false;
late DisposableBuildContext<State<TransitionImage>> _scrollAwareContext;
Object? _lastException;
StackTrace? _lastStack;
ImageStreamCompleterHandle? _completerHandle;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_scrollAwareContext = DisposableBuildContext<State<TransitionImage>>(this);
}
@override @override
void dispose() { void dispose() {
assert(_imageStream != null); assert(_imageStream != null);
WidgetsBinding.instance.removeObserver(this);
_stopListeningToStream(); _stopListeningToStream();
_completerHandle?.dispose();
_scrollAwareContext.dispose();
_replaceImage(info: null);
super.dispose(); super.dispose();
} }
@ -52,21 +81,23 @@ class _TransitionImageState extends State<TransitionImage> {
if (TickerMode.of(context)) { if (TickerMode.of(context)) {
_listenToStream(); _listenToStream();
} else { } else {
_stopListeningToStream(); _stopListeningToStream(keepStreamAlive: true);
} }
super.didChangeDependencies(); super.didChangeDependencies();
} }
@override @override
void didUpdateWidget(covariant TransitionImage oldWidget) { void didUpdateWidget(TransitionImage oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (_isListeningToStream) { if (_isListeningToStream && (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
final ImageStreamListener oldListener = _getListener(); final ImageStreamListener oldListener = _getListener();
_imageStream!.addListener(_getListener(recreateListener: true)); _imageStream!.addListener(_getListener(recreateListener: true));
_imageStream!.removeListener(oldListener); _imageStream!.removeListener(oldListener);
} }
if (widget.image != oldWidget.image) _resolveImage(); if (widget.image != oldWidget.image) {
_resolveImage();
}
} }
@override @override
@ -76,8 +107,11 @@ class _TransitionImageState extends State<TransitionImage> {
} }
void _resolveImage() { void _resolveImage() {
final provider = widget.image; final ScrollAwareImageProvider provider = ScrollAwareImageProvider<Object>(
final newStream = provider.resolve(createLocalImageConfiguration( context: _scrollAwareContext,
imageProvider: widget.image,
);
final ImageStream newStream = provider.resolve(createLocalImageConfiguration(
context, context,
size: widget.width != null && widget.height != null ? Size(widget.width!, widget.height!) : null, size: widget.width != null && widget.height != null ? Size(widget.width!, widget.height!) : null,
)); ));
@ -88,8 +122,26 @@ class _TransitionImageState extends State<TransitionImage> {
ImageStreamListener _getListener({bool recreateListener = false}) { ImageStreamListener _getListener({bool recreateListener = false}) {
if (_imageStreamListener == null || recreateListener) { if (_imageStreamListener == null || recreateListener) {
_lastException = null;
_lastStack = null;
_imageStreamListener = ImageStreamListener( _imageStreamListener = ImageStreamListener(
_handleImageFrame, _handleImageFrame,
onChunk: widget.loadingBuilder == null ? null : _handleImageChunk,
onError: widget.errorBuilder != null || kDebugMode
? (error, stackTrace) {
setState(() {
_lastException = error;
_lastStack = stackTrace;
});
assert(() {
if (widget.errorBuilder == null) {
// ignore: only_throw_errors, since we're just proxying the error.
throw error; // Ensures the error message is printed to the console.
}
return true;
}());
}
: null,
); );
} }
return _imageStreamListener!; return _imageStreamListener!;
@ -98,15 +150,32 @@ class _TransitionImageState extends State<TransitionImage> {
void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) { void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
setState(() { setState(() {
_replaceImage(info: imageInfo); _replaceImage(info: imageInfo);
_loadingProgress = null;
_lastException = null;
_lastStack = null;
_frameNumber = _frameNumber == null ? 0 : _frameNumber! + 1; _frameNumber = _frameNumber == null ? 0 : _frameNumber! + 1;
_wasSynchronouslyLoaded = _wasSynchronouslyLoaded | synchronousCall;
});
}
void _handleImageChunk(ImageChunkEvent event) {
assert(widget.loadingBuilder != null);
setState(() {
_loadingProgress = event;
_lastException = null;
_lastStack = null;
}); });
} }
void _replaceImage({required ImageInfo? info}) { void _replaceImage({required ImageInfo? info}) {
_imageInfo?.dispose(); final ImageInfo? oldImageInfo = _imageInfo;
SchedulerBinding.instance.addPostFrameCallback((_) => oldImageInfo?.dispose());
_imageInfo = info; _imageInfo = info;
} }
// Updates _imageStream to newStream, and moves the stream listener
// registration from the old stream to the new stream (if a listener was
// registered).
void _updateSourceStream(ImageStream newStream) { void _updateSourceStream(ImageStream newStream) {
if (_imageStream?.key == newStream.key) { if (_imageStream?.key == newStream.key) {
return; return;
@ -123,7 +192,9 @@ class _TransitionImageState extends State<TransitionImage> {
} }
setState(() { setState(() {
_loadingProgress = null;
_frameNumber = null; _frameNumber = null;
_wasSynchronouslyLoaded = false;
}); });
_imageStream = newStream; _imageStream = newStream;
@ -138,22 +209,72 @@ class _TransitionImageState extends State<TransitionImage> {
} }
_imageStream!.addListener(_getListener()); _imageStream!.addListener(_getListener());
_completerHandle?.dispose();
_completerHandle = null;
_isListeningToStream = true; _isListeningToStream = true;
} }
void _stopListeningToStream() { /// Stops listening to the image stream, if this state object has attached a
/// listener.
///
/// If the listener from this state is the last listener on the stream, the
/// stream will be disposed. To keep the stream alive, set `keepStreamAlive`
/// to true, which create [ImageStreamCompleterHandle] to keep the completer
/// alive and is compatible with the [TickerMode] being off.
void _stopListeningToStream({bool keepStreamAlive = false}) {
if (!_isListeningToStream) { if (!_isListeningToStream) {
return; return;
} }
if (keepStreamAlive && _completerHandle == null && _imageStream?.completer != null) {
_completerHandle = _imageStream!.completer!.keepAlive();
}
_imageStream!.removeListener(_getListener()); _imageStream!.removeListener(_getListener());
_isListeningToStream = false; _isListeningToStream = false;
} }
Widget _debugBuildErrorWidget(BuildContext context, Object error) {
return Stack(
alignment: Alignment.center,
children: <Widget>[
const Positioned.fill(
child: Placeholder(
color: Color(0xCF8D021F),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: FittedBox(
child: Text(
'$error',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: const TextStyle(
shadows: <Shadow>[
Shadow(blurRadius: 1.0),
],
),
),
),
),
],
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<double>( if (_lastException != null) {
if (widget.errorBuilder != null) {
return widget.errorBuilder!(context, _lastException!, _lastStack);
}
if (kDebugMode) {
return _debugBuildErrorWidget(context, _lastException!);
}
}
Widget result = ValueListenableBuilder<double>(
valueListenable: widget.animation, valueListenable: widget.animation,
builder: (context, t, child) => CustomPaint( builder: (context, t, child) => CustomPaint(
painter: _TransitionImagePainter( painter: _TransitionImagePainter(
@ -166,6 +287,35 @@ class _TransitionImageState extends State<TransitionImage> {
), ),
), ),
); );
if (!widget.excludeFromSemantics) {
result = Semantics(
container: widget.semanticLabel != null,
image: true,
label: widget.semanticLabel ?? '',
child: result,
);
}
if (widget.frameBuilder != null) {
result = widget.frameBuilder!(context, result, _frameNumber, _wasSynchronouslyLoaded);
}
if (widget.loadingBuilder != null) {
result = widget.loadingBuilder!(context, result, _loadingProgress);
}
return result;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
description.add(DiagnosticsProperty<ImageChunkEvent>('loadingProgress', _loadingProgress));
description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber));
description.add(DiagnosticsProperty<bool>('wasSynchronouslyLoaded', _wasSynchronouslyLoaded));
} }
} }
@ -194,8 +344,11 @@ class _TransitionImagePainter extends CustomPainter {
const alignment = Alignment.center; const alignment = Alignment.center;
final rect = ui.Rect.fromLTWH(0, 0, size.width, size.height); final rect = ui.Rect.fromLTWH(0, 0, size.width, size.height);
final inputSize = Size(image!.width.toDouble(), image!.height.toDouble()); if (rect.isEmpty) {
return;
}
final outputSize = rect.size; final outputSize = rect.size;
final inputSize = Size(image!.width.toDouble(), image!.height.toDouble());
final thumbnailSizes = applyBoxFit(thumbnailFit, inputSize / scale, size); final thumbnailSizes = applyBoxFit(thumbnailFit, inputSize / scale, size);
final viewerSizes = applyBoxFit(viewerFit, inputSize / scale, size); final viewerSizes = applyBoxFit(viewerFit, inputSize / scale, size);

View file

@ -44,24 +44,18 @@ class _GridItemTrackerState<T> extends State<GridItemTracker<T>> with WidgetsBin
return (scrollableContext.findRenderObject() as RenderBox).size; return (scrollableContext.findRenderObject() as RenderBox).size;
} }
Orientation get _windowOrientation {
final size = WidgetsBinding.instance.window.physicalSize;
return size.width > size.height ? Orientation.landscape : Orientation.portrait;
}
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
// grid section metrics before the app is laid out with the new orientation // grid section metrics before the app is laid out with the new orientation
late SectionedListLayout<T> _lastSectionedListLayout; late SectionedListLayout<T> _lastSectionedListLayout;
late Size _lastScrollableSize; late Size _lastScrollableSize;
late Orientation _lastOrientation; Orientation _lastOrientation = Orientation.portrait;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final highlightInfo = context.read<HighlightInfo>(); final highlightInfo = context.read<HighlightInfo>();
_subscriptions.add(highlightInfo.eventBus.on<TrackEvent<T>>().listen(_trackItem)); _subscriptions.add(highlightInfo.eventBus.on<TrackEvent<T>>().listen(_trackItem));
_lastOrientation = _windowOrientation;
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_saveLayoutMetrics(); _saveLayoutMetrics();
} }
@ -78,9 +72,10 @@ class _GridItemTrackerState<T> extends State<GridItemTracker<T>> with WidgetsBin
@override @override
void didChangeMetrics() { void didChangeMetrics() {
// the order of `WidgetsBindingObserver` metrics change notification is unreliable // the order of `WidgetsBindingObserver` metrics change notification is unreliable
// w.r.t. the `MediaQuery` update, and consequentially to this widget update: // w.r.t. the `View` update, and consequentially to this widget update:
// `WidgetsBindingObserver` is notified mostly before, sometimes after, the widget update // `WidgetsBindingObserver` is notified mostly before, sometimes after, the widget update
final orientation = _windowOrientation; final size = View.of(context).physicalSize;
final orientation = size.width > size.height ? Orientation.landscape : Orientation.portrait;
if (_lastOrientation != orientation) { if (_lastOrientation != orientation) {
_lastOrientation = orientation; _lastOrientation = orientation;
_onLayoutChanged(); _onLayoutChanged();
@ -138,7 +133,7 @@ class _GridItemTrackerState<T> extends State<GridItemTracker<T>> with WidgetsBin
Future<void> _saveLayoutMetrics() async { Future<void> _saveLayoutMetrics() async {
// use a delay to obtain current layout metrics // use a delay to obtain current layout metrics
// so that we can handle window orientation change with the previous metrics, // so that we can handle window orientation change with the previous metrics,
// regardless of the `MediaQuery`/`WidgetsBindingObserver` order uncertainty // regardless of the `View`/`WidgetsBindingObserver` order uncertainty
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
if (mounted) { if (mounted) {

View file

@ -57,7 +57,7 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final gestureSettings = context.select<MediaQueryData, DeviceGestureSettings>((mq) => mq.gestureSettings); final gestureSettings = MediaQuery.gestureSettingsOf(context);
final child = GestureDetector( final child = GestureDetector(
// Horizontal/vertical drag gestures are interpreted as scaling // Horizontal/vertical drag gestures are interpreted as scaling
@ -116,11 +116,9 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
switch (tileLayout) { switch (tileLayout) {
case TileLayout.mosaic: case TileLayout.mosaic:
_startSize = Size.square(tileExtentController.extentNotifier.value); _startSize = Size.square(tileExtentController.extentNotifier.value);
break;
case TileLayout.grid: case TileLayout.grid:
case TileLayout.list: case TileLayout.list:
_startSize = renderMetaData.size; _startSize = renderMetaData.size;
break;
} }
_scaledSizeNotifier = ValueNotifier(_startSize!); _scaledSizeNotifier = ValueNotifier(_startSize!);
@ -143,7 +141,6 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
itemBuilder: widget.mosaicItemBuilder, itemBuilder: widget.mosaicItemBuilder,
), ),
); );
break;
case TileLayout.grid: case TileLayout.grid:
case TileLayout.list: case TileLayout.list:
final tileCenter = renderMetaData.localToGlobal(Offset(halfSize.width, halfSize.height)); final tileCenter = renderMetaData.localToGlobal(Offset(halfSize.width, halfSize.height));
@ -163,7 +160,6 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
), ),
), ),
); );
break;
} }
Overlay.of(scrollableContext).insert(_overlayEntry!); Overlay.of(scrollableContext).insert(_overlayEntry!);
} }
@ -176,11 +172,9 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
case TileLayout.grid: case TileLayout.grid:
final scaledWidth = (_startSize!.width * s).clamp(_extentMin!, _extentMax!); final scaledWidth = (_startSize!.width * s).clamp(_extentMin!, _extentMax!);
_scaledSizeNotifier!.value = Size(scaledWidth, widget.heightForWidth(scaledWidth)); _scaledSizeNotifier!.value = Size(scaledWidth, widget.heightForWidth(scaledWidth));
break;
case TileLayout.list: case TileLayout.list:
final scaledHeight = (_startSize!.height * s).clamp(_extentMin!, _extentMax!); final scaledHeight = (_startSize!.height * s).clamp(_extentMin!, _extentMax!);
_scaledSizeNotifier!.value = Size(_startSize!.width, scaledHeight); _scaledSizeNotifier!.value = Size(_startSize!.width, scaledHeight);
break;
} }
} }
@ -200,10 +194,8 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
case TileLayout.mosaic: case TileLayout.mosaic:
case TileLayout.grid: case TileLayout.grid:
preferredExtent = _scaledSizeNotifier!.value.width; preferredExtent = _scaledSizeNotifier!.value.width;
break;
case TileLayout.list: case TileLayout.list:
preferredExtent = _scaledSizeNotifier!.value.height; preferredExtent = _scaledSizeNotifier!.value.height;
break;
} }
final newExtent = tileExtentController.setUserPreferredExtent(preferredExtent); final newExtent = tileExtentController.setUserPreferredExtent(preferredExtent);
_scaledSizeNotifier = null; _scaledSizeNotifier = null;

View file

@ -5,7 +5,7 @@ class FixedExtentGridRow extends MultiChildRenderObjectWidget {
final double width, height, spacing; final double width, height, spacing;
final TextDirection textDirection; final TextDirection textDirection;
FixedExtentGridRow({ const FixedExtentGridRow({
super.key, super.key,
required this.width, required this.width,
required this.height, required this.height,

View file

@ -49,7 +49,6 @@ class FixedExtentGridPainter extends CustomPainter {
1, 1,
], ],
); );
break;
case TileLayout.list: case TileLayout.list:
chipSize = Size.square(tileSize.shortestSide); chipSize = Size.square(tileSize.shortestSide);
final chipCenterToEdge = chipSize.width / 2; final chipCenterToEdge = chipSize.width / 2;
@ -71,7 +70,6 @@ class FixedExtentGridPainter extends CustomPainter {
1, 1,
], ],
); );
break;
} }
final strokePaint = Paint() final strokePaint = Paint()
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke

View file

@ -4,7 +4,6 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FixedExtentScaleOverlay extends StatelessWidget { class FixedExtentScaleOverlay extends StatelessWidget {
final TileLayout tileLayout; final TileLayout tileLayout;
@ -105,7 +104,7 @@ class _OverlayBackgroundState extends State<_OverlayBackground> {
return _initialized return _initialized
? BoxDecoration( ? BoxDecoration(
gradient: RadialGradient( gradient: RadialGradient(
center: FractionalOffset.fromOffsetAndSize(gradientCenter, context.select<MediaQueryData, Size>((mq) => mq.size)), center: FractionalOffset.fromOffsetAndSize(gradientCenter, MediaQuery.sizeOf(context)),
radius: 1, radius: 1,
colors: isDark colors: isDark
? const [ ? const [

View file

@ -8,7 +8,7 @@ class MosaicGridRow extends MultiChildRenderObjectWidget {
final double spacing; final double spacing;
final TextDirection textDirection; final TextDirection textDirection;
MosaicGridRow({ const MosaicGridRow({
super.key, super.key,
required this.rowLayout, required this.rowLayout,
required this.spacing, required this.spacing,

View file

@ -32,19 +32,13 @@ class AvesAppBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final useTvLayout = settings.useTvLayout; final useTvLayout = settings.useTvLayout;
return Selector<MediaQueryData, double>(
selector: (context, mq) => mq.padding.top,
builder: (context, mqPaddingTop, child) {
return SliverPersistentHeader( return SliverPersistentHeader(
floating: !useTvLayout, floating: !useTvLayout,
pinned: pinned, pinned: pinned,
delegate: _SliverAppBarDelegate( delegate: _SliverAppBarDelegate(
height: mqPaddingTop + appBarHeightForContentHeight(contentHeight), height: MediaQuery.paddingOf(context).top + appBarHeightForContentHeight(contentHeight),
child: child!,
),
);
},
child: DirectionalSafeArea( child: DirectionalSafeArea(
start: !useTvLayout, start: !useTvLayout,
bottom: false, bottom: false,
@ -56,7 +50,7 @@ class AvesAppBar extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
SizedBox( SizedBox(
height: kToolbarHeight * context.select<MediaQueryData, double>((mq) => mq.textScaleFactor), height: kToolbarHeight * textScaleFactor,
child: Row( child: Row(
children: [ children: [
leading != null leading != null
@ -102,6 +96,7 @@ class AvesAppBar extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }

View file

@ -31,12 +31,12 @@ enum HeroType { always, onTap, never }
@immutable @immutable
class AvesFilterDecoration { class AvesFilterDecoration {
final Widget widget;
final Radius radius; final Radius radius;
final Widget widget;
const AvesFilterDecoration({ const AvesFilterDecoration({
required this.widget,
required this.radius, required this.radius,
required this.widget,
}); });
BorderRadius get textBorderRadius => BorderRadius.vertical(bottom: radius); BorderRadius get textBorderRadius => BorderRadius.vertical(bottom: radius);
@ -88,9 +88,9 @@ class AvesFilterChip extends StatefulWidget {
required double chipPadding, required double chipPadding,
required double rowPadding, required double rowPadding,
}) { }) {
return context.select<MediaQueryData, double>((mq) { final mqWidth = MediaQuery.sizeOf(context).width;
return (mq.size.width - mq.padding.horizontal - chipPadding * minChipPerRow - rowPadding) / minChipPerRow; final mqHorizontalPadding = MediaQuery.paddingOf(context).horizontal;
}); return (mqWidth - mqHorizontalPadding - chipPadding * minChipPerRow - rowPadding) / minChipPerRow;
} }
static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async { static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {

View file

@ -37,7 +37,7 @@ class AvesLogo extends StatelessWidget {
radius: size / 2, radius: size / 2,
child: CircleAvatar( child: CircleAvatar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
radius: size / 2 - AvesBorder.curvedBorderWidth, radius: size / 2 - AvesBorder.curvedBorderWidth(context),
child: Padding( child: Padding(
padding: EdgeInsets.only(top: size / 15), padding: EdgeInsets.only(top: size / 15),
child: child, child: child,

View file

@ -73,7 +73,7 @@ class _OverlayButtonState extends State<OverlayButton> {
builder: (context, focused, child) { builder: (context, focused, child) {
final border = AvesBorder.border( final border = AvesBorder.border(
context, context,
width: AvesBorder.curvedBorderWidth * (focused ? 3 : 1), width: AvesBorder.curvedBorderWidth(context) * (focused ? 3 : 1),
); );
return borderRadius != null return borderRadius != null
? BlurredRRect( ? BlurredRRect(

View file

@ -42,7 +42,6 @@ class MapButtonPanel extends StatelessWidget {
tooltip: MaterialLocalizations.of(context).backButtonTooltip, tooltip: MaterialLocalizations.of(context).backButtonTooltip,
); );
} }
break;
case MapNavigationButton.map: case MapNavigationButton.map:
if (openMapPage != null) { if (openMapPage != null) {
navigationButton = MapOverlayButton( navigationButton = MapOverlayButton(
@ -51,7 +50,6 @@ class MapButtonPanel extends StatelessWidget {
tooltip: context.l10n.openMapPageTooltip, tooltip: context.l10n.openMapPageTooltip,
); );
} }
break;
case MapNavigationButton.none: case MapNavigationButton.none:
break; break;
} }

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'dart:ui';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/model/entry/extensions/images.dart';
@ -35,6 +34,7 @@ class GeoMap extends StatefulWidget {
final AvesMapController? controller; final AvesMapController? controller;
final Listenable? collectionListenable; final Listenable? collectionListenable;
final List<AvesEntry> entries; final List<AvesEntry> entries;
final Size availableSize;
final LatLng? initialCenter; final LatLng? initialCenter;
final ValueNotifier<bool> isAnimatingNotifier; final ValueNotifier<bool> isAnimatingNotifier;
final ValueNotifier<LatLng?>? dotLocationNotifier; final ValueNotifier<LatLng?>? dotLocationNotifier;
@ -60,6 +60,7 @@ class GeoMap extends StatefulWidget {
this.controller, this.controller,
this.collectionListenable, this.collectionListenable,
required this.entries, required this.entries,
required this.availableSize,
this.initialCenter, this.initialCenter,
required this.isAnimatingNotifier, required this.isAnimatingNotifier,
this.dotLocationNotifier, this.dotLocationNotifier,
@ -133,6 +134,13 @@ class _GeoMapState extends State<GeoMap> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final devicePixelRatio = View.of(context).devicePixelRatio;
void onMarkerLongPress(GeoEntry<AvesEntry> geoEntry, LatLng tapLocation) => _onMarkerLongPress(
geoEntry: geoEntry,
tapLocation: tapLocation,
devicePixelRatio: devicePixelRatio,
);
return Selector<Settings, EntryMapStyle?>( return Selector<Settings, EntryMapStyle?>(
selector: (context, s) => s.mapStyle, selector: (context, s) => s.mapStyle,
builder: (context, mapStyle, child) { builder: (context, mapStyle, child) {
@ -143,6 +151,7 @@ class _GeoMapState extends State<GeoMap> {
buildThumbnailImage: (extent) => ThumbnailImage( buildThumbnailImage: (extent) => ThumbnailImage(
entry: key.entry, entry: key.entry,
extent: extent, extent: extent,
devicePixelRatio: devicePixelRatio,
progressive: !isHeavy, progressive: !isHeavy,
), ),
); );
@ -173,9 +182,8 @@ class _GeoMapState extends State<GeoMap> {
onUserZoomChange: widget.onUserZoomChange, onUserZoomChange: widget.onUserZoomChange,
onMapTap: widget.onMapTap, onMapTap: widget.onMapTap,
onMarkerTap: _onMarkerTap, onMarkerTap: _onMarkerTap,
onMarkerLongPress: _onMarkerLongPress, onMarkerLongPress: onMarkerLongPress,
); );
break;
case EntryMapStyle.osmHot: case EntryMapStyle.osmHot:
case EntryMapStyle.stamenToner: case EntryMapStyle.stamenToner:
case EntryMapStyle.stamenWatercolor: case EntryMapStyle.stamenWatercolor:
@ -204,9 +212,8 @@ class _GeoMapState extends State<GeoMap> {
onUserZoomChange: widget.onUserZoomChange, onUserZoomChange: widget.onUserZoomChange,
onMapTap: widget.onMapTap, onMapTap: widget.onMapTap,
onMarkerTap: _onMarkerTap, onMarkerTap: _onMarkerTap,
onMarkerLongPress: _onMarkerLongPress, onMarkerLongPress: onMarkerLongPress,
); );
break;
} }
} else { } else {
final overlay = Center( final overlay = Center(
@ -360,8 +367,8 @@ class _GeoMapState extends State<GeoMap> {
); );
bounds = bounds.copyWith(zoom: max(minInitialZoom, bounds.zoom.floorToDouble())); bounds = bounds.copyWith(zoom: max(minInitialZoom, bounds.zoom.floorToDouble()));
final availableSize = window.physicalSize / window.devicePixelRatio;
final neededSize = bounds.toDisplaySize(); final neededSize = bounds.toDisplaySize();
final availableSize = widget.availableSize;
if (neededSize.width > availableSize.width || neededSize.height > availableSize.height) { if (neededSize.width > availableSize.width || neededSize.height > availableSize.height) {
return _initBoundsForEntries(entries: entries, recentCount: (recentCount ?? 10000) ~/ 10); return _initBoundsForEntries(entries: entries, recentCount: (recentCount ?? 10000) ~/ 10);
} }
@ -457,7 +464,11 @@ class _GeoMapState extends State<GeoMap> {
onTap(markerLocation, markerEntry); onTap(markerLocation, markerEntry);
} }
Future<void> _onMarkerLongPress(GeoEntry<AvesEntry> geoEntry, LatLng tapLocation) async { Future<void> _onMarkerLongPress({
required GeoEntry<AvesEntry> geoEntry,
required LatLng tapLocation,
required double devicePixelRatio,
}) async {
final onMarkerLongPress = widget.onMarkerLongPress; final onMarkerLongPress = widget.onMarkerLongPress;
if (onMarkerLongPress == null) return; if (onMarkerLongPress == null) return;
@ -478,6 +489,7 @@ class _GeoMapState extends State<GeoMap> {
buildThumbnailImage: (extent) => ThumbnailImage( buildThumbnailImage: (extent) => ThumbnailImage(
entry: markerEntry, entry: markerEntry,
extent: extent, extent: extent,
devicePixelRatio: devicePixelRatio,
), ),
); );
onMarkerLongPress( onMarkerLongPress(

View file

@ -82,7 +82,6 @@ class ScaleLayerWidget extends StatelessWidget {
// meters // meters
distanceMeters = scaleMeters[scaleLevel]; distanceMeters = scaleMeters[scaleLevel];
displayDistance = distanceMeters >= metersInAKilometer ? '${(distanceMeters / metersInAKilometer).toStringAsFixed(0)} km' : '${distanceMeters.toStringAsFixed(0)} m'; displayDistance = distanceMeters >= metersInAKilometer ? '${(distanceMeters / metersInAKilometer).toStringAsFixed(0)} km' : '${distanceMeters.toStringAsFixed(0)} m';
break;
case UnitSystem.imperial: case UnitSystem.imperial:
if (scaleLevel < 15) { if (scaleLevel < 15) {
// miles // miles
@ -95,7 +94,6 @@ class ScaleLayerWidget extends StatelessWidget {
distanceMeters = distanceFeet * metersInAFoot; distanceMeters = distanceFeet * metersInAFoot;
displayDistance = '${distanceFeet.toStringAsFixed(0)} ft'; displayDistance = '${distanceFeet.toStringAsFixed(0)} ft';
} }
break;
} }
final start = map.project(center); final start = map.project(center);

View file

@ -1,7 +1,6 @@
import 'package:aves/model/device.dart'; import 'package:aves/model/device.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:provider/provider.dart';
const _tileLayerBackgroundColor = Colors.transparent; const _tileLayerBackgroundColor = Colors.transparent;
@ -14,7 +13,7 @@ class OSMHotLayer extends StatelessWidget {
urlTemplate: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', urlTemplate: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
subdomains: const ['a', 'b', 'c'], subdomains: const ['a', 'b', 'c'],
backgroundColor: _tileLayerBackgroundColor, backgroundColor: _tileLayerBackgroundColor,
retinaMode: context.select<MediaQueryData, double>((mq) => mq.devicePixelRatio) > 1, retinaMode: MediaQuery.devicePixelRatioOf(context) > 1,
userAgentPackageName: device.userAgent, userAgentPackageName: device.userAgent,
); );
} }
@ -29,7 +28,7 @@ class StamenTonerLayer extends StatelessWidget {
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.png', urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.png',
subdomains: const ['a', 'b', 'c', 'd'], subdomains: const ['a', 'b', 'c', 'd'],
backgroundColor: _tileLayerBackgroundColor, backgroundColor: _tileLayerBackgroundColor,
retinaMode: context.select<MediaQueryData, double>((mq) => mq.devicePixelRatio) > 1, retinaMode: MediaQuery.devicePixelRatioOf(context) > 1,
userAgentPackageName: device.userAgent, userAgentPackageName: device.userAgent,
); );
} }
@ -44,7 +43,7 @@ class StamenWatercolorLayer extends StatelessWidget {
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg', urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg',
subdomains: const ['a', 'b', 'c', 'd'], subdomains: const ['a', 'b', 'c', 'd'],
backgroundColor: _tileLayerBackgroundColor, backgroundColor: _tileLayerBackgroundColor,
retinaMode: context.select<MediaQueryData, double>((mq) => mq.devicePixelRatio) > 1, retinaMode: MediaQuery.devicePixelRatioOf(context) > 1,
userAgentPackageName: device.userAgent, userAgentPackageName: device.userAgent,
); );
} }

View file

@ -26,13 +26,10 @@ class MapActionDelegate {
), ),
onSelection: (v) => settings.mapStyle = v, onSelection: (v) => settings.mapStyle = v,
); );
break;
case MapAction.zoomIn: case MapAction.zoomIn:
controller?.zoomBy(1); controller?.zoomBy(1);
break;
case MapAction.zoomOut: case MapAction.zoomOut:
controller?.zoomBy(-1); controller?.zoomBy(-1);
break;
} }
} }
} }

View file

@ -114,13 +114,11 @@ class _SearchPageState extends State<SearchPage> {
key: const ValueKey<SearchBody>(SearchBody.suggestions), key: const ValueKey<SearchBody>(SearchBody.suggestions),
child: widget.delegate.buildSuggestions(context), child: widget.delegate.buildSuggestions(context),
); );
break;
case SearchBody.results: case SearchBody.results:
body = KeyedSubtree( body = KeyedSubtree(
key: const ValueKey<SearchBody>(SearchBody.results), key: const ValueKey<SearchBody>(SearchBody.results),
child: widget.delegate.buildResults(context), child: widget.delegate.buildResults(context),
); );
break;
case null: case null:
break; break;
} }

View file

@ -14,7 +14,8 @@ class DecoratedThumbnail extends StatelessWidget {
final Object? Function()? heroTagger; final Object? Function()? heroTagger;
static final Color borderColor = Colors.grey.shade700; static final Color borderColor = Colors.grey.shade700;
static final double borderWidth = AvesBorder.straightBorderWidth;
static double borderWidth(BuildContext context) => AvesBorder.straightBorderWidth(context);
const DecoratedThumbnail({ const DecoratedThumbnail({
super.key, super.key,
@ -35,6 +36,7 @@ class DecoratedThumbnail extends StatelessWidget {
Widget child = ThumbnailImage( Widget child = ThumbnailImage(
entry: entry, entry: entry,
extent: tileExtent, extent: tileExtent,
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
isMosaic: isMosaic, isMosaic: isMosaic,
cancellableNotifier: cancellableNotifier, cancellableNotifier: cancellableNotifier,
heroTag: heroTagger?.call(), heroTag: heroTagger?.call(),
@ -64,7 +66,7 @@ class DecoratedThumbnail extends StatelessWidget {
foregroundDecoration: BoxDecoration( foregroundDecoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide( border: Border.fromBorderSide(BorderSide(
color: borderColor, color: borderColor,
width: borderWidth, width: borderWidth(context),
)), )),
), ),
width: thumbnailWidth, width: thumbnailWidth,

View file

@ -1,5 +1,4 @@
import 'dart:math'; import 'dart:math';
import 'dart:ui';
import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/thumbnail_provider.dart';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
@ -20,7 +19,7 @@ import 'package:provider/provider.dart';
class ThumbnailImage extends StatefulWidget { class ThumbnailImage extends StatefulWidget {
final AvesEntry entry; final AvesEntry entry;
final double extent; final double extent, devicePixelRatio;
final bool isMosaic, progressive; final bool isMosaic, progressive;
final BoxFit? fit; final BoxFit? fit;
final bool showLoadingBackground; final bool showLoadingBackground;
@ -31,6 +30,7 @@ class ThumbnailImage extends StatefulWidget {
super.key, super.key,
required this.entry, required this.entry,
required this.extent, required this.extent,
required this.devicePixelRatio,
this.progressive = true, this.progressive = true,
this.isMosaic = false, this.isMosaic = false,
this.fit, this.fit,
@ -57,7 +57,6 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
ImageInfo? _lastImageInfo; ImageInfo? _lastImageInfo;
Object? _lastException; Object? _lastException;
late final ImageStreamListener _streamListener; late final ImageStreamListener _streamListener;
late DisposableBuildContext<State<ThumbnailImage>> _scrollAwareContext;
AvesEntry get entry => widget.entry; AvesEntry get entry => widget.entry;
@ -69,7 +68,6 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
void initState() { void initState() {
super.initState(); super.initState();
_streamListener = ImageStreamListener(_onImageLoad, onError: _onError); _streamListener = ImageStreamListener(_onImageLoad, onError: _onError);
_scrollAwareContext = DisposableBuildContext<State<ThumbnailImage>>(this);
_registerWidget(widget); _registerWidget(widget);
} }
@ -85,7 +83,6 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
@override @override
void dispose() { void dispose() {
_unregisterWidget(widget); _unregisterWidget(widget);
_scrollAwareContext.dispose();
super.dispose(); super.dispose();
} }
@ -126,16 +123,10 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
_providers.addAll([ _providers.addAll([
if (lowQuality != null) if (lowQuality != null)
_ConditionalImageProvider( _ConditionalImageProvider(
ScrollAwareImageProvider( lowQuality,
context: _scrollAwareContext,
imageProvider: lowQuality,
),
), ),
_ConditionalImageProvider( _ConditionalImageProvider(
ScrollAwareImageProvider( highQuality,
context: _scrollAwareContext,
imageProvider: highQuality,
),
_needSizedProvider, _needSizedProvider,
), ),
]); ]);
@ -176,8 +167,7 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
bool _needSizedProvider(ImageInfo? currentImageInfo) { bool _needSizedProvider(ImageInfo? currentImageInfo) {
if (currentImageInfo == null) return true; if (currentImageInfo == null) return true;
final currentImage = currentImageInfo.image; final currentImage = currentImageInfo.image;
// directly uses `devicePixelRatio` as it never changes, to avoid visiting ancestors via `MediaQuery` final sizedThreshold = extent * widget.devicePixelRatio;
final sizedThreshold = extent * window.devicePixelRatio;
return sizedThreshold > min(currentImage.width, currentImage.height); return sizedThreshold > min(currentImage.width, currentImage.height);
} }

View file

@ -182,18 +182,15 @@ class _AppDebugPageState extends State<AppDebugPage> {
}, false); }, false);
await favourites.clear(); await favourites.clear();
await favourites.add(source.visibleEntries); await favourites.add(source.visibleEntries);
break;
case AppDebugAction.prepScreenshotStats: case AppDebugAction.prepScreenshotStats:
settings.changeFilterVisibility(settings.hiddenFilters, true); settings.changeFilterVisibility(settings.hiddenFilters, true);
settings.changeFilterVisibility({ settings.changeFilterVisibility({
PathFilter('/storage/emulated/0/Pictures/Dev'), PathFilter('/storage/emulated/0/Pictures/Dev'),
}, false); }, false);
break;
case AppDebugAction.prepScreenshotCountries: case AppDebugAction.prepScreenshotCountries:
settings.changeFilterVisibility({ settings.changeFilterVisibility({
LocationFilter(LocationLevel.country, 'Belgium;BE'), LocationFilter(LocationLevel.country, 'Belgium;BE'),
}, false); }, false);
break;
case AppDebugAction.mediaStoreScanDir: case AppDebugAction.mediaStoreScanDir:
// scan files copied from test assets // scan files copied from test assets
// we do it via the app instead of broadcasting via ADB // we do it via the app instead of broadcasting via ADB
@ -202,7 +199,6 @@ class _AppDebugPageState extends State<AppDebugPage> {
context: context, context: context,
builder: (context) => const MediaStoreScanDirDialog(), builder: (context) => const MediaStoreScanDirDialog(),
); );
break;
case AppDebugAction.greenScreen: case AppDebugAction.greenScreen:
await Navigator.maybeOf(context)?.push( await Navigator.maybeOf(context)?.push(
MaterialPageRoute( MaterialPageRoute(
@ -212,7 +208,6 @@ class _AppDebugPageState extends State<AppDebugPage> {
), ),
), ),
); );
break;
} }
} }
} }

View file

@ -66,7 +66,7 @@ class DebugSettingsSection extends StatelessWidget {
'recentDestinationAlbums': toMultiline(settings.recentDestinationAlbums), 'recentDestinationAlbums': toMultiline(settings.recentDestinationAlbums),
'recentTags': toMultiline(settings.recentTags), 'recentTags': toMultiline(settings.recentTags),
'locale': '${settings.locale}', 'locale': '${settings.locale}',
'systemLocales': '${WidgetsBinding.instance.window.locales}', 'systemLocales': '${WidgetsBinding.instance.platformDispatcher.locales}',
'topEntryIds': '${settings.topEntryIds}', 'topEntryIds': '${settings.topEntryIds}',
}, },
), ),

View file

@ -8,7 +8,6 @@ import 'package:aves/widgets/dialogs/item_picker.dart';
import 'package:aves/widgets/dialogs/pick_dialogs/item_pick_page.dart'; import 'package:aves/widgets/dialogs/pick_dialogs/item_pick_page.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'aves_dialog.dart'; import 'aves_dialog.dart';
@ -60,7 +59,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: Builder( child: Builder(
builder: (context) { builder: (context) {
final shortestSide = context.select<MediaQueryData, double>((mq) => mq.size.shortestSide); final shortestSide = MediaQuery.sizeOf(context).shortestSide;
final extent = (shortestSide / 3.0).clamp(60.0, 160.0); final extent = (shortestSide / 3.0).clamp(60.0, 160.0);
return AvesDialog( return AvesDialog(
scrollableContent: [ scrollableContent: [

View file

@ -72,16 +72,12 @@ void _skipConfirmation(ConfirmationDialog type) {
switch (type) { switch (type) {
case ConfirmationDialog.createVault: case ConfirmationDialog.createVault:
settings.confirmCreateVault = false; settings.confirmCreateVault = false;
break;
case ConfirmationDialog.deleteForever: case ConfirmationDialog.deleteForever:
settings.confirmDeleteForever = false; settings.confirmDeleteForever = false;
break;
case ConfirmationDialog.moveToBin: case ConfirmationDialog.moveToBin:
settings.confirmMoveToBin = false; settings.confirmMoveToBin = false;
break;
case ConfirmationDialog.moveUndatedItems: case ConfirmationDialog.moveUndatedItems:
settings.confirmMoveUndatedItems = false; settings.confirmMoveUndatedItems = false;
break;
} }
} }

View file

@ -3,7 +3,6 @@ import 'dart:ui';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AvesDialog extends StatelessWidget { class AvesDialog extends StatelessWidget {
static const confirmationRouteName = '/dialog/confirmation'; static const confirmationRouteName = '/dialog/confirmation';
@ -104,7 +103,7 @@ class AvesDialog extends StatelessWidget {
// workaround because the dialog tries // workaround because the dialog tries
// to size itself to the content intrinsic size, // to size itself to the content intrinsic size,
// but the `ListView` viewport does not have one // but the `ListView` viewport does not have one
width: context.select<MediaQueryData, double>((mq) => mq.size.width / 2), width: MediaQuery.sizeOf(context).width / 2,
child: DecoratedBox( child: DecoratedBox(
decoration: contentDecoration(context), decoration: contentDecoration(context),
child: child, child: child,

View file

@ -78,11 +78,9 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
final displaySize = entries.first.displaySize; final displaySize = entries.first.displaySize;
_widthController.text = '${displaySize.width.round()}'; _widthController.text = '${displaySize.width.round()}';
_heightController.text = '${displaySize.height.round()}'; _heightController.text = '${displaySize.height.round()}';
break;
case LengthUnit.percent: case LengthUnit.percent:
_widthController.text = '100'; _widthController.text = '100';
_heightController.text = '100'; _heightController.text = '100';
break;
} }
} }
@ -149,10 +147,8 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
switch (_lengthUnit) { switch (_lengthUnit) {
case LengthUnit.px: case LengthUnit.px:
_heightController.text = '${(width / entries.first.displayAspectRatio).round()}'; _heightController.text = '${(width / entries.first.displayAspectRatio).round()}';
break;
case LengthUnit.percent: case LengthUnit.percent:
_heightController.text = '$width'; _heightController.text = '$width';
break;
} }
} else { } else {
_heightController.text = ''; _heightController.text = '';
@ -175,10 +171,8 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
switch (_lengthUnit) { switch (_lengthUnit) {
case LengthUnit.px: case LengthUnit.px:
_widthController.text = '${(height * entries.first.displayAspectRatio).round()}'; _widthController.text = '${(height * entries.first.displayAspectRatio).round()}';
break;
case LengthUnit.percent: case LengthUnit.percent:
_widthController.text = '$height'; _widthController.text = '$height';
break;
} }
} else { } else {
_widthController.text = ''; _widthController.text = '';

View file

@ -145,7 +145,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
Widget _buildSetCustomContent(BuildContext context) { Widget _buildSetCustomContent(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final locale = l10n.localeName; final locale = l10n.localeName;
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat); final use24hour = MediaQuery.alwaysUse24HourFormatOf(context);
return Padding( return Padding(
padding: const EdgeInsetsDirectional.only(start: 16, end: 8), padding: const EdgeInsetsDirectional.only(start: 16, end: 8),
@ -179,7 +179,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
Widget _buildCopyItemContent(BuildContext context) { Widget _buildCopyItemContent(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final locale = l10n.localeName; final locale = l10n.localeName;
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat); final use24hour = MediaQuery.alwaysUse24HourFormatOf(context);
return Padding( return Padding(
padding: const EdgeInsetsDirectional.only(start: 16, end: 8), padding: const EdgeInsetsDirectional.only(start: 16, end: 8),
@ -365,11 +365,9 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
case DateEditAction.copyItem: case DateEditAction.copyItem:
case DateEditAction.extractFromTitle: case DateEditAction.extractFromTitle:
_isValidNotifier.value = true; _isValidNotifier.value = true;
break;
case DateEditAction.shift: case DateEditAction.shift:
case DateEditAction.remove: case DateEditAction.remove:
_isValidNotifier.value = _fields.isNotEmpty; _isValidNotifier.value = _fields.isNotEmpty;
break;
} }
} }

View file

@ -307,33 +307,26 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
switch (_action) { switch (_action) {
case LocationEditAction.chooseOnMap: case LocationEditAction.chooseOnMap:
_isValidNotifier.value = _mapCoordinates != null; _isValidNotifier.value = _mapCoordinates != null;
break;
case LocationEditAction.copyItem: case LocationEditAction.copyItem:
_isValidNotifier.value = _copyItemSource.hasGps; _isValidNotifier.value = _copyItemSource.hasGps;
break;
case LocationEditAction.setCustom: case LocationEditAction.setCustom:
_isValidNotifier.value = _parseLatLng() != null; _isValidNotifier.value = _parseLatLng() != null;
break;
case LocationEditAction.remove: case LocationEditAction.remove:
_isValidNotifier.value = true; _isValidNotifier.value = true;
break;
} }
} }
void _submit(BuildContext context) { void _submit(BuildContext context) {
final navigator = Navigator.maybeOf(context);
switch (_action) { switch (_action) {
case LocationEditAction.chooseOnMap: case LocationEditAction.chooseOnMap:
Navigator.maybeOf(context)?.pop(_mapCoordinates); navigator?.pop(_mapCoordinates);
break;
case LocationEditAction.copyItem: case LocationEditAction.copyItem:
Navigator.maybeOf(context)?.pop(_copyItemSource.latLng); navigator?.pop(_copyItemSource.latLng);
break;
case LocationEditAction.setCustom: case LocationEditAction.setCustom:
Navigator.maybeOf(context)?.pop(_parseLatLng()); navigator?.pop(_parseLatLng());
break;
case LocationEditAction.remove: case LocationEditAction.remove:
Navigator.maybeOf(context)?.pop(ExtraAvesEntryMetadataEdition.removalLocation); navigator?.pop(ExtraAvesEntryMetadataEdition.removalLocation);
break;
} }
} }
} }

View file

@ -32,11 +32,9 @@ class _EditEntryRatingDialogState extends State<EditEntryRatingDialog> {
case -1: case -1:
_action = _RatingAction.rejected; _action = _RatingAction.rejected;
_rating = 0; _rating = 0;
break;
case 0: case 0:
_action = _RatingAction.unrated; _action = _RatingAction.unrated;
_rating = 0; _rating = 0;
break;
default: default:
_action = _RatingAction.set; _action = _RatingAction.set;
_rating = entryRating; _rating = entryRating;
@ -121,13 +119,10 @@ class _EditEntryRatingDialogState extends State<EditEntryRatingDialog> {
switch (_action) { switch (_action) {
case _RatingAction.set: case _RatingAction.set:
entryRating = _rating; entryRating = _rating;
break;
case _RatingAction.rejected: case _RatingAction.rejected:
entryRating = -1; entryRating = -1;
break;
case _RatingAction.unrated: case _RatingAction.unrated:
entryRating = 0; entryRating = 0;
break;
} }
Navigator.maybeOf(context)?.pop(entryRating); Navigator.maybeOf(context)?.pop(entryRating);
} }

View file

@ -16,7 +16,6 @@ import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
import 'package:aves/widgets/common/thumbnail/decorated.dart'; import 'package:aves/widgets/common/thumbnail/decorated.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class RenameEntrySetPage extends StatefulWidget { class RenameEntrySetPage extends StatefulWidget {
static const routeName = '/rename_entry_set'; static const routeName = '/rename_entry_set';
@ -60,6 +59,8 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final effectiveThumbnailExtent = max(thumbnailExtent, thumbnailExtent * textScaleFactor);
return AvesScaffold( return AvesScaffold(
appBar: AppBar( appBar: AppBar(
title: Text(l10n.renameEntrySetPageTitle), title: Text(l10n.renameEntrySetPageTitle),
@ -121,11 +122,7 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
), ),
), ),
Expanded( Expanded(
child: Selector<MediaQueryData, double>( child: GridTheme(
selector: (context, mq) => mq.textScaleFactor,
builder: (context, textScaleFactor, child) {
final effectiveThumbnailExtent = max(thumbnailExtent, thumbnailExtent * textScaleFactor);
return GridTheme(
extent: effectiveThumbnailExtent, extent: effectiveThumbnailExtent,
child: ListView.separated( child: ListView.separated(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -176,8 +173,7 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
), ),
itemCount: min(entryCount, previewMax), itemCount: min(entryCount, previewMax),
), ),
); ),
}),
), ),
const Divider(height: 0), const Divider(height: 0),
Center( Center(

View file

@ -57,7 +57,10 @@ class _CoverSelectionDialogState extends State<CoverSelectionDialog> {
static const double itemPickerExtent = 46; static const double itemPickerExtent = 46;
static const double appPickerExtent = 32; static const double appPickerExtent = 32;
double tabBarHeight(BuildContext context) => 64 * max(1, MediaQuery.textScaleFactorOf(context)); double tabBarHeight(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
return 64 * max(1, textScaleFactor);
}
static const double tabIndicatorWeight = 2; static const double tabIndicatorWeight = 2;

View file

@ -2,7 +2,6 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pattern_lock/pattern_lock.dart'; import 'package:pattern_lock/pattern_lock.dart';
import 'package:provider/provider.dart';
class PatternDialog extends StatefulWidget { class PatternDialog extends StatefulWidget {
static const routeName = '/dialog/pattern'; static const routeName = '/dialog/pattern';
@ -33,7 +32,7 @@ class _PatternDialogState extends State<PatternDialog> {
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: SizedBox.square( child: SizedBox.square(
dimension: context.select<MediaQueryData, double>((mq) => mq.size.shortestSide / 2), dimension: MediaQuery.sizeOf(context).shortestSide / 2,
child: PatternLock( child: PatternLock(
relativePadding: .4, relativePadding: .4,
selectedColor: colorScheme.secondary, selectedColor: colorScheme.secondary,

Some files were not shown because too many files have changed in this diff Show more