Files
Aq-Accounting-Flutter/lib/pages/watermark/widgets/watermark_content_sheet.dart
T
2026-09-28 11:39:19 +08:00

421 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import '../../../core/storage/local_storage.dart';
import '../../../models/watermark.dart';
/// 编辑这一张照片的水印内容(时间 / 地址 / 天气 / 温度 / 备注)
///
/// 返回 null 表示用户取消了。
Future<WatermarkData?> showWatermarkContentSheet(
BuildContext context, {
required WatermarkData initial,
}) {
return showModalBottomSheet<WatermarkData>(
context: context,
backgroundColor: Colors.white,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (_) => _WatermarkContentSheet(initial: initial),
);
}
class _WatermarkContentSheet extends StatefulWidget {
const _WatermarkContentSheet({required this.initial});
final WatermarkData initial;
@override
State<_WatermarkContentSheet> createState() => _WatermarkContentSheetState();
}
class _WatermarkContentSheetState extends State<_WatermarkContentSheet> {
late final TextEditingController _address;
late final TextEditingController _weather;
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);
_remark = TextEditingController(text: widget.initial.remark);
}
@override
void dispose() {
_address.dispose();
_weather.dispose();
_temperature.dispose();
_remark.dispose();
super.dispose();
}
void _submit() {
Navigator.of(context).pop(
widget.initial.copyWith(
time: _time,
address: _address.text.trim(),
weather: _weather.text.trim(),
temperature: _temperature.text.trim(),
remark: _remark.text.trim(),
),
);
}
// ---------------- 时间 ----------------
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(
// 键盘弹出时把内容顶上去,否则输入框会被挡住
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
child: 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),
_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: 12),
Row(
children: [
Expanded(
child: SizedBox(
height: 46,
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFFC8956E),
side: const BorderSide(color: Color(0xFFC8956E)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(23),
),
),
child: const Text('取消'),
),
),
),
const SizedBox(width: 12),
Expanded(
child: SizedBox(
height: 46,
child: FilledButton(
onPressed: _submit,
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFFC8956E),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(23),
),
),
child: const Text('确定'),
),
),
),
],
),
],
),
),
),
);
}
/// 时间行:点一下弹日期 + 时间选择器
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,
required String hint,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontSize: 13, color: Color(0xFF8D6E63)),
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: const Color(0xFFF7F4F1),
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: controller,
style: const TextStyle(fontSize: 14, color: Color(0xFF5D4037)),
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: const EdgeInsets.symmetric(vertical: 13),
hintText: hint,
hintStyle: const TextStyle(fontSize: 14, color: Color(0xFFD7CCC8)),
),
),
),
],
),
);
}
}