mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat(mobile): Manual asset upload (#3445)
* fix: exclude albums filter in backup provider * refactor: Separate builder methods for Top Control App Bar buttons * fix: Show download button only for Remote only assets * fix(mobile): Force Refresh duration is too low to trigger it consistently * feat(mobile): Make Buttons dynamic in Home Selection DraggableScrollableSheet * feat(mobile): Manual Asset upload * refactor(mobile): Replace _showToast with ImmichToast calls * refactor(mobile): home_page selectionAssetState handling * chore(mobile): min and initial size of DraggableScrollState increased This is to prevent the buttons in the bottom sheet getting clipped behind the 3 way navigation buttons in the default density of Android devices * feat(mobile): notifications for manual upload progress * wording --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
f1b92718d5
commit
deaf81e2a4
27 changed files with 887 additions and 163 deletions
|
|
@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
|
||||
|
|
@ -15,10 +17,12 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
final void Function() onDelete;
|
||||
final Function(Album album) onAddToAlbum;
|
||||
final void Function() onCreateNewAlbum;
|
||||
final void Function() onUpload;
|
||||
|
||||
final List<Album> albums;
|
||||
final List<Album> sharedAlbums;
|
||||
final bool enabled;
|
||||
final AssetState selectionAssetState;
|
||||
|
||||
const ControlBottomAppBar({
|
||||
Key? key,
|
||||
|
|
@ -30,12 +34,15 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
required this.albums,
|
||||
required this.onAddToAlbum,
|
||||
required this.onCreateNewAlbum,
|
||||
required this.onUpload,
|
||||
this.selectionAssetState = AssetState.remote,
|
||||
this.enabled = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
var hasRemote = selectionAssetState == AssetState.remote;
|
||||
|
||||
Widget renderActionButtons() {
|
||||
return Row(
|
||||
|
|
@ -47,11 +54,12 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
label: "control_bottom_app_bar_share".tr(),
|
||||
onPressed: enabled ? onShare : null,
|
||||
),
|
||||
ControlBoxButton(
|
||||
iconData: Icons.favorite_border_rounded,
|
||||
label: "control_bottom_app_bar_favorite".tr(),
|
||||
onPressed: enabled ? onFavorite : null,
|
||||
),
|
||||
if (hasRemote)
|
||||
ControlBoxButton(
|
||||
iconData: Icons.favorite_border_rounded,
|
||||
label: "control_bottom_app_bar_favorite".tr(),
|
||||
onPressed: enabled ? onFavorite : null,
|
||||
),
|
||||
ControlBoxButton(
|
||||
iconData: Icons.delete_outline_rounded,
|
||||
label: "control_bottom_app_bar_delete".tr(),
|
||||
|
|
@ -66,19 +74,35 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
)
|
||||
: null,
|
||||
),
|
||||
ControlBoxButton(
|
||||
iconData: Icons.archive,
|
||||
label: "control_bottom_app_bar_archive".tr(),
|
||||
onPressed: enabled ? onArchive : null,
|
||||
),
|
||||
if (!hasRemote)
|
||||
ControlBoxButton(
|
||||
iconData: Icons.backup_outlined,
|
||||
label: "Upload",
|
||||
onPressed: enabled
|
||||
? () => showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return UploadDialog(
|
||||
onUpload: onUpload,
|
||||
);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (hasRemote)
|
||||
ControlBoxButton(
|
||||
iconData: Icons.archive,
|
||||
label: "control_bottom_app_bar_archive".tr(),
|
||||
onPressed: enabled ? onArchive : null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return DraggableScrollableSheet(
|
||||
initialChildSize: 0.30,
|
||||
minChildSize: 0.15,
|
||||
maxChildSize: 0.57,
|
||||
initialChildSize: hasRemote ? 0.30 : 0.18,
|
||||
minChildSize: 0.18,
|
||||
maxChildSize: hasRemote ? 0.57 : 0.18,
|
||||
snap: true,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
|
|
@ -105,29 +129,33 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
const CustomDraggingHandle(),
|
||||
const SizedBox(height: 12),
|
||||
renderActionButtons(),
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
thickness: 1,
|
||||
),
|
||||
AddToAlbumTitleRow(
|
||||
onCreateNewAlbum: enabled ? onCreateNewAlbum : null,
|
||||
),
|
||||
if (hasRemote)
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
thickness: 1,
|
||||
),
|
||||
if (hasRemote)
|
||||
AddToAlbumTitleRow(
|
||||
onCreateNewAlbum: enabled ? onCreateNewAlbum : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: AddToAlbumSliverList(
|
||||
albums: albums,
|
||||
sharedAlbums: sharedAlbums,
|
||||
onAddToAlbum: onAddToAlbum,
|
||||
enabled: enabled,
|
||||
if (hasRemote)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: AddToAlbumSliverList(
|
||||
albums: albums,
|
||||
sharedAlbums: sharedAlbums,
|
||||
onAddToAlbum: onAddToAlbum,
|
||||
enabled: enabled,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 200),
|
||||
)
|
||||
if (hasRemote)
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 200),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer_header.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/server_info_box.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
|
|
@ -35,6 +36,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
|||
onTap: () async {
|
||||
await ref.watch(authenticationProvider.notifier).logout();
|
||||
|
||||
ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||
ref.watch(backupProvider.notifier).cancelBackup();
|
||||
ref.watch(assetProvider.notifier).clearAllAsset();
|
||||
ref.watch(websocketProvider.notifier).disconnect();
|
||||
|
|
|
|||
16
mobile/lib/modules/home/ui/upload_dialog.dart
Normal file
16
mobile/lib/modules/home/ui/upload_dialog.dart
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
|
||||
|
||||
class UploadDialog extends ConfirmDialog {
|
||||
final Function onUpload;
|
||||
|
||||
const UploadDialog({Key? key, required this.onUpload})
|
||||
: super(
|
||||
key: key,
|
||||
title: 'upload_dialog_title',
|
||||
content: 'upload_dialog_info',
|
||||
cancel: 'upload_dialog_cancel',
|
||||
ok: 'upload_dialog_ok',
|
||||
onOk: onUpload,
|
||||
);
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
|||
import 'package:immich_mobile/modules/album/providers/album_detail.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/backup/providers/manual_upload.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
|
||||
|
|
@ -36,6 +37,7 @@ class HomePage extends HookConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final multiselectEnabled = ref.watch(multiselectProvider.notifier);
|
||||
final selectionEnabledHook = useState(false);
|
||||
final selectionAssetState = useState(AssetState.remote);
|
||||
|
||||
final selection = useState(<Asset>{});
|
||||
final albums = ref.watch(albumProvider).where((a) => a.isRemote).toList();
|
||||
|
|
@ -80,6 +82,9 @@ class HomePage extends HookConsumerWidget {
|
|||
) {
|
||||
selectionEnabledHook.value = multiselect;
|
||||
selection.value = selectedAssets;
|
||||
selectionAssetState.value = selectedAssets.any((e) => e.isRemote)
|
||||
? AssetState.remote
|
||||
: AssetState.local;
|
||||
}
|
||||
|
||||
void onShareAssets() {
|
||||
|
|
@ -172,6 +177,28 @@ class HomePage extends HookConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
void onUpload() async {
|
||||
processing.value = true;
|
||||
try {
|
||||
final Set<Asset> assets = selection.value;
|
||||
if (assets.length > 30) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'home_page_upload_err_limit'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
await ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.uploadAssets(context, assets);
|
||||
}
|
||||
} finally {
|
||||
processing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void onAddToAlbum(Album album) async {
|
||||
processing.value = true;
|
||||
try {
|
||||
|
|
@ -253,7 +280,7 @@ class HomePage extends HookConsumerWidget {
|
|||
} else {
|
||||
refreshCount.value++;
|
||||
// set counter back to 0 if user does not request refresh again
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
Timer(const Duration(seconds: 4), () {
|
||||
refreshCount.value = 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -330,7 +357,9 @@ class HomePage extends HookConsumerWidget {
|
|||
albums: albums,
|
||||
sharedAlbums: sharedAlbums,
|
||||
onCreateNewAlbum: onCreateNewAlbum,
|
||||
onUpload: onUpload,
|
||||
enabled: !processing.value,
|
||||
selectionAssetState: selectionAssetState.value,
|
||||
),
|
||||
if (processing.value) const Center(child: ImmichLoadingIndicator())
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue