Replace tuple with records

This commit is contained in:
Ming Ming 2024-07-01 03:00:11 +08:00
parent 051c721d33
commit 1870830b39
34 changed files with 142 additions and 173 deletions

View file

@ -19,7 +19,6 @@ import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.dart';
import 'package:np_string/np_string.dart';
import 'package:to_string/to_string.dart';
import 'package:tuple/tuple.dart';
import 'package:woozy_search/woozy_search.dart';
part 'home_search_suggestion.g.dart';
@ -172,17 +171,17 @@ class HomeSearchSuggestionBloc
.map((e) {
if (e.value!.toKeywords().any((k) => k.startsWith(ev.phrase))) {
// prefer names that start exactly with the search phrase
return Tuple2(e.score + 1, e.value);
return (score: e.score + 1, item: e.value);
} else {
return Tuple2(e.score, e.value);
return (score: e.score, item: e.value);
}
})
.sorted((a, b) => b.item1.compareTo(a.item1))
.sorted((a, b) => b.score.compareTo(a.score))
.distinctIf(
(a, b) => identical(a.item2, b.item2),
(a) => a.item2.hashCode,
(a, b) => identical(a.item, b.item),
(a) => a.item.hashCode,
)
.map((e) => e.item2!.toResult())
.map((e) => e.item!.toResult())
.toList();
emit(HomeSearchSuggestionBlocSuccess(matches));
}

View file

@ -6,7 +6,6 @@ import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.dart';
import 'package:np_string/np_string.dart';
import 'package:to_string/to_string.dart';
import 'package:tuple/tuple.dart';
import 'package:woozy_search/woozy_search.dart';
part 'search_suggestion.g.dart';
@ -92,18 +91,18 @@ class SearchSuggestionBloc<T>
if (itemToKeywords(e.value as T)
.any((k) => k.startsWith(ev.phrase))) {
// prefer names that start exactly with the search phrase
return Tuple2(e.score + 1, e.value as T);
return (score: e.score + 1, item: e.value as T);
} else {
return Tuple2(e.score, e.value as T);
return (score: e.score, item: e.value as T);
}
})
.sorted((a, b) => a.item1.compareTo(b.item1))
.sorted((a, b) => a.score.compareTo(b.score))
.reversed
.distinctIf(
(a, b) => identical(a.item2, b.item2),
(a) => a.item2.hashCode,
(a, b) => identical(a.item, b.item),
(a) => a.item.hashCode,
)
.map((e) => e.item2)
.map((e) => e.item)
.toList();
emit(SearchSuggestionBlocSuccess(matches));
_lastSearch = ev;

View file

@ -21,7 +21,6 @@ import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:tuple/tuple.dart';
part 'download_handler.g.dart';
@ -79,7 +78,7 @@ class _DownlaodHandlerAndroid extends _DownloadHandlerBase {
);
final id = await nm.notify(notif);
final successes = <Tuple2<File, dynamic>>[];
final successes = <({File file, dynamic result})>[];
StreamSubscription<DownloadCancelEvent>? subscription;
try {
bool isCancel = false;
@ -117,7 +116,7 @@ class _DownlaodHandlerAndroid extends _DownloadHandlerBase {
}
});
final result = await download();
successes.add(Tuple2(f, result));
successes.add((file: f, result: result));
} on PermissionException catch (_) {
_log.warning("[downloadFiles] Permission not granted");
SnackBarManager().showSnackBar(SnackBar(
@ -143,8 +142,8 @@ class _DownlaodHandlerAndroid extends _DownloadHandlerBase {
} finally {
unawaited(subscription?.cancel());
if (successes.isNotEmpty) {
await _onDownloadSuccessful(successes.map((e) => e.item1).toList(),
successes.map((e) => e.item2).toList(), id);
await _onDownloadSuccessful(successes.map((e) => e.file).toList(),
successes.map((e) => e.result).toList(), id);
} else {
await nm.dismiss(id);
}

View file

@ -9,7 +9,6 @@ import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/type.dart';
import 'package:np_db/np_db.dart';
import 'package:np_string/np_string.dart';
import 'package:tuple/tuple.dart';
part 'upgrader.g.dart';
@ -143,22 +142,22 @@ class AlbumUpgraderV4 implements AlbumUpgrader {
// remove metadata
e.remove("metadata");
if (latestItemTime != null) {
return Tuple2(latestItemTime, e);
return (latestItemTime: latestItemTime, item: e);
} else {
return null;
}
})
.whereType<Tuple2<DateTime, JsonObj>>()
.sorted((a, b) => a.item1.compareTo(b.item1))
.whereType<({DateTime latestItemTime, JsonObj item})>()
.sorted((a, b) => a.latestItemTime.compareTo(b.latestItemTime))
.lastOrNull;
if (latestItem != null) {
// save the latest item time
result["provider"]["content"]["latestItemTime"] =
latestItem.item1.toIso8601String();
latestItem.latestItemTime.toIso8601String();
if (result["coverProvider"]["type"] == "auto") {
// save the cover
result["coverProvider"]["content"]["coverFile"] =
Map.of(latestItem.item2);
Map.of(latestItem.item);
}
}
} catch (e, stackTrace) {

View file

@ -35,7 +35,6 @@ import 'package:np_collection/np_collection.dart';
import 'package:np_common/or_null.dart';
import 'package:np_common/type.dart';
import 'package:np_string/np_string.dart';
import 'package:tuple/tuple.dart';
part 'album.g.dart';
@ -135,20 +134,20 @@ class CollectionAlbumAdapter implements CollectionAdapter {
try {
final group = items
.withIndex()
.groupListsBy((e) => e.item2 is AlbumAdaptedCollectionItem);
.groupListsBy((e) => e.e is AlbumAdaptedCollectionItem);
var failed = 0;
if (group[true]?.isNotEmpty ?? false) {
final newAlbum = await RemoveFromAlbum(_c)(
account,
_provider.album,
group[true]!
.map((e) => e.item2)
.map((e) => e.e)
.cast<AlbumAdaptedCollectionItem>()
.map((e) => e.albumItem)
.toList(),
onError: (i, item, e, stackTrace) {
++failed;
final actualIndex = group[true]![i].item1;
final actualIndex = group[true]![i].i;
try {
onError?.call(actualIndex, items[actualIndex], e, stackTrace);
} catch (e, stackTrace) {
@ -162,8 +161,9 @@ class CollectionAlbumAdapter implements CollectionAdapter {
),
));
}
for (final pair in (group[false] ?? const <Tuple2<int, int>>[])) {
final actualIndex = pair.item1;
for (final (:i, e: _)
in (group[false] ?? const <({int i, CollectionItem e})>[])) {
final actualIndex = i;
onError?.call(
actualIndex,
items[actualIndex],
@ -174,8 +174,8 @@ class CollectionAlbumAdapter implements CollectionAdapter {
}
return (group[true] ?? []).length - failed;
} catch (e, stackTrace) {
for (final pair in items.withIndex()) {
onError?.call(pair.item1, pair.item2, e, stackTrace);
for (final (:i, e: item) in items.withIndex()) {
onError?.call(i, item, e, stackTrace);
}
return 0;
}

View file

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:nc_photos/entity/collection.dart';
import 'package:np_string/np_string.dart';
import 'package:to_string/to_string.dart';
import 'package:tuple/tuple.dart';
part 'util.g.dart';
@ -43,28 +42,28 @@ enum CollectionShareResult {
extension CollectionListExtension on Iterable<Collection> {
List<Collection> sortedBy(CollectionSort by) {
return map<Tuple2<Comparable, Collection>>((e) {
return map<({Comparable comparable, Collection collection})>((e) {
switch (by) {
case CollectionSort.nameAscending:
case CollectionSort.nameDescending:
return Tuple2(e.name.toLowerCase(), e);
return (comparable: e.name.toLowerCase(), collection: e);
case CollectionSort.dateAscending:
case CollectionSort.dateDescending:
return Tuple2(e.contentProvider.lastModified, e);
return (comparable: e.contentProvider.lastModified, collection: e);
}
})
.sorted((a, b) {
final x = by.isAscending() ? a : b;
final y = by.isAscending() ? b : a;
final tmp = x.item1.compareTo(y.item1);
final tmp = x.comparable.compareTo(y.comparable);
if (tmp != 0) {
return tmp;
} else {
return x.item2.name.compareTo(y.item2.name);
return x.collection.name.compareTo(y.collection.name);
}
})
.map((e) => e.item2)
.map((e) => e.collection)
.toList();
}
}

View file

@ -6,7 +6,6 @@ import 'package:nc_photos/entity/collection_item/util.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.dart';
import 'package:tuple/tuple.dart';
part 'sorter.g.dart';
@ -58,24 +57,24 @@ class CollectionTimeSorter implements CollectionSorter {
prevFileTime = e.file.fdDateTime;
}
// for non file items, use the sibling file's time
return Tuple2(prevFileTime, e);
return (dateTime: prevFileTime, item: e);
})
.stableSorted((x, y) {
if (x.item1 == null && y.item1 == null) {
if (x.dateTime == null && y.dateTime == null) {
return 0;
} else if (x.item1 == null) {
} else if (x.dateTime == null) {
return -1;
} else if (y.item1 == null) {
} else if (y.dateTime == null) {
return 1;
} else {
if (isAscending) {
return x.item1!.compareTo(y.item1!);
return x.dateTime!.compareTo(y.dateTime!);
} else {
return y.item1!.compareTo(x.item1!);
return y.dateTime!.compareTo(x.dateTime!);
}
}
})
.map((e) => e.item2)
.map((e) => e.item)
.toList();
}
@ -98,24 +97,24 @@ class CollectionFilenameSorter implements CollectionSorter {
prevFilename = e.file.filename;
}
// for non file items, use the sibling file's name
return Tuple2(prevFilename, e);
return (dateTime: prevFilename, item: e);
})
.stableSorted((x, y) {
if (x.item1 == null && y.item1 == null) {
if (x.dateTime == null && y.dateTime == null) {
return 0;
} else if (x.item1 == null) {
} else if (x.dateTime == null) {
return -1;
} else if (y.item1 == null) {
} else if (y.dateTime == null) {
return 1;
} else {
if (isAscending) {
return compareNatural(x.item1!, y.item1!);
return compareNatural(x.dateTime!, y.dateTime!);
} else {
return compareNatural(y.item1!, x.item1!);
return compareNatural(y.dateTime!, x.dateTime!);
}
}
})
.map((e) => e.item2)
.map((e) => e.item)
.toList();
}

View file

@ -56,8 +56,7 @@ class InternalDownloadHandler {
);
try {
final results = <MapEntry<File, dynamic>>[];
for (final pair in files.withIndex()) {
final i = pair.item1, f = pair.item2;
for (final (:i, e: f) in files.withIndex()) {
controller.add(_DownloadProgress(current: i));
try {
final dynamic result;
@ -122,8 +121,7 @@ class InternalDownloadHandler {
);
try {
final results = <MapEntry<File, dynamic>>[];
for (final pair in files.withIndex()) {
final i = pair.item1, f = pair.item2;
for (final (:i, e: f) in files.withIndex()) {
controller.add(_DownloadProgress(current: i));
try {
download = DownloadFile().build(

View file

@ -4,7 +4,6 @@ import 'package:logging/logging.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.dart';
import 'package:np_string/np_string.dart';
import 'package:tuple/tuple.dart';
import 'package:woozy_search/woozy_search.dart';
part 'suggester.g.dart';
@ -34,18 +33,18 @@ class Suggester<T> {
.map((e) {
if (itemToKeywords(e.value as T).any((k) => k.startsWith(phrase))) {
// prefer names that start exactly with the search phrase
return Tuple2(e.score + 1, e.value as T);
return (score: e.score + 1, item: e.value as T);
} else {
return Tuple2(e.score, e.value as T);
return (score: e.score, item: e.value as T);
}
})
.sorted((a, b) => a.item1.compareTo(b.item1))
.sorted((a, b) => a.score.compareTo(b.score))
.reversed
.distinctIf(
(a, b) => identical(a.item2, b.item2),
(a) => a.item2.hashCode,
(a, b) => identical(a.item, b.item),
(a) => a.item.hashCode,
)
.map((e) => e.item2)
.map((e) => e.item)
.toList();
return matches;
}

View file

@ -39,9 +39,7 @@ class Remove {
await _cleanUpAlbums(account, files);
}
var count = 0;
for (final pair in files.withIndex()) {
final i = pair.item1;
final f = pair.item2;
for (final (:i, e: f) in files.withIndex()) {
try {
await _c.fileRepo2.remove(account, f);
++count;

View file

@ -11,7 +11,6 @@ import 'package:nc_photos/progress_util.dart';
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
import 'package:nc_photos/use_case/ls_single_file.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:tuple/tuple.dart';
part 'sync_dir.g.dart';
@ -52,7 +51,7 @@ class SyncDir {
ValueChanged<Progress>? onProgressUpdate,
}) async {
final status = await _checkContentUpdated(account, remoteDir, dirCache);
if (!status.item1) {
if (!status.isUpdated) {
_log.finer("[_syncDir] Dir unchanged: ${remoteDir.path}");
return false;
}
@ -60,7 +59,7 @@ class SyncDir {
final dataSrc = FileCachedDataSource(_c, shouldCheckCache: true);
final syncState = await dataSrc.beginSync(account, remoteDir,
remoteTouchEtag: status.item2);
remoteTouchEtag: status.touchResult);
final children = syncState.files;
if (!isRecursive) {
await dataSrc.concludeSync(syncState);
@ -97,19 +96,19 @@ class SyncDir {
return true;
}
Future<Tuple2<bool, String?>> _checkContentUpdated(
Future<({bool isUpdated, String? touchResult})> _checkContentUpdated(
Account account, File remoteDir, Map<int, String> dirCache) async {
String? touchResult;
try {
touchResult = await _c.touchManager.checkTouchEtag(account, remoteDir);
if (touchResult == null &&
dirCache[remoteDir.fileId!] == remoteDir.etag!) {
return const Tuple2(false, null);
return const (isUpdated: false, touchResult: null);
}
} catch (e, stackTrace) {
_log.severe("[_isContentUpdated] Uncaught exception", e, stackTrace);
}
return Tuple2(true, touchResult);
return (isUpdated: true, touchResult: touchResult);
}
Future<Map<int, String>> _queryAllDirEtags(

View file

@ -38,7 +38,6 @@ import 'package:np_codegen/np_codegen.dart';
import 'package:np_collection/np_collection.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:to_string/to_string.dart';
import 'package:tuple/tuple.dart';
part 'file_sharer_dialog.g.dart';
part 'file_sharer_dialog/bloc.dart';

View file

@ -97,9 +97,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
emit(state.copyWith(
fileState: _FileState.init(count: files.length),
));
final results = <Tuple2<FileDescriptor, dynamic>>[];
for (final pair in files.withIndex()) {
final i = pair.item1, f = pair.item2;
final results = <({FileDescriptor file, dynamic result})>[];
for (final (:i, e: f) in files.withIndex()) {
emit(state.copyWith(
fileState: state.fileState?.copyWith(
index: i,
@ -124,7 +123,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
if (state.fileState?.shouldRun == false) {
throw const JobCanceledException();
}
results.add(Tuple2(f, result));
results.add((file: f, result: result));
} on PermissionException catch (e, stackTrace) {
_log.warning("[_doShareFile] Permission not granted");
emit(state.copyWith(error: ExceptionEvent(e, stackTrace)));
@ -141,7 +140,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
}
if (results.isNotEmpty) {
final share = AndroidFileShare(results
.map((e) => AndroidFileShareFile(e.item2 as String, e.item1.fdMime))
.map((e) => AndroidFileShareFile(e.result as String, e.file.fdMime))
.toList());
unawaited(share.share());
}
@ -153,9 +152,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
emit(state.copyWith(
previewState: _PreviewState(index: 0, count: files.length),
));
final results = <Tuple2<FileDescriptor, dynamic>>[];
for (final pair in files.withIndex()) {
final i = pair.item1, f = pair.item2;
final results = <({FileDescriptor file, dynamic result})>[];
for (final (:i, e: f) in files.withIndex()) {
emit(state.copyWith(
previewState: state.previewState?.copyWith(index: i),
));
@ -166,7 +164,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
} else {
uri = await DownloadFile()(account, f, shouldNotify: false);
}
results.add(Tuple2(f, uri));
results.add((file: f, result: uri));
} catch (e, stackTrace) {
_log.shout(
"[_doSharePreview] Failed while DownloadPreview", e, stackTrace);
@ -175,7 +173,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
}
if (results.isNotEmpty) {
final share = AndroidFileShare(results
.map((e) => AndroidFileShareFile(e.item2 as String, e.item1.fdMime))
.map((e) => AndroidFileShareFile(e.result as String, e.file.fdMime))
.toList());
unawaited(share.share());
}

View file

@ -477,11 +477,12 @@ class _Bloc extends Bloc<_Event, _State>
month: localToday.month,
day: localToday.day,
cover: e.value
.map((e) => Tuple2(e.bestDateTime.difference(center), e))
.sorted((a, b) => a.item1.compareTo(b.item1))
.map((e) =>
(comparable: e.bestDateTime.difference(center), item: e))
.sorted((a, b) => a.comparable.compareTo(b.comparable))
.firstOrNull
?.let((e) => DbFileDescriptorConverter.fromDb(
account.userId.toString(), e.item2)),
account.userId.toString(), e.item)),
),
);
}).toList(),

View file

@ -67,7 +67,6 @@ import 'package:np_db/np_db.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:np_ui/np_ui.dart';
import 'package:to_string/to_string.dart';
import 'package:tuple/tuple.dart';
import 'package:visibility_detector/visibility_detector.dart';
part 'home_photos/app_bar.dart';

View file

@ -12,7 +12,6 @@ import 'package:nc_photos/widget/page_visibility_mixin.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_ui/np_ui.dart';
import 'package:to_string/to_string.dart';
import 'package:tuple/tuple.dart';
part 'enhancement/bloc.dart';
part 'enhancement/state_event.dart';
@ -135,9 +134,9 @@ class _WrappedEnhancementSettingsState
_SizeSlider(
initialWidth: initialSize.width,
initialHeight: initialSize.height,
onChanged: (value) {
width = value.item1;
height = value.item2;
onChanged: (size) {
width = size.w;
height = size.h;
},
)
],
@ -175,7 +174,7 @@ class _SizeSlider extends StatefulWidget {
final int initialWidth;
final int initialHeight;
final ValueChanged<Tuple2<int, int>>? onChanged;
final ValueChanged<({int w, int h})>? onChanged;
}
class _SizeSliderState extends State<_SizeSlider> {
@ -202,8 +201,8 @@ class _SizeSliderState extends State<_SizeSlider> {
onChangeEnd: (value) async {
final resolution = sliderValueToResolution(value.toInt());
setState(() {
_width = resolution.item1;
_height = resolution.item2;
_width = resolution.w;
_height = resolution.h;
});
widget.onChanged?.call(resolution);
},
@ -212,22 +211,22 @@ class _SizeSliderState extends State<_SizeSlider> {
);
}
static Tuple2<int, int> sliderValueToResolution(int value) {
static ({int w, int h}) sliderValueToResolution(int value) {
switch (value) {
case -3:
return const Tuple2(1024, 768);
return const (w: 1024, h: 768);
case -2:
return const Tuple2(1280, 960);
return const (w: 1280, h: 960);
case -1:
return const Tuple2(1600, 1200);
return const (w: 1600, h: 1200);
case 1:
return const Tuple2(2560, 1920);
return const (w: 2560, h: 1920);
case 2:
return const Tuple2(3200, 2400);
return const (w: 3200, h: 2400);
case 3:
return const Tuple2(4096, 3072);
return const (w: 4096, h: 3072);
default:
return const Tuple2(2048, 1536);
return const (w: 2048, h: 1536);
}
}

View file

@ -40,7 +40,6 @@ import 'package:np_platform_util/np_platform_util.dart';
import 'package:np_string/np_string.dart';
import 'package:np_ui/np_ui.dart';
import 'package:path/path.dart' as path_lib;
import 'package:tuple/tuple.dart';
part 'viewer_detail_pane.g.dart';
@ -405,7 +404,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
final lng = exif.gpsLongitudeDeg;
if (lat != null && lng != null) {
_log.fine("GPS: ($lat, $lng)");
_gps = Tuple2(lat, lng);
_gps = (lat: lat, lng: lng);
_location = _file!.location;
}
}
@ -503,7 +502,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
if (getRawPlatform() == NpPlatform.android) {
final intent = AndroidIntent(
action: "action_view",
data: Uri.encodeFull("geo:${_gps!.item1},${_gps!.item2}?z=16"),
data: Uri.encodeFull("geo:${_gps!.lat},${_gps!.lng}?z=16"),
);
intent.launch();
}
@ -597,7 +596,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
String? _exposureTime;
double? _focalLength;
int? _isoSpeedRatings;
Tuple2<double, double>? _gps;
({double lat, double lng})? _gps;
ImageLocation? _location;
final _tags = <String>[];

View file

@ -1660,14 +1660,6 @@ packages:
url: "https://gitlab.com/nkming2/dart-to-string"
source: git
version: "1.0.0"
tuple:
dependency: "direct main"
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:

View file

@ -150,7 +150,6 @@ dependencies:
url: https://gitlab.com/nkming2/dart-to-string
ref: to_string-1.0.0
path: to_string
tuple: ^2.0.2
url_launcher: ^6.2.6
uuid: ^3.0.7
video_player:

View file

@ -28,7 +28,6 @@ import 'package:np_db_sqlite/np_db_sqlite.dart';
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
import 'package:np_geocoder/np_geocoder.dart';
import 'package:np_string/np_string.dart';
import 'package:tuple/tuple.dart';
part 'test_compat_util.dart';
@ -589,10 +588,11 @@ Future<Map<File, Set<File>>> listSqliteDbDirs(compat.SqliteDb db) async {
}).get());
final dirQuery = db.select(db.dirFiles);
final dirs = await dirQuery.map((r) => Tuple2(r.dir, r.child)).get();
final dirs =
await dirQuery.map((r) => (dirRowId: r.dir, childRowId: r.child)).get();
final result = <File, Set<File>>{};
for (final d in dirs) {
(result[fileMap[d.item1]!] ??= <File>{}).add(fileMap[d.item2]!);
for (final (:dirRowId, :childRowId) in dirs) {
(result[fileMap[dirRowId]!] ??= <File>{}).add(fileMap[childRowId]!);
}
return result;
}
@ -616,18 +616,19 @@ Future<Set<Album>> listSqliteDbAlbums(compat.SqliteDb db) async {
null,
),
);
return Tuple2(
r.read(db.albums.rowId)!,
return (
rowId: r.read(db.albums.rowId)!,
album:
_SqliteAlbumConverter.fromSql(r.readTable(db.albums), albumFile, []),
);
}).get();
final results = <Album>{};
for (final a in albums) {
for (final (:rowId, :album) in albums) {
final shareQuery = db.select(db.albumShares)
..where((t) => t.album.equals(a.item1));
..where((t) => t.album.equals(rowId));
final dbShares = await shareQuery.get();
results.add(a.item2.copyWith(
results.add(album.copyWith(
lastUpdated: const OrNull(null),
shares: dbShares.isEmpty
? null
@ -635,7 +636,8 @@ Future<Set<Album>> listSqliteDbAlbums(compat.SqliteDb db) async {
.map((s) => AlbumShare(
userId: s.userId.toCi(),
displayName: s.displayName,
sharedAt: s.sharedAt))
sharedAt: s.sharedAt,
))
.toList()),
));
}

View file

@ -8,7 +8,6 @@ import 'package:nc_photos/use_case/face_recognition_person/sync_face_recognition
import 'package:np_db/np_db.dart';
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
import 'package:test/test.dart';
import 'package:tuple/tuple.dart';
import '../../mock_type.dart';
import '../../test_util.dart' as util;
@ -157,13 +156,15 @@ Future<Map<String, Set<DbFaceRecognitionPerson>>> _listSqliteDbPersons(
db.accounts.rowId.equalsExp(db.faceRecognitionPersons.account)),
]);
final result = await query
.map((r) => Tuple2(
r.readTable(db.accounts), r.readTable(db.faceRecognitionPersons)))
.map((r) => (
account: r.readTable(db.accounts),
faceRecognitionPerson: r.readTable(db.faceRecognitionPersons),
))
.get();
final product = <String, Set<DbFaceRecognitionPerson>>{};
for (final r in result) {
(product[r.item1.userId] ??= {})
.add(compat.FaceRecognitionPersonConverter.fromSql(r.item2));
(product[r.account.userId] ??= {}).add(
compat.FaceRecognitionPersonConverter.fromSql(r.faceRecognitionPerson));
}
return product;
}

View file

@ -8,7 +8,6 @@ import 'package:nc_photos/use_case/sync_tag.dart';
import 'package:np_db/np_db.dart';
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
import 'package:test/test.dart';
import 'package:tuple/tuple.dart';
import '../mock_type.dart';
import '../test_util.dart' as util;
@ -175,11 +174,11 @@ Future<Map<String, Set<DbTag>>> _listSqliteDbTags(compat.SqliteDb db) async {
sql.innerJoin(db.servers, db.servers.rowId.equalsExp(db.tags.server)),
]);
final result = await query
.map((r) => Tuple2(r.readTable(db.servers), r.readTable(db.tags)))
.map((r) => (server: r.readTable(db.servers), tag: r.readTable(db.tags)))
.get();
final product = <String, Set<DbTag>>{};
for (final r in result) {
(product[r.item1.address] ??= {}).add(compat.TagConverter.fromSql(r.item2));
(product[r.server.address] ??= {}).add(compat.TagConverter.fromSql(r.tag));
}
return product;
}

View file

@ -28,13 +28,13 @@ Future<List<T>> waitOr<T>(
}
}
for (final p in futures.withIndex()) {
for (final (:i, :e) in futures.withIndex()) {
unawaited(
p.item2.then((value) {
results[p.item1] = value;
e.then((value) {
results[i] = value;
onResult();
}).onError((error, stackTrace) {
results[p.item1] = onError(error!, stackTrace);
results[i] = onError(error!, stackTrace);
onResult();
}),
);

View file

@ -4,7 +4,6 @@ import 'dart:collection';
import 'package:collection/collection.dart';
import 'package:np_collection/src/list_extension.dart';
import 'package:quiver/iterables.dart';
import 'package:tuple/tuple.dart';
extension IterableExtension<T> on Iterable<T> {
/// Return a new stable sorted list
@ -15,7 +14,7 @@ extension IterableExtension<T> on Iterable<T> {
/// toString for each items
String toReadableString() => "[${join(', ')}]";
Iterable<Tuple2<int, T>> withIndex() => mapIndexed((i, e) => Tuple2(i, e));
Iterable<({int i, T e})> withIndex() => mapIndexed((i, e) => (i: i, e: e));
/// Whether the collection contains an element equal to [element] using the
/// equality function [equalFn]

View file

@ -12,7 +12,6 @@ dependencies:
np_math:
path: ../np_math
quiver: ^3.2.1
tuple: ^2.0.2
dev_dependencies:
np_lints:

View file

@ -2,18 +2,17 @@ import 'package:np_collection/src/iterable_extension.dart';
import 'package:np_math/np_math.dart';
import 'package:quiver/core.dart';
import 'package:test/test.dart';
import 'package:tuple/tuple.dart';
void main() {
group("IterableExtension", () {
test("withIndex", () {
final src = [1, 4, 5, 2, 3];
final result = src.withIndex().toList();
expect(result[0], const Tuple2(0, 1));
expect(result[1], const Tuple2(1, 4));
expect(result[2], const Tuple2(2, 5));
expect(result[3], const Tuple2(3, 2));
expect(result[4], const Tuple2(4, 3));
expect(result[0], const (i: 0, e: 1));
expect(result[1], const (i: 1, e: 4));
expect(result[2], const (i: 2, e: 5));
expect(result[3], const (i: 3, e: 2));
expect(result[4], const (i: 4, e: 3));
});
test("containsIf", () {

View file

@ -8,7 +8,7 @@ extension SqliteDbCompatExtension on SqliteDb {
final count = await countQ.map((r) => r.read(countExp)!).getSingle();
onProgress?.call(0, count);
final dateTimeUpdates = <Tuple2<int, DateTime>>[];
final dateTimeUpdates = <({int rowId, DateTime dateTime})>[];
final imageRemoves = <int>[];
for (var i = 0; i < count; i += 1000) {
final q = select(files).join([
@ -40,7 +40,10 @@ extension SqliteDbCompatExtension on SqliteDb {
);
if (f.accountFile.bestDateTime != bestDateTime) {
// need update
dateTimeUpdates.add(Tuple2(f.accountFile.rowId, bestDateTime));
dateTimeUpdates.add((
rowId: f.accountFile.rowId,
dateTime: bestDateTime,
));
}
if (f.file.contentType == "image/heic" &&
@ -56,7 +59,7 @@ extension SqliteDbCompatExtension on SqliteDb {
"[migrateV55] ${dateTimeUpdates.length} rows require updating, ${imageRemoves.length} rows require removing");
if (kDebugMode) {
_log.fine(
"[migrateV55] dateTimeUpdates: ${dateTimeUpdates.map((e) => e.item1).toReadableString()}");
"[migrateV55] dateTimeUpdates: ${dateTimeUpdates.map((e) => e.rowId).toReadableString()}");
_log.fine(
"[migrateV55] imageRemoves: ${imageRemoves.map((e) => e).toReadableString()}");
}
@ -65,9 +68,9 @@ extension SqliteDbCompatExtension on SqliteDb {
batch.update(
accountFiles,
AccountFilesCompanion(
bestDateTime: Value(pair.item2),
bestDateTime: Value(pair.dateTime),
),
where: ($AccountFilesTable table) => table.rowId.equals(pair.item1),
where: ($AccountFilesTable table) => table.rowId.equals(pair.rowId),
);
}
for (final r in imageRemoves) {

View file

@ -22,7 +22,6 @@ import 'package:np_db_sqlite/src/util.dart';
import 'package:np_geocoder/np_geocoder.dart';
import 'package:np_platform_lock/np_platform_lock.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:tuple/tuple.dart';
part 'database/account_extension.dart';
part 'database/album_extension.dart';

View file

@ -50,7 +50,6 @@ dependencies:
url: https://gitlab.com/nkming2/dart-to-string
ref: to_string-1.0.0
path: to_string
tuple: ^2.0.2
dev_dependencies:
build_runner: ^2.4.9

View file

@ -4,7 +4,6 @@ import 'package:np_gps_map/src/native/google_gps_map.dart'
if (dart.library.html) 'package:np_gps_map/src/web/google_gps_map.dart';
import 'package:np_gps_map/src/osm_gps_map.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:tuple/tuple.dart';
enum GpsMapProvider {
google,
@ -50,7 +49,7 @@ class GpsMap extends StatelessWidget {
final GpsMapProvider providerHint;
/// A pair of latitude and longitude coordinates, stored as degrees
final Tuple2<double, double> center;
final ({double lat, double lng}) center;
final double zoom;
final void Function()? onTap;

View file

@ -1,6 +1,5 @@
import 'package:flutter/widgets.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:tuple/tuple.dart';
class GoogleGpsMap extends StatelessWidget {
const GoogleGpsMap({
@ -12,7 +11,7 @@ class GoogleGpsMap extends StatelessWidget {
@override
Widget build(BuildContext context) {
final centerLl = LatLng(center.item1, center.item2);
final centerLl = LatLng(center.lat, center.lng);
return GoogleMap(
compassEnabled: false,
mapToolbarEnabled: false,
@ -41,7 +40,7 @@ class GoogleGpsMap extends StatelessWidget {
);
}
final Tuple2<double, double> center;
final ({double lat, double lng}) center;
final double zoom;
final VoidCallback? onTap;
}

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:tuple/tuple.dart';
import 'package:url_launcher/url_launcher_string.dart';
class OsmGpsMap extends StatelessWidget {
@ -15,11 +14,11 @@ class OsmGpsMap extends StatelessWidget {
@override
Widget build(BuildContext context) {
const double pinSize = 48;
final centerLl = LatLng(center.item1, center.item2);
final centerLl = LatLng(center.lat, center.lng);
return GestureDetector(
onTap: () {
launchUrlString(
"https://www.openstreetmap.org/?mlat=${center.item1}&mlon=${center.item2}#map=${zoom.toInt()}/${center.item1}/${center.item2}",
"https://www.openstreetmap.org/?mlat=${center.lat}&mlon=${center.lng}#map=${zoom.toInt()}/${center.lat}/${center.lng}",
mode: LaunchMode.externalApplication,
);
},
@ -62,7 +61,7 @@ class OsmGpsMap extends StatelessWidget {
);
}
final Tuple2<double, double> center;
final ({double lat, double lng}) center;
final double zoom;
final void Function()? onTap;
}

View file

@ -4,7 +4,6 @@ import 'dart:html';
import 'package:flutter/widgets.dart';
import 'package:np_gps_map/src/ui_hack.dart' if (dart.library.html) 'dart:ui'
as ui;
import 'package:tuple/tuple.dart';
class GoogleGpsMap extends StatefulWidget {
const GoogleGpsMap({
@ -17,7 +16,7 @@ class GoogleGpsMap extends StatefulWidget {
@override
State<StatefulWidget> createState() => _GoogleGpsMapState();
final Tuple2<double, double> center;
final ({double lat, double lng}) center;
final double zoom;
final void Function()? onTap;
}
@ -28,7 +27,7 @@ class _GoogleGpsMapState extends State<GoogleGpsMap> {
super.initState();
final iframe = IFrameElement()
..src = "https://www.google.com/maps/embed/v1/place?key=$_apiKey"
"&q=${widget.center.item1},${widget.center.item2}"
"&q=${widget.center.lat},${widget.center.lng}"
"&zoom=${widget.zoom}"
..style.border = "none";
ui.platformViewRegistry.registerViewFactory(viewType, (_) => iframe);
@ -44,5 +43,5 @@ class _GoogleGpsMapState extends State<GoogleGpsMap> {
static const _apiKey = "";
String get viewType =>
"googleMapIframe(${widget.center.item1},${widget.center.item2})";
"googleMapIframe(${widget.center.lat},${widget.center.lng})";
}

View file

@ -18,7 +18,6 @@ dependencies:
path: ../np_async
np_platform_util:
path: ../np_platform_util
tuple: ^2.0.2
url_launcher: ^6.1.11
dev_dependencies: