水印相机前端
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user