0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-11 02:23:09 -05:00
immich/mobile/lib/pages/share_intent/share_intent.page.dart
shenlong 76d95cd348
refactor(mobile): move store settings and store into domain folder (#16201)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-02-19 19:27:32 +00:00

262 lines
8.3 KiB
Dart

import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
@RoutePage()
class ShareIntentPage extends HookConsumerWidget {
const ShareIntentPage({super.key, required this.attachments});
final List<ShareIntentAttachment> attachments;
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentEndpoint = Store.get(StoreKey.serverEndpoint);
final candidates = ref.watch(shareIntentUploadProvider);
final isUploaded = useState(false);
void removeAttachment(ShareIntentAttachment attachment) {
ref.read(shareIntentUploadProvider.notifier).removeAttachment(attachment);
}
void addAttachments(List<ShareIntentAttachment> attachments) {
ref.read(shareIntentUploadProvider.notifier).addAttachments(attachments);
}
void upload() async {
for (final attachment in candidates) {
await ref
.read(shareIntentUploadProvider.notifier)
.upload(attachment.file);
}
isUploaded.value = true;
}
bool isSelected(ShareIntentAttachment attachment) {
return candidates.contains(attachment);
}
void toggleSelection(ShareIntentAttachment attachment) {
if (isSelected(attachment)) {
removeAttachment(attachment);
} else {
addAttachments([attachment]);
}
}
return Scaffold(
appBar: AppBar(
title: Column(
children: [
const Text('upload_to_immich').tr(
args: [
candidates.length.toString(),
],
),
Text(
currentEndpoint,
style: context.textTheme.labelMedium?.copyWith(
color: context.colorScheme.onSurface.withAlpha(200),
),
),
],
),
),
body: ListView.builder(
itemCount: attachments.length,
itemBuilder: (context, index) {
final attachment = attachments[index];
final target = candidates.firstWhere(
(element) => element.id == attachment.id,
orElse: () => attachment,
);
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 16,
),
child: LargeLeadingTile(
onTap: () => toggleSelection(attachment),
disabled: isUploaded.value,
selected: isSelected(attachment),
leading: Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: attachment.isImage
? Image.file(
attachment.file,
width: 64,
height: 64,
fit: BoxFit.cover,
)
: const SizedBox(
width: 64,
height: 64,
child: Center(
child: Icon(
Icons.videocam,
color: Colors.white,
),
),
),
),
if (attachment.isImage)
const Positioned(
top: 8,
right: 8,
child: Icon(
Icons.image,
color: Colors.white,
size: 20,
shadows: [
Shadow(
offset: Offset(0, 0),
blurRadius: 8.0,
color: Colors.black45,
),
],
),
),
],
),
title: Text(
attachment.fileName,
style: context.textTheme.titleSmall,
),
subtitle: Text(
attachment.fileSize,
style: context.textTheme.labelLarge,
),
trailing: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: UploadStatusIcon(
selected: isSelected(attachment),
status: target.status,
progress: target.uploadProgress,
),
),
),
);
},
),
bottomNavigationBar: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
height: 48,
child: ElevatedButton(
onPressed: isUploaded.value ? null : upload,
child: isUploaded.value
? UploadingText(candidates: candidates)
: const Text('upload').tr(),
),
),
),
),
);
}
}
class UploadingText extends StatelessWidget {
const UploadingText({super.key, required this.candidates});
final List<ShareIntentAttachment> candidates;
@override
Widget build(BuildContext context) {
final uploadedCount = candidates.where((element) {
return element.status == UploadStatus.complete;
}).length;
return const Text("shared_intent_upload_button_progress_text")
.tr(args: [uploadedCount.toString(), candidates.length.toString()]);
}
}
class UploadStatusIcon extends StatelessWidget {
const UploadStatusIcon({
super.key,
required this.status,
required this.selected,
this.progress = 0,
});
final UploadStatus status;
final double progress;
final bool selected;
@override
Widget build(BuildContext context) {
if (!selected) {
return Icon(
Icons.check_circle_outline_rounded,
color: context.colorScheme.onSurface.withAlpha(100),
semanticLabel: 'not_selected'.tr(),
);
}
final statusIcon = switch (status) {
UploadStatus.enqueued => Icon(
Icons.check_circle_rounded,
color: context.primaryColor,
semanticLabel: 'enqueued'.tr(),
),
UploadStatus.running => Stack(
alignment: AlignmentDirectional.center,
children: [
SizedBox(
width: 40,
height: 40,
child: TweenAnimationBuilder(
tween: Tween<double>(begin: 0.0, end: progress),
duration: const Duration(milliseconds: 500),
builder: (context, value, _) => CircularProgressIndicator(
backgroundColor: context.colorScheme.surfaceContainerLow,
strokeWidth: 3,
value: value,
semanticsLabel: 'uploading'.tr(),
),
),
),
Text(
(progress * 100).toStringAsFixed(0),
style: context.textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
UploadStatus.complete => Icon(
Icons.check_circle_rounded,
color: Colors.green,
semanticLabel: 'completed'.tr(),
),
UploadStatus.notFound || UploadStatus.failed => Icon(
Icons.error_rounded,
color: Colors.red,
semanticLabel: 'failed'.tr(),
),
UploadStatus.canceled => Icon(
Icons.cancel_rounded,
color: Colors.red,
semanticLabel: 'canceled'.tr(),
),
UploadStatus.waitingtoRetry || UploadStatus.paused => Icon(
Icons.pause_circle_rounded,
color: context.primaryColor,
semanticLabel: 'paused'.tr(),
),
};
return statusIcon;
}
}