水印优化+ORC优化
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/storage/local_storage.dart';
|
||||
import '../../../models/watermark.dart';
|
||||
|
||||
/// 编辑这一张照片的水印内容(地址 / 天气 / 温度 / 备注)
|
||||
/// 编辑这一张照片的水印内容(时间 / 地址 / 天气 / 温度 / 备注)
|
||||
///
|
||||
/// 返回 null 表示用户取消了。时间不在这里改 —— 它总是「现在」,
|
||||
/// 让用户手改反而容易把水印弄成假的时间。
|
||||
/// 返回 null 表示用户取消了。
|
||||
Future<WatermarkData?> showWatermarkContentSheet(
|
||||
BuildContext context, {
|
||||
required WatermarkData initial,
|
||||
@@ -36,9 +36,18 @@ class _WatermarkContentSheetState extends State<_WatermarkContentSheet> {
|
||||
late final TextEditingController _temperature;
|
||||
late final TextEditingController _remark;
|
||||
|
||||
/// 时间可以改 —— 补加水印、或者当时手机时间不对时要靠它
|
||||
late DateTime _time;
|
||||
|
||||
/// 常用地址,从 LocalStorage 读(同步可读)
|
||||
late List<String> _addresses;
|
||||
bool _managing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_time = widget.initial.time;
|
||||
_addresses = List<String>.from(LocalStorage.instance.getWatermarkAddresses());
|
||||
_address = TextEditingController(text: widget.initial.address);
|
||||
_weather = TextEditingController(text: widget.initial.weather);
|
||||
_temperature = TextEditingController(text: widget.initial.temperature);
|
||||
@@ -57,6 +66,7 @@ class _WatermarkContentSheetState extends State<_WatermarkContentSheet> {
|
||||
void _submit() {
|
||||
Navigator.of(context).pop(
|
||||
widget.initial.copyWith(
|
||||
time: _time,
|
||||
address: _address.text.trim(),
|
||||
weather: _weather.text.trim(),
|
||||
temperature: _temperature.text.trim(),
|
||||
@@ -65,6 +75,88 @@ class _WatermarkContentSheetState extends State<_WatermarkContentSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- 时间 ----------------
|
||||
|
||||
Future<void> _pickTime() async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _time,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2100),
|
||||
);
|
||||
if (date == null || !mounted) return;
|
||||
|
||||
final time = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.fromDateTime(_time),
|
||||
);
|
||||
if (time == null || !mounted) return;
|
||||
|
||||
setState(() {
|
||||
_time = DateTime(date.year, date.month, date.day, time.hour, time.minute);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------- 常用地址 ----------------
|
||||
|
||||
Future<void> _addAddress() async {
|
||||
if (_addresses.length >= LocalStorage.watermarkAddressLimit) return;
|
||||
// 预填当前地址:常见的用法是「改一改然后存成常用」,省得再敲一遍
|
||||
final text = await _promptAddress(initial: _address.text.trim());
|
||||
if (text == null || !mounted) return;
|
||||
|
||||
final next = [..._addresses, text];
|
||||
await LocalStorage.instance.saveWatermarkAddresses(next);
|
||||
if (!mounted) return;
|
||||
setState(() => _addresses = next);
|
||||
}
|
||||
|
||||
Future<void> _removeAddress(int index) async {
|
||||
final next = [..._addresses]..removeAt(index);
|
||||
await LocalStorage.instance.saveWatermarkAddresses(next);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_addresses = next;
|
||||
if (_addresses.isEmpty) _managing = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<String?> _promptAddress({String initial = ''}) {
|
||||
final controller = TextEditingController(text: initial);
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Text('添加常用地址', style: TextStyle(fontSize: 16)),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '例如:云南省昆明市五华区…',
|
||||
hintStyle: TextStyle(fontSize: 13, color: Color(0xFFD7CCC8)),
|
||||
),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
child: const Text('取消', style: TextStyle(color: Color(0xFF8D6E63))),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final text = controller.text.trim();
|
||||
Navigator.of(dialogContext).pop(text.isEmpty ? null : text);
|
||||
},
|
||||
child: const Text('添加', style: TextStyle(color: Color(0xFFC8956E))),
|
||||
),
|
||||
],
|
||||
),
|
||||
).whenComplete(controller.dispose);
|
||||
}
|
||||
|
||||
// ---------------- 构建 ----------------
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
@@ -89,12 +181,17 @@ class _WatermarkContentSheetState extends State<_WatermarkContentSheet> {
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
_timeRow(),
|
||||
const SizedBox(height: 16),
|
||||
_quickAddressSection(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_field(label: '地址', controller: _address, hint: '自动定位,也可手改'),
|
||||
_field(label: '天气', controller: _weather, hint: '如:晴'),
|
||||
_field(label: '温度', controller: _temperature, hint: '如:25℃'),
|
||||
_field(label: '备注', controller: _remark, hint: '想写点什么'),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -139,6 +236,150 @@ class _WatermarkContentSheetState extends State<_WatermarkContentSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 时间行:点一下弹日期 + 时间选择器
|
||||
Widget _timeRow() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('时间', style: TextStyle(fontSize: 13, color: Color(0xFF8D6E63))),
|
||||
const SizedBox(height: 6),
|
||||
InkWell(
|
||||
onTap: _pickTime,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF7F4F1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
formatWatermarkTime(_time),
|
||||
style: const TextStyle(fontSize: 14, color: Color(0xFF5D4037)),
|
||||
),
|
||||
),
|
||||
const Icon(Icons.event, size: 17, color: Color(0xFFBCAAA4)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _quickAddressSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
'常用地址',
|
||||
style: TextStyle(fontSize: 13, color: Color(0xFF8D6E63)),
|
||||
),
|
||||
const Spacer(),
|
||||
if (_addresses.isNotEmpty)
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _managing = !_managing),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
child: Text(
|
||||
_managing ? '完成' : '管理',
|
||||
style: const TextStyle(fontSize: 12.5, color: Color(0xFFC8956E)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (var i = 0; i < _addresses.length; i++)
|
||||
_addressChip(i, _addresses[i]),
|
||||
if (!_managing && _addresses.length < LocalStorage.watermarkAddressLimit)
|
||||
_addChip(),
|
||||
],
|
||||
),
|
||||
if (_addresses.isEmpty && !_managing)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
'把常去的地点存下来,以后一键替换',
|
||||
style: TextStyle(fontSize: 11.5, color: Color(0xFFBCAAA4)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _addressChip(int index, String address) {
|
||||
return GestureDetector(
|
||||
// 管理模式下点整体是删除,正常模式点一下填进地址框
|
||||
onTap: _managing
|
||||
? () => _removeAddress(index)
|
||||
: () => setState(() => _address.text = address),
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 210),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: _managing ? const Color(0xFFFDECEA) : const Color(0xFFFFF3E6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
address,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
color: _managing
|
||||
? const Color(0xFFE53935)
|
||||
: const Color(0xFF8D6E63),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_managing) ...[
|
||||
const SizedBox(width: 6),
|
||||
const Icon(Icons.close, size: 14, color: Color(0xFFE53935)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _addChip() {
|
||||
return GestureDetector(
|
||||
onTap: _addAddress,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFFE0C9A6)),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.add, size: 14, color: Color(0xFFC8956E)),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'添加',
|
||||
style: TextStyle(fontSize: 12.5, color: Color(0xFFC8956E)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _field({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../providers/watermark_provider.dart';
|
||||
|
||||
/// 首页仪表盘上「水印相机」卡片的摘要:已上传张数 + 最近一张的日期。
|
||||
///
|
||||
/// 卡片高度由 [AppModule.summary] 那段固定高度占位决定(见 dashboard_page),
|
||||
/// 所以这里只负责填内容,布局和「笔记」卡片保持一致 ——
|
||||
/// 每格都是「标签 + 加粗数值」两行,少一个卡片就会比别的矮一截。
|
||||
///
|
||||
/// 数据来自 [photoOverviewProvider](只取 1 条),不碰列表页那套状态。
|
||||
class WatermarkModuleSummary extends ConsumerWidget {
|
||||
const WatermarkModuleSummary({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final page = ref.watch(photoOverviewProvider).value;
|
||||
final latest = (page == null || page.items.isEmpty)
|
||||
? null
|
||||
: page.items.first.takenAt;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(child: _item('已存', page == null ? null : '${page.total} 张')),
|
||||
Expanded(child: _item('最近拍摄', latest == null ? null : _shortDate(latest))),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 后端给的是 `yyyy-MM-dd HH:mm`,卡片上只要 `MM-dd`
|
||||
String _shortDate(String value) =>
|
||||
value.length >= 10 ? value.substring(5, 10) : value;
|
||||
|
||||
Widget _item(String label, String? value) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontSize: 11, color: Color(0xFF8D6E63))),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
value ?? '--',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user