test fix, tuple record migration wip

This commit is contained in:
Thibault Deckers 2023-08-16 22:29:52 +02:00
parent 7afbdfaa84
commit 1117da068b
13 changed files with 61 additions and 63 deletions

View file

@ -13,7 +13,6 @@ import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:tuple/tuple.dart';
final Covers covers = Covers._private(); final Covers covers = Covers._private();
@ -40,11 +39,11 @@ class Covers {
Set<CoverRow> get all => Set.unmodifiable(_rows); Set<CoverRow> get all => Set.unmodifiable(_rows);
Tuple3<int?, String?, Color?>? of(CollectionFilter filter) { (int? entryId, String? packageName, Color? color)? of(CollectionFilter filter) {
if (filter is AlbumFilter && vaults.isLocked(filter.album)) return null; if (filter is AlbumFilter && vaults.isLocked(filter.album)) return null;
final row = _rows.firstWhereOrNull((row) => row.filter == filter); final row = _rows.firstWhereOrNull((row) => row.filter == filter);
return row != null ? Tuple3(row.entryId, row.packageName, row.color) : null; return row != null ? (row.entryId, row.packageName, row.color) : null;
} }
Future<void> set({ Future<void> set({
@ -113,7 +112,7 @@ class Covers {
} }
AlbumType effectiveAlbumType(String albumPath) { AlbumType effectiveAlbumType(String albumPath) {
final filterPackage = of(AlbumFilter(albumPath, null))?.item2; final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
if (filterPackage != null) { if (filterPackage != null) {
return filterPackage.isEmpty ? AlbumType.regular : AlbumType.app; return filterPackage.isEmpty ? AlbumType.regular : AlbumType.app;
} else { } else {
@ -122,7 +121,7 @@ class Covers {
} }
String? effectiveAlbumPackage(String albumPath) { String? effectiveAlbumPackage(String albumPath) {
final filterPackage = of(AlbumFilter(albumPath, null))?.item2; final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath); return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath);
} }

View file

@ -64,7 +64,7 @@ class AlbumFilter extends CoveredCollectionFilter {
@override @override
Future<Color> color(BuildContext context) { Future<Color> color(BuildContext context) {
// custom color has precedence over others, even custom app color // custom color has precedence over others, even custom app color
final customColor = covers.of(this)?.item3; final customColor = covers.of(this)?.$3;
if (customColor != null) return SynchronousFuture(customColor); if (customColor != null) return SynchronousFuture(customColor);
final colors = context.read<AvesColorsData>(); final colors = context.read<AvesColorsData>();

View file

@ -157,7 +157,7 @@ abstract class CoveredCollectionFilter extends CollectionFilter {
@override @override
Future<Color> color(BuildContext context) { Future<Color> color(BuildContext context) {
final customColor = covers.of(this)?.item3; final customColor = covers.of(this)?.$3;
if (customColor != null) { if (customColor != null) {
return SynchronousFuture(customColor); return SynchronousFuture(customColor);
} }

View file

@ -274,9 +274,9 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
final existingCover = covers.of(oldFilter); final existingCover = covers.of(oldFilter);
await covers.set( await covers.set(
filter: newFilter, filter: newFilter,
entryId: existingCover?.item1, entryId: existingCover?.$1,
packageName: existingCover?.item2, packageName: existingCover?.$2,
color: existingCover?.item3, color: existingCover?.$3,
); );
renameNewAlbum(sourceAlbum, destinationAlbum); renameNewAlbum(sourceAlbum, destinationAlbum);
@ -547,7 +547,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
} }
AvesEntry? coverEntry(CollectionFilter filter) { AvesEntry? coverEntry(CollectionFilter filter) {
final id = covers.of(filter)?.item1; final id = covers.of(filter)?.$1;
if (id != null) { if (id != null) {
final entry = visibleEntries.firstWhereOrNull((entry) => entry.id == id); final entry = visibleEntries.firstWhereOrNull((entry) => entry.id == id);
if (entry != null) return entry; if (entry != null) return entry;

View file

@ -14,7 +14,6 @@ import 'package:aves/services/common/services.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
mixin LocationMixin on CountryMixin, StateMixin { mixin LocationMixin on CountryMixin, StateMixin {
static const commitCountThreshold = 200; static const commitCountThreshold = 200;
@ -96,16 +95,16 @@ mixin LocationMixin on CountryMixin, StateMixin {
// - 652 calls (22%) when approximating to 2 decimal places (~1km - town or village) // - 652 calls (22%) when approximating to 2 decimal places (~1km - town or village)
// cf https://en.wikipedia.org/wiki/Decimal_degrees#Precision // cf https://en.wikipedia.org/wiki/Decimal_degrees#Precision
final latLngFactor = pow(10, 2); final latLngFactor = pow(10, 2);
Tuple2<int, int> approximateLatLng(AvesEntry entry) { (int latitude, int longitude) approximateLatLng(AvesEntry entry) {
// entry has coordinates // entry has coordinates
final catalogMetadata = entry.catalogMetadata!; final catalogMetadata = entry.catalogMetadata!;
final lat = catalogMetadata.latitude!; final lat = catalogMetadata.latitude!;
final lng = catalogMetadata.longitude!; final lng = catalogMetadata.longitude!;
return Tuple2<int, int>((lat * latLngFactor).round(), (lng * latLngFactor).round()); return ((lat * latLngFactor).round(), (lng * latLngFactor).round());
} }
final located = visibleEntries.where((entry) => entry.hasGps).toSet().difference(todo); final located = visibleEntries.where((entry) => entry.hasGps).toSet().difference(todo);
final knownLocations = <Tuple2<int, int>, AddressDetails?>{}; final knownLocations = <(int, int), AddressDetails?>{};
located.forEach((entry) { located.forEach((entry) {
knownLocations.putIfAbsent(approximateLatLng(entry), () => entry.addressDetails); knownLocations.putIfAbsent(approximateLatLng(entry), () => entry.addressDetails);
}); });

View file

@ -3,13 +3,12 @@ import 'dart:collection';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
final ServicePolicy servicePolicy = ServicePolicy._private(); final ServicePolicy servicePolicy = ServicePolicy._private();
class ServicePolicy { class ServicePolicy {
final StreamController<QueueState> _queueStreamController = StreamController.broadcast(); final StreamController<QueueState> _queueStreamController = StreamController.broadcast();
final Map<Object, Tuple2<int, _Task>> _paused = {}; final Map<Object, (int, _Task)> _paused = {};
final SplayTreeMap<int, LinkedHashMap<Object, _Task>> _queues = SplayTreeMap(); final SplayTreeMap<int, LinkedHashMap<Object, _Task>> _queues = SplayTreeMap();
final LinkedHashMap<Object, _Task> _runningQueue = LinkedHashMap(); final LinkedHashMap<Object, _Task> _runningQueue = LinkedHashMap();
@ -30,8 +29,8 @@ class ServicePolicy {
key ??= platformCall.hashCode; key ??= platformCall.hashCode;
final toResume = _paused.remove(key); final toResume = _paused.remove(key);
if (toResume != null) { if (toResume != null) {
priority = toResume.item1; priority = toResume.$1;
task = toResume.item2 as _Task<T>; task = toResume.$2 as _Task<T>;
completer = task.completer; completer = task.completer;
} else { } else {
completer = Completer<T>(); completer = Completer<T>();
@ -56,8 +55,8 @@ class ServicePolicy {
Future<T>? resume<T>(Object key) { Future<T>? resume<T>(Object key) {
final toResume = _paused.remove(key); final toResume = _paused.remove(key);
if (toResume != null) { if (toResume != null) {
final priority = toResume.item1; final priority = toResume.$1;
final task = toResume.item2 as _Task<T>; final task = toResume.$2 as _Task<T>;
_getQueue(priority)[key] = task; _getQueue(priority)[key] = task;
_pickNext(); _pickNext();
return task.completer.future; return task.completer.future;
@ -97,7 +96,7 @@ class ServicePolicy {
} }
bool pause(Object key, Iterable<int> priorities) { bool pause(Object key, Iterable<int> priorities) {
return _takeOut(key, priorities, (priority, task) => _paused.putIfAbsent(key, () => Tuple2(priority, task))); return _takeOut(key, priorities, (priority, task) => _paused.putIfAbsent(key, () => (priority, task)));
} }
bool isPaused(Object key) => _paused.containsKey(key); bool isPaused(Object key) => _paused.containsKey(key);

View file

@ -1,8 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:tuple/tuple.dart';
int highestPowerOf2(num x) => x < 1 ? 0 : pow(2, (log(x) / ln2).floor()).toInt(); int highestPowerOf2(num x) => x < 1 ? 0 : pow(2, (log(x) / ln2).floor()).toInt();
int smallestPowerOf2(num x) => x < 1 ? 1 : pow(2, (log(x) / ln2).ceil()).toInt(); int smallestPowerOf2(num x) => x < 1 ? 1 : pow(2, (log(x) / ln2).ceil()).toInt();
@ -10,16 +8,16 @@ int smallestPowerOf2(num x) => x < 1 ? 1 : pow(2, (log(x) / ln2).ceil()).toInt()
double roundToPrecision(final double value, {required final int decimals}) => (value * pow(10, decimals)).round() / pow(10, decimals); double roundToPrecision(final double value, {required final int decimals}) => (value * pow(10, decimals)).round() / pow(10, decimals);
// cf https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments // cf https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments
Offset? segmentIntersection(Tuple2<Offset, Offset> s1, Tuple2<Offset, Offset> s2) { Offset? segmentIntersection((Offset, Offset) s1, (Offset, Offset) s2) {
final x1 = s1.item1.dx; final x1 = s1.$1.dx;
final y1 = s1.item1.dy; final y1 = s1.$1.dy;
final x2 = s1.item2.dx; final x2 = s1.$2.dx;
final y2 = s1.item2.dy; final y2 = s1.$2.dy;
final x3 = s2.item1.dx; final x3 = s2.$1.dx;
final y3 = s2.item1.dy; final y3 = s2.$1.dy;
final x4 = s2.item2.dx; final x4 = s2.$2.dx;
final y4 = s2.item2.dy; final y4 = s2.$2.dy;
final a1 = x2 - x1; final a1 = x2 - x1;
final b1 = -(x4 - x3); final b1 = -(x4 - x3);

View file

@ -4,7 +4,6 @@ import 'dart:math';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:tuple/tuple.dart';
class TileExtentController { class TileExtentController {
final String settingsRouteKey; final String settingsRouteKey;
@ -102,7 +101,7 @@ class TileExtentController {
double get effectiveExtentMax => _extentForColumnCount(_effectiveColumnCountMin()); double get effectiveExtentMax => _extentForColumnCount(_effectiveColumnCountMin());
Tuple2<int, int> get effectiveColumnRange => Tuple2(_effectiveColumnCountMin(), _effectiveColumnCountMax()); (int min, int max) get effectiveColumnRange => (_effectiveColumnCountMin(), _effectiveColumnCountMax());
int get columnCount => _effectiveColumnCountForExtent(extentNotifier.value); int get columnCount => _effectiveColumnCountForExtent(extentNotifier.value);

View file

@ -40,7 +40,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
if (_collection != null) { if (_collection != null) {
final entries = _collection.sortedEntries; final entries = _collection.sortedEntries;
if (entries.isNotEmpty) { if (entries.isNotEmpty) {
final coverEntries = _collection.filters.map((filter) => covers.of(filter)?.item1).whereNotNull().map((id) => entries.firstWhereOrNull((entry) => entry.id == id)).whereNotNull(); final coverEntries = _collection.filters.map((filter) => covers.of(filter)?.$1).whereNotNull().map((id) => entries.firstWhereOrNull((entry) => entry.id == id)).whereNotNull();
_coverEntry = coverEntries.firstOrNull ?? entries.first; _coverEntry = coverEntries.firstOrNull ?? entries.first;
} }
} }

View file

@ -70,8 +70,8 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
final extentController = tileExtentController; final extentController = tileExtentController;
final columnRange = extentController.effectiveColumnRange; final columnRange = extentController.effectiveColumnRange;
_columnMin = columnRange.item1; _columnMin = columnRange.$1;
_columnMax = columnRange.item2; _columnMax = columnRange.$2;
} }
@override @override

View file

@ -326,15 +326,15 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
if (!await unlockFilter(context, filter)) return; if (!await unlockFilter(context, filter)) return;
final existingCover = covers.of(filter); final existingCover = covers.of(filter);
final entryId = existingCover?.item1; final entryId = existingCover?.$1;
final customEntry = entryId != null ? context.read<CollectionSource>().visibleEntries.firstWhereOrNull((entry) => entry.id == entryId) : null; final customEntry = entryId != null ? context.read<CollectionSource>().visibleEntries.firstWhereOrNull((entry) => entry.id == entryId) : null;
final selectedCover = await showDialog<Tuple3<AvesEntry?, String?, Color?>>( final selectedCover = await showDialog<Tuple3<AvesEntry?, String?, Color?>>(
context: context, context: context,
builder: (context) => CoverSelectionDialog( builder: (context) => CoverSelectionDialog(
filter: filter, filter: filter,
customEntry: customEntry, customEntry: customEntry,
customPackage: existingCover?.item2, customPackage: existingCover?.$2,
customColor: existingCover?.item3, customColor: existingCover?.$3,
), ),
routeSettings: const RouteSettings(name: CoverSelectionDialog.routeName), routeSettings: const RouteSettings(name: CoverSelectionDialog.routeName),
); );

View file

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/availability.dart'; import 'package:aves/model/availability.dart';
import 'package:aves/model/covers.dart'; import 'package:aves/model/covers.dart';
import 'package:aves/model/db/db_metadata.dart'; import 'package:aves/model/db/db_metadata.dart';
@ -201,7 +202,7 @@ void main() {
await covers.set(filter: albumFilter, entryId: image1.id, packageName: null, color: null); await covers.set(filter: albumFilter, entryId: image1.id, packageName: null, color: null);
expect(covers.count, 1); expect(covers.count, 1);
expect(covers.of(albumFilter)?.item1, image1.id); expect(covers.of(albumFilter)?.$1, image1.id);
await covers.set(filter: albumFilter, entryId: null, packageName: null, color: null); await covers.set(filter: albumFilter, entryId: null, packageName: null, color: null);
expect(covers.count, 0); expect(covers.count, 0);
@ -229,7 +230,7 @@ void main() {
expect(favourites.count, 1); expect(favourites.count, 1);
expect(image1.isFavourite, true); expect(image1.isFavourite, true);
expect(covers.count, 1); expect(covers.count, 1);
expect(covers.of(albumFilter)?.item1, image1.id); expect(covers.of(albumFilter)?.$1, image1.id);
}); });
test('favourites and covers are cleared when removing entries', () async { test('favourites and covers are cleared when removing entries', () async {
@ -348,7 +349,7 @@ void main() {
expect(favourites.count, 1); expect(favourites.count, 1);
expect(image1.isFavourite, true); expect(image1.isFavourite, true);
expect(covers.count, 1); expect(covers.count, 1);
expect(covers.of(albumFilter)?.item1, image1.id); expect(covers.of(albumFilter)?.$1, image1.id);
}); });
testWidgets('unique album names', (tester) async { testWidgets('unique album names', (tester) async {
@ -369,7 +370,10 @@ void main() {
final source = await _initSource(); final source = await _initSource();
await tester.pumpWidget( await tester.pumpWidget(
Builder( Localizations(
locale: AppLocalizations.supportedLocales.first,
delegates: AppLocalizations.localizationsDelegates,
child: Builder(
builder: (context) { builder: (context) {
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Elea/Zeno'), 'Elea/Zeno'); expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Elea/Zeno'), 'Elea/Zeno');
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Citium/Zeno'), 'Citium/Zeno'); expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Citium/Zeno'), 'Citium/Zeno');
@ -386,6 +390,7 @@ void main() {
return const Placeholder(); return const Placeholder();
}, },
), ),
),
); );
}); });
} }

View file

@ -2,7 +2,6 @@ import 'dart:ui';
import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/math_utils.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:tuple/tuple.dart';
void main() { void main() {
test('highest power of 2 that is smaller than or equal to the number', () { test('highest power of 2 that is smaller than or equal to the number', () {
@ -29,8 +28,8 @@ void main() {
}); });
test('segment intersection', () { test('segment intersection', () {
const s1 = Tuple2(Offset(1, 1), Offset(3, 2)); const s1 = (Offset(1, 1), Offset(3, 2));
const s2 = Tuple2(Offset(1, 4), Offset(2, -1)); const s2 = (Offset(1, 4), Offset(2, -1));
expect(segmentIntersection(s1, s2), const Offset(17 / 11, 14 / 11)); expect(segmentIntersection(s1, s2), const Offset(17 / 11, 14 / 11));
}); });
} }