mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
fix(mobile): handle readonly and offline assets (#5565)
* feat: add isReadOnly and isOffline fields to Asset collection * refactor: move asset iterable filters to extension * hide asset actions based on offline and readOnly fields * pr changes * chore: doc comments --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
56cde0438c
commit
a233e176e5
@ -189,6 +189,10 @@
|
|||||||
"home_page_building_timeline": "Building the timeline",
|
"home_page_building_timeline": "Building the timeline",
|
||||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||||
|
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||||
|
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||||
|
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
|
||||||
|
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
|
||||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
|
|
||||||
extension ListExtension<E> on List<E> {
|
extension ListExtension<E> on List<E> {
|
||||||
List<E> uniqueConsecutive({
|
List<E> uniqueConsecutive({
|
||||||
@ -39,3 +41,58 @@ extension IntListExtension on Iterable<int> {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AssetListExtension on Iterable<Asset> {
|
||||||
|
/// Returns the assets that are already available in the Immich server
|
||||||
|
Iterable<Asset> remoteOnly({
|
||||||
|
void Function()? errorCallback,
|
||||||
|
}) {
|
||||||
|
final bool onlyRemote = every((e) => e.isRemote);
|
||||||
|
if (!onlyRemote) {
|
||||||
|
if (errorCallback != null) errorCallback();
|
||||||
|
return where((a) => a.isRemote);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the assets that are owned by the user passed to the [owner] param
|
||||||
|
/// If [owner] is null, an empty list is returned
|
||||||
|
Iterable<Asset> ownedOnly(
|
||||||
|
User? owner, {
|
||||||
|
void Function()? errorCallback,
|
||||||
|
}) {
|
||||||
|
if (owner == null) return [];
|
||||||
|
final userId = owner.isarId;
|
||||||
|
final bool onlyOwned = every((e) => e.ownerId == userId);
|
||||||
|
if (!onlyOwned) {
|
||||||
|
if (errorCallback != null) errorCallback();
|
||||||
|
return where((a) => a.ownerId == userId);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the assets that are present on a file system which has write permission
|
||||||
|
/// This filters out assets on readOnly external library to which we cannot perform any write operation
|
||||||
|
Iterable<Asset> writableOnly({
|
||||||
|
void Function()? errorCallback,
|
||||||
|
}) {
|
||||||
|
final bool onlyWritable = every((e) => !e.isReadOnly);
|
||||||
|
if (!onlyWritable) {
|
||||||
|
if (errorCallback != null) errorCallback();
|
||||||
|
return where((a) => !a.isReadOnly);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filters out offline assets and returns those that are still accessible by the Immich server
|
||||||
|
Iterable<Asset> nonOfflineOnly({
|
||||||
|
void Function()? errorCallback,
|
||||||
|
}) {
|
||||||
|
final bool onlyLive = every((e) => !e.isOffline);
|
||||||
|
if (!onlyLive) {
|
||||||
|
if (errorCallback != null) errorCallback();
|
||||||
|
return where((a) => !a.isOffline);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -156,7 +156,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
buildLocation() {
|
buildLocation() {
|
||||||
// Guard no lat/lng
|
// Guard no lat/lng
|
||||||
if (!hasCoordinates()) {
|
if (!hasCoordinates()) {
|
||||||
return asset.isRemote
|
return asset.isRemote && !asset.isReadOnly
|
||||||
? ListTile(
|
? ListTile(
|
||||||
minLeadingWidth: 0,
|
minLeadingWidth: 0,
|
||||||
contentPadding: const EdgeInsets.all(0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
@ -194,7 +194,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
if (asset.isRemote)
|
if (asset.isRemote && !asset.isReadOnly)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleEditLocation(
|
onPressed: () => handleEditLocation(
|
||||||
ref,
|
ref,
|
||||||
@ -251,7 +251,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (asset.isRemote)
|
if (asset.isRemote && !asset.isReadOnly)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => handleEditDateTime(
|
onPressed: () => handleEditDateTime(
|
||||||
ref,
|
ref,
|
||||||
|
@ -166,7 +166,8 @@ class TopControlAppBar extends HookConsumerWidget {
|
|||||||
if (asset.isRemote && isOwner) buildFavoriteButton(a),
|
if (asset.isRemote && isOwner) buildFavoriteButton(a),
|
||||||
if (asset.livePhotoVideoId != null) buildLivePhotoButton(),
|
if (asset.livePhotoVideoId != null) buildLivePhotoButton(),
|
||||||
if (asset.isLocal && !asset.isRemote) buildUploadButton(),
|
if (asset.isLocal && !asset.isRemote) buildUploadButton(),
|
||||||
if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(),
|
if (asset.isRemote && !asset.isLocal && !asset.isOffline && isOwner)
|
||||||
|
buildDownloadButton(),
|
||||||
if (asset.isRemote && (isOwner || isPartner)) buildAddToAlbumButtom(),
|
if (asset.isRemote && (isOwner || isPartner)) buildAddToAlbumButtom(),
|
||||||
if (album != null && album.shared) buildActivitiesButton(),
|
if (album != null && album.shared) buildActivitiesButton(),
|
||||||
buildMoreInfoButton(),
|
buildMoreInfoButton(),
|
||||||
|
@ -187,8 +187,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
void showInfo() {
|
void showInfo() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
shape: RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15.0),
|
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||||
),
|
),
|
||||||
barrierColor: Colors.transparent,
|
barrierColor: Colors.transparent,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
@ -220,6 +220,16 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleDelete(Asset deleteAsset) async {
|
void handleDelete(Asset deleteAsset) async {
|
||||||
|
// Cannot delete readOnly / external assets. They are handled through library offline jobs
|
||||||
|
if (asset().isReadOnly) {
|
||||||
|
ImmichToast.show(
|
||||||
|
durationInSecond: 1,
|
||||||
|
context: context,
|
||||||
|
msg: 'asset_action_delete_err_read_only'.tr(),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
Future<bool> onDelete(bool force) async {
|
Future<bool> onDelete(bool force) async {
|
||||||
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(
|
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(
|
||||||
{deleteAsset},
|
{deleteAsset},
|
||||||
@ -319,11 +329,20 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shareAsset() {
|
shareAsset() {
|
||||||
ref.watch(imageViewerStateProvider.notifier).shareAsset(asset(), context);
|
if (asset().isOffline) {
|
||||||
|
ImmichToast.show(
|
||||||
|
durationInSecond: 1,
|
||||||
|
context: context,
|
||||||
|
msg: 'asset_action_share_err_offline'.tr(),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ref.read(imageViewerStateProvider.notifier).shareAsset(asset(), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleArchive(Asset asset) {
|
handleArchive(Asset asset) {
|
||||||
ref.watch(assetProvider.notifier).toggleArchive([asset]);
|
ref.read(assetProvider.notifier).toggleArchive([asset]);
|
||||||
if (isParent) {
|
if (isParent) {
|
||||||
context.popRoute();
|
context.popRoute();
|
||||||
return;
|
return;
|
||||||
@ -346,6 +365,26 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDownload() {
|
||||||
|
if (asset().isLocal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (asset().isOffline) {
|
||||||
|
ImmichToast.show(
|
||||||
|
durationInSecond: 1,
|
||||||
|
context: context,
|
||||||
|
msg: 'asset_action_share_err_offline'.tr(),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.read(imageViewerStateProvider.notifier).downloadAsset(
|
||||||
|
asset(),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleActivities() {
|
handleActivities() {
|
||||||
if (album != null && album.shared && album.remoteId != null) {
|
if (album != null && album.shared && album.remoteId != null) {
|
||||||
context.pushRoute(const ActivitiesRoute());
|
context.pushRoute(const ActivitiesRoute());
|
||||||
@ -371,12 +410,11 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
asset().isLocal ? () => handleUpload(asset()) : null,
|
asset().isLocal ? () => handleUpload(asset()) : null,
|
||||||
onDownloadPressed: asset().isLocal
|
onDownloadPressed: asset().isLocal
|
||||||
? null
|
? null
|
||||||
: () => ref
|
: () =>
|
||||||
.watch(imageViewerStateProvider.notifier)
|
ref.read(imageViewerStateProvider.notifier).downloadAsset(
|
||||||
.downloadAsset(
|
asset(),
|
||||||
asset(),
|
context,
|
||||||
context,
|
),
|
||||||
),
|
|
||||||
onToggleMotionVideo: (() {
|
onToggleMotionVideo: (() {
|
||||||
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
|
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
|
||||||
}),
|
}),
|
||||||
@ -641,13 +679,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
if (isOwner) (_) => handleArchive(asset()),
|
if (isOwner) (_) => handleArchive(asset()),
|
||||||
if (isOwner && stack.isNotEmpty) (_) => showStackActionItems(),
|
if (isOwner && stack.isNotEmpty) (_) => showStackActionItems(),
|
||||||
if (isOwner) (_) => handleDelete(asset()),
|
if (isOwner) (_) => handleDelete(asset()),
|
||||||
if (!isOwner)
|
if (!isOwner) (_) => handleDownload(),
|
||||||
(_) => asset().isLocal
|
|
||||||
? null
|
|
||||||
: ref.watch(imageViewerStateProvider.notifier).downloadAsset(
|
|
||||||
asset(),
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return IgnorePointer(
|
return IgnorePointer(
|
||||||
|
@ -32,6 +32,8 @@ class Asset {
|
|||||||
isFavorite = remote.isFavorite,
|
isFavorite = remote.isFavorite,
|
||||||
isArchived = remote.isArchived,
|
isArchived = remote.isArchived,
|
||||||
isTrashed = remote.isTrashed,
|
isTrashed = remote.isTrashed,
|
||||||
|
isReadOnly = remote.isReadOnly,
|
||||||
|
isOffline = remote.isOffline,
|
||||||
stackParentId = remote.stackParentId,
|
stackParentId = remote.stackParentId,
|
||||||
stackCount = remote.stackCount;
|
stackCount = remote.stackCount;
|
||||||
|
|
||||||
@ -49,6 +51,8 @@ class Asset {
|
|||||||
isFavorite = local.isFavorite,
|
isFavorite = local.isFavorite,
|
||||||
isArchived = false,
|
isArchived = false,
|
||||||
isTrashed = false,
|
isTrashed = false,
|
||||||
|
isReadOnly = false,
|
||||||
|
isOffline = false,
|
||||||
stackCount = 0,
|
stackCount = 0,
|
||||||
fileCreatedAt = local.createDateTime {
|
fileCreatedAt = local.createDateTime {
|
||||||
if (fileCreatedAt.year == 1970) {
|
if (fileCreatedAt.year == 1970) {
|
||||||
@ -77,11 +81,13 @@ class Asset {
|
|||||||
required this.fileName,
|
required this.fileName,
|
||||||
this.livePhotoVideoId,
|
this.livePhotoVideoId,
|
||||||
this.exifInfo,
|
this.exifInfo,
|
||||||
required this.isFavorite,
|
this.isFavorite = false,
|
||||||
required this.isArchived,
|
this.isArchived = false,
|
||||||
required this.isTrashed,
|
this.isTrashed = false,
|
||||||
this.stackParentId,
|
this.stackParentId,
|
||||||
required this.stackCount,
|
this.stackCount = 0,
|
||||||
|
this.isReadOnly = false,
|
||||||
|
this.isOffline = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
@ -148,6 +154,10 @@ class Asset {
|
|||||||
|
|
||||||
bool isTrashed;
|
bool isTrashed;
|
||||||
|
|
||||||
|
bool isReadOnly;
|
||||||
|
|
||||||
|
bool isOffline;
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
ExifInfo? exifInfo;
|
ExifInfo? exifInfo;
|
||||||
|
|
||||||
@ -256,6 +266,8 @@ class Asset {
|
|||||||
isFavorite != a.isFavorite ||
|
isFavorite != a.isFavorite ||
|
||||||
isArchived != a.isArchived ||
|
isArchived != a.isArchived ||
|
||||||
isTrashed != a.isTrashed ||
|
isTrashed != a.isTrashed ||
|
||||||
|
isReadOnly != a.isReadOnly ||
|
||||||
|
isOffline != a.isOffline ||
|
||||||
a.exifInfo?.latitude != exifInfo?.latitude ||
|
a.exifInfo?.latitude != exifInfo?.latitude ||
|
||||||
a.exifInfo?.longitude != exifInfo?.longitude ||
|
a.exifInfo?.longitude != exifInfo?.longitude ||
|
||||||
// no local stack count or different count from remote
|
// no local stack count or different count from remote
|
||||||
@ -288,6 +300,7 @@ class Asset {
|
|||||||
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
|
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Revisit this and remove all bool field assignments
|
||||||
return a._copyWith(
|
return a._copyWith(
|
||||||
id: id,
|
id: id,
|
||||||
remoteId: remoteId,
|
remoteId: remoteId,
|
||||||
@ -297,6 +310,8 @@ class Asset {
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
isArchived: isArchived,
|
isArchived: isArchived,
|
||||||
isTrashed: isTrashed,
|
isTrashed: isTrashed,
|
||||||
|
isReadOnly: isReadOnly,
|
||||||
|
isOffline: isOffline,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -314,6 +329,8 @@ class Asset {
|
|||||||
isFavorite: a.isFavorite,
|
isFavorite: a.isFavorite,
|
||||||
isArchived: a.isArchived,
|
isArchived: a.isArchived,
|
||||||
isTrashed: a.isTrashed,
|
isTrashed: a.isTrashed,
|
||||||
|
isReadOnly: a.isReadOnly,
|
||||||
|
isOffline: a.isOffline,
|
||||||
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -346,6 +363,8 @@ class Asset {
|
|||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
bool? isArchived,
|
bool? isArchived,
|
||||||
bool? isTrashed,
|
bool? isTrashed,
|
||||||
|
bool? isReadOnly,
|
||||||
|
bool? isOffline,
|
||||||
ExifInfo? exifInfo,
|
ExifInfo? exifInfo,
|
||||||
String? stackParentId,
|
String? stackParentId,
|
||||||
int? stackCount,
|
int? stackCount,
|
||||||
@ -368,6 +387,8 @@ class Asset {
|
|||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
isArchived: isArchived ?? this.isArchived,
|
isArchived: isArchived ?? this.isArchived,
|
||||||
isTrashed: isTrashed ?? this.isTrashed,
|
isTrashed: isTrashed ?? this.isTrashed,
|
||||||
|
isReadOnly: isReadOnly ?? this.isReadOnly,
|
||||||
|
isOffline: isOffline ?? this.isOffline,
|
||||||
exifInfo: exifInfo ?? this.exifInfo,
|
exifInfo: exifInfo ?? this.exifInfo,
|
||||||
stackParentId: stackParentId ?? this.stackParentId,
|
stackParentId: stackParentId ?? this.stackParentId,
|
||||||
stackCount: stackCount ?? this.stackCount,
|
stackCount: stackCount ?? this.stackCount,
|
||||||
@ -426,7 +447,9 @@ class Asset {
|
|||||||
"width": ${width ?? "N/A"},
|
"width": ${width ?? "N/A"},
|
||||||
"height": ${height ?? "N/A"},
|
"height": ${height ?? "N/A"},
|
||||||
"isArchived": $isArchived,
|
"isArchived": $isArchived,
|
||||||
"isTrashed": $isTrashed
|
"isTrashed": $isTrashed,
|
||||||
|
"isReadOnly": $isReadOnly,
|
||||||
|
"isOffline": $isOffline,
|
||||||
}""";
|
}""";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
mobile/lib/shared/models/asset.g.dart
generated
BIN
mobile/lib/shared/models/asset.g.dart
generated
Binary file not shown.
@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
||||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||||
@ -108,52 +109,33 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Iterable<Asset> remoteOnly(
|
|
||||||
Iterable<Asset> assets, {
|
|
||||||
void Function()? errorCallback,
|
|
||||||
}) {
|
|
||||||
final bool onlyRemote = assets.every((e) => e.isRemote);
|
|
||||||
if (!onlyRemote) {
|
|
||||||
if (errorCallback != null) errorCallback();
|
|
||||||
return assets.where((a) => a.isRemote);
|
|
||||||
}
|
|
||||||
return assets;
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<Asset> ownedOnly(
|
|
||||||
Iterable<Asset> assets, {
|
|
||||||
void Function()? errorCallback,
|
|
||||||
}) {
|
|
||||||
if (currentUser == null) return [];
|
|
||||||
final userId = currentUser.isarId;
|
|
||||||
final bool onlyOwned = assets.every((e) => e.ownerId == userId);
|
|
||||||
if (!onlyOwned) {
|
|
||||||
if (errorCallback != null) errorCallback();
|
|
||||||
return assets.where((a) => a.ownerId == userId);
|
|
||||||
}
|
|
||||||
return assets;
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<Asset> ownedRemoteSelection({
|
Iterable<Asset> ownedRemoteSelection({
|
||||||
String? localErrorMessage,
|
String? localErrorMessage,
|
||||||
String? ownerErrorMessage,
|
String? ownerErrorMessage,
|
||||||
}) {
|
}) {
|
||||||
final assets = selection.value;
|
final assets = selection.value;
|
||||||
return remoteOnly(
|
return assets
|
||||||
ownedOnly(assets, errorCallback: errorBuilder(ownerErrorMessage)),
|
.remoteOnly(errorCallback: errorBuilder(ownerErrorMessage))
|
||||||
errorCallback: errorBuilder(localErrorMessage),
|
.ownedOnly(
|
||||||
);
|
currentUser,
|
||||||
|
errorCallback: errorBuilder(localErrorMessage),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Asset> remoteSelection({String? errorMessage}) => remoteOnly(
|
Iterable<Asset> remoteSelection({String? errorMessage}) =>
|
||||||
selection.value,
|
selection.value.remoteOnly(
|
||||||
errorCallback: errorBuilder(errorMessage),
|
errorCallback: errorBuilder(errorMessage),
|
||||||
);
|
);
|
||||||
|
|
||||||
void onShareAssets(bool shareLocal) {
|
void onShareAssets(bool shareLocal) {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
if (shareLocal) {
|
if (shareLocal) {
|
||||||
handleShareAssets(ref, context, selection.value.toList());
|
// Share = Download + Send to OS specific share sheet
|
||||||
|
// Filter offline assets since we cannot fetch their original file
|
||||||
|
final liveAssets = selection.value.nonOfflineOnly(
|
||||||
|
errorCallback: errorBuilder('asset_action_share_err_offline'.tr()),
|
||||||
|
);
|
||||||
|
handleShareAssets(ref, context, liveAssets);
|
||||||
} else {
|
} else {
|
||||||
final ids =
|
final ids =
|
||||||
remoteSelection(errorMessage: "home_page_share_err_local".tr())
|
remoteSelection(errorMessage: "home_page_share_err_local".tr())
|
||||||
@ -199,10 +181,17 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||||||
try {
|
try {
|
||||||
final trashEnabled =
|
final trashEnabled =
|
||||||
ref.read(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
ref.read(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||||
final toDelete = ownedOnly(
|
final toDelete = selection.value
|
||||||
selection.value,
|
.ownedOnly(
|
||||||
errorCallback: errorBuilder('home_page_delete_err_partner'.tr()),
|
currentUser,
|
||||||
).toList();
|
errorCallback: errorBuilder('home_page_delete_err_partner'.tr()),
|
||||||
|
)
|
||||||
|
// Cannot delete readOnly / external assets. They are handled through library offline jobs
|
||||||
|
.writableOnly(
|
||||||
|
errorCallback:
|
||||||
|
errorBuilder('asset_action_delete_err_read_only'.tr()),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
await ref
|
await ref
|
||||||
.read(assetProvider.notifier)
|
.read(assetProvider.notifier)
|
||||||
.deleteAssets(toDelete, force: !trashEnabled);
|
.deleteAssets(toDelete, force: !trashEnabled);
|
||||||
@ -331,6 +320,11 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||||||
final remoteAssets = ownedRemoteSelection(
|
final remoteAssets = ownedRemoteSelection(
|
||||||
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
||||||
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
||||||
|
).writableOnly(
|
||||||
|
// Assume readOnly assets to be present in a read-only mount. So do not write sidecar
|
||||||
|
errorCallback: errorBuilder(
|
||||||
|
'multiselect_grid_edit_date_time_err_read_only'.tr(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (remoteAssets.isNotEmpty) {
|
if (remoteAssets.isNotEmpty) {
|
||||||
handleEditDateTime(ref, context, remoteAssets.toList());
|
handleEditDateTime(ref, context, remoteAssets.toList());
|
||||||
@ -345,6 +339,11 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||||||
final remoteAssets = ownedRemoteSelection(
|
final remoteAssets = ownedRemoteSelection(
|
||||||
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
||||||
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
||||||
|
).writableOnly(
|
||||||
|
// Assume readOnly assets to be present in a read-only mount. So do not write sidecar
|
||||||
|
errorCallback: errorBuilder(
|
||||||
|
'multiselect_grid_edit_gps_err_read_only'.tr(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (remoteAssets.isNotEmpty) {
|
if (remoteAssets.isNotEmpty) {
|
||||||
handleEditLocation(ref, context, remoteAssets.toList());
|
handleEditLocation(ref, context, remoteAssets.toList());
|
||||||
|
@ -13,6 +13,8 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
|||||||
await _migrateTo(db, 3);
|
await _migrateTo(db, 3);
|
||||||
case 3:
|
case 3:
|
||||||
await _migrateTo(db, 4);
|
await _migrateTo(db, 4);
|
||||||
|
case 4:
|
||||||
|
await _migrateTo(db, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import 'package:latlong2/latlong.dart';
|
|||||||
void handleShareAssets(
|
void handleShareAssets(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<Asset> selection,
|
Iterable<Asset> selection,
|
||||||
) {
|
) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
Loading…
Reference in New Issue
Block a user