fix(viewer): replace text arrow share icon with SVG share glyph
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
/// 无变更时保存禁用。成功 pop(true) 供调用方刷新列表/详情;失败保留输入。
|
||||
library;
|
||||
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
@@ -25,6 +27,7 @@ import '../core/error_l10n.dart';
|
||||
import '../features/albums/album_format.dart';
|
||||
import '../features/albums/album_form.dart';
|
||||
import '../features/albums/models.dart';
|
||||
import '../features/uploads/enqueue.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../ui/components/section_header.dart';
|
||||
import '../ui/tokens.dart';
|
||||
@@ -82,6 +85,15 @@ class _AlbumFormPageState extends ConsumerState<AlbumFormPage>
|
||||
List<AlbumFormError> _errors = const <AlbumFormError>[];
|
||||
bool _submitting = false;
|
||||
|
||||
/// 用户选择的封面本机文件路径(null = 未选)。
|
||||
/// 创建模式:相册创建成功后上传;编辑模式:保存后上传。
|
||||
String? _coverFilePath;
|
||||
|
||||
/// 编辑模式下的现有封面 URL(来自 AlbumSummary.cover,即时预览)。
|
||||
String? get _existingCoverUrl => _coverFilePath == null
|
||||
? (widget.existing?.cover?.url)
|
||||
: null;
|
||||
|
||||
bool get _isCreate => widget.existing == null;
|
||||
|
||||
@override
|
||||
@@ -225,17 +237,37 @@ class _AlbumFormPageState extends ConsumerState<AlbumFormPage>
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final navigator = Navigator.of(context);
|
||||
try {
|
||||
late final String albumId;
|
||||
if (_isCreate) {
|
||||
await ref.read(apiClientProvider).createAlbum(draft.toCreateRequest());
|
||||
final album = await ref
|
||||
.read(apiClientProvider)
|
||||
.createAlbum(draft.toCreateRequest());
|
||||
albumId = album.id;
|
||||
} else {
|
||||
final patch = draft.toUpdatePatch(widget.existing!);
|
||||
if (patch == null) {
|
||||
if (patch == null && _coverFilePath == null) {
|
||||
navigator.pop(false);
|
||||
return;
|
||||
}
|
||||
await ref
|
||||
.read(apiClientProvider)
|
||||
.updateAlbum(widget.existing!.id, patch);
|
||||
if (patch != null) {
|
||||
await ref
|
||||
.read(apiClientProvider)
|
||||
.updateAlbum(widget.existing!.id, patch);
|
||||
}
|
||||
albumId = widget.existing!.id;
|
||||
}
|
||||
// 封面上传:相册保存成功后执行;失败不阻塞表单结果(封面可后补)。
|
||||
if (_coverFilePath != null) {
|
||||
try {
|
||||
await ref
|
||||
.read(apiClientProvider)
|
||||
.uploadAlbumCoverFromFile(albumId, _coverFilePath!);
|
||||
} on Object {
|
||||
// 封面失败仅提示,不影响相册保存结果。
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text(l10n.coverUploadFailed)),
|
||||
);
|
||||
}
|
||||
}
|
||||
navigator.pop(true);
|
||||
} on AppException catch (error) {
|
||||
@@ -359,7 +391,82 @@ class _AlbumFormPageState extends ConsumerState<AlbumFormPage>
|
||||
);
|
||||
}
|
||||
|
||||
/// Details(= 创建表单):分组字段 + 校验错误汇总。
|
||||
/// 封面选择器:16:9 可点击区域。编辑模式显示现有封面预览。
|
||||
Widget _buildCoverPicker(AppLocalizations l10n) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.coverPicker,
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: kSpace8),
|
||||
InkWell(
|
||||
onTap: _submitting ? null : _pickCoverImage,
|
||||
borderRadius: BorderRadius.circular(kRadiusInput),
|
||||
child: Container(
|
||||
height: 180,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(kRadiusInput),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(kRadiusInput),
|
||||
child: _coverFilePath != null
|
||||
? Image.file(
|
||||
File(_coverFilePath!),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, _, _) => _coverPlaceholder(l10n),
|
||||
)
|
||||
: _existingCoverUrl != null
|
||||
? Image.network(
|
||||
_existingCoverUrl!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, _, _) => _coverPlaceholder(l10n),
|
||||
)
|
||||
: _coverPlaceholder(l10n),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: kSpace4),
|
||||
Text(
|
||||
_coverFilePath != null
|
||||
? l10n.coverSelected
|
||||
: l10n.coverPickerHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _coverPlaceholder(AppLocalizations l10n) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_photo_alternate_outlined,
|
||||
size: 48, color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(height: kSpace8),
|
||||
Text(
|
||||
l10n.coverPickerHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Future<void> _pickCoverImage() async {
|
||||
final path = await ref.read(singleImagePickerProvider)();
|
||||
if (path != null) {
|
||||
setState(() => _coverFilePath = path);
|
||||
}
|
||||
}
|
||||
|
||||
/// Details(= 创建表单):封面选择 + 分组字段 + 校验错误汇总。
|
||||
Widget _detailsForm(AppLocalizations l10n) {
|
||||
return Form(
|
||||
child: SingleChildScrollView(
|
||||
@@ -368,6 +475,10 @@ class _AlbumFormPageState extends ConsumerState<AlbumFormPage>
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// ── 封面选择(16:9 可点击区域) ─────────────────
|
||||
_buildCoverPicker(l10n),
|
||||
const SizedBox(height: kSpace24),
|
||||
|
||||
// ── 分组 1:Event details ──────────────────────────
|
||||
SectionHeader(title: l10n.formEventDetails),
|
||||
const SizedBox(height: kSpace12),
|
||||
|
||||
Reference in New Issue
Block a user