Merge branch 'main' into edit-date-time-action
This commit is contained in:
@@ -1,16 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class UploadActionButton extends ConsumerWidget {
|
||||
const UploadActionButton({super.key});
|
||||
final ActionSource source;
|
||||
|
||||
const UploadActionButton({super.key, required this.source});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).upload(source);
|
||||
|
||||
final successMessage = 'upload_action_prompt'.t(
|
||||
context: context,
|
||||
args: {'count': result.count.toString()},
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.backup_outlined,
|
||||
label: "upload".t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,7 @@ class StackChildrenNotifier
|
||||
extends AutoDisposeFamilyAsyncNotifier<List<RemoteAsset>, BaseAsset?> {
|
||||
@override
|
||||
Future<List<RemoteAsset>> build(BaseAsset? asset) async {
|
||||
if (asset == null ||
|
||||
asset is! RemoteAsset ||
|
||||
asset.stackId == null ||
|
||||
// The stackCount check is to ensure we only fetch stacks for timelines that have stacks
|
||||
asset.stackCount == 0) {
|
||||
if (asset == null || asset is! RemoteAsset || asset.stackId == null) {
|
||||
return const [];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
@@ -37,6 +38,8 @@ class ViewerBottomBar extends ConsumerWidget {
|
||||
|
||||
final actions = <Widget>[
|
||||
const ShareActionButton(source: ActionSource.viewer),
|
||||
if (asset.isLocalOnly)
|
||||
const UploadActionButton(source: ActionSource.viewer),
|
||||
if (asset.hasRemote && isOwner)
|
||||
const ArchiveActionButton(source: ActionSource.viewer),
|
||||
];
|
||||
@@ -50,31 +53,30 @@ class ViewerBottomBar extends ConsumerWidget {
|
||||
duration: Durations.short4,
|
||||
child: isSheetOpen
|
||||
? const SizedBox.shrink()
|
||||
: SafeArea(
|
||||
child: Theme(
|
||||
data: context.themeData.copyWith(
|
||||
iconTheme:
|
||||
const IconThemeData(size: 22, color: Colors.white),
|
||||
textTheme: context.themeData.textTheme.copyWith(
|
||||
labelLarge:
|
||||
context.themeData.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
: Theme(
|
||||
data: context.themeData.copyWith(
|
||||
iconTheme:
|
||||
const IconThemeData(size: 22, color: Colors.white),
|
||||
textTheme: context.themeData.textTheme.copyWith(
|
||||
labelLarge:
|
||||
context.themeData.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
height: asset.isVideo ? 160 : 80,
|
||||
color: Colors.black.withAlpha(125),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (asset.isVideo) const VideoControls(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: actions,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
height: context.padding.bottom + (asset.isVideo ? 160 : 80),
|
||||
color: Colors.black.withAlpha(125),
|
||||
padding: EdgeInsets.only(bottom: context.padding.bottom),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (asset.isVideo) const VideoControls(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: actions,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -63,7 +63,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
],
|
||||
if (asset.storage == AssetState.local) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.viewer),
|
||||
const UploadActionButton(),
|
||||
const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -27,6 +27,26 @@ import 'package:logging/logging.dart';
|
||||
import 'package:native_video_player/native_video_player.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
bool _isCurrentAsset(
|
||||
BaseAsset asset,
|
||||
BaseAsset? currentAsset,
|
||||
) {
|
||||
if (asset is RemoteAsset) {
|
||||
return switch (currentAsset) {
|
||||
RemoteAsset remoteAsset => remoteAsset.id == asset.id,
|
||||
LocalAsset localAsset => localAsset.remoteId == asset.id,
|
||||
_ => false,
|
||||
};
|
||||
} else if (asset is LocalAsset) {
|
||||
return switch (currentAsset) {
|
||||
RemoteAsset remoteAsset => remoteAsset.localId == asset.id,
|
||||
LocalAsset localAsset => localAsset.id == asset.id,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class NativeVideoViewer extends HookConsumerWidget {
|
||||
final BaseAsset asset;
|
||||
final bool showControls;
|
||||
@@ -56,7 +76,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
// If the swipe is completed, `isCurrent` will be true for video B after a delay.
|
||||
// If the swipe is canceled, `currentAsset` will not have changed and video A will continue to play.
|
||||
final currentAsset = useState(ref.read(currentAssetNotifier));
|
||||
final isCurrent = currentAsset.value == asset;
|
||||
final isCurrent = _isCurrentAsset(asset, currentAsset.value);
|
||||
|
||||
// Used to show the placeholder during hero animations for remote videos to avoid a stutter
|
||||
final isVisible = useState(Platform.isIOS && asset.hasLocal);
|
||||
|
||||
@@ -53,7 +53,7 @@ class ArchiveBottomSheet extends ConsumerWidget {
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
const UploadActionButton(),
|
||||
const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
const UploadActionButton(),
|
||||
const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ class GeneralBottomSheet extends ConsumerWidget {
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
const UploadActionButton(),
|
||||
const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ class LocalAlbumBottomSheet extends ConsumerWidget {
|
||||
actions: [
|
||||
ShareActionButton(source: ActionSource.timeline),
|
||||
DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
UploadActionButton(),
|
||||
UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class RemoteAlbumBottomSheet extends ConsumerWidget {
|
||||
],
|
||||
if (multiselect.hasLocal) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
const UploadActionButton(),
|
||||
const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
RemoveFromAlbumActionButton(
|
||||
source: ActionSource.timeline,
|
||||
|
||||
@@ -54,7 +54,7 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
: const BoxDecoration();
|
||||
|
||||
final hasStack =
|
||||
asset is RemoteAsset && (asset as RemoteAsset).stackCount > 0;
|
||||
asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -86,9 +86,7 @@ class ThumbnailTile extends ConsumerWidget {
|
||||
right: 10.0,
|
||||
top: asset.isVideo ? 24.0 : 6.0,
|
||||
),
|
||||
child: _StackIndicator(
|
||||
stackCount: (asset as RemoteAsset).stackCount,
|
||||
),
|
||||
child: const _TileOverlayIcon(Icons.burst_mode_rounded),
|
||||
),
|
||||
),
|
||||
if (asset.isVideo)
|
||||
@@ -198,40 +196,6 @@ class _SelectionIndicator extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _StackIndicator extends StatelessWidget {
|
||||
final int stackCount;
|
||||
|
||||
const _StackIndicator({required this.stackCount});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
spacing: 3,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
// CrossAxisAlignment.start looks more centered vertically than CrossAxisAlignment.center
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
stackCount.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
shadows: [
|
||||
Shadow(
|
||||
blurRadius: 5.0,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.6),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const _TileOverlayIcon(Icons.burst_mode_rounded),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _VideoIndicator extends StatelessWidget {
|
||||
final Duration duration;
|
||||
const _VideoIndicator(this.duration);
|
||||
|
||||
Reference in New Issue
Block a user