diff --git a/.flutter b/.flutter
index 4d9e56e69..84a1e904f 160000
--- a/.flutter
+++ b/.flutter
@@ -1 +1 @@
-Subproject commit 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
+Subproject commit 84a1e904f44f9b0e9c4510138010edcc653163f8
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34eed96ef..7c35f01c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,12 @@ All notable changes to this project will be documented in this file.
### Added
- option to set the Tags page as home
+- support for animated PNG
### Changed
- remember whether to show the title filter when picking albums
+- upgraded Flutter to stable v3.10.0
## [v1.8.6] - 2023-04-30
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 7f038fe1a..bf19c8257 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
plugins {
id 'com.android.application'
id 'kotlin-android'
@@ -46,7 +48,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace 'deckers.thibault.aves'
- compileSdkVersion 33
+ compileSdk 33
ndkVersion flutter.ndkVersion
compileOptions {
@@ -60,10 +62,6 @@ android {
disable 'InvalidPackage'
}
- kotlinOptions {
- jvmTarget = '1.8'
- }
-
sourceSets {
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 {
source '../..'
}
@@ -195,10 +202,10 @@ repositories {
}
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.core:core-ktx:1.10.0'
+ implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation 'androidx.lifecycle:lifecycle-process:2.6.1'
implementation 'androidx.media:media:1.6.0'
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 09b752faf..eec888a79 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -8,7 +8,6 @@ This change eventually prevents building the app with Flutter v3.7.11.
-->
{
}
@override
- ImageStreamCompleter loadBuffer(AppIconImageKey key, DecoderBufferCallback decode) {
+ ImageStreamCompleter loadImage(AppIconImageKey key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
scale: key.scale,
@@ -37,7 +37,7 @@ class AppIconImage extends ImageProvider {
);
}
- Future _loadAsync(AppIconImageKey key, DecoderBufferCallback decode) async {
+ Future _loadAsync(AppIconImageKey key, ImageDecoderCallback decode) async {
try {
final bytes = await appService.getAppIcon(key.packageName, key.size);
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes.isEmpty ? kTransparentImage : bytes);
diff --git a/lib/image_providers/region_provider.dart b/lib/image_providers/region_provider.dart
index 135215324..dc85ccaa1 100644
--- a/lib/image_providers/region_provider.dart
+++ b/lib/image_providers/region_provider.dart
@@ -18,7 +18,7 @@ class RegionProvider extends ImageProvider {
}
@override
- ImageStreamCompleter loadBuffer(RegionProviderKey key, DecoderBufferCallback decode) {
+ ImageStreamCompleter loadImage(RegionProviderKey key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
scale: 1.0,
@@ -28,7 +28,7 @@ class RegionProvider extends ImageProvider {
);
}
- Future _loadAsync(RegionProviderKey key, DecoderBufferCallback decode) async {
+ Future _loadAsync(RegionProviderKey key, ImageDecoderCallback decode) async {
final uri = key.uri;
final mimeType = key.mimeType;
final pageId = key.pageId;
diff --git a/lib/image_providers/thumbnail_provider.dart b/lib/image_providers/thumbnail_provider.dart
index bf13c32bc..33f05fec4 100644
--- a/lib/image_providers/thumbnail_provider.dart
+++ b/lib/image_providers/thumbnail_provider.dart
@@ -19,7 +19,7 @@ class ThumbnailProvider extends ImageProvider {
}
@override
- ImageStreamCompleter loadBuffer(ThumbnailProviderKey key, DecoderBufferCallback decode) {
+ ImageStreamCompleter loadImage(ThumbnailProviderKey key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
scale: 1.0,
@@ -30,7 +30,7 @@ class ThumbnailProvider extends ImageProvider {
);
}
- Future _loadAsync(ThumbnailProviderKey key, DecoderBufferCallback decode) async {
+ Future _loadAsync(ThumbnailProviderKey key, ImageDecoderCallback decode) async {
final uri = key.uri;
final mimeType = key.mimeType;
final pageId = key.pageId;
diff --git a/lib/image_providers/uri_image_provider.dart b/lib/image_providers/uri_image_provider.dart
index ffcb622f7..38842e742 100644
--- a/lib/image_providers/uri_image_provider.dart
+++ b/lib/image_providers/uri_image_provider.dart
@@ -32,7 +32,7 @@ class UriImage extends ImageProvider with EquatableMixin {
}
@override
- ImageStreamCompleter loadBuffer(UriImage key, DecoderBufferCallback decode) {
+ ImageStreamCompleter loadImage(UriImage key, ImageDecoderCallback decode) {
final chunkEvents = StreamController();
return MultiFrameImageStreamCompleter(
@@ -45,7 +45,7 @@ class UriImage extends ImageProvider with EquatableMixin {
);
}
- Future _loadAsync(UriImage key, DecoderBufferCallback decode, StreamController chunkEvents) async {
+ Future _loadAsync(UriImage key, ImageDecoderCallback decode, StreamController chunkEvents) async {
assert(key == this);
try {
diff --git a/lib/model/db/db_metadata_sqflite_upgrade.dart b/lib/model/db/db_metadata_sqflite_upgrade.dart
index f2840e2ee..ef0b66074 100644
--- a/lib/model/db/db_metadata_sqflite_upgrade.dart
+++ b/lib/model/db/db_metadata_sqflite_upgrade.dart
@@ -21,34 +21,24 @@ class MetadataDbUpgrader {
switch (oldVersion) {
case 1:
await _upgradeFrom1(db);
- break;
case 2:
await _upgradeFrom2(db);
- break;
case 3:
await _upgradeFrom3(db);
- break;
case 4:
await _upgradeFrom4(db);
- break;
case 5:
await _upgradeFrom5(db);
- break;
case 6:
await _upgradeFrom6(db);
- break;
case 7:
await _upgradeFrom7(db);
- break;
case 8:
await _upgradeFrom8(db);
- break;
case 9:
await _upgradeFrom9(db);
- break;
case 10:
await _upgradeFrom10(db);
- break;
}
oldVersion++;
}
diff --git a/lib/model/entry/extensions/catalog.dart b/lib/model/entry/extensions/catalog.dart
index 6a7aa331e..73ee03ac6 100644
--- a/lib/model/entry/extensions/catalog.dart
+++ b/lib/model/entry/extensions/catalog.dart
@@ -39,7 +39,7 @@ extension ExtraAvesEntryCatalog on AvesEntry {
if (isGeotiff && !hasGps) {
final info = await metadataFetchService.getGeoTiffInfo(this);
if (info != null) {
- final center = MappedGeoTiff(
+ final center = GeoTiffCoordinateConverter(
info: info,
entry: this,
).center;
diff --git a/lib/model/entry/extensions/metadata_edition.dart b/lib/model/entry/extensions/metadata_edition.dart
index 9d1f2f7c3..c28c26d8d 100644
--- a/lib/model/entry/extensions/metadata_edition.dart
+++ b/lib/model/entry/extensions/metadata_edition.dart
@@ -52,7 +52,6 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
case DateEditAction.copyItem:
case DateEditAction.extractFromTitle:
editCreateDateXmp(descriptions, appliedModifier.setDateTime);
- break;
case DateEditAction.shift:
final xmpDate = XMP.getString(descriptions, XmpAttributes.xmpCreateDate, namespace: XmpNamespaces.xmp);
if (xmpDate != null) {
@@ -65,10 +64,8 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
reportService.recordError('failed to parse XMP date=$xmpDate', null);
}
}
- break;
case DateEditAction.remove:
editCreateDateXmp(descriptions, null);
- break;
}
return true;
}),
@@ -541,10 +538,8 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
}
}
} on FileSystemException catch (_) {}
- break;
default:
date = await metadataFetchService.getDate(this, source.toMetadataField()!);
- break;
}
}
return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null;
diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart
index 7f5f8aa77..34e4ab626 100644
--- a/lib/model/filters/album.dart
+++ b/lib/model/filters/album.dart
@@ -78,7 +78,6 @@ class AlbumFilter extends CoveredCollectionFilter {
case AlbumType.app:
final appColor = colors.appColor(album);
if (appColor != null) return appColor;
- break;
case AlbumType.camera:
return SynchronousFuture(colors.albumCamera);
case AlbumType.download:
diff --git a/lib/model/filters/aspect_ratio.dart b/lib/model/filters/aspect_ratio.dart
index 92ee97279..f21f48280 100644
--- a/lib/model/filters/aspect_ratio.dart
+++ b/lib/model/filters/aspect_ratio.dart
@@ -21,13 +21,10 @@ class AspectRatioFilter extends CollectionFilter {
switch (op) {
case QueryFilter.opEqual:
_test = (entry) => entry.displayAspectRatio == threshold;
- break;
case QueryFilter.opLower:
_test = (entry) => entry.displayAspectRatio < threshold;
- break;
case QueryFilter.opGreater:
_test = (entry) => entry.displayAspectRatio > threshold;
- break;
}
}
diff --git a/lib/model/filters/date.dart b/lib/model/filters/date.dart
index 3c82ae15c..4749fcd4d 100644
--- a/lib/model/filters/date.dart
+++ b/lib/model/filters/date.dart
@@ -25,13 +25,10 @@ class DateFilter extends CollectionFilter {
switch (level) {
case DateLevel.y:
_test = (entry) => entry.bestDate?.isAtSameYearAs(_effectiveDate) ?? false;
- break;
case DateLevel.ym:
_test = (entry) => entry.bestDate?.isAtSameMonthAs(_effectiveDate) ?? false;
- break;
case DateLevel.ymd:
_test = (entry) => entry.bestDate?.isAtSameDayAs(_effectiveDate) ?? false;
- break;
case DateLevel.md:
final month = _effectiveDate.month;
final day = _effectiveDate.day;
@@ -39,15 +36,12 @@ class DateFilter extends CollectionFilter {
final bestDate = entry.bestDate;
return bestDate != null && bestDate.month == month && bestDate.day == day;
};
- break;
case DateLevel.m:
final month = _effectiveDate.month;
_test = (entry) => entry.bestDate?.month == month;
- break;
case DateLevel.d:
final day = _effectiveDate.day;
_test = (entry) => entry.bestDate?.day == day;
- break;
}
}
diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart
index 0c547defd..3a22391b5 100644
--- a/lib/model/filters/location.dart
+++ b/lib/model/filters/location.dart
@@ -29,13 +29,10 @@ class LocationFilter extends CoveredCollectionFilter {
switch (level) {
case LocationLevel.country:
_test = (entry) => entry.addressDetails?.countryCode == _code;
- break;
case LocationLevel.state:
_test = (entry) => entry.addressDetails?.stateCode == _code;
- break;
case LocationLevel.place:
_test = (entry) => entry.addressDetails?.place == _location;
- break;
}
}
}
@@ -57,7 +54,6 @@ class LocationFilter extends CoveredCollectionFilter {
if (_code != null) {
location = _nameAndCode;
}
- break;
case LocationLevel.place:
break;
}
diff --git a/lib/model/filters/missing.dart b/lib/model/filters/missing.dart
index 6060d3f36..9128d1eb1 100644
--- a/lib/model/filters/missing.dart
+++ b/lib/model/filters/missing.dart
@@ -26,15 +26,12 @@ class MissingFilter extends CollectionFilter {
case _date:
_test = (entry) => (entry.catalogMetadata?.dateMillis ?? 0) == 0;
_icon = AIcons.dateUndated;
- break;
case _fineAddress:
_test = (entry) => entry.hasGps && !entry.hasFineAddress;
_icon = AIcons.locationUnlocated;
- break;
case _title:
_test = (entry) => (entry.catalogMetadata?.xmpTitle ?? '').isEmpty;
_icon = AIcons.descriptionUntitled;
- break;
}
}
diff --git a/lib/model/filters/placeholder.dart b/lib/model/filters/placeholder.dart
index 32f22b1f4..bbeab3bed 100644
--- a/lib/model/filters/placeholder.dart
+++ b/lib/model/filters/placeholder.dart
@@ -28,13 +28,10 @@ class PlaceholderFilter extends CollectionFilter {
switch (placeholder) {
case _country:
_icon = AIcons.country;
- break;
case _state:
_icon = AIcons.state;
- break;
case _place:
_icon = AIcons.place;
- break;
}
}
@@ -74,7 +71,6 @@ class PlaceholderFilter extends CollectionFilter {
case _place:
return address.place;
}
- break;
}
return null;
}
diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart
index 523a1b156..2c1689551 100644
--- a/lib/model/filters/query.dart
+++ b/lib/model/filters/query.dart
@@ -117,7 +117,6 @@ class QueryFilter extends CollectionFilter {
if (op == opEqual) {
return (entry) => entry.contentId == valueInt;
}
- break;
case keyContentYear:
if (valueInt == null) return null;
switch (op) {
@@ -128,7 +127,6 @@ class QueryFilter extends CollectionFilter {
case opGreater:
return (entry) => (entry.bestDate?.year ?? 0) > valueInt;
}
- break;
case keyContentMonth:
if (valueInt == null) return null;
switch (op) {
@@ -139,7 +137,6 @@ class QueryFilter extends CollectionFilter {
case opGreater:
return (entry) => (entry.bestDate?.month ?? 0) > valueInt;
}
- break;
case keyContentDay:
if (valueInt == null) return null;
switch (op) {
@@ -150,7 +147,6 @@ class QueryFilter extends CollectionFilter {
case opGreater:
return (entry) => (entry.bestDate?.day ?? 0) > valueInt;
}
- break;
case keyContentWidth:
if (valueInt == null) return null;
switch (op) {
@@ -161,7 +157,6 @@ class QueryFilter extends CollectionFilter {
case opGreater:
return (entry) => entry.displaySize.width > valueInt;
}
- break;
case keyContentHeight:
if (valueInt == null) return null;
switch (op) {
@@ -172,7 +167,6 @@ class QueryFilter extends CollectionFilter {
case opGreater:
return (entry) => entry.displaySize.height > valueInt;
}
- break;
case keyContentSize:
match = _fileSizePattern.firstMatch(valueString);
if (match == null) return null;
@@ -187,13 +181,10 @@ class QueryFilter extends CollectionFilter {
switch (multiplierString) {
case 'K':
bytes *= kilo;
- break;
case 'M':
bytes *= mega;
- break;
case 'G':
bytes *= giga;
- break;
}
switch (op) {
@@ -204,7 +195,6 @@ class QueryFilter extends CollectionFilter {
case opGreater:
return (entry) => (entry.sizeBytes ?? 0) > bytes;
}
- break;
}
return null;
diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart
index 9eafa93b3..b94dcb6ac 100644
--- a/lib/model/filters/type.dart
+++ b/lib/model/filters/type.dart
@@ -37,27 +37,21 @@ class TypeFilter extends CollectionFilter {
case _animated:
_test = (entry) => entry.isAnimated;
_icon = AIcons.animated;
- break;
case _geotiff:
_test = (entry) => entry.isGeotiff;
_icon = AIcons.geo;
- break;
case _motionPhoto:
_test = (entry) => entry.isMotionPhoto;
_icon = AIcons.motionPhoto;
- break;
case _panorama:
_test = (entry) => entry.isImage && entry.is360;
_icon = AIcons.panorama;
- break;
case _raw:
_test = (entry) => entry.isRaw;
_icon = AIcons.raw;
- break;
case _sphericalVideo:
_test = (entry) => entry.isVideo && entry.is360;
_icon = AIcons.sphericalVideo;
- break;
}
}
diff --git a/lib/model/geotiff.dart b/lib/model/geotiff.dart
index efa6afb46..d549cd29d 100644
--- a/lib/model/geotiff.dart
+++ b/lib/model/geotiff.dart
@@ -8,8 +8,7 @@ import 'package:aves/ref/metadata/geotiff.dart';
import 'package:aves/utils/math_utils.dart';
import 'package:aves_map/aves_map.dart';
import 'package:equatable/equatable.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/painting.dart';
+import 'package:flutter/widgets.dart';
import 'package:latlong2/latlong.dart';
import 'package:proj4dart/proj4dart.dart' as proj4;
@@ -42,19 +41,145 @@ class GeoTiffInfo extends Equatable {
class MappedGeoTiff with MapOverlay {
final AvesEntry entry;
- late LatLng? Function(Point pixel) pointToLatLng;
- late Point? Function(Point smPoint) epsg3857ToPoint;
- static final mapServiceTileSize = (256 * ui.window.devicePixelRatio).round();
- static final mapServiceHelper = MapServiceHelper(mapServiceTileSize);
- static final tileImagePaint = Paint();
- static final tileMissingPaint = Paint()
+ late final GeoTiffCoordinateConverter _converter;
+ late final int _mapServiceTileSize;
+ late final MapServiceHelper _mapServiceHelper;
+
+ static final _tileImagePaint = Paint();
+ static final _tileMissingPaint = Paint()
..style = PaintingStyle.fill
..color = const Color(0xFF000000);
MappedGeoTiff({
required GeoTiffInfo info,
required this.entry,
+ required double devicePixelRatio,
+ }) {
+ _converter = GeoTiffCoordinateConverter(info: info, entry: entry);
+ _mapServiceTileSize = (256 * devicePixelRatio).round();
+ _mapServiceHelper = MapServiceHelper(_mapServiceTileSize);
+ }
+
+ @override
+ Future 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(1, highestPowerOf2(tileXScale));
+ final region = entry.getRegion(
+ sampleSize: sampleSize,
+ region: Rectangle(regionLeft, regionTop, regionWidth, regionHeight),
+ );
+
+ final imageInfoCompleter = Completer();
+ 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 pixel) pointToLatLng;
+ late Point? Function(Point smPoint) epsg3857ToPoint;
+
+ GeoTiffCoordinateConverter({
+ required GeoTiffInfo info,
+ required this.entry,
}) {
pointToLatLng = (_) => null;
epsg3857ToPoint = (_) => null;
@@ -129,113 +254,13 @@ class MappedGeoTiff with MapOverlay {
};
}
- @override
- Future 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(1, highestPowerOf2(tileXScale));
- final region = entry.getRegion(
- sampleSize: sampleSize,
- region: Rectangle(regionLeft, regionTop, regionWidth, regionHeight),
- );
-
- final imageInfoCompleter = Completer();
- 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 height => entry.height;
- @override
- bool get canOverlay => center != null;
-
LatLng? get center => pointToLatLng(Point((width / 2).round(), (height / 2).round()));
- @override
LatLng? get topLeft => pointToLatLng(const Point(0, 0));
- @override
LatLng? get bottomRight => pointToLatLng(Point(width, height));
}
diff --git a/lib/model/naming_pattern.dart b/lib/model/naming_pattern.dart
index 2fd502291..e5eee02d4 100644
--- a/lib/model/naming_pattern.dart
+++ b/lib/model/naming_pattern.dart
@@ -38,10 +38,8 @@ class NamingPattern {
if (processorOptions != null) {
processors.add(DateNamingProcessor(processorOptions.trim()));
}
- break;
case NameNamingProcessor.key:
processors.add(const NameNamingProcessor());
- break;
case CounterNamingProcessor.key:
int? start, padding;
_applyProcessorOptions(processorOptions, (key, value) {
@@ -50,18 +48,14 @@ class NamingPattern {
switch (key) {
case CounterNamingProcessor.optionStart:
start = valueInt;
- break;
case CounterNamingProcessor.optionPadding:
padding = valueInt;
- break;
}
}
});
processors.add(CounterNamingProcessor(start: start ?? defaultCounterStart, padding: padding ?? defaultCounterPadding));
- break;
default:
debugPrint('unsupported naming processor: ${match.group(0)}');
- break;
}
index = end;
});
diff --git a/lib/model/settings/enums/display_refresh_rate_mode.dart b/lib/model/settings/enums/display_refresh_rate_mode.dart
index 942d901b9..32d1cd28b 100644
--- a/lib/model/settings/enums/display_refresh_rate_mode.dart
+++ b/lib/model/settings/enums/display_refresh_rate_mode.dart
@@ -15,13 +15,10 @@ extension ExtraDisplayRefreshRateMode on DisplayRefreshRateMode {
switch (this) {
case DisplayRefreshRateMode.auto:
await FlutterDisplayMode.setPreferredMode(DisplayMode.auto);
- break;
case DisplayRefreshRateMode.highest:
await FlutterDisplayMode.setHighRefreshRate();
- break;
case DisplayRefreshRateMode.lowest:
await FlutterDisplayMode.setLowRefreshRate();
- break;
}
}
}
diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart
index b9a0ea3c8..966b545db 100644
--- a/lib/model/settings/settings.dart
+++ b/lib/model/settings/settings.dart
@@ -375,7 +375,7 @@ class Settings extends ChangeNotifier {
if (_locale != null) {
preferredLocales.add(_locale);
} else {
- preferredLocales.addAll(WidgetsBinding.instance.window.locales);
+ preferredLocales.addAll(WidgetsBinding.instance.platformDispatcher.locales);
if (preferredLocales.isEmpty) {
// the `window` locales may be empty in a window-less service context
preferredLocales.addAll(_systemLocalesFallback);
@@ -1022,7 +1022,6 @@ class Settings extends ChangeNotifier {
if (value is num) {
isRotationLocked = value == 0;
}
- break;
case platformTransitionAnimationScaleKey:
if (value is num) {
areAnimationsRemoved = value == 0;
@@ -1080,7 +1079,6 @@ class Settings extends ChangeNotifier {
} else {
debugPrint('failed to import key=$key, value=$newValue is not an int');
}
- break;
case subtitleFontSizeKey:
case infoMapZoomKey:
if (newValue is double) {
@@ -1088,7 +1086,6 @@ class Settings extends ChangeNotifier {
} else {
debugPrint('failed to import key=$key, value=$newValue is not a double');
}
- break;
case isInstalledAppAccessAllowedKey:
case isErrorReportingAllowedKey:
case enableDynamicColorKey:
@@ -1144,7 +1141,6 @@ class Settings extends ChangeNotifier {
} else {
debugPrint('failed to import key=$key, value=$newValue is not a bool');
}
- break;
case localeKey:
case displayRefreshRateModeKey:
case themeBrightnessKey:
@@ -1187,7 +1183,6 @@ class Settings extends ChangeNotifier {
} else {
debugPrint('failed to import key=$key, value=$newValue is not a string');
}
- break;
case drawerTypeBookmarksKey:
case drawerAlbumBookmarksKey:
case drawerPageBookmarksKey:
@@ -1203,7 +1198,6 @@ class Settings extends ChangeNotifier {
} else {
debugPrint('failed to import key=$key, value=$newValue is not a list');
}
- break;
}
}
if (oldValue != newValue) {
diff --git a/lib/model/source/album.dart b/lib/model/source/album.dart
index e4524418e..809086b97 100644
--- a/lib/model/source/album.dart
+++ b/lib/model/source/album.dart
@@ -47,13 +47,10 @@ mixin AlbumMixin on SourceBase {
switch (androidFileUtils.getAlbumType(album)) {
case AlbumType.regular:
regularAlbums.add(album);
- break;
case AlbumType.app:
appAlbums.add(album);
- break;
default:
specialAlbums.add(album);
- break;
}
}
return Map.fromEntries([...specialAlbums, ...appAlbums, ...regularAlbums].map((album) => MapEntry(
diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart
index 44b37f548..dfc922a3b 100644
--- a/lib/model/source/collection_lens.dart
+++ b/lib/model/source/collection_lens.dart
@@ -68,10 +68,8 @@ class CollectionLens with ChangeNotifier {
case MoveType.move:
case MoveType.fromBin:
refresh();
- break;
case MoveType.toBin:
_onEntryRemoved(e.entries);
- break;
}
}));
_subscriptions.add(sourceEvents.on().listen((e) => refresh()));
@@ -213,16 +211,12 @@ class CollectionLens with ChangeNotifier {
switch (sortFactor) {
case EntrySortFactor.date:
_filteredSortedEntries.sort(AvesEntrySort.compareByDate);
- break;
case EntrySortFactor.name:
_filteredSortedEntries.sort(AvesEntrySort.compareByName);
- break;
case EntrySortFactor.rating:
_filteredSortedEntries.sort(AvesEntrySort.compareByRating);
- break;
case EntrySortFactor.size:
_filteredSortedEntries.sort(AvesEntrySort.compareBySize);
- break;
}
if (sortReverse) {
_filteredSortedEntries = _filteredSortedEntries.reversed.toList();
@@ -240,33 +234,25 @@ class CollectionLens with ChangeNotifier {
switch (sectionFactor) {
case EntryGroupFactor.album:
sections = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
- break;
case EntryGroupFactor.month:
sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken));
- break;
case EntryGroupFactor.day:
sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken));
- break;
case EntryGroupFactor.none:
sections = Map.fromEntries([
MapEntry(const SectionKey(), _filteredSortedEntries),
]);
- break;
}
- break;
case EntrySortFactor.name:
final byAlbum = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
final compare = sortReverse ? (a, b) => source.compareAlbumsByName(b.directory!, a.directory!) : (a, b) => source.compareAlbumsByName(a.directory!, b.directory!);
sections = SplayTreeMap>.of(byAlbum, compare);
- break;
case EntrySortFactor.rating:
sections = groupBy(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
- break;
case EntrySortFactor.size:
sections = Map.fromEntries([
MapEntry(const SectionKey(), _filteredSortedEntries),
]);
- break;
}
}
sections = Map.unmodifiable(sections);
diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart
index ec7d49304..5464e6774 100644
--- a/lib/model/source/collection_source.dart
+++ b/lib/model/source/collection_source.dart
@@ -221,18 +221,14 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
switch (key) {
case 'contentId':
entry.contentId = newValue as int?;
- break;
case 'dateModifiedSecs':
// `dateModifiedSecs` changes when moving entries to another directory,
// but it does not change when renaming the containing directory
entry.dateModifiedSecs = newValue as int?;
- break;
case 'path':
entry.path = newValue as String?;
- break;
case 'title':
entry.sourceTitle = newValue as String?;
- break;
case 'trashed':
final trashed = newValue as bool;
entry.trashed = trashed;
@@ -243,13 +239,10 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
dateMillis: DateTime.now().millisecondsSinceEpoch,
)
: null;
- break;
case 'uri':
entry.uri = newValue as String;
- break;
case 'origin':
entry.origin = newValue as int;
- break;
}
});
if (entry.trashed) {
@@ -371,16 +364,13 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
switch (moveType) {
case MoveType.copy:
addEntries(movedEntries);
- break;
case MoveType.move:
case MoveType.export:
cleanEmptyAlbums(fromAlbums.whereNotNull().toSet());
addDirectories(albums: destinationAlbums);
- break;
case MoveType.toBin:
case MoveType.fromBin:
updateDerivedFilters(movedEntries);
- break;
}
invalidateAlbumFilterSummary(directories: fromAlbums);
_invalidate(entries: movedEntries);
diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart
index eab4c5031..25593eb39 100644
--- a/lib/model/video/metadata.dart
+++ b/lib/model/video/metadata.dart
@@ -210,38 +210,29 @@ class VideoMetadataFormatter {
case Keys.androidCaptureFramerate:
final captureFps = double.parse(value);
save('Capture Frame Rate', '${roundToPrecision(captureFps, decimals: 3).toString()} FPS');
- break;
case Keys.androidManufacturer:
save('Android Manufacturer', value);
- break;
case Keys.androidModel:
save('Android Model', value);
- break;
case Keys.androidVersion:
save('Android Version', value);
- break;
case Keys.bitrate:
case Keys.bps:
save('Bit Rate', _formatMetric(value, 'b/s'));
- break;
case Keys.byteCount:
save('Size', _formatFilesize(value));
- break;
case Keys.channelLayout:
save('Channel Layout', _formatChannelLayout(value));
- break;
case Keys.codecName:
if (value != 'none') {
save('Format', _formatCodecName(value));
}
- break;
case Keys.codecPixelFormat:
if (streamType == MediaStreamTypes.video) {
// this is just a short name used by FFmpeg
// user-friendly descriptions for related enums are defined in libavutil/pixfmt.h
save('Pixel Format', (value as String).toUpperCase());
}
- break;
case Keys.codecProfileId:
{
final profile = int.tryParse(value);
@@ -260,18 +251,14 @@ class VideoMetadataFormatter {
profileString = Hevc.formatProfile(profile, level);
}
}
- break;
}
case Codecs.aac:
profileString = AAC.formatProfile(profile);
- break;
default:
profileString = profile.toString();
- break;
}
save('Format Profile', profileString);
}
- break;
}
case Keys.compatibleBrands:
final formattedBrands = RegExp(r'.{4}').allMatches(value).map((m) {
@@ -279,52 +266,37 @@ class VideoMetadataFormatter {
return _formatBrand(brand);
}).join(', ');
save('Compatible Brands', formattedBrands);
- break;
case Keys.creationTime:
save('Creation Time', _formatDate(value));
- break;
case Keys.date:
if (value is String && value != '0') {
final charCount = value.length;
save(charCount == 4 ? 'Year' : 'Date', value);
}
- break;
case Keys.duration:
save('Duration', _formatDuration(value));
- break;
case Keys.durationMicros:
if (value != 0) save('Duration', formatPreciseDuration(Duration(microseconds: value)));
- break;
case Keys.fpsDen:
save('Frame Rate', '${roundToPrecision(info[Keys.fpsNum] / info[Keys.fpsDen], decimals: 3).toString()} FPS');
- break;
case Keys.frameCount:
save('Frame Count', value);
- break;
case Keys.height:
save('Height', '$value pixels');
- break;
case Keys.language:
if (value != 'und') save('Language', _formatLanguage(value));
- break;
case Keys.location:
save('Location', _formatLocation(value));
- break;
case Keys.majorBrand:
save('Major Brand', _formatBrand(value));
- break;
case Keys.mediaFormat:
save('Format', (value as String).splitMapJoin(',', onMatch: (s) => ', ', onNonMatch: _formatCodecName));
- break;
case Keys.mediaType:
save('Media Type', value);
- break;
case Keys.minorVersion:
if (value != '0') save('Minor Version', value);
- break;
case Keys.quicktimeLocationAccuracyHorizontal:
save('QuickTime Location Horizontal Accuracy', value);
- break;
case Keys.quicktimeCreationDate:
case Keys.quicktimeLocationIso6709:
case Keys.quicktimeMake:
@@ -334,37 +306,27 @@ class VideoMetadataFormatter {
break;
case Keys.rotate:
save('Rotation', '$value°');
- break;
case Keys.sampleRate:
save('Sample Rate', _formatMetric(value, 'Hz'));
- break;
case Keys.sarDen:
final sarNum = info[Keys.sarNum];
final sarDen = info[Keys.sarDen];
// skip common square pixels (1:1)
if (sarNum != sarDen) save('SAR', '$sarNum:$sarDen');
- break;
case Keys.sourceOshash:
save('Source OSHash', value);
- break;
case Keys.startMicros:
if (value != 0) save('Start', formatPreciseDuration(Duration(microseconds: value)));
- break;
case Keys.statisticsWritingApp:
save('Stats Writing App', value);
- break;
case Keys.statisticsWritingDateUtc:
save('Stats Writing Date', _formatDate(value));
- break;
case Keys.track:
if (value != '0') save('Track', value);
- break;
case Keys.width:
save('Width', '$value pixels');
- break;
case Keys.xiaomiSlowMoment:
save('Xiaomi Slow Moment', value);
- break;
default:
save(key.toSentenceCase(), value.toString());
}
diff --git a/lib/model/video/profiles/h264.dart b/lib/model/video/profiles/h264.dart
index c9fe1dae6..5fb99ace3 100644
--- a/lib/model/video/profiles/h264.dart
+++ b/lib/model/video/profiles/h264.dart
@@ -28,43 +28,30 @@ class H264 {
switch (profileIndex) {
case profileBaseline:
profile = 'Baseline';
- break;
case profileConstrainedBaseline:
profile = 'Constrained Baseline';
- break;
case profileMain:
profile = 'Main';
- break;
case profileExtended:
profile = 'Extended';
- break;
case profileHigh:
profile = 'High';
- break;
case profileHigh10:
profile = 'High 10';
- break;
case profileHigh10Intra:
profile = 'High 10 Intra';
- break;
case profileHigh422:
profile = 'High 4:2:2';
- break;
case profileHigh422Intra:
profile = 'High 4:2:2 Intra';
- break;
case profileHigh444:
profile = 'High 4:4:4';
- break;
case profileHigh444Predictive:
profile = 'High 4:4:4 Predictive';
- break;
case profileHigh444Intra:
profile = 'High 4:4:4 Intra';
- break;
case profileCAVLC444:
profile = 'CAVLC 4:4:4';
- break;
default:
return '$profileIndex';
}
diff --git a/lib/model/video/profiles/hevc.dart b/lib/model/video/profiles/hevc.dart
index 4678fa699..880982eef 100644
--- a/lib/model/video/profiles/hevc.dart
+++ b/lib/model/video/profiles/hevc.dart
@@ -9,16 +9,12 @@ class Hevc {
switch (profileIndex) {
case profileMain:
profile = 'Main';
- break;
case profileMain10:
profile = 'Main 10';
- break;
case profileMainStillPicture:
profile = 'Main Still Picture';
- break;
case profileRExt:
profile = 'Format Range';
- break;
default:
return '$profileIndex';
}
diff --git a/lib/ref/poi.dart b/lib/ref/poi.dart
index 1aebe9aae..951239d39 100644
--- a/lib/ref/poi.dart
+++ b/lib/ref/poi.dart
@@ -12,4 +12,4 @@ class PointsOfInterest {
LatLng(37.637861, 21.63),
LatLng(37.949722, 27.363889),
];
-}
\ No newline at end of file
+}
diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart
index 50fea1499..9c656db62 100644
--- a/lib/services/analysis_service.dart
+++ b/lib/services/analysis_service.dart
@@ -145,11 +145,9 @@ class Analyzer {
case AnalyzerState.stopping:
await _stopPlatformService();
_serviceStateNotifier.value = AnalyzerState.stopped;
- break;
case AnalyzerState.stopped:
_controller?.stopSignal.value = true;
_stopUpdateTimer();
- break;
}
}
diff --git a/lib/services/media/media_session_service.dart b/lib/services/media/media_session_service.dart
index 8280965c7..2d287f910 100644
--- a/lib/services/media/media_session_service.dart
+++ b/lib/services/media/media_session_service.dart
@@ -96,25 +96,19 @@ class PlatformMediaSessionService implements MediaSessionService, Disposable {
switch (command) {
case 'play':
event = const MediaCommandEvent(MediaCommand.play);
- break;
case 'pause':
event = const MediaCommandEvent(MediaCommand.pause);
- break;
case 'skip_to_next':
event = const MediaCommandEvent(MediaCommand.skipToNext);
- break;
case 'skip_to_previous':
event = const MediaCommandEvent(MediaCommand.skipToPrevious);
- break;
case 'stop':
event = const MediaCommandEvent(MediaCommand.stop);
- break;
case 'seek':
final position = fields['position'] as int?;
if (position != null) {
event = MediaSeekCommandEvent(MediaCommand.stop, position: position);
}
- break;
}
if (event != null) {
_streamController.add(event);
diff --git a/lib/services/metadata/svg_metadata_service.dart b/lib/services/metadata/svg_metadata_service.dart
index 677bb97dc..0567b1908 100644
--- a/lib/services/metadata/svg_metadata_service.dart
+++ b/lib/services/metadata/svg_metadata_service.dart
@@ -80,7 +80,7 @@ class SvgMetadataService {
final docDir = Map.fromEntries([
...root.attributes.where((a) => _attributes.contains(a.name.qualified)).map((a) => MapEntry(formatKey(a.name.qualified), a.value)),
..._textElements.map((name) {
- final value = root.getElement(name)?.text;
+ final value = root.getElement(name)?.innerText;
return value != null ? MapEntry(formatKey(name), value) : null;
}).whereNotNull(),
]);
diff --git a/lib/services/window_service.dart b/lib/services/window_service.dart
index 81796bff8..44f9a9d73 100644
--- a/lib/services/window_service.dart
+++ b/lib/services/window_service.dart
@@ -74,15 +74,12 @@ class PlatformWindowService implements WindowService {
case Orientation.landscape:
// SCREEN_ORIENTATION_SENSOR_LANDSCAPE
orientationCode = 6;
- break;
case Orientation.portrait:
// SCREEN_ORIENTATION_SENSOR_PORTRAIT
orientationCode = 7;
- break;
default:
// SCREEN_ORIENTATION_UNSPECIFIED
orientationCode = -1;
- break;
}
try {
await _platform.invokeMethod('requestOrientation', {
diff --git a/lib/theme/text.dart b/lib/theme/text.dart
index b7840d33f..27501eb06 100644
--- a/lib/theme/text.dart
+++ b/lib/theme/text.dart
@@ -4,4 +4,4 @@ class AText {
static const separator = ' ${UniChars.bullet} ';
static const resolutionSeparator = ' ${UniChars.multiplicationSign} ';
static const valueNotAvailable = UniChars.emDash;
-}
\ No newline at end of file
+}
diff --git a/lib/utils/diff_match.dart b/lib/utils/diff_match.dart
index 29042c4de..4c4a7b9ef 100644
--- a/lib/utils/diff_match.dart
+++ b/lib/utils/diff_match.dart
@@ -274,11 +274,9 @@ class DiffMatchPatch {
case Operation.insert:
count_insert++;
text_insert.write(diffs[pointer].text);
- break;
case Operation.delete:
count_delete++;
text_delete.write(diffs[pointer].text);
- break;
case Operation.equal:
// Upon reaching an equality, check for prior redundancies.
if (count_delete >= 1 && count_insert >= 1) {
@@ -295,7 +293,6 @@ class DiffMatchPatch {
count_delete = 0;
text_delete.clear();
text_insert.clear();
- break;
}
pointer++;
}
@@ -1013,12 +1010,10 @@ class DiffMatchPatch {
count_insert++;
text_insert += diffs[pointer].text;
pointer++;
- break;
case Operation.delete:
count_delete++;
text_delete += diffs[pointer].text;
pointer++;
- break;
case Operation.equal:
// Upon reaching an equality, check for prior redundancies.
if (count_delete + count_insert > 1) {
@@ -1068,7 +1063,6 @@ class DiffMatchPatch {
count_delete = 0;
text_delete = '';
text_insert = '';
- break;
}
}
if (diffs.last.text.isEmpty) {
@@ -1155,17 +1149,14 @@ class DiffMatchPatch {
html.write('');
html.write(text);
html.write('');
- break;
case Operation.delete:
html.write('');
html.write(text);
html.write('');
- break;
case Operation.equal:
html.write('');
html.write(text);
html.write('');
- break;
}
}
return html.toString();
@@ -1209,16 +1200,13 @@ class DiffMatchPatch {
switch (aDiff.operation) {
case Operation.insert:
insertions += aDiff.text.length;
- break;
case Operation.delete:
deletions += aDiff.text.length;
- break;
case Operation.equal:
// A deletion and an insertion is one substitution.
levenshtein += max(insertions, deletions);
insertions = 0;
deletions = 0;
- break;
}
}
levenshtein += max(insertions, deletions);
@@ -1239,17 +1227,14 @@ class DiffMatchPatch {
text.write('+');
text.write(Uri.encodeFull(aDiff.text));
text.write('\t');
- break;
case Operation.delete:
text.write('-');
text.write(aDiff.text.length);
text.write('\t');
- break;
case Operation.equal:
text.write('=');
text.write(aDiff.text.length);
text.write('\t');
- break;
}
}
String delta = text.toString();
@@ -1289,7 +1274,6 @@ class DiffMatchPatch {
throw ArgumentError('Illegal escape in diff_fromDelta: $param');
}
diffs.add(Diff(Operation.insert, param));
- break;
case '-':
// Fall through.
case '=':
@@ -1314,7 +1298,6 @@ class DiffMatchPatch {
} else {
diffs.add(Diff(Operation.delete, text));
}
- break;
default:
// Anything else is an error.
throw ArgumentError('Invalid diff operation in diff_fromDelta: ${token[0]}');
diff --git a/lib/utils/xmp_utils.dart b/lib/utils/xmp_utils.dart
index c46274fa7..0953bac31 100644
--- a/lib/utils/xmp_utils.dart
+++ b/lib/utils/xmp_utils.dart
@@ -26,7 +26,7 @@ class XMP {
static const nsXmp = XmpNamespaces.xmp;
// 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
static bool _hasMeaningfulAttributes(XmlNode description) {
diff --git a/lib/widget_common.dart b/lib/widget_common.dart
index fe14fee12..4ea33f334 100644
--- a/lib/widget_common.dart
+++ b/lib/widget_common.dart
@@ -79,10 +79,8 @@ Future _getWidgetEntry(int widgetId, bool reuseEntry) async {
switch (settings.getWidgetDisplayedItem(widgetId)) {
case WidgetDisplayedItem.random:
entries.shuffle();
- break;
case WidgetDisplayedItem.mostRecent:
entries.sort(AvesEntrySort.compareByDate);
- break;
}
final entry = entries.firstOrNull;
if (entry != null) {
diff --git a/lib/widgets/about/app_ref.dart b/lib/widgets/about/app_ref.dart
index 83c6000d0..af20e6d55 100644
--- a/lib/widgets/about/app_ref.dart
+++ b/lib/widgets/about/app_ref.dart
@@ -94,11 +94,12 @@ class _AppReferenceState extends State {
return FutureBuilder(
future: _packageInfoLoader,
builder: (context, snapshot) {
+ final textScaleFactor = MediaQuery.textScaleFactorOf(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
AvesLogo(
- size: _appTitleStyle.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
+ size: _appTitleStyle.fontSize! * textScaleFactor * 1.3,
),
const SizedBox(width: 8),
Text(
diff --git a/lib/widgets/about/bug_report.dart b/lib/widgets/about/bug_report.dart
index 954146ea2..6cb94eb44 100644
--- a/lib/widgets/about/bug_report.dart
+++ b/lib/widgets/about/bug_report.dart
@@ -157,7 +157,7 @@ class _BugReportState extends State with FeedbackMixin {
'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
'Geocoder: ${device.hasGeocoder ? '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 grants: ${storageGrants.join(', ')}',
'Error reporting: ${settings.isErrorReportingAllowed}',
diff --git a/lib/widgets/about/tv_license_page.dart b/lib/widgets/about/tv_license_page.dart
index fa4e2b7e2..ef229b3e9 100644
--- a/lib/widgets/about/tv_license_page.dart
+++ b/lib/widgets/about/tv_license_page.dart
@@ -1,7 +1,9 @@
+import 'dart:developer' show Flow, Timeline;
+
import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/behaviour/intents.dart';
import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
+import 'package:flutter/material.dart' hide Flow;
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
@@ -209,10 +211,21 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
bool _loaded = false;
Future _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) {
if (!mounted) {
return;
}
+ assert(() {
+ Timeline.timeSync('_initLicenses()', () {}, flow: Flow.step(debugFlowId));
+ return true;
+ }());
final List paragraphs = await SchedulerBinding.instance.scheduleTask>(
license.paragraphs.toList,
Priority.animation,
@@ -237,6 +250,7 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
),
));
} else {
+ assert(paragraph.indent >= 0);
_licenses.add(Padding(
padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
child: Text(paragraph.text),
@@ -248,16 +262,21 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
setState(() {
_loaded = true;
});
+ assert(() {
+ Timeline.timeSync('Build scheduled', () {}, flow: Flow.end(debugFlowId));
+ return true;
+ }());
}
@override
Widget build(BuildContext context) {
+ assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final ThemeData theme = Theme.of(context);
final String title = widget.packageName;
final String subtitle = localizations.licensesPackageDetailText(widget.licenseEntries.length);
- const double pad = 24;
- const EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad);
+ final double pad = _getGutterSize(context);
+ final EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad);
final List listWidgets = [
..._licenses,
if (!_loaded)
@@ -274,9 +293,11 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
page = Scaffold(
appBar: AppBar(
title: _PackageLicensePageTitle(
- title,
- subtitle,
- theme.primaryTextTheme,
+ title: title,
+ subtitle: subtitle,
+ theme: theme.useMaterial3 ? theme.textTheme : theme.primaryTextTheme,
+ titleTextStyle: theme.appBarTheme.titleTextStyle,
+ foregroundColor: theme.appBarTheme.foregroundColor,
),
),
body: Center(
@@ -292,7 +313,11 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
// A Scrollbar is built-in below.
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
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,
pinned: true,
backgroundColor: theme.cardColor,
- title: _PackageLicensePageTitle(title, subtitle, theme.textTheme),
+ title: _PackageLicensePageTitle(
+ title: title,
+ subtitle: subtitle,
+ theme: theme.textTheme,
+ titleTextStyle: theme.textTheme.titleLarge,
+ ),
),
SliverPadding(
padding: padding,
@@ -334,27 +364,36 @@ class _PackageLicensePageState extends State<_PackageLicensePage> {
}
class _PackageLicensePageTitle extends StatelessWidget {
- const _PackageLicensePageTitle(
- this.title,
- this.subtitle,
- this.theme,
- );
+ const _PackageLicensePageTitle({
+ required this.title,
+ required this.subtitle,
+ required this.theme,
+ this.titleTextStyle,
+ this.foregroundColor,
+ });
final String title;
final String subtitle;
final TextTheme theme;
+ final TextStyle? titleTextStyle;
+ final Color? foregroundColor;
@override
Widget build(BuildContext context) {
- final Color? color = Theme.of(context).appBarTheme.foregroundColor;
-
+ final TextStyle? effectiveTitleTextStyle = titleTextStyle ?? theme.titleLarge;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(title, style: theme.titleLarge?.copyWith(color: color)),
- Text(subtitle, style: theme.titleSmall?.copyWith(color: color)),
+ Text(title, style: effectiveTitleTextStyle?.copyWith(color: foregroundColor)),
+ 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;
diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart
index 01ef95154..d34dfd3a4 100644
--- a/lib/widgets/aves_app.dart
+++ b/lib/widgets/aves_app.dart
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:math';
-import 'dart:ui';
import 'package:aves/app_flavor.dart';
import 'package:aves/app_mode.dart';
@@ -180,8 +179,6 @@ class _AvesAppState extends State with WidgetsBindingObserver {
super.initState();
EquatableConfig.stringify = true;
_appSetup = _setup();
- // remember screen size to use it later, when `context` and `window` are no longer reliable
- _screenSize = _getScreenSize();
_shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont();
_dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette();
_subscriptions.add(_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChanged(event as String?)));
@@ -206,6 +203,9 @@ class _AvesAppState extends State with WidgetsBindingObserver {
@override
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`
// so it can be used during navigation transitions
return MultiProvider(
@@ -266,38 +266,31 @@ class _AvesAppState extends State with WidgetsBindingObserver {
// KEYCODE_ENTER, KEYCODE_BUTTON_A, KEYCODE_NUMPAD_ENTER
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
},
- child: MediaQuery.fromWindow(
- child: Builder(
- builder: (context) {
- return MediaQuery(
- data: MediaQuery.of(context).copyWith(
- // disable accessible navigation, as it impacts snack bar action timer
- // for all users of apps registered as accessibility services,
- // even though they are not for accessibility purposes (like TalkBack is)
- accessibleNavigation: false,
- ),
- child: MaterialApp(
- navigatorKey: _navigatorKey,
- home: home,
- navigatorObservers: _navigatorObservers,
- builder: (context, child) => _decorateAppChild(
- context: context,
- initialized: initialized,
- child: child,
- ),
- onGenerateTitle: (context) => context.l10n.appName,
- theme: lightTheme,
- darkTheme: darkTheme,
- themeMode: themeBrightness.appThemeMode,
- locale: settingsLocale,
- localizationsDelegates: AppLocalizations.localizationsDelegates,
- supportedLocales: AvesApp.supportedLocales,
- // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906
- scrollBehavior: StretchMaterialScrollBehavior(),
- useInheritedMediaQuery: true,
- ),
- );
- },
+ child: MediaQuery(
+ data: MediaQuery.of(context).copyWith(
+ // disable accessible navigation, as it impacts snack bar action timer
+ // for all users of apps registered as accessibility services,
+ // even though they are not for accessibility purposes (like TalkBack is)
+ accessibleNavigation: false,
+ ),
+ child: MaterialApp(
+ navigatorKey: _navigatorKey,
+ home: home,
+ navigatorObservers: _navigatorObservers,
+ builder: (context, child) => _decorateAppChild(
+ context: context,
+ initialized: initialized,
+ child: child,
+ ),
+ onGenerateTitle: (context) => context.l10n.appName,
+ theme: lightTheme,
+ darkTheme: darkTheme,
+ themeMode: themeBrightness.appThemeMode,
+ locale: settingsLocale,
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: AvesApp.supportedLocales,
+ // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906
+ scrollBehavior: StretchMaterialScrollBehavior(),
),
),
);
@@ -390,7 +383,6 @@ class _AvesAppState extends State with WidgetsBindingObserver {
case AppMode.pickSingleMediaExternal:
case AppMode.pickMultipleMediaExternal:
_saveTopEntries();
- break;
case AppMode.pickCollectionFiltersExternal:
case AppMode.pickMediaInternal:
case AppMode.pickFilterInternal:
@@ -400,10 +392,8 @@ class _AvesAppState extends State with WidgetsBindingObserver {
case AppMode.view:
break;
}
- break;
case AppLifecycleState.resumed:
RecentlyAddedFilter.updateNow();
- break;
case AppLifecycleState.paused:
case AppLifecycleState.detached:
break;
@@ -421,9 +411,10 @@ class _AvesAppState extends State with WidgetsBindingObserver {
Widget _getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage();
- Size? _getScreenSize() {
- final physicalSize = window.physicalSize;
- final ratio = window.devicePixelRatio;
+ Size? _getScreenSize(BuildContext context) {
+ final view = View.of(context);
+ final physicalSize = view.physicalSize;
+ final ratio = view.devicePixelRatio;
return physicalSize > Size.zero && ratio > 0 ? physicalSize / ratio : null;
}
@@ -431,7 +422,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
void _saveTopEntries() {
if (!settings.initialized) return;
- final screenSize = _screenSize ?? _getScreenSize();
+ final screenSize = _screenSize;
if (screenSize == null) return;
var tileExtent = settings.getTileExtent(CollectionPage.routeName);
@@ -525,10 +516,8 @@ class _AvesAppState extends State with WidgetsBindingObserver {
case MaxBrightness.never:
case MaxBrightness.viewerOnly:
ScreenBrightness().resetScreenBrightness();
- break;
case MaxBrightness.always:
ScreenBrightness().setScreenBrightness(1);
- break;
}
}
@@ -586,7 +575,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
: 'debug',
'has_mobile_services': mobileServices.isServiceAvailable,
'is_television': device.isTelevision,
- 'locales': WidgetsBinding.instance.window.locales.join(', '),
+ 'locales': WidgetsBinding.instance.platformDispatcher.locales.join(', '),
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
});
await reportService.log('Launch');
diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart
index f3db7ad22..63a181814 100644
--- a/lib/widgets/collection/app_bar.dart
+++ b/lib/widgets/collection/app_bar.dart
@@ -224,7 +224,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
}
double get appBarContentHeight {
- final textScaleFactor = context.read().textScaleFactor;
+ final textScaleFactor = MediaQuery.textScaleFactorOf(context);
double height = kToolbarHeight * textScaleFactor;
if (settings.useTvLayout) {
height += CaptionedButton.getTelevisionButtonHeight(context);
@@ -511,16 +511,13 @@ class _CollectionAppBarState extends State with SingleTickerPr
queryEnabled: context.read().enabled,
isMenuItem: true,
);
- break;
case EntrySetAction.toggleFavourite:
child = FavouriteToggler(
entries: _getExpandedSelectedItems(selection),
isMenuItem: true,
);
- break;
default:
child = MenuRow(text: action.getText(context), icon: action.getIcon());
- break;
}
return PopupMenuItem(
key: _getActionKey(action),
@@ -598,7 +595,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
void _onQueryFocusRequest() => _queryBarFocusNode.requestFocus();
void _updateStatusBarHeight() {
- _statusBarHeight = context.read().padding.top;
+ _statusBarHeight = MediaQuery.paddingOf(context).top;
_updateAppBarHeight();
}
@@ -611,16 +608,12 @@ class _CollectionAppBarState extends State with SingleTickerPr
// general
case EntrySetAction.configureView:
await _configureView();
- break;
case EntrySetAction.select:
context.read>().select();
- break;
case EntrySetAction.selectAll:
context.read>().addToSelection(collection.sortedEntries);
- break;
case EntrySetAction.selectNone:
context.read>().clearSelection();
- break;
// browsing
case EntrySetAction.searchCollection:
case EntrySetAction.toggleTitleSearch:
@@ -650,7 +643,6 @@ class _CollectionAppBarState extends State with SingleTickerPr
case EntrySetAction.editTags:
case EntrySetAction.removeMetadata:
_actionDelegate.onActionSelected(context, action);
- break;
}
}
diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart
index 23d4c148a..059bd0a63 100644
--- a/lib/widgets/collection/collection_grid.dart
+++ b/lib/widgets/collection/collection_grid.dart
@@ -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_model/aves_model.dart';
import 'package:collection/collection.dart';
-import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:intl/intl.dart';
@@ -374,6 +373,8 @@ class _CollectionScaler extends StatelessWidget {
final tileSpacing = metrics.item1;
final horizontalPadding = metrics.item2;
final brightness = Theme.of(context).brightness;
+ final borderColor = DecoratedThumbnail.borderColor;
+ final borderWidth = DecoratedThumbnail.borderWidth(context);
return GridScaleGestureDetector(
scrollableKey: scrollableKey,
tileLayout: tileLayout,
@@ -385,9 +386,9 @@ class _CollectionScaler extends StatelessWidget {
tileSize: tileSize,
spacing: tileSpacing,
horizontalPadding: horizontalPadding,
- borderWidth: DecoratedThumbnail.borderWidth,
+ borderWidth: borderWidth,
borderRadius: Radius.zero,
- color: DecoratedThumbnail.borderColor,
+ color: borderColor,
textDirection: Directionality.of(context),
),
child: child,
@@ -404,8 +405,8 @@ class _CollectionScaler extends StatelessWidget {
decoration: BoxDecoration(
color: ThumbnailImage.computeLoadingBackgroundColor(index * 10, brightness).withOpacity(.9),
border: Border.all(
- color: DecoratedThumbnail.borderColor,
- width: DecoratedThumbnail.borderWidth,
+ color: borderColor,
+ 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
? const NeverScrollableScrollPhysics()
: SloppyScrollPhysics(
- gestureSettings: context.select((mq) => mq.gestureSettings),
+ gestureSettings: MediaQuery.gestureSettingsOf(context),
parent: const AlwaysScrollableScrollPhysics(),
),
cacheExtent: context.select((controller) => controller.effectiveExtentMax),
@@ -677,7 +677,6 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
switch (collection.sectionFactor) {
case EntryGroupFactor.album:
addAlbums(collection, sectionLayouts, crumbs);
- break;
case EntryGroupFactor.month:
case EntryGroupFactor.day:
final firstKey = sectionLayouts.first.sectionKey;
@@ -701,14 +700,11 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
});
}
}
- break;
case EntryGroupFactor.none:
break;
}
- break;
case EntrySortFactor.name:
addAlbums(collection, sectionLayouts, crumbs);
- break;
case EntrySortFactor.rating:
case EntrySortFactor.size:
break;
diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart
index 908ad0861..2eb7a9bdc 100644
--- a/lib/widgets/collection/entry_set_action_delegate.dart
+++ b/lib/widgets/collection/entry_set_action_delegate.dart
@@ -173,79 +173,55 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
// browsing
case EntrySetAction.searchCollection:
_goToSearch(context);
- break;
case EntrySetAction.toggleTitleSearch:
context.read().toggle();
- break;
case EntrySetAction.addShortcut:
_addShortcut(context);
- break;
// browsing or selecting
case EntrySetAction.map:
_goToMap(context);
- break;
case EntrySetAction.slideshow:
_goToSlideshow(context);
- break;
case EntrySetAction.stats:
_goToStats(context);
- break;
case EntrySetAction.rescan:
_rescan(context);
- break;
// selecting
case EntrySetAction.share:
_share(context);
- break;
case EntrySetAction.delete:
case EntrySetAction.emptyBin:
_delete(context);
- break;
case EntrySetAction.restore:
_move(context, moveType: MoveType.fromBin);
- break;
case EntrySetAction.copy:
_move(context, moveType: MoveType.copy);
- break;
case EntrySetAction.move:
_move(context, moveType: MoveType.move);
- break;
case EntrySetAction.rename:
_rename(context);
- break;
case EntrySetAction.convert:
_convert(context);
- break;
case EntrySetAction.toggleFavourite:
_toggleFavourite(context);
- break;
case EntrySetAction.rotateCCW:
_rotate(context, clockwise: false);
- break;
case EntrySetAction.rotateCW:
_rotate(context, clockwise: true);
- break;
case EntrySetAction.flip:
_flip(context);
- break;
case EntrySetAction.editDate:
editDate(context);
- break;
case EntrySetAction.editLocation:
_editLocation(context);
- break;
case EntrySetAction.editTitleDescription:
_editTitleDescription(context);
- break;
case EntrySetAction.editRating:
_editRating(context);
- break;
case EntrySetAction.editTags:
_editTags(context);
- break;
case EntrySetAction.removeMetadata:
_removeMetadata(context);
- break;
}
}
diff --git a/lib/widgets/collection/grid/headers/any.dart b/lib/widgets/collection/grid/headers/any.dart
index c99c0c49d..556380fcc 100644
--- a/lib/widgets/collection/grid/headers/any.dart
+++ b/lib/widgets/collection/grid/headers/any.dart
@@ -57,7 +57,6 @@ class CollectionSectionHeader extends StatelessWidget {
case EntryGroupFactor.none:
break;
}
- break;
case EntrySortFactor.name:
return _buildAlbumHeader(context);
case EntrySortFactor.rating:
diff --git a/lib/widgets/collection/grid/list_details.dart b/lib/widgets/collection/grid/list_details.dart
index 4d680b81d..1175dc0bd 100644
--- a/lib/widgets/collection/grid/list_details.dart
+++ b/lib/widgets/collection/grid/list_details.dart
@@ -78,7 +78,7 @@ class EntryListDetails extends StatelessWidget {
Widget _buildDateRow(BuildContext context, TextStyle style) {
final locale = context.l10n.localeName;
- final use24hour = context.select((v) => v.alwaysUse24HourFormat);
+ final use24hour = MediaQuery.alwaysUse24HourFormatOf(context);
final date = entry.bestDate;
final dateText = date != null ? formatDateTime(date, locale, use24hour) : AText.valueNotAvailable;
diff --git a/lib/widgets/collection/grid/tile.dart b/lib/widgets/collection/grid/tile.dart
index da346a880..a4e132d0f 100644
--- a/lib/widgets/collection/grid/tile.dart
+++ b/lib/widgets/collection/grid/tile.dart
@@ -41,17 +41,13 @@ class InteractiveTile extends StatelessWidget {
} else {
OpenViewerNotification(entry).dispatch(context);
}
- break;
case AppMode.pickSingleMediaExternal:
IntentService.submitPickedItems([entry.uri]);
- break;
case AppMode.pickMultipleMediaExternal:
final selection = context.read>();
selection.toggleSelection(entry);
- break;
case AppMode.pickMediaInternal:
Navigator.maybeOf(context)?.pop(entry);
- break;
case AppMode.pickCollectionFiltersExternal:
case AppMode.pickFilterInternal:
case AppMode.screenSaver:
diff --git a/lib/widgets/collection/query_bar.dart b/lib/widgets/collection/query_bar.dart
index 005c7ec02..70a7a43e1 100644
--- a/lib/widgets/collection/query_bar.dart
+++ b/lib/widgets/collection/query_bar.dart
@@ -52,7 +52,7 @@ class _EntryQueryBarState extends State {
@override
Widget build(BuildContext context) {
- final textScaleFactor = context.select((mq) => mq.textScaleFactor);
+ final textScaleFactor = MediaQuery.textScaleFactorOf(context);
return Container(
height: EntryQueryBar.getPreferredHeight(textScaleFactor),
alignment: Alignment.topCenter,
diff --git a/lib/widgets/common/action_controls/quick_choosers/common/menu.dart b/lib/widgets/common/action_controls/quick_choosers/common/menu.dart
index 5beae8e82..aff175503 100644
--- a/lib/widgets/common/action_controls/quick_choosers/common/menu.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/common/menu.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'dart:ui';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/action_controls/quick_choosers/common/quick_chooser.dart';
@@ -51,10 +50,15 @@ class _MenuQuickChooserState extends State> {
@override
void initState() {
super.initState();
- _selectedRowRect.value = Rect.fromLTWH(0, window.physicalSize.height * (reversed ? 1 : -1), 0, 0);
_registerWidget(widget);
}
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _selectedRowRect.value = Rect.fromLTWH(0, MediaQuery.sizeOf(context).height * (reversed ? 1 : -1), 0, 0);
+ }
+
@override
void didUpdateWidget(covariant MenuQuickChooser oldWidget) {
super.didUpdateWidget(oldWidget);
diff --git a/lib/widgets/common/action_controls/quick_choosers/common/route_layout.dart b/lib/widgets/common/action_controls/quick_choosers/common/route_layout.dart
index 07a2b4624..835f69107 100644
--- a/lib/widgets/common/action_controls/quick_choosers/common/route_layout.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/common/route_layout.dart
@@ -33,10 +33,8 @@ class QuickChooserRouteLayout extends SingleChildLayoutDelegate {
switch (menuPosition) {
case PopupMenuPosition.over:
y = triggerRect.top - childSize.height;
- break;
case PopupMenuPosition.under:
y = size.height - triggerRect.bottom;
- break;
}
double x = (triggerRect.left + (size.width - triggerRect.right) - childSize.width) / 2;
final wantedPosition = Offset(x, y);
diff --git a/lib/widgets/common/action_mixins/entry_storage.dart b/lib/widgets/common/action_mixins/entry_storage.dart
index 24ecffa80..c41508ae1 100644
--- a/lib/widgets/common/action_mixins/entry_storage.dart
+++ b/lib/widgets/common/action_mixins/entry_storage.dart
@@ -309,17 +309,14 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
..remove(destinationAlbum)
..insert(0, destinationAlbum);
entriesByDestination[destinationAlbum] = entries;
- break;
case MoveType.toBin:
entriesByDestination[AndroidFileUtils.trashDirPath] = entries;
- break;
case MoveType.fromBin:
groupBy(entries, (e) => e.directory).forEach((originAlbum, dirEntries) {
if (originAlbum != null) {
entriesByDestination[originAlbum] = dirEntries.toSet();
}
});
- break;
}
await doQuickMove(
diff --git a/lib/widgets/common/action_mixins/overlay_snack_bar.dart b/lib/widgets/common/action_mixins/overlay_snack_bar.dart
index 778654cca..1c212f712 100644
--- a/lib/widgets/common/action_mixins/overlay_snack_bar.dart
+++ b/lib/widgets/common/action_mixins/overlay_snack_bar.dart
@@ -8,66 +8,82 @@ import 'package:flutter/material.dart';
// This overlay entry is not below a `Scaffold` (which is expected by `SnackBar`
// and `SnackBarAction`), and is not dismissed the same way.
// This adaptation assumes the `SnackBarBehavior.floating` behavior and no animation.
-class OverlaySnackBar extends StatelessWidget {
+class OverlaySnackBar extends StatefulWidget {
final Widget content;
final Widget? action;
final DismissDirection dismissDirection;
final VoidCallback onDismiss;
+ final Clip clipBehavior;
const OverlaySnackBar({
super.key,
required this.content,
- required this.action,
- required this.dismissDirection,
+ this.action,
+ this.dismissDirection = DismissDirection.down,
+ this.clipBehavior = Clip.hardEdge,
required this.onDismiss,
});
+ @override
+ State createState() => _OverlaySnackBarState();
+}
+
+class _OverlaySnackBarState extends State {
@override
Widget build(BuildContext context) {
- 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;
+ 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);
- final brightness = isThemeDark ? Brightness.light : Brightness.dark;
- final themeBackgroundColor = isThemeDark ? colorScheme.onSurface : Color.alphaBlend(colorScheme.onSurface.withOpacity(0.80), colorScheme.surface);
- final inverseTheme = theme.copyWith(
- colorScheme: ColorScheme(
- primary: colorScheme.onPrimary,
- secondary: buttonColor,
- surface: colorScheme.onSurface,
- background: themeBackgroundColor,
- error: colorScheme.onError,
- onPrimary: colorScheme.primary,
- onSecondary: colorScheme.secondary,
- onSurface: colorScheme.surface,
- onBackground: colorScheme.background,
- onError: colorScheme.error,
- brightness: brightness,
- ),
- );
+ // SnackBar uses a theme that is the opposite brightness from
+ // the surrounding theme.
+ final Brightness brightness = isThemeDark ? Brightness.light : Brightness.dark;
- final contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.titleMedium;
+ // 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(
+ primary: colorScheme.onPrimary,
+ secondary: buttonColor,
+ surface: colorScheme.onSurface,
+ background: defaults.backgroundColor!,
+ error: colorScheme.onError,
+ onPrimary: colorScheme.primary,
+ onSecondary: colorScheme.secondary,
+ onSurface: colorScheme.surface,
+ onBackground: colorScheme.background,
+ onError: colorScheme.error,
+ brightness: brightness,
+ ),
+ );
+
+ final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? defaults.contentTextStyle;
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;
+ final EdgeInsets margin = snackBarTheme.insetPadding ?? defaults.insetPadding!;
+
Widget snackBar = Padding(
padding: padding,
child: Row(
children: [
Expanded(
child: Container(
- padding: action != null ? null : const EdgeInsets.symmetric(vertical: singleLineVerticalPadding),
+ padding: widget.action != null ? null : const EdgeInsets.symmetric(vertical: singleLineVerticalPadding),
child: DefaultTextStyle(
style: contentTextStyle!,
- child: content,
+ child: widget.content,
),
),
),
- if (action != null)
+ if (widget.action != null)
TextButtonTheme(
data: TextButtonThemeData(
style: TextButton.styleFrom(
@@ -75,36 +91,28 @@ class OverlaySnackBar extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
),
),
- child: action!,
+ child: widget.action!,
),
],
),
);
- final elevation = snackBarTheme.elevation ?? 6.0;
- final backgroundColor = snackBarTheme.backgroundColor ?? inverseTheme.colorScheme.background;
- final shape = snackBarTheme.shape ?? const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
+ final double elevation = snackBarTheme.elevation ?? defaults.elevation!;
+ final Color backgroundColor = snackBarTheme.backgroundColor ?? defaults.backgroundColor!;
+ final ShapeBorder? shape = snackBarTheme.shape ?? defaults.shape;
snackBar = Material(
shape: shape,
elevation: elevation,
color: backgroundColor,
child: Theme(
- data: inverseTheme,
+ data: effectiveTheme,
child: snackBar,
),
);
- const topMargin = 5.0;
- const bottomMargin = 10.0;
- const horizontalMargin = 15.0;
snackBar = Padding(
- padding: const EdgeInsets.fromLTRB(
- horizontalMargin,
- topMargin,
- horizontalMargin,
- bottomMargin,
- ),
+ padding: margin,
child: snackBar,
);
@@ -117,16 +125,138 @@ class OverlaySnackBar extends StatelessWidget {
snackBar = Semantics(
container: true,
liveRegion: true,
- onDismiss: onDismiss,
+ onDismiss: widget.onDismiss,
child: Dismissible(
key: const Key('dismissible'),
- direction: dismissDirection,
+ direction: widget.dismissDirection,
resizeDuration: null,
- onDismissed: (direction) => onDismiss(),
+ onDismissed: (direction) => widget.onDismiss(),
child: snackBar,
),
);
- return snackBar;
+ final Widget snackBarTransition = snackBar;
+
+ return Hero(
+ tag: '',
+ 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
diff --git a/lib/widgets/common/action_mixins/size_aware.dart b/lib/widgets/common/action_mixins/size_aware.dart
index f8998d308..10723ce6f 100644
--- a/lib/widgets/common/action_mixins/size_aware.dart
+++ b/lib/widgets/common/action_mixins/size_aware.dart
@@ -35,7 +35,6 @@ mixin SizeAwareMixin {
case MoveType.copy:
case MoveType.export:
needed = selection.fold(0, sumSize);
- break;
case MoveType.move:
case MoveType.toBin:
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
final largestSingle = selection.fold(0, (largest, entry) => max(largest, entry.sizeBytes ?? 0));
needed = max(fromOtherVolumes, largestSingle);
- break;
}
final hasEnoughSpace = needed < free;
diff --git a/lib/widgets/common/action_mixins/vault_aware.dart b/lib/widgets/common/action_mixins/vault_aware.dart
index 137591878..d900a3382 100644
--- a/lib/widgets/common/action_mixins/vault_aware.dart
+++ b/lib/widgets/common/action_mixins/vault_aware.dart
@@ -36,7 +36,6 @@ mixin VaultAwareMixin on FeedbackMixin {
await reportService.recordError(e, stack);
}
}
- break;
case VaultLockType.pattern:
final pattern = await showDialog(
context: context,
@@ -46,7 +45,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pattern != null) {
confirmed = pattern == await securityService.readValue(details.passKey);
}
- break;
case VaultLockType.pin:
final pin = await showDialog(
context: context,
@@ -56,7 +54,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pin != null) {
confirmed = pin == await securityService.readValue(details.passKey);
}
- break;
case VaultLockType.password:
final password = await showDialog(
context: context,
@@ -66,7 +63,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (password != null) {
confirmed = password == await securityService.readValue(details.passKey);
}
- break;
}
if (confirmed == null || !confirmed) return false;
@@ -120,7 +116,6 @@ mixin VaultAwareMixin on FeedbackMixin {
await reportService.recordError(e, stack);
}
}
- break;
case VaultLockType.pattern:
final pattern = await showDialog(
context: context,
@@ -130,7 +125,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pattern != null) {
return await securityService.writeValue(details.passKey, pattern);
}
- break;
case VaultLockType.pin:
final pin = await showDialog(
context: context,
@@ -140,7 +134,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (pin != null) {
return await securityService.writeValue(details.passKey, pin);
}
- break;
case VaultLockType.password:
final password = await showDialog(
context: context,
@@ -150,7 +143,6 @@ mixin VaultAwareMixin on FeedbackMixin {
if (password != null) {
return await securityService.writeValue(details.passKey, password);
}
- break;
}
return false;
}
diff --git a/lib/widgets/common/app_bar/app_bar_title.dart b/lib/widgets/common/app_bar/app_bar_title.dart
index 19283c9d9..a0274db45 100644
--- a/lib/widgets/common/app_bar/app_bar_title.dart
+++ b/lib/widgets/common/app_bar/app_bar_title.dart
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
class InteractiveAppBarTitle extends StatelessWidget {
final GestureTapCallback? onTap;
@@ -13,6 +12,7 @@ class InteractiveAppBarTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final textScaleFactor = MediaQuery.textScaleFactorOf(context);
return GestureDetector(
onTap: onTap,
// use a `Container` with a dummy color to make it expand
@@ -20,7 +20,7 @@ class InteractiveAppBarTitle extends StatelessWidget {
child: Container(
alignment: AlignmentDirectional.centerStart,
color: Colors.transparent,
- height: kToolbarHeight * context.select((mq) => mq.textScaleFactor),
+ height: kToolbarHeight * textScaleFactor,
child: child,
),
);
diff --git a/lib/widgets/common/basic/font_size_icon_theme.dart b/lib/widgets/common/basic/font_size_icon_theme.dart
index ea43d4449..7b143fcf8 100644
--- a/lib/widgets/common/basic/font_size_icon_theme.dart
+++ b/lib/widgets/common/basic/font_size_icon_theme.dart
@@ -11,10 +11,11 @@ class FontSizeIconTheme extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final iconTheme = IconTheme.of(context);
return IconTheme(
data: iconTheme.copyWith(
- size: iconTheme.size! * MediaQuery.textScaleFactorOf(context),
+ size: iconTheme.size! * textScaleFactor,
),
child: child,
);
diff --git a/lib/widgets/common/basic/insets.dart b/lib/widgets/common/basic/insets.dart
index fa1a73d46..106756878 100644
--- a/lib/widgets/common/basic/insets.dart
+++ b/lib/widgets/common/basic/insets.dart
@@ -22,7 +22,7 @@ class BottomGestureAreaProtector extends StatelessWidget {
left: 0,
right: 0,
bottom: 0,
- height: context.select((mq) => mq.systemGestureInsets.bottom),
+ height: MediaQuery.systemGestureInsetsOf(context).bottom,
child: GestureDetector(
// absorb vertical gestures only
onVerticalDragDown: (details) {},
@@ -42,7 +42,7 @@ class TopGestureAreaProtector extends StatelessWidget {
left: 0,
top: 0,
right: 0,
- height: context.select((mq) => mq.systemGestureInsets.top),
+ height: MediaQuery.systemGestureInsetsOf(context).top,
child: GestureDetector(
// absorb vertical gestures only
onVerticalDragDown: (details) {},
@@ -64,7 +64,7 @@ class SideGestureAreaProtector extends StatelessWidget {
textDirection: TextDirection.ltr,
children: [
SizedBox(
- width: context.select((mq) => mq.systemGestureInsets.left),
+ width: MediaQuery.systemGestureInsetsOf(context).left,
child: GestureDetector(
// absorb horizontal gestures only
onHorizontalDragDown: (details) {},
@@ -73,7 +73,7 @@ class SideGestureAreaProtector extends StatelessWidget {
),
const Spacer(),
SizedBox(
- width: context.select((mq) => mq.systemGestureInsets.right),
+ width: MediaQuery.systemGestureInsetsOf(context).right,
child: GestureDetector(
// absorb horizontal gestures only
onHorizontalDragDown: (details) {},
diff --git a/lib/widgets/common/basic/list_tiles/reselectable_radio.dart b/lib/widgets/common/basic/list_tiles/reselectable_radio.dart
index b3d456627..f6a33aacf 100644
--- a/lib/widgets/common/basic/list_tiles/reselectable_radio.dart
+++ b/lib/widgets/common/basic/list_tiles/reselectable_radio.dart
@@ -54,11 +54,9 @@ class ReselectableRadioListTile extends StatelessWidget {
case ListTileControlAffinity.platform:
leading = control;
trailing = secondary;
- break;
case ListTileControlAffinity.trailing:
leading = secondary;
trailing = control;
- break;
}
return MergeSemantics(
child: ListTileTheme.merge(
diff --git a/lib/widgets/common/basic/markdown_container.dart b/lib/widgets/common/basic/markdown_container.dart
index 14cd63158..62c054210 100644
--- a/lib/widgets/common/basic/markdown_container.dart
+++ b/lib/widgets/common/basic/markdown_container.dart
@@ -54,7 +54,7 @@ class MarkdownContainer extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
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)),
),
constraints: BoxConstraints(maxWidth: useTvLayout ? double.infinity : mobileMaxWidth),
diff --git a/lib/widgets/common/basic/scaffold.dart b/lib/widgets/common/basic/scaffold.dart
index e70c0fdda..81058a618 100644
--- a/lib/widgets/common/basic/scaffold.dart
+++ b/lib/widgets/common/basic/scaffold.dart
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
class AvesScaffold extends StatelessWidget {
final PreferredSizeWidget? appBar;
@@ -26,7 +25,7 @@ class AvesScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
// prevent conflict between drawer drag gesture and Android navigation gestures
- final drawerEnableOpenDragGesture = context.select((mq) => mq.systemGestureInsets.horizontal == 0);
+ final drawerEnableOpenDragGesture = MediaQuery.systemGestureInsetsOf(context).horizontal == 0;
return Scaffold(
appBar: appBar,
diff --git a/lib/widgets/common/basic/text/outlined.dart b/lib/widgets/common/basic/text/outlined.dart
index 22630b843..3ccf14529 100644
--- a/lib/widgets/common/basic/text/outlined.dart
+++ b/lib/widgets/common/basic/text/outlined.dart
@@ -31,44 +31,51 @@ class OutlinedText extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 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
- // in case of mixed alphabetic-ideographic text. The painted boxes depends on the script.
+ // as of Flutter v3.10.0, the area computed for `backgroundColor` has inconsistent height
+ // in case of mixed alphabetic-ideographic text. The painted boxes depend on the script.
// Possible workarounds would be to use metrics from:
// - `TextPainter.getBoxesForSelection`
// - `Paragraph.getBoxesForRange`
// and paint the background at the bottom of the `Stack`
+
final hasOutline = outlineWidth > 0;
+
+ Widget? outline;
+ if (hasOutline) {
+ outline = Text.rich(
+ TextSpan(
+ children: textSpans.map(_toStrokeSpan).toList(),
+ ),
+ textAlign: textAlign,
+ softWrap: softWrap,
+ overflow: overflow,
+ maxLines: maxLines,
+ );
+ if (outlineBlurSigma > 0) {
+ outline = ImageFiltered(
+ imageFilter: ImageFilter.blur(
+ sigmaX: outlineBlurSigma,
+ sigmaY: outlineBlurSigma,
+ ),
+ child: outline,
+ );
+ }
+ }
+
+ final fill = Text.rich(
+ TextSpan(
+ children: hasOutline ? textSpans.map(_toFillSpan).toList() : textSpans,
+ ),
+ textAlign: textAlign,
+ softWrap: softWrap,
+ overflow: overflow,
+ maxLines: maxLines,
+ );
+
return Stack(
children: [
- if (hasOutline)
- ImageFiltered(
- imageFilter: outlineBlurSigma > 0
- ? ImageFilter.blur(
- sigmaX: outlineBlurSigma,
- sigmaY: outlineBlurSigma,
- )
- : ImageFilter.matrix(
- Matrix4.identity().storage,
- ),
- child: Text.rich(
- TextSpan(
- children: textSpans.map(_toStrokeSpan).toList(),
- ),
- textAlign: textAlign,
- softWrap: softWrap,
- overflow: overflow,
- maxLines: maxLines,
- ),
- ),
- Text.rich(
- TextSpan(
- children: hasOutline ? textSpans.map(_toFillSpan).toList() : textSpans,
- ),
- textAlign: textAlign,
- softWrap: softWrap,
- overflow: overflow,
- maxLines: maxLines,
- ),
+ if (outline != null) outline,
+ fill,
],
);
}
@@ -89,6 +96,7 @@ class OutlinedText extends StatelessWidget {
children: span.children,
style: (span.style ?? const TextStyle()).copyWith(
backgroundColor: Colors.transparent,
+ shadows: [],
),
);
}
diff --git a/lib/widgets/common/basic/wheel.dart b/lib/widgets/common/basic/wheel.dart
index b6f190380..1682eb5cb 100644
--- a/lib/widgets/common/basic/wheel.dart
+++ b/lib/widgets/common/basic/wheel.dart
@@ -46,10 +46,11 @@ class _WheelSelectorState extends State> {
@override
Widget build(BuildContext context) {
+ final textScaleFactor = MediaQuery.textScaleFactorOf(context);
const background = Colors.transparent;
final foreground = DefaultTextStyle.of(context).style.color!;
final transitionDuration = context.select((v) => v.formTransition);
- final itemSize = Size.square(40 * context.select((mq) => mq.textScaleFactor));
+ final itemSize = Size.square(40 * textScaleFactor);
return FocusableActionDetector(
shortcuts: const {
@@ -138,10 +139,8 @@ class _WheelSelectorState extends State> {
switch (intent.type) {
case _ValueAdjustmentType.up:
delta = -1;
- break;
case _ValueAdjustmentType.down:
delta = 1;
- break;
}
final targetItem = _controller.selectedItem + delta;
final duration = context.read().formTransition;
diff --git a/lib/widgets/common/behaviour/eager_scale_gesture_recognizer.dart b/lib/widgets/common/behaviour/eager_scale_gesture_recognizer.dart
index 9149e0b63..ce2b4c682 100644
--- a/lib/widgets/common/behaviour/eager_scale_gesture_recognizer.dart
+++ b/lib/widgets/common/behaviour/eager_scale_gesture_recognizer.dart
@@ -26,20 +26,47 @@ enum _ScaleState {
}
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;
- double scale;
- double rotation;
+ _PointerPanZoomData.fromUpdateEvent(this.parent, PointerPanZoomUpdateEvent event)
+ : _position = event.position,
+ _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
- 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) {
- assert(velocity != null);
final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
}
@@ -57,9 +84,7 @@ class _LineBetweenPointers {
this.pointerStartId = 0,
this.pointerEndLocation = Offset.zero,
this.pointerEndId = 1,
- }) : assert(pointerStartLocation != null && pointerEndLocation != null),
- assert(pointerStartId != null && pointerEndId != null),
- assert(pointerStartId != pointerEndId);
+ }) : assert(pointerStartId != pointerEndId);
// The location and the id of the pointer that marks the start of the line.
final Offset pointerStartLocation;
@@ -74,23 +99,22 @@ class _LineBetweenPointers {
///
/// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and
/// calculates their focal point, indicated scale, and rotation. When a focal
-/// pointer is established, the recognizer calls [onStart]. As the focal point,
-/// scale, rotation change, the recognizer calls [onUpdate]. When the pointers
-/// are no longer in contact with the screen, the recognizer calls [onEnd].
+/// point is established, the recognizer calls [onStart]. As the focal point,
+/// scale, and rotation change, the recognizer calls [onUpdate]. When the
+/// pointers are no longer in contact with the screen, the recognizer calls
+/// [onEnd].
class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
/// Create a gesture recognizer for interactions intended for scaling content.
///
/// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
EagerScaleGestureRecognizer({
super.debugOwner,
- @Deprecated(
- 'Migrate to supportedDevices. '
- 'This feature was deprecated after v2.3.0-1.0.pre.',
- )
- super.kind,
super.supportedDevices,
+ super.allowedButtonsFilter,
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
/// involving this gesture.
@@ -137,6 +161,26 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
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;
Offset? _currentFocalPoint;
late double _initialSpan;
@@ -151,6 +195,7 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
final Map _pointerLocations = {};
final List _pointerQueue = []; // A queue to sort pointers in order of entrance
final Map _velocityTrackers = {};
+ VelocityTracker? _scaleVelocityTracker;
late Offset _delta;
final Map _pointerPanZooms = {};
double _initialPanZoomScaleFactor = 1;
@@ -271,15 +316,16 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_lastTransform = event.transform;
} else if (event is PointerPanZoomStartEvent) {
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;
shouldStartIfAccepted = true;
+ _lastTransform = event.transform;
} else if (event is PointerPanZoomUpdateEvent) {
assert(_pointerPanZooms[event.pointer] != null);
- if (!event.synthesized) {
+ if (!event.synthesized && !trackpadScrollCausesScale) {
_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;
shouldStartIfAccepted = true;
} else if (event is PointerPanZoomEndEvent) {
@@ -292,7 +338,7 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_update();
if (!didChangeConfiguration || _reconfigure(event.pointer)) {
- _advanceStateMachine(shouldStartIfAccepted, event.kind);
+ _advanceStateMachine(shouldStartIfAccepted, event);
}
stopTrackingIfPointerNoLongerDown(event);
}
@@ -403,18 +449,20 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) {
velocity = Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
}
- invokeCallback('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, pointerCount: _pointerCount)));
+ invokeCallback('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
} else {
- invokeCallback('onEnd', () => onEnd!(ScaleEndDetails(pointerCount: _pointerCount)));
+ invokeCallback('onEnd', () => onEnd!(ScaleEndDetails(scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
}
}
_state = _ScaleState.accepted;
+ _scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
return false;
}
+ _scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
return true;
}
- void _advanceStateMachine(bool shouldStartIfAccepted, PointerDeviceKind pointerDeviceKind) {
+ void _advanceStateMachine(bool shouldStartIfAccepted, PointerEvent event) {
if (_state == _ScaleState.ready) {
_state = _ScaleState.possible;
}
@@ -428,7 +476,7 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
if (_state == _ScaleState.possible) {
final double spanDelta = (_currentSpan - _initialSpan).abs();
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);
}
} else if (_state.index >= _ScaleState.accepted.index) {
@@ -440,19 +488,22 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_dispatchOnStartCallbackIfNeeded();
}
- if (_state == _ScaleState.started && onUpdate != null) {
- invokeCallback('onUpdate', () {
- onUpdate!(ScaleUpdateDetails(
- scale: _scaleFactor,
- horizontalScale: _horizontalScaleFactor,
- verticalScale: _verticalScaleFactor,
- focalPoint: _currentFocalPoint!,
- localFocalPoint: _localFocalPoint,
- rotation: _computeRotationFactor(),
- pointerCount: _pointerCount,
- focalPointDelta: _delta,
- ));
- });
+ if (_state == _ScaleState.started) {
+ _scaleVelocityTracker?.addPosition(event.timeStamp, Offset(_scaleFactor, 0));
+ if (onUpdate != null) {
+ invokeCallback('onUpdate', () {
+ onUpdate!(ScaleUpdateDetails(
+ scale: _scaleFactor,
+ horizontalScale: _horizontalScaleFactor,
+ verticalScale: _verticalScaleFactor,
+ focalPoint: _currentFocalPoint!,
+ localFocalPoint: _localFocalPoint,
+ rotation: _computeRotationFactor(),
+ pointerCount: _pointerCount,
+ focalPointDelta: _delta,
+ ));
+ });
+ }
}
}
@@ -504,15 +555,12 @@ class EagerScaleGestureRecognizer extends OneSequenceGestureRecognizer {
switch (_state) {
case _ScaleState.possible:
resolve(GestureDisposition.rejected);
- break;
case _ScaleState.ready:
assert(false); // We should have not seen a pointer yet
- break;
case _ScaleState.accepted:
break;
case _ScaleState.started:
assert(false); // We should be in the accepted state when user is done
- break;
}
_state = _ScaleState.ready;
}
diff --git a/lib/widgets/common/behaviour/intents.dart b/lib/widgets/common/behaviour/intents.dart
index d58dc2e8f..98dcfd723 100644
--- a/lib/widgets/common/behaviour/intents.dart
+++ b/lib/widgets/common/behaviour/intents.dart
@@ -14,11 +14,9 @@ class ScrollControllerAction extends CallbackAction {
case AxisDirection.up:
case AxisDirection.left:
factor = -1;
- break;
case AxisDirection.down:
case AxisDirection.right:
factor = 1;
- break;
}
scrollController.animateTo(
scrollController.offset + factor * 150,
diff --git a/lib/widgets/common/behaviour/known_extent_scroll_physics.dart b/lib/widgets/common/behaviour/known_extent_scroll_physics.dart
index 0dce20b84..57bb1b809 100644
--- a/lib/widgets/common/behaviour/known_extent_scroll_physics.dart
+++ b/lib/widgets/common/behaviour/known_extent_scroll_physics.dart
@@ -53,7 +53,7 @@ class KnownExtentScrollPhysics extends ScrollPhysics {
// Scenario 3:
// If there's no velocity and we're already at where we intend to land,
// 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;
}
@@ -66,7 +66,7 @@ class KnownExtentScrollPhysics extends ScrollPhysics {
metrics.pixels,
settlingPixels,
velocity,
- tolerance: tolerance,
+ tolerance: toleranceFor(position),
);
}
@@ -77,7 +77,7 @@ class KnownExtentScrollPhysics extends ScrollPhysics {
metrics.pixels,
settlingPixels,
velocity,
- tolerance.velocity * velocity.sign,
+ toleranceFor(position).velocity * velocity.sign,
);
}
}
diff --git a/lib/widgets/common/behaviour/springy_scroll_physics.dart b/lib/widgets/common/behaviour/springy_scroll_physics.dart
index 1b39c0540..51b887cde 100644
--- a/lib/widgets/common/behaviour/springy_scroll_physics.dart
+++ b/lib/widgets/common/behaviour/springy_scroll_physics.dart
@@ -16,5 +16,4 @@ class SpringyScrollPhysics extends ScrollPhysics {
parent: buildParent(ancestor),
);
}
-
}
diff --git a/lib/widgets/common/fx/borders.dart b/lib/widgets/common/fx/borders.dart
index b9dc02a0d..cc474869a 100644
--- a/lib/widgets/common/fx/borders.dart
+++ b/lib/widgets/common/fx/borders.dart
@@ -1,26 +1,22 @@
-import 'dart:ui';
-
import 'package:flutter/material.dart';
class AvesBorder {
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
- 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
- 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(
color: _borderColor(context),
- width: width ?? straightBorderWidth,
+ width: width ?? straightBorderWidth(context),
);
static BorderSide curvedSide(BuildContext context, {double? width}) => BorderSide(
color: _borderColor(context),
- width: width ?? curvedBorderWidth,
+ width: width ?? curvedBorderWidth(context),
);
static Border border(BuildContext context, {double? width}) => Border.fromBorderSide(curvedSide(context, width: width));
diff --git a/lib/widgets/common/fx/transition_image.dart b/lib/widgets/common/fx/transition_image.dart
index 4ee1b8390..f9aa1284c 100644
--- a/lib/widgets/common/fx/transition_image.dart
+++ b/lib/widgets/common/fx/transition_image.dart
@@ -2,6 +2,8 @@ import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/semantics.dart';
// adapted from Flutter `_ImageState` in `/widgets/image.dart`
// and `DecorationImagePainter` in `/painting/decoration_image.dart`
@@ -13,6 +15,11 @@ class TransitionImage extends StatefulWidget {
final ImageProvider image;
final ValueListenable animation;
final BoxFit thumbnailFit, viewerFit;
+ final ImageFrameBuilder? frameBuilder;
+ final ImageLoadingBuilder? loadingBuilder;
+ final ImageErrorWidgetBuilder? errorBuilder;
+ final String? semanticLabel;
+ final bool excludeFromSemantics;
final double? width, height;
final bool gaplessPlayback = false;
final Color? background;
@@ -23,6 +30,11 @@ class TransitionImage extends StatefulWidget {
required this.animation,
required this.thumbnailFit,
required this.viewerFit,
+ this.frameBuilder,
+ this.loadingBuilder,
+ this.errorBuilder,
+ this.semanticLabel,
+ this.excludeFromSemantics = false,
this.width,
this.height,
this.background,
@@ -32,16 +44,33 @@ class TransitionImage extends StatefulWidget {
State createState() => _TransitionImageState();
}
-class _TransitionImageState extends State {
+class _TransitionImageState extends State with WidgetsBindingObserver {
ImageStream? _imageStream;
ImageInfo? _imageInfo;
+ ImageChunkEvent? _loadingProgress;
bool _isListeningToStream = false;
int? _frameNumber;
+ bool _wasSynchronouslyLoaded = false;
+ late DisposableBuildContext> _scrollAwareContext;
+ Object? _lastException;
+ StackTrace? _lastStack;
+ ImageStreamCompleterHandle? _completerHandle;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addObserver(this);
+ _scrollAwareContext = DisposableBuildContext>(this);
+ }
@override
void dispose() {
assert(_imageStream != null);
+ WidgetsBinding.instance.removeObserver(this);
_stopListeningToStream();
+ _completerHandle?.dispose();
+ _scrollAwareContext.dispose();
+ _replaceImage(info: null);
super.dispose();
}
@@ -52,21 +81,23 @@ class _TransitionImageState extends State {
if (TickerMode.of(context)) {
_listenToStream();
} else {
- _stopListeningToStream();
+ _stopListeningToStream(keepStreamAlive: true);
}
super.didChangeDependencies();
}
@override
- void didUpdateWidget(covariant TransitionImage oldWidget) {
+ void didUpdateWidget(TransitionImage oldWidget) {
super.didUpdateWidget(oldWidget);
- if (_isListeningToStream) {
+ if (_isListeningToStream && (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
final ImageStreamListener oldListener = _getListener();
_imageStream!.addListener(_getListener(recreateListener: true));
_imageStream!.removeListener(oldListener);
}
- if (widget.image != oldWidget.image) _resolveImage();
+ if (widget.image != oldWidget.image) {
+ _resolveImage();
+ }
}
@override
@@ -76,8 +107,11 @@ class _TransitionImageState extends State {
}
void _resolveImage() {
- final provider = widget.image;
- final newStream = provider.resolve(createLocalImageConfiguration(
+ final ScrollAwareImageProvider provider = ScrollAwareImageProvider