diff --git a/android/app/build.gradle b/android/app/build.gradle index 42cdb453a..999193448 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -55,6 +55,10 @@ android { defaultConfig { applicationId appId + // minSdkVersion constraints: + // - Flutter & other plugins: 16 + // - google_maps_flutter v2.0.5: 20 + // - Aves native: 19 minSdkVersion 20 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() diff --git a/lib/image_providers/app_icon_image_provider.dart b/lib/image_providers/app_icon_image_provider.dart index a51ea806c..91aecf1ac 100644 --- a/lib/image_providers/app_icon_image_provider.dart +++ b/lib/image_providers/app_icon_image_provider.dart @@ -1,6 +1,7 @@ import 'dart:ui' as ui show Codec; import 'package:aves/services/android_app_service.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -49,23 +50,18 @@ class AppIconImage extends ImageProvider { } } -class AppIconImageKey { +@immutable +class AppIconImageKey extends Equatable { final String packageName; final double size; final double scale; + @override + List get props => [packageName, size, scale]; + const AppIconImageKey({ required this.packageName, required this.size, this.scale = 1.0, }); - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is AppIconImageKey && other.packageName == packageName && other.size == size && other.scale == scale; - } - - @override - int get hashCode => hashValues(packageName, size, scale); } diff --git a/lib/image_providers/region_provider.dart b/lib/image_providers/region_provider.dart index 0cc1f2c4a..75908bb30 100644 --- a/lib/image_providers/region_provider.dart +++ b/lib/image_providers/region_provider.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'dart:ui' as ui show Codec; import 'package:aves/services/services.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -62,7 +63,8 @@ class RegionProvider extends ImageProvider { void pause() => imageFileService.cancelRegion(key); } -class RegionProviderKey { +@immutable +class RegionProviderKey extends Equatable { // do not store the entry as it is, because the key should be constant // but the entry attributes may change over time final String uri, mimeType; @@ -72,6 +74,9 @@ class RegionProviderKey { final Rectangle region; final Size imageSize; + @override + List get props => [uri, pageId, rotationDegrees, isFlipped, sampleSize, region, imageSize]; + const RegionProviderKey({ required this.uri, required this.mimeType, @@ -83,24 +88,6 @@ class RegionProviderKey { required this.imageSize, }); - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is RegionProviderKey && other.uri == uri && other.mimeType == mimeType && other.pageId == pageId && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.sampleSize == sampleSize && other.region == region && other.imageSize == imageSize; - } - - @override - int get hashCode => hashValues( - uri, - mimeType, - pageId, - rotationDegrees, - isFlipped, - sampleSize, - region, - imageSize, - ); - @override String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, pageId=$pageId, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, sampleSize=$sampleSize, region=$region, imageSize=$imageSize}'; } diff --git a/lib/image_providers/thumbnail_provider.dart b/lib/image_providers/thumbnail_provider.dart index 309f696cc..5e64e045f 100644 --- a/lib/image_providers/thumbnail_provider.dart +++ b/lib/image_providers/thumbnail_provider.dart @@ -1,6 +1,7 @@ import 'dart:ui' as ui show Codec; import 'package:aves/services/services.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -63,7 +64,8 @@ class ThumbnailProvider extends ImageProvider { void pause() => imageFileService.cancelThumbnail(key); } -class ThumbnailProviderKey { +@immutable +class ThumbnailProviderKey extends Equatable { // do not store the entry as it is, because the key should be constant // but the entry attributes may change over time final String uri, mimeType; @@ -73,6 +75,9 @@ class ThumbnailProviderKey { final int dateModifiedSecs; final double extent; + @override + List get props => [uri, pageId, dateModifiedSecs, extent]; + const ThumbnailProviderKey({ required this.uri, required this.mimeType, @@ -83,20 +88,6 @@ class ThumbnailProviderKey { this.extent = 0, }); - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is ThumbnailProviderKey && other.uri == uri && other.pageId == pageId && other.dateModifiedSecs == dateModifiedSecs && other.extent == extent; - } - - @override - int get hashCode => hashValues( - uri, - pageId, - dateModifiedSecs, - extent, - ); - @override String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, pageId=$pageId, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, dateModifiedSecs=$dateModifiedSecs, extent=$extent}'; } diff --git a/lib/image_providers/uri_image_provider.dart b/lib/image_providers/uri_image_provider.dart index 823644af0..23e11f9f5 100644 --- a/lib/image_providers/uri_image_provider.dart +++ b/lib/image_providers/uri_image_provider.dart @@ -3,15 +3,20 @@ import 'dart:ui' as ui show Codec; import 'package:aves/services/services.dart'; import 'package:aves/utils/pedantic.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -class UriImage extends ImageProvider { +@immutable +class UriImage extends ImageProvider with EquatableMixin { final String uri, mimeType; final int? pageId, rotationDegrees, expectedContentLength; final bool isFlipped; final double scale; + @override + List get props => [uri, pageId, rotationDegrees, isFlipped, scale]; + const UriImage({ required this.uri, required this.mimeType, @@ -71,22 +76,6 @@ class UriImage extends ImageProvider { } } - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is UriImage && other.uri == uri && other.mimeType == mimeType && other.rotationDegrees == rotationDegrees && other.isFlipped == isFlipped && other.pageId == pageId && other.scale == scale; - } - - @override - int get hashCode => hashValues( - uri, - mimeType, - rotationDegrees, - isFlipped, - pageId, - scale, - ); - @override String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, mimeType=$mimeType, rotationDegrees=$rotationDegrees, isFlipped=$isFlipped, pageId=$pageId, scale=$scale}'; } diff --git a/lib/model/covers.dart b/lib/model/covers.dart index ffebf4b98..f6b84fee8 100644 --- a/lib/model/covers.dart +++ b/lib/model/covers.dart @@ -3,6 +3,7 @@ import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/services/services.dart'; import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -77,10 +78,13 @@ class Covers with ChangeNotifier { } @immutable -class CoverRow { +class CoverRow extends Equatable { final CollectionFilter filter; final int contentId; + @override + List get props => [filter, contentId]; + const CoverRow({ required this.filter, required this.contentId, @@ -99,16 +103,4 @@ class CoverRow { 'filter': filter.toJson(), 'contentId': contentId, }; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is CoverRow && other.filter == filter && other.contentId == contentId; - } - - @override - int get hashCode => hashValues(filter, contentId); - - @override - String toString() => '$runtimeType#${shortHash(this)}{filter=$filter, contentId=$contentId}'; } diff --git a/lib/model/favourites.dart b/lib/model/favourites.dart index 524ad4be3..a64633507 100644 --- a/lib/model/favourites.dart +++ b/lib/model/favourites.dart @@ -1,7 +1,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/services/services.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; final Favourites favourites = Favourites._private(); @@ -62,10 +62,13 @@ class Favourites with ChangeNotifier { } @immutable -class FavouriteRow { +class FavouriteRow extends Equatable { final int contentId; final String path; + @override + List get props => [contentId, path]; + const FavouriteRow({ required this.contentId, required this.path, @@ -82,16 +85,4 @@ class FavouriteRow { 'contentId': contentId, 'path': path, }; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is FavouriteRow && other.contentId == contentId && other.path == path; - } - - @override - int get hashCode => hashValues(contentId, path); - - @override - String toString() => '$runtimeType#${shortHash(this)}{contentId=$contentId, path=$path}'; } diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 7e8c708e5..d7c8fab3f 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -16,6 +16,9 @@ class AlbumFilter extends CollectionFilter { final String album; final String? displayName; + @override + List get props => [album]; + const AlbumFilter(this.album, this.displayName); AlbumFilter.fromMap(Map json) @@ -78,16 +81,4 @@ class AlbumFilter extends CollectionFilter { // key `album-{path}` is expected by test driver @override String get key => '$type-$album'; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is AlbumFilter && other.album == album; - } - - @override - int get hashCode => hashValues(type, album); - - @override - String toString() => '$runtimeType#${shortHash(this)}{album=$album}'; } diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart index 79c65a073..634b76264 100644 --- a/lib/model/filters/favourite.dart +++ b/lib/model/filters/favourite.dart @@ -10,6 +10,9 @@ class FavouriteFilter extends CollectionFilter { static const instance = FavouriteFilter._private(); + @override + List get props => []; + const FavouriteFilter._private(); @override @@ -37,13 +40,4 @@ class FavouriteFilter extends CollectionFilter { @override String get key => type; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is FavouriteFilter; - } - - @override - int get hashCode => type.hashCode; } diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 33d9c9952..725085d3a 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -11,10 +11,12 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -abstract class CollectionFilter implements Comparable { +@immutable +abstract class CollectionFilter extends Equatable implements Comparable { static const List categoryOrder = [ QueryFilter.type, FavouriteFilter.type, @@ -88,20 +90,15 @@ abstract class CollectionFilter implements Comparable { } } -class FilterGridItem { +@immutable +class FilterGridItem with EquatableMixin { final T filter; final AvesEntry? entry; + @override + List get props => [filter, entry?.uri]; + const FilterGridItem(this.filter, this.entry); - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is FilterGridItem && other.filter == filter && other.entry == entry; - } - - @override - int get hashCode => hashValues(filter, entry); } typedef EntryFilter = bool Function(AvesEntry); diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index 6b4793275..01b9b1b71 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -2,7 +2,6 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; class LocationFilter extends CollectionFilter { @@ -10,14 +9,17 @@ class LocationFilter extends CollectionFilter { static const locationSeparator = ';'; final LocationLevel level; - String _location; - String? _countryCode; - late EntryFilter _test; + late final String _location; + late final String? _countryCode; + late final EntryFilter _test; - LocationFilter(this.level, this._location) { - final split = _location.split(locationSeparator); - if (split.isNotEmpty) _location = split[0]; - if (split.length > 1) _countryCode = split[1]; + @override + List get props => [level, _location, _countryCode]; + + LocationFilter(this.level, String location) { + final split = location.split(locationSeparator); + _location = split.isNotEmpty ? split[0] : location; + _countryCode = split.length > 1 ? split[1] : null; if (_location.isEmpty) { _test = (entry) => !entry.hasGps; @@ -75,18 +77,6 @@ class LocationFilter extends CollectionFilter { @override String get key => '$type-$level-$_location'; - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is LocationFilter && other.level == level && other._location == _location; - } - - @override - int get hashCode => hashValues(type, level, _location); - - @override - String toString() => '$runtimeType#${shortHash(this)}{level=$level, location=$_location}'; - // U+0041 Latin Capital letter A // U+1F1E6 🇦 REGIONAL INDICATOR SYMBOL LETTER A static const _countryCodeToFlagDiff = 0x1F1E6 - 0x0041; diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index 750cdf0a4..9aba9d61f 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -3,20 +3,22 @@ import 'package:aves/ref/mime_types.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/mime_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; class MimeFilter extends CollectionFilter { static const type = 'mime'; final String mime; - late EntryFilter _test; - late String _label; - late IconData _icon; + late final EntryFilter _test; + late final String _label; + late final IconData _icon; static final image = MimeFilter(MimeTypes.anyImage); static final video = MimeFilter(MimeTypes.anyVideo); + @override + List get props => [mime]; + MimeFilter(this.mime) { IconData? icon; var lowMime = mime.toLowerCase(); @@ -73,16 +75,4 @@ class MimeFilter extends CollectionFilter { @override String get key => '$type-$mime'; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is MimeFilter && other.mime == mime; - } - - @override - int get hashCode => hashValues(type, mime); - - @override - String toString() => '$runtimeType#${shortHash(this)}{mime=$mime}'; } diff --git a/lib/model/filters/path.dart b/lib/model/filters/path.dart index f4708579d..8ae97aeab 100644 --- a/lib/model/filters/path.dart +++ b/lib/model/filters/path.dart @@ -1,12 +1,13 @@ import 'package:aves/model/filters/filters.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; class PathFilter extends CollectionFilter { static const type = 'path'; final String path; + @override + List get props => [path]; + const PathFilter(this.path); PathFilter.fromMap(Map json) @@ -31,16 +32,4 @@ class PathFilter extends CollectionFilter { @override String get key => '$type-$path'; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is PathFilter && other.path == path; - } - - @override - int get hashCode => hashValues(type, path); - - @override - String toString() => '$runtimeType#${shortHash(this)}{path=$path}'; } diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index c9ffc60a2..a166edf5a 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -12,7 +12,10 @@ class QueryFilter extends CollectionFilter { final String query; final bool colorful; - late EntryFilter _test; + late final EntryFilter _test; + + @override + List get props => [query]; QueryFilter(this.query, {this.colorful = true}) { var upQuery = query.toUpperCase(); @@ -63,16 +66,4 @@ class QueryFilter extends CollectionFilter { @override String get key => '$type-$query'; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is QueryFilter && other.query == query; - } - - @override - int get hashCode => hashValues(type, query); - - @override - String toString() => '$runtimeType#${shortHash(this)}{query=$query}'; } diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index c64fa04d2..1f5c1e0dc 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -1,14 +1,16 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; class TagFilter extends CollectionFilter { static const type = 'tag'; final String tag; - late EntryFilter _test; + late final EntryFilter _test; + + @override + List get props => [tag]; TagFilter(this.tag) { if (tag.isEmpty) { @@ -49,16 +51,4 @@ class TagFilter extends CollectionFilter { @override String get key => '$type-$tag'; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is TagFilter && other.tag == tag; - } - - @override - int get hashCode => hashValues(type, tag); - - @override - String toString() => '$runtimeType#${shortHash(this)}{tag=$tag}'; } diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart index ec0f51aeb..ff7d79b9b 100644 --- a/lib/model/filters/type.dart +++ b/lib/model/filters/type.dart @@ -1,7 +1,6 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; class TypeFilter extends CollectionFilter { @@ -14,8 +13,8 @@ class TypeFilter extends CollectionFilter { static const _sphericalVideo = 'spherical_video'; // subset of videos final String itemType; - late EntryFilter _test; - late IconData _icon; + late final EntryFilter _test; + late final IconData _icon; static final animated = TypeFilter._private(_animated); static final geotiff = TypeFilter._private(_geotiff); @@ -23,12 +22,19 @@ class TypeFilter extends CollectionFilter { static final panorama = TypeFilter._private(_panorama); static final sphericalVideo = TypeFilter._private(_sphericalVideo); + @override + List get props => [itemType]; + TypeFilter._private(this.itemType) { switch (itemType) { 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; @@ -41,10 +47,6 @@ class TypeFilter extends CollectionFilter { _test = (entry) => entry.isVideo && entry.is360; _icon = AIcons.threeSixty; break; - case _geotiff: - _test = (entry) => entry.isGeotiff; - _icon = AIcons.geo; - break; } } @@ -91,16 +93,4 @@ class TypeFilter extends CollectionFilter { @override String get key => '$type-$itemType'; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is TypeFilter && other.itemType == itemType; - } - - @override - int get hashCode => hashValues(type, itemType); - - @override - String toString() => '$runtimeType#${shortHash(this)}{itemType=$itemType}'; } diff --git a/lib/model/source/section_keys.dart b/lib/model/source/section_keys.dart index be43543ee..fd455cf02 100644 --- a/lib/model/source/section_keys.dart +++ b/lib/model/source/section_keys.dart @@ -1,41 +1,25 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; +@immutable class SectionKey { const SectionKey(); } -class EntryAlbumSectionKey extends SectionKey { +class EntryAlbumSectionKey extends SectionKey with EquatableMixin { final String? directory; + @override + List get props => [directory]; + const EntryAlbumSectionKey(this.directory); - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is EntryAlbumSectionKey && other.directory == directory; - } - - @override - int get hashCode => directory.hashCode; - - @override - String toString() => '$runtimeType#${shortHash(this)}{directory=$directory}'; } -class EntryDateSectionKey extends SectionKey { +class EntryDateSectionKey extends SectionKey with EquatableMixin { final DateTime? date; + @override + List get props => [date]; + const EntryDateSectionKey(this.date); - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is EntryDateSectionKey && other.date == date; - } - - @override - int get hashCode => date.hashCode; - - @override - String toString() => '$runtimeType#${shortHash(this)}{date=$date}'; } diff --git a/lib/services/image_op_events.dart b/lib/services/image_op_events.dart index 6d172f8a1..52bcde5a6 100644 --- a/lib/services/image_op_events.dart +++ b/lib/services/image_op_events.dart @@ -1,11 +1,15 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @immutable -class ImageOpEvent { +class ImageOpEvent extends Equatable { final bool success; final String uri; + @override + List get props => [success, uri]; + const ImageOpEvent({ required this.success, required this.uri, @@ -17,18 +21,6 @@ class ImageOpEvent { uri: map['uri'], ); } - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is ImageOpEvent && other.success == success && other.uri == uri; - } - - @override - int get hashCode => hashValues(success, uri); - - @override - String toString() => '$runtimeType#${shortHash(this)}{success=$success, uri=$uri}'; } class MoveOpEvent extends ImageOpEvent { @@ -55,6 +47,9 @@ class MoveOpEvent extends ImageOpEvent { class ExportOpEvent extends MoveOpEvent { final int? pageId; + @override + List get props => [success, uri, pageId]; + const ExportOpEvent({required bool success, required String uri, this.pageId, required Map newFields}) : super( success: success, @@ -70,16 +65,4 @@ class ExportOpEvent extends MoveOpEvent { newFields: map['newFields'] ?? {}, ); } - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is ExportOpEvent && other.success == success && other.uri == uri && other.pageId == pageId; - } - - @override - int get hashCode => hashValues(success, uri, pageId); - - @override - String toString() => '$runtimeType#${shortHash(this)}{success=$success, uri=$uri, pageId=$pageId, newFields=$newFields}'; } diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart index d38ae562f..e071beab5 100644 --- a/lib/utils/android_file_utils.dart +++ b/lib/utils/android_file_utils.dart @@ -3,6 +3,7 @@ import 'package:aves/services/services.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -155,9 +156,12 @@ class StorageVolume { } @immutable -class VolumeRelativeDirectory { +class VolumeRelativeDirectory extends Equatable { final String volumePath, relativeDir; + @override + List get props => [volumePath, relativeDir]; + const VolumeRelativeDirectory({ required this.volumePath, required this.relativeDir, @@ -187,13 +191,4 @@ class VolumeRelativeDirectory { final volume = androidFileUtils.storageVolumes.firstWhereOrNull((volume) => volume.path == volumePath); return volume?.getDescription(context) ?? volumePath; } - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is VolumeRelativeDirectory && other.volumePath == volumePath && other.relativeDir == relativeDir; - } - - @override - int get hashCode => hashValues(volumePath, relativeDir); } diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 583c64f86..c054097a6 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -44,7 +44,6 @@ class Constants { Dependency( name: 'AndroidSVG', license: 'Apache 2.0', - licenseUrl: 'https://github.com/BigBadaboom/androidsvg/blob/master/LICENSE', sourceUrl: 'https://github.com/BigBadaboom/androidsvg', ), Dependency( @@ -56,19 +55,16 @@ class Constants { Dependency( name: 'CWAC-Document', license: 'Apache 2.0', - licenseUrl: 'https://github.com/commonsguy/cwac-document/blob/master/LICENSE', sourceUrl: 'https://github.com/commonsguy/cwac-document', ), Dependency( name: 'Glide', license: 'Apache 2.0, BSD 2-Clause', - licenseUrl: 'https://github.com/bumptech/glide/blob/master/LICENSE', sourceUrl: 'https://github.com/bumptech/glide', ), Dependency( name: 'Metadata Extractor', license: 'Apache 2.0', - licenseUrl: 'https://github.com/drewnoakes/metadata-extractor/blob/master/LICENSE', sourceUrl: 'https://github.com/drewnoakes/metadata-extractor', ), ]; @@ -77,49 +73,44 @@ class Constants { Dependency( name: 'Connectivity Plus', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/connectivity_plus/LICENSE', + licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/connectivity_plus/connectivity_plus/LICENSE', sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/connectivity_plus', ), Dependency( name: 'FlutterFire (Core, Crashlytics)', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/FirebaseExtended/flutterfire/blob/master/LICENSE', sourceUrl: 'https://github.com/FirebaseExtended/flutterfire', ), Dependency( name: 'fijkplayer (Aves fork)', license: 'MIT', - licenseUrl: 'https://github.com/deckerst/fijkplayer/blob/master/LICENSE', sourceUrl: 'https://github.com/deckerst/fijkplayer', ), Dependency( name: 'Google API Availability', license: 'MIT', - licenseUrl: 'https://github.com/Baseflow/flutter-google-api-availability/blob/master/LICENSE', sourceUrl: 'https://github.com/Baseflow/flutter-google-api-availability', ), Dependency( name: 'Google Maps for Flutter', license: 'BSD 3-Clause', licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter/LICENSE', - sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter', + sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter', ), Dependency( name: 'Package Info Plus', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/LICENSE', + licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/package_info_plus/package_info_plus/LICENSE', sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus', ), Dependency( name: 'Permission Handler', license: 'MIT', - licenseUrl: 'https://github.com/Baseflow/flutter-permission-handler/blob/develop/permission_handler/LICENSE', sourceUrl: 'https://github.com/Baseflow/flutter-permission-handler', ), Dependency( name: 'Printing', license: 'Apache 2.0', - licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE', sourceUrl: 'https://github.com/DavBfr/dart_pdf', ), Dependency( @@ -130,21 +121,19 @@ class Constants { ), Dependency( name: 'sqflite', - license: 'MIT', - licenseUrl: 'https://github.com/tekartik/sqflite/blob/master/sqflite/LICENSE', + license: 'BSD 2-Clause', sourceUrl: 'https://github.com/tekartik/sqflite', ), Dependency( name: 'Streams Channel (Aves fork)', license: 'Apache 2.0', - licenseUrl: 'https://github.com/deckerst/aves_streams_channel/blob/master/LICENSE', sourceUrl: 'https://github.com/deckerst/aves_streams_channel', ), Dependency( name: 'URL Launcher', license: 'BSD 3-Clause', licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE', - sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher', + sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher', ), ]; @@ -152,37 +141,31 @@ class Constants { Dependency( name: 'Charts', license: 'Apache 2.0', - licenseUrl: 'https://github.com/google/charts/blob/master/LICENSE', sourceUrl: 'https://github.com/google/charts', ), Dependency( name: 'Decorated Icon', license: 'MIT', - licenseUrl: 'https://github.com/benPesso/flutter_decorated_icon/blob/master/LICENSE', sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon', ), Dependency( name: 'Expansion Tile Card (Aves fork)', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/deckerst/expansion_tile_card/blob/master/LICENSE', sourceUrl: 'https://github.com/deckerst/expansion_tile_card', ), Dependency( name: 'FlexColorPicker', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/rydmike/flex_color_picker/blob/master/LICENSE', sourceUrl: 'https://github.com/rydmike/flex_color_picker', ), Dependency( name: 'Flutter Highlight', license: 'MIT', - licenseUrl: 'https://github.com/git-touch/highlight/blob/master/LICENSE', sourceUrl: 'https://github.com/git-touch/highlight', ), Dependency( name: 'Flutter Map', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/fleaflet/flutter_map/blob/master/LICENSE', sourceUrl: 'https://github.com/fleaflet/flutter_map', ), Dependency( @@ -194,19 +177,16 @@ class Constants { Dependency( name: 'Flutter Staggered Animations', license: 'MIT', - licenseUrl: 'https://github.com/mobiten/flutter_staggered_animations/blob/master/LICENSE', sourceUrl: 'https://github.com/mobiten/flutter_staggered_animations', ), Dependency( name: 'Material Design Icons Flutter', license: 'MIT', - licenseUrl: 'https://github.com/ziofat/material_design_icons_flutter/blob/master/LICENSE', sourceUrl: 'https://github.com/ziofat/material_design_icons_flutter', ), Dependency( name: 'Overlay Support', license: 'Apache 2.0', - licenseUrl: 'https://github.com/boyan01/overlay_support/blob/master/LICENSE', sourceUrl: 'https://github.com/boyan01/overlay_support', ), Dependency( @@ -218,19 +198,16 @@ class Constants { Dependency( name: 'Panorama', license: 'Apache 2.0', - licenseUrl: 'https://github.com/zesage/panorama/blob/master/LICENSE', sourceUrl: 'https://github.com/zesage/panorama', ), Dependency( name: 'Percent Indicator', license: 'BSD 2-Clause', - licenseUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/blob/master/LICENSE', - sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/', + sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator', ), Dependency( name: 'Provider', license: 'MIT', - licenseUrl: 'https://github.com/rrousselGit/provider/blob/master/LICENSE', sourceUrl: 'https://github.com/rrousselGit/provider', ), ]; @@ -239,19 +216,21 @@ class Constants { Dependency( name: 'Collection', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/dart-lang/collection/blob/master/LICENSE', sourceUrl: 'https://github.com/dart-lang/collection', ), Dependency( name: 'Country Code', license: 'MIT', - licenseUrl: 'https://github.com/denixport/dart.country/blob/master/LICENSE', sourceUrl: 'https://github.com/denixport/dart.country', ), + Dependency( + name: 'Equatable', + license: 'MIT', + sourceUrl: 'https://github.com/felangel/equatable', + ), Dependency( name: 'Event Bus', license: 'MIT', - licenseUrl: 'https://github.com/marcojakob/dart-event-bus/blob/master/LICENSE', sourceUrl: 'https://github.com/marcojakob/dart-event-bus', ), Dependency( @@ -263,49 +242,41 @@ class Constants { Dependency( name: 'Get It', license: 'MIT', - licenseUrl: 'https://github.com/fluttercommunity/get_it/blob/master/LICENSE', sourceUrl: 'https://github.com/fluttercommunity/get_it', ), Dependency( name: 'Github', license: 'MIT', - licenseUrl: 'https://github.com/SpinlockLabs/github.dart/blob/master/LICENSE', sourceUrl: 'https://github.com/SpinlockLabs/github.dart', ), Dependency( name: 'Intl', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/dart-lang/intl/blob/master/LICENSE', sourceUrl: 'https://github.com/dart-lang/intl', ), Dependency( name: 'LatLong2', license: 'Apache 2.0', - licenseUrl: 'https://github.com/jifalops/dart-latlong/blob/master/LICENSE', sourceUrl: 'https://github.com/jifalops/dart-latlong', ), Dependency( name: 'PDF for Dart and Flutter', license: 'Apache 2.0', - licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE', sourceUrl: 'https://github.com/DavBfr/dart_pdf', ), Dependency( name: 'Tuple', license: 'BSD 2-Clause', - licenseUrl: 'https://github.com/dart-lang/tuple/blob/master/LICENSE', sourceUrl: 'https://github.com/dart-lang/tuple', ), Dependency( name: 'Version', license: 'BSD 3-Clause', - licenseUrl: 'https://github.com/dartninja/version/blob/master/LICENSE', sourceUrl: 'https://github.com/dartninja/version', ), Dependency( name: 'XML', license: 'MIT', - licenseUrl: 'https://github.com/renggli/dart-xml/blob/master/LICENSE', sourceUrl: 'https://github.com/renggli/dart-xml', ), ]; @@ -320,7 +291,7 @@ class Dependency { const Dependency({ required this.name, required this.license, - required this.licenseUrl, + String? licenseUrl, required this.sourceUrl, - }); + }) : licenseUrl = licenseUrl ?? '$sourceUrl/blob/master/LICENSE'; } diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 3a432e0eb..6f2c93fea 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -15,6 +15,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; import 'package:aves/widgets/home_page.dart'; import 'package:aves/widgets/welcome_page.dart'; +import 'package:equatable/equatable.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; @@ -50,6 +51,7 @@ class _AvesAppState extends State { @override void initState() { super.initState(); + EquatableConfig.stringify = true; initPlatformServices(); _appSetup = _setup(); _mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChange(event as String?)); diff --git a/lib/widgets/common/grid/section_layout.dart b/lib/widgets/common/grid/section_layout.dart index 9b95ceffa..7f8d141bd 100644 --- a/lib/widgets/common/grid/section_layout.dart +++ b/lib/widgets/common/grid/section_layout.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/theme/durations.dart'; import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -221,13 +222,17 @@ class SectionedListLayout { String toString() => '$runtimeType#${shortHash(this)}{sectionCount=${sections.length} columnCount=$columnCount, tileExtent=$tileExtent}'; } -class SectionLayout { +@immutable +class SectionLayout extends Equatable { final SectionKey sectionKey; final int firstIndex, lastIndex, bodyFirstIndex; final double minOffset, maxOffset, bodyMinOffset; final double headerExtent, tileExtent, spacing, mainAxisStride; final IndexedWidgetBuilder builder; + @override + List get props => [sectionKey, firstIndex, lastIndex, minOffset, maxOffset, headerExtent, tileExtent, spacing]; + const SectionLayout({ required this.sectionKey, required this.firstIndex, @@ -263,15 +268,6 @@ class SectionLayout { if (scrollOffset < 0) return firstIndex; return bodyFirstIndex + (scrollOffset / mainAxisStride).ceil() - 1; } - - @override - bool operator ==(Object other) => identical(this, other) || other is SectionLayout && runtimeType == other.runtimeType && sectionKey == other.sectionKey && firstIndex == other.firstIndex && lastIndex == other.lastIndex && minOffset == other.minOffset && maxOffset == other.maxOffset && headerExtent == other.headerExtent && tileExtent == other.tileExtent && spacing == other.spacing; - - @override - int get hashCode => hashValues(sectionKey, firstIndex, lastIndex, minOffset, maxOffset, headerExtent, tileExtent, spacing); - - @override - String toString() => '$runtimeType#${shortHash(this)}{sectionKey=$sectionKey, firstIndex=$firstIndex, lastIndex=$lastIndex, minOffset=$minOffset, maxOffset=$maxOffset, headerExtent=$headerExtent, tileExtent=$tileExtent, spacing=$spacing}'; } class _GridRow extends MultiChildRenderObjectWidget { diff --git a/lib/widgets/common/magnifier/controller/state.dart b/lib/widgets/common/magnifier/controller/state.dart index 949c692c0..ee8313d13 100644 --- a/lib/widgets/common/magnifier/controller/state.dart +++ b/lib/widgets/common/magnifier/controller/state.dart @@ -1,28 +1,22 @@ import 'dart:ui'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; @immutable -class MagnifierState { - const MagnifierState({ - required this.position, - required this.scale, - required this.source, - }); - +class MagnifierState extends Equatable { final Offset position; final double? scale; final ChangeSource source; @override - bool operator ==(Object other) => identical(this, other) || other is MagnifierState && runtimeType == other.runtimeType && position == other.position && scale == other.scale; + List get props => [position, scale, source]; - @override - int get hashCode => hashValues(position, scale, source); - - @override - String toString() => '$runtimeType#${shortHash(this)}{position: $position, scale: $scale, source: $source}'; + const MagnifierState({ + required this.position, + required this.scale, + required this.source, + }); } enum ChangeSource { internal, gesture, animation } diff --git a/lib/widgets/common/magnifier/core/core.dart b/lib/widgets/common/magnifier/core/core.dart index 7ea5035e3..a84051e1c 100644 --- a/lib/widgets/common/magnifier/core/core.dart +++ b/lib/widgets/common/magnifier/core/core.dart @@ -6,6 +6,7 @@ import 'package:aves/widgets/common/magnifier/magnifier.dart'; import 'package:aves/widgets/common/magnifier/pan/corner_hit_detector.dart'; import 'package:aves/widgets/common/magnifier/scale/scale_boundaries.dart'; import 'package:aves/widgets/common/magnifier/scale/state.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; /// Internal widget in which controls all animations lifecycle, core responses @@ -276,17 +277,21 @@ class _MagnifierCoreState extends State with TickerProviderStateM } } -class _CenterWithOriginalSizeDelegate extends SingleChildLayoutDelegate { +@immutable +class _CenterWithOriginalSizeDelegate extends SingleChildLayoutDelegate with EquatableMixin { + final Size subjectSize; + final Alignment basePosition; + final bool applyScale; + + @override + List get props => [subjectSize, basePosition, applyScale]; + const _CenterWithOriginalSizeDelegate( this.subjectSize, this.basePosition, this.applyScale, ); - final Size subjectSize; - final Alignment basePosition; - final bool applyScale; - @override Offset getPositionForChild(Size size, Size childSize) { final childWidth = applyScale ? subjectSize.width : childSize.width; @@ -309,10 +314,4 @@ class _CenterWithOriginalSizeDelegate extends SingleChildLayoutDelegate { bool shouldRelayout(_CenterWithOriginalSizeDelegate oldDelegate) { return oldDelegate != this; } - - @override - bool operator ==(Object other) => identical(this, other) || other is _CenterWithOriginalSizeDelegate && runtimeType == other.runtimeType && subjectSize == other.subjectSize && basePosition == other.basePosition && applyScale == other.applyScale; - - @override - int get hashCode => hashValues(subjectSize, basePosition, applyScale); } diff --git a/lib/widgets/common/magnifier/scale/scale_boundaries.dart b/lib/widgets/common/magnifier/scale/scale_boundaries.dart index 3c9db4994..85e54b0a8 100644 --- a/lib/widgets/common/magnifier/scale/scale_boundaries.dart +++ b/lib/widgets/common/magnifier/scale/scale_boundaries.dart @@ -2,17 +2,22 @@ import 'dart:ui'; import 'package:aves/widgets/common/magnifier/controller/controller.dart'; import 'package:aves/widgets/common/magnifier/scale/scale_level.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; /// Internal class to wrap custom scale boundaries (min, max and initial) /// Also, stores values regarding the two sizes: the container and the child. -class ScaleBoundaries { +@immutable +class ScaleBoundaries extends Equatable { final ScaleLevel _minScale; final ScaleLevel _maxScale; final ScaleLevel _initialScale; final Size viewportSize; final Size childSize; + @override + List get props => [_minScale, _maxScale, _initialScale, viewportSize, childSize]; + const ScaleBoundaries({ required ScaleLevel minScale, required ScaleLevel maxScale, @@ -57,13 +62,4 @@ class ScaleBoundaries { Offset childToStatePosition(double scale, Offset childPosition) { return (_childCenter - childPosition) * scale; } - - @override - bool operator ==(Object other) => identical(this, other) || other is ScaleBoundaries && runtimeType == other.runtimeType && _minScale == other._minScale && _maxScale == other._maxScale && _initialScale == other._initialScale && viewportSize == other.viewportSize && childSize == other.childSize; - - @override - int get hashCode => hashValues(_minScale, _maxScale, _initialScale, viewportSize, childSize); - - @override - String toString() => '$runtimeType#${shortHash(this)}{viewportSize=$viewportSize, childSize=$childSize, initialScale=$initialScale, minScale=$minScale, maxScale=$maxScale}'; } diff --git a/lib/widgets/common/magnifier/scale/scale_level.dart b/lib/widgets/common/magnifier/scale/scale_level.dart index ac7b5b1a4..a076181ae 100644 --- a/lib/widgets/common/magnifier/scale/scale_level.dart +++ b/lib/widgets/common/magnifier/scale/scale_level.dart @@ -1,12 +1,17 @@ import 'dart:math'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -class ScaleLevel { +@immutable +class ScaleLevel extends Equatable { final ScaleReference ref; final double factor; + @override + List get props => [ref, factor]; + const ScaleLevel({ this.ref = ScaleReference.absolute, this.factor = 1.0, @@ -15,18 +20,6 @@ class ScaleLevel { static double scaleForContained(Size containerSize, Size childSize) => min(containerSize.width / childSize.width, containerSize.height / childSize.height); static double scaleForCovering(Size containerSize, Size childSize) => max(containerSize.width / childSize.width, containerSize.height / childSize.height); - - @override - String toString() => '$runtimeType#${shortHash(this)}{ref=$ref, factor=$factor}'; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is ScaleLevel && other.ref == ref && other.factor == factor; - } - - @override - int get hashCode => hashValues(ref, factor); } enum ScaleReference { absolute, contained, covered } diff --git a/lib/widgets/common/magnifier/scale/state.dart b/lib/widgets/common/magnifier/scale/state.dart index 1a587bb50..fed627b22 100644 --- a/lib/widgets/common/magnifier/scale/state.dart +++ b/lib/widgets/common/magnifier/scale/state.dart @@ -1,29 +1,24 @@ import 'dart:ui'; import 'package:aves/widgets/common/magnifier/controller/state.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @immutable -class ScaleStateChange { - const ScaleStateChange({ - required this.state, - required this.source, - this.childFocalPoint, - }); - +class ScaleStateChange extends Equatable { final ScaleState state; final ChangeSource source; final Offset? childFocalPoint; @override - bool operator ==(Object other) => identical(this, other) || other is ScaleStateChange && runtimeType == other.runtimeType && state == other.state && childFocalPoint == other.childFocalPoint; + List get props => [state, source, childFocalPoint]; - @override - int get hashCode => hashValues(state, source, childFocalPoint); - - @override - String toString() => '$runtimeType#${shortHash(this)}{scaleState: $state, source: $source, childFocalPoint: $childFocalPoint}'; + const ScaleStateChange({ + required this.state, + required this.source, + this.childFocalPoint, + }); } enum ScaleState { diff --git a/lib/widgets/filter_grids/common/section_keys.dart b/lib/widgets/filter_grids/common/section_keys.dart index 79154fd29..4b883709b 100644 --- a/lib/widgets/filter_grids/common/section_keys.dart +++ b/lib/widgets/filter_grids/common/section_keys.dart @@ -2,29 +2,20 @@ import 'package:aves/model/source/section_keys.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/foundation.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -class ChipSectionKey extends SectionKey { +class ChipSectionKey extends SectionKey with EquatableMixin { final String title; + @override + List get props => [title]; + const ChipSectionKey({ this.title = '', }); Widget? get leading => null; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is ChipSectionKey && other.title == title; - } - - @override - int get hashCode => title.hashCode; - - @override - String toString() => '$runtimeType#${shortHash(this)}{title=$title}'; } class AlbumImportanceSectionKey extends ChipSectionKey { diff --git a/lib/widgets/viewer/hero.dart b/lib/widgets/viewer/hero.dart index b502a0a93..6c828d519 100644 --- a/lib/widgets/viewer/hero.dart +++ b/lib/widgets/viewer/hero.dart @@ -1,21 +1,17 @@ import 'package:aves/model/entry.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; -class HeroInfo { +@immutable +class HeroInfo extends Equatable { // hero tag should include a collection identifier, so that it animates // between different views of the entry in the same collection (e.g. thumbnails <-> viewer) // but not between different collection instances, even with the same attributes (e.g. reloading collection page via drawer) final int? collectionId; final AvesEntry? entry; + @override + List get props => [collectionId, entry?.uri]; + const HeroInfo(this.collectionId, this.entry); - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is HeroInfo && other.collectionId == collectionId && other.entry == entry; - } - - @override - int get hashCode => hashValues(collectionId, entry); } diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index c4c056260..81fbde239 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -12,13 +12,18 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_ns/photoshop.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/tiff.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/xmp.dart'; import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -class XmpNamespace { +@immutable +class XmpNamespace extends Equatable { final String namespace; final Map rawProps; + @override + List get props => [namespace]; + const XmpNamespace(this.namespace, this.rawProps); factory XmpNamespace.create(String namespace, Map rawProps) { @@ -119,20 +124,6 @@ class XmpNamespace { String formatValue(XmpProp prop) => prop.value; Map linkifyValues(List props) => {}; - - // identity - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is XmpNamespace && other.namespace == namespace; - } - - @override - int get hashCode => namespace.hashCode; - - @override - String toString() => '$runtimeType#${shortHash(this)}{namespace=$namespace}'; } class XmpProp { diff --git a/lib/widgets/viewer/visual/subtitle/line.dart b/lib/widgets/viewer/visual/subtitle/line.dart index c7fb5c60a..dd44cb49c 100644 --- a/lib/widgets/viewer/visual/subtitle/line.dart +++ b/lib/widgets/viewer/visual/subtitle/line.dart @@ -1,13 +1,17 @@ import 'package:aves/widgets/viewer/visual/subtitle/span.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @immutable -class StyledSubtitleLine with Diagnosticable { +class StyledSubtitleLine extends Equatable with Diagnosticable { final List spans; final List? clip; final Offset? position; + @override + List get props => [spans, clip, position]; + const StyledSubtitleLine({ required this.spans, this.clip, @@ -33,17 +37,4 @@ class StyledSubtitleLine with Diagnosticable { properties.add(DiagnosticsProperty>('clip', clip)); properties.add(DiagnosticsProperty('position', position)); } - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is StyledSubtitleLine && other.spans == spans && other.clip == clip && other.position == position; - } - - @override - int get hashCode => hashValues( - spans, - clip, - position, - ); } diff --git a/lib/widgets/viewer/visual/subtitle/span.dart b/lib/widgets/viewer/visual/subtitle/span.dart index b83389591..8cee296a0 100644 --- a/lib/widgets/viewer/visual/subtitle/span.dart +++ b/lib/widgets/viewer/visual/subtitle/span.dart @@ -1,12 +1,16 @@ import 'package:aves/widgets/viewer/visual/subtitle/style.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @immutable -class StyledSubtitleSpan with Diagnosticable { +class StyledSubtitleSpan extends Equatable with Diagnosticable { final TextSpan textSpan; final SubtitleStyle extraStyle; + @override + List get props => [textSpan, extraStyle]; + const StyledSubtitleSpan({ required this.textSpan, required this.extraStyle, @@ -28,16 +32,4 @@ class StyledSubtitleSpan with Diagnosticable { properties.add(DiagnosticsProperty('textSpan', textSpan)); properties.add(DiagnosticsProperty('extraStyle', extraStyle)); } - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is StyledSubtitleSpan && other.textSpan == textSpan && other.extraStyle == extraStyle; - } - - @override - int get hashCode => hashValues( - textSpan, - extraStyle, - ); } diff --git a/lib/widgets/viewer/visual/subtitle/style.dart b/lib/widgets/viewer/visual/subtitle/style.dart index af9a19948..0519cfe17 100644 --- a/lib/widgets/viewer/visual/subtitle/style.dart +++ b/lib/widgets/viewer/visual/subtitle/style.dart @@ -1,8 +1,9 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @immutable -class SubtitleStyle with Diagnosticable { +class SubtitleStyle extends Equatable with Diagnosticable { final TextAlign? hAlign; final TextAlignVertical? vAlign; final Color? borderColor; @@ -15,6 +16,23 @@ class SubtitleStyle with Diagnosticable { bool get shearing => (shearX ?? 0) != 0 || (shearY ?? 0) != 0; + @override + List get props => [ + hAlign, + vAlign, + borderColor, + borderWidth, + edgeBlur, + rotationX, + rotationY, + rotationZ, + scaleX, + scaleY, + shearX, + shearY, + drawingPaths?.length, + ]; + const SubtitleStyle({ this.hAlign, this.vAlign, @@ -80,27 +98,4 @@ class SubtitleStyle with Diagnosticable { properties.add(DoubleProperty('shearY', shearY)); properties.add(DiagnosticsProperty>('drawingPaths', drawingPaths)); } - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is SubtitleStyle && other.hAlign == hAlign && other.vAlign == vAlign && other.borderColor == borderColor && other.borderWidth == borderWidth && other.edgeBlur == edgeBlur && other.rotationX == rotationX && other.rotationY == rotationY && other.rotationZ == rotationZ && other.scaleX == scaleX && other.scaleY == scaleY && other.shearX == shearX && other.shearY == shearY && other.drawingPaths == drawingPaths; - } - - @override - int get hashCode => hashValues( - hAlign, - vAlign, - borderColor, - borderWidth, - edgeBlur, - rotationX, - rotationY, - rotationZ, - scaleX, - scaleY, - shearX, - shearY, - drawingPaths?.length, - ); } diff --git a/pubspec.lock b/pubspec.lock index 4c5e02561..0d135e585 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -183,6 +183,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" event_bus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index dceee2b57..dc926fcf8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: connectivity_plus: country_code: decorated_icon: + equatable: event_bus: expansion_tile_card: git: