Merge branch 'main' into fix/bring-back-delete-backed-up-only
This commit is contained in:
@@ -36,7 +36,7 @@ class UnStackActionButton extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.filter_none_rounded,
|
||||
iconData: Icons.layers_clear_outlined,
|
||||
label: "unstack".t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
|
||||
@@ -51,6 +51,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
isArchived: isArchived,
|
||||
isTrashEnabled: isTrashEnable,
|
||||
isInLockedView: isInLockedView,
|
||||
isStacked: asset.hasRemote && (asset as RemoteAsset).stackId != null,
|
||||
currentAlbum: currentAlbum,
|
||||
advancedTroubleshooting: advancedTroubleshooting,
|
||||
source: ActionSource.viewer,
|
||||
|
||||
@@ -57,7 +57,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
|
||||
final actions = <Widget>[
|
||||
if (asset.hasRemote) const DownloadActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
if (asset.isRemoteOnly) const DownloadActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true),
|
||||
if (album != null && album.isActivityEnabled && album.isShared)
|
||||
IconButton(
|
||||
|
||||
@@ -65,7 +65,9 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
||||
|
||||
final uploadTasks = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
||||
|
||||
final isUploading = uploadTasks.isNotEmpty;
|
||||
final isSyncing = ref.watch(driftBackupProvider.select((state) => state.isSyncing));
|
||||
|
||||
final isProcessing = uploadTasks.isNotEmpty || isSyncing;
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
@@ -129,7 +131,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
||||
],
|
||||
),
|
||||
),
|
||||
child: isUploading
|
||||
child: isProcessing
|
||||
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: Icon(Icons.cloud_upload_outlined, color: context.primaryColor, size: 24),
|
||||
),
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
@@ -44,6 +45,7 @@ class ArchiveBottomSheet extends ConsumerWidget {
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
@@ -44,6 +45,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
||||
@@ -19,6 +20,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_b
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
@@ -62,11 +64,19 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
||||
return;
|
||||
}
|
||||
|
||||
final remoteAssets = selectedAssets.whereType<RemoteAsset>();
|
||||
final addedCount = await ref
|
||||
.read(remoteAlbumProvider.notifier)
|
||||
.addAssets(album.id, selectedAssets.map((e) => (e as RemoteAsset).id).toList());
|
||||
.addAssets(album.id, remoteAssets.map((e) => e.id).toList());
|
||||
|
||||
if (addedCount != selectedAssets.length) {
|
||||
if (selectedAssets.length != remoteAssets.length) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'add_to_album_bottom_sheet_some_local_assets'.t(context: context),
|
||||
);
|
||||
}
|
||||
|
||||
if (addedCount != remoteAssets.length) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {"album": album.name}),
|
||||
@@ -108,15 +118,18 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
const DeleteActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasLocal) const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
slivers: [
|
||||
const AddToAlbumHeader(),
|
||||
AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand),
|
||||
],
|
||||
slivers: multiselect.hasRemote
|
||||
? [
|
||||
const AddToAlbumHeader(),
|
||||
AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand),
|
||||
]
|
||||
: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
|
||||
class MapBottomSheet extends StatelessWidget {
|
||||
const MapBottomSheet({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const BaseBottomSheet(
|
||||
return BaseBottomSheet(
|
||||
initialChildSize: 0.25,
|
||||
maxChildSize: 0.9,
|
||||
shouldCloseOnMinExtent: false,
|
||||
resizeOnScroll: false,
|
||||
actions: [],
|
||||
slivers: [SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())],
|
||||
backgroundColor: context.themeData.colorScheme.surface,
|
||||
slivers: [const SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -30,8 +33,13 @@ class _ScopedMapTimeline extends StatelessWidget {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith((ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
throw Exception('User must be logged in to access archive');
|
||||
}
|
||||
|
||||
final bounds = ref.watch(mapStateProvider).bounds;
|
||||
final timelineService = ref.watch(timelineFactoryProvider).map(bounds);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).map(user.id, bounds);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
}),
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_b
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
@@ -102,6 +103,7 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
|
||||
const EditLocationActionButton(source: ActionSource.timeline),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
|
||||
@@ -123,28 +123,14 @@ ImageProvider getFullImageProvider(BaseAsset asset, {Size size = const Size(1080
|
||||
return provider;
|
||||
}
|
||||
|
||||
ImageProvider getThumbnailImageProvider({BaseAsset? asset, String? remoteId, Size size = kThumbnailResolution}) {
|
||||
assert(asset != null || remoteId != null, 'Either asset or remoteId must be provided');
|
||||
|
||||
if (remoteId != null) {
|
||||
return RemoteThumbProvider(assetId: remoteId);
|
||||
}
|
||||
|
||||
if (_shouldUseLocalAsset(asset!)) {
|
||||
ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnailResolution}) {
|
||||
if (_shouldUseLocalAsset(asset)) {
|
||||
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).localId!;
|
||||
return LocalThumbProvider(id: id, size: size, assetType: asset.type);
|
||||
}
|
||||
|
||||
final String assetId;
|
||||
if (asset is LocalAsset && asset.hasRemote) {
|
||||
assetId = asset.remoteId!;
|
||||
} else if (asset is RemoteAsset) {
|
||||
assetId = asset.id;
|
||||
} else {
|
||||
throw ArgumentError("Unsupported asset type: ${asset.runtimeType}");
|
||||
}
|
||||
|
||||
return RemoteThumbProvider(assetId: assetId);
|
||||
final assetId = asset is RemoteAsset ? asset.id : (asset as LocalAsset).remoteId;
|
||||
return assetId != null ? RemoteThumbProvider(assetId: assetId) : null;
|
||||
}
|
||||
|
||||
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumb_hash_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||
@@ -39,14 +38,7 @@ class Thumbnail extends StatefulWidget {
|
||||
),
|
||||
_ => null,
|
||||
},
|
||||
imageProvider = switch (asset) {
|
||||
RemoteAsset() =>
|
||||
asset.localId == null
|
||||
? RemoteThumbProvider(assetId: asset.id)
|
||||
: LocalThumbProvider(id: asset.localId!, size: size, assetType: asset.type),
|
||||
LocalAsset() => LocalThumbProvider(id: asset.id, size: size, assetType: asset.type),
|
||||
_ => null,
|
||||
};
|
||||
imageProvider = asset == null ? null : getThumbnailImageProvider(asset, size: size);
|
||||
|
||||
@override
|
||||
State<Thumbnail> createState() => _ThumbnailState();
|
||||
|
||||
@@ -54,8 +54,6 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
)
|
||||
: const BoxDecoration();
|
||||
|
||||
final hasStack = asset is RemoteAsset && asset.stackId != null;
|
||||
|
||||
final bool storageIndicator =
|
||||
showStorageIndicator ?? ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator)));
|
||||
|
||||
@@ -77,21 +75,10 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
child: Thumbnail.fromAsset(asset: asset, size: size),
|
||||
),
|
||||
),
|
||||
if (hasStack)
|
||||
if (asset != null)
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 10.0, top: asset.isVideo ? 24.0 : 6.0),
|
||||
child: const _TileOverlayIcon(Icons.burst_mode_rounded),
|
||||
),
|
||||
),
|
||||
if (asset != null && asset.isVideo)
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0, top: 6.0),
|
||||
child: _VideoIndicator(asset.duration),
|
||||
),
|
||||
child: _AssetTypeIcons(asset: asset),
|
||||
),
|
||||
if (storageIndicator && asset != null)
|
||||
switch (asset.storage) {
|
||||
@@ -214,3 +201,34 @@ class _TileOverlayIcon extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetTypeIcons extends StatelessWidget {
|
||||
final BaseAsset asset;
|
||||
|
||||
const _AssetTypeIcons({required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
|
||||
final isLivePhoto = asset is RemoteAsset && asset.livePhotoVideoId != null;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (asset.isVideo)
|
||||
Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)),
|
||||
if (hasStack)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 10.0, top: 6.0),
|
||||
child: _TileOverlayIcon(Icons.burst_mode_rounded),
|
||||
),
|
||||
if (isLivePhoto)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 10.0, top: 6.0),
|
||||
child: _TileOverlayIcon(Icons.motion_photos_on_rounded),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
@@ -9,8 +10,8 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||
import 'package:immich_mobile/utils/debounce.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
@@ -187,6 +188,8 @@ class _Map extends StatelessWidget {
|
||||
styleString: style,
|
||||
onMapCreated: onMapCreated,
|
||||
onStyleLoadedCallback: onMapReady,
|
||||
attributionButtonPosition: AttributionButtonPosition.topRight,
|
||||
attributionButtonMargins: Platform.isIOS ? const Point(40, 12) : const Point(40, 72),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -212,11 +212,14 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
if (fallbackSegment != null) {
|
||||
// Scroll to the segment with a small offset to show the header
|
||||
final targetOffset = fallbackSegment.startOffset - 50;
|
||||
_scrollController.animateTo(
|
||||
targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
ref.read(timelineStateProvider.notifier).setScrubbing(true);
|
||||
_scrollController
|
||||
.animateTo(
|
||||
targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
)
|
||||
.whenComplete(() => ref.read(timelineStateProvider.notifier).setScrubbing(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user