水印相机前端

This commit is contained in:
2026-09-28 09:59:43 +08:00
parent 187a7e0911
commit c5b3d81d07
19 changed files with 2761 additions and 3 deletions
@@ -0,0 +1,221 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../providers/watermark_provider.dart';
/// 水印设置(开关 + 文字颜色)
///
/// 用底部弹层而不是独立页面:编辑页里改完能立刻在预览上看到效果,
/// 入口页也能从同一个地方进。
Future<void> showWatermarkSettingsSheet(BuildContext context) {
return showModalBottomSheet<void>(
context: context,
backgroundColor: Colors.white,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (_) => const _WatermarkSettingsSheet(),
);
}
/// 可选文字颜色。白色是默认 —— 深色照片上最通用;浅色照片可以换黑色或暖黄。
const List<({String label, int value})> _textColors = [
(label: '白', value: 0xFFFFFFFF),
(label: '黑', value: 0xFF000000),
(label: '暖黄', value: 0xFFFFD54F),
(label: '品牌黄', value: 0xFFFFB800),
(label: '品牌棕', value: 0xFFC8956E),
(label: '浅蓝', value: 0xFF4FC3F7),
];
class _WatermarkSettingsSheet extends ConsumerWidget {
const _WatermarkSettingsSheet();
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(watermarkSettingsProvider);
final notifier = ref.read(watermarkSettingsProvider.notifier);
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Center(
child: Text(
'水印设置',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
),
),
const SizedBox(height: 18),
_sectionTitle('显示内容'),
_switchRow(
'时间',
'如 2026.09.28 14:30',
settings.showTime,
(v) => notifier.update(settings.copyWith(showTime: v)),
),
_switchRow(
'地点',
'自动定位,也可在编辑页手填',
settings.showAddress,
(v) => notifier.update(settings.copyWith(showAddress: v)),
),
_switchRow(
'天气温度',
'如 晴 25℃(拿不到时这一行不显示)',
settings.showWeather,
(v) => notifier.update(settings.copyWith(showWeather: v)),
),
_switchRow(
'备注',
'每张照片单独填',
settings.showRemark,
(v) => notifier.update(settings.copyWith(showRemark: v)),
),
_switchRow(
'右下角品牌角标',
'相机图标 + 水印相机',
settings.showBrand,
(v) => notifier.update(settings.copyWith(showBrand: v)),
),
const SizedBox(height: 18),
_sectionTitle('文字颜色'),
const SizedBox(height: 10),
Wrap(
spacing: 12,
runSpacing: 12,
children: _textColors.map((c) {
final selected = settings.colorValue == c.value;
return GestureDetector(
onTap: () => notifier.update(settings.copyWith(colorValue: c.value)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: Color(c.value),
shape: BoxShape.circle,
border: Border.all(
color: selected
? const Color(0xFFC8956E)
: const Color(0xFFE0E0E0),
width: selected ? 3 : 1,
),
),
child: selected
? Icon(
Icons.check,
size: 18,
color: _isLight(c.value)
? const Color(0xFF5D4037)
: Colors.white,
)
: null,
),
const SizedBox(height: 4),
Text(
c.label,
style: const TextStyle(fontSize: 11, color: Color(0xFF8D6E63)),
),
],
),
);
}).toList(),
),
const SizedBox(height: 18),
_sectionTitle('水印位置'),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
decoration: BoxDecoration(
color: const Color(0xFFFFF3E6),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Expanded(
child: Text(
'位置在编辑页直接拖动水印调整,会自动记住',
style: TextStyle(fontSize: 13, color: Color(0xFF6D4C41), height: 1.5),
),
),
if (settings.hasCustomPosition)
TextButton(
onPressed: notifier.resetPosition,
child: const Text(
'恢复默认',
style: TextStyle(fontSize: 13, color: Color(0xFFC8956E)),
),
),
],
),
),
],
),
),
);
}
Widget _sectionTitle(String text) {
return Text(
text,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF8D6E63),
),
);
}
Widget _switchRow(String label, String hint, bool value, ValueChanged<bool> onChanged) {
return InkWell(
onTap: () => onChanged(!value),
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontSize: 15, color: Color(0xFF5D4037)),
),
const SizedBox(height: 2),
Text(
hint,
style: const TextStyle(fontSize: 11.5, color: Color(0xFFBCAAA4)),
),
],
),
),
Switch(value: value, onChanged: onChanged),
],
),
),
);
}
/// 浅色底上的对勾要用深色,不然看不见
static bool _isLight(int argb) {
final r = (argb >> 16) & 0xFF;
final g = (argb >> 8) & 0xFF;
final b = argb & 0xFF;
return (r * 299 + g * 587 + b * 114) / 1000 > 160;
}
}