Files
Aq-Accounting-Flutter/lib/pages/watermark/watermark_camera_page.dart
T
2026-09-28 09:59:43 +08:00

212 lines
7.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import '../../../models/watermark.dart';
import '../../../providers/watermark_provider.dart';
import '../../../utils/toast.dart';
import 'watermark_edit_page.dart';
import 'widgets/watermark_settings_sheet.dart';
/// 水印相机模块入口:拍照 / 相册选图 → 进编辑页加水印
///
/// 刻意不做自绘相机预览(原项目也是调系统相机):省掉一整套相机权限、
/// 预览流、方向处理的复杂度,代价只是没法实时看到水印。
class WatermarkCameraPage extends ConsumerWidget {
const WatermarkCameraPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(watermarkSettingsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('水印相机'),
actions: [
IconButton(
tooltip: '水印设置',
icon: const Icon(Icons.tune),
onPressed: () => showWatermarkSettingsSheet(context),
),
],
),
body: ListView(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
children: [
_introCard(),
const SizedBox(height: 20),
_captureButton(
icon: Icons.camera_alt,
label: '拍照',
primary: true,
onTap: () => _pick(context, ImageSource.camera),
),
const SizedBox(height: 12),
_captureButton(
icon: Icons.photo_library_outlined,
label: '从相册选',
primary: false,
onTap: () => _pick(context, ImageSource.gallery),
),
const SizedBox(height: 24),
_settingsCard(context, settings),
],
),
);
}
Widget _introCard() {
return Container(
padding: const EdgeInsets.fromLTRB(18, 18, 18, 18),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFFE5CC), Color(0xFFFFD4A8)],
),
borderRadius: BorderRadius.circular(16),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'拍一张,自动加水印',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: Color(0xFF5D4037),
),
),
SizedBox(height: 6),
Text(
'时间、地点、天气自动填好,可以拖动位置、换颜色。\n'
'加好水印的照片直接存进手机相册;原图会同时备份到服务器。',
style: TextStyle(fontSize: 12.5, height: 1.6, color: Color(0xFF8D6E63)),
),
],
),
);
}
Widget _captureButton({
required IconData icon,
required String label,
required bool primary,
required VoidCallback onTap,
}) {
final style = FilledButton.styleFrom(
backgroundColor: primary ? const Color(0xFFC8956E) : Colors.white,
foregroundColor: primary ? Colors.white : const Color(0xFFC8956E),
minimumSize: const Size.fromHeight(52),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
side: primary
? BorderSide.none
: const BorderSide(color: Color(0xFFC8956E)),
),
elevation: primary ? 0 : 0,
);
return FilledButton.icon(
onPressed: onTap,
style: style,
icon: Icon(icon, size: 20),
label: Text(
label,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
);
}
Widget _settingsCard(BuildContext context, WatermarkSettings settings) {
final items = <String>[
if (settings.showTime) '时间',
if (settings.showAddress) '地点',
if (settings.showWeather) '天气',
if (settings.showRemark) '备注',
if (settings.showBrand) '品牌角标',
];
return InkWell(
onTap: () => showWatermarkSettingsSheet(context),
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.fromLTRB(18, 16, 14, 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'当前水印内容',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 8),
if (items.isEmpty)
const Text(
'全部关闭(照片不会加任何水印)',
style: TextStyle(fontSize: 12, color: Color(0xFFBCAAA4)),
)
else
Wrap(
spacing: 6,
runSpacing: 6,
children: items
.map(
(t) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: const Color(0xFFFFF3E6),
borderRadius: BorderRadius.circular(10),
),
child: Text(
t,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF8D6E63),
),
),
),
)
.toList(),
),
],
),
),
const Icon(Icons.chevron_right, color: Color(0xFFBCAAA4)),
],
),
),
);
}
/// 选图。**不传 imageQuality / maxWidth** —— 要的就是原图,
/// 编辑页会在合成时按最长边 1920 缩放,原始文件则原样备份到服务器。
Future<void> _pick(BuildContext context, ImageSource source) async {
try {
final file = await ImagePicker().pickImage(source: source);
if (file == null) return;
if (!context.mounted) return;
await Navigator.of(context).push(
MaterialPageRoute(builder: (_) => WatermarkEditPage(imagePath: file.path)),
);
} catch (e) {
if (context.mounted) {
showToast(context, '打开相机/相册失败:$e', error: true);
}
}
}
}