Files
AIOA/mobile/lib/features/form/presentation/leave_attachment_sheet.dart
T
2026-07-18 19:20:07 +08:00

139 lines
4.7 KiB
Dart

import 'package:aioa_mobile/features/form/application/leave_attachment_controller.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class LeaveAttachmentSheet extends ConsumerStatefulWidget {
const LeaveAttachmentSheet({required this.leaveRequestId, super.key});
final String leaveRequestId;
@override
ConsumerState<LeaveAttachmentSheet> createState() =>
_LeaveAttachmentSheetState();
}
class _LeaveAttachmentSheetState extends ConsumerState<LeaveAttachmentSheet> {
@override
void initState() {
super.initState();
Future.microtask(
() => ref
.read(leaveAttachmentProvider(widget.leaveRequestId).notifier)
.load(),
);
}
@override
Widget build(BuildContext context) {
final state = ref.watch(leaveAttachmentProvider(widget.leaveRequestId));
return SafeArea(
child: Padding(
padding: EdgeInsets.fromLTRB(
20,
0,
20,
20 + MediaQuery.viewInsetsOf(context).bottom,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'添加附件',
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
),
const SizedBox(height: 6),
const Text('支持 JPEG、PNG、PDF,单个文件不超过 10 MB。文件将直接上传至对象存储。'),
if (state.error != null) ...[
const SizedBox(height: 10),
Text(
state.error!,
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
],
if (state.uploading) ...[
const SizedBox(height: 14),
LinearProgressIndicator(value: state.progress),
const SizedBox(height: 6),
Text('正在上传 ${(state.progress * 100).round()}%'),
],
if (state.items.isNotEmpty) ...[
const SizedBox(height: 14),
for (final item in state.items)
ListTile(
contentPadding: EdgeInsets.zero,
leading: Icon(
item.contentType == 'application/pdf'
? Icons.picture_as_pdf_outlined
: Icons.image_outlined,
),
title: Text(
item.fileName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
'${_formatBytes(item.sizeBytes)} · ${item.status == 'READY' ? '已上传' : '处理中'}',
),
trailing: item.status == 'READY'
? const Icon(Icons.check_circle, color: Colors.green)
: null,
),
],
const SizedBox(height: 14),
OutlinedButton.icon(
onPressed: state.uploading ? null : _pickAndUpload,
icon: const Icon(Icons.attach_file),
label: const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('选择附件'),
),
),
const SizedBox(height: 8),
FilledButton(
onPressed: state.uploading ? null : () => Navigator.pop(context),
child: const Text('完成'),
),
],
),
),
);
}
Future<void> _pickAndUpload() async {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: const ['jpg', 'jpeg', 'png', 'pdf'],
withData: true,
);
if (result == null || !mounted) return;
final file = result.files.single;
final bytes = file.bytes;
if (bytes == null) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('无法读取所选文件')));
return;
}
final contentType = switch (file.extension?.toLowerCase()) {
'jpg' || 'jpeg' => 'image/jpeg',
'png' => 'image/png',
'pdf' => 'application/pdf',
_ => null,
};
if (contentType == null) return;
await ref
.read(leaveAttachmentProvider(widget.leaveRequestId).notifier)
.upload(fileName: file.name, contentType: contentType, bytes: bytes);
}
String _formatBytes(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
}