Files
2026-09-28 11:39:19 +08:00

125 lines
3.9 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'json_util.dart';
import 'watermark.dart';
/// 一张已上传的照片。
///
/// ⚠️ [url] 指向的是**未加水印的原图** —— 服务器只存原图,水印一直是
/// App 本地合成的。所以「重新打水印」(换颜色、换位置、补备注)随时可行。
class PhotoItem {
const PhotoItem({
required this.id,
required this.url,
required this.thumbUrl,
required this.takenAt,
required this.address,
required this.weather,
required this.remark,
required this.latitude,
required this.longitude,
});
final int id;
/// 原图地址(相对路径,如 `/uploads/photo/2026/09/28/xxx.jpg`)
final String url;
/// 缩略图地址。为 null 表示后端没生成出来,客户端只能回退用原图。
final String? thumbUrl;
/// 拍摄时间,已格式化;后端两个时间字段取到哪个用哪个
final String takenAt;
final String address;
/// 「晴 25℃」这种拼好的天气文本
final String weather;
final String remark;
final double? latitude;
final double? longitude;
factory PhotoItem.fromJson(Map<String, dynamic> json) {
// 拍摄时间是客户端传的,可能为空(格式没对上时后端会存 null),
// 那就退回记录创建时间 —— 至少能显示个日期
final taken = formatDateTime(json['takenTime']);
final created = formatDateTime(json['createTime']);
return PhotoItem(
id: asInt(json['id']),
url: asString(json['url']),
thumbUrl: asString(json['thumbUrl']).isEmpty
? null
: asString(json['thumbUrl']),
takenAt: taken.isNotEmpty ? taken : created,
address: asString(json['address']),
weather: asString(json['weather']),
remark: asString(json['remark']),
latitude: asNullableDouble(json['latitude']),
longitude: asNullableDouble(json['longitude']),
);
}
/// 还原成水印数据,「重新打水印」时用。
///
/// 关键点是**复用这张照片当时的地址和天气**,而不是拿当前位置 ——
/// 不然给三年前的照片重新加个水印,地点会变成「你现在站的地方」。
WatermarkData toWatermarkData() {
final split = splitWeatherText(weather);
return WatermarkData(
time: parsePhotoTime(takenAt),
address: address,
weather: split.weather,
temperature: split.temperature,
remark: remark,
);
}
}
/// 把后端存的 `yyyy-MM-dd HH:mm` 解析回 DateTime;解析不了就用当前时间
DateTime parsePhotoTime(String value) {
final normalized = value.trim().replaceFirst(' ', 'T');
return DateTime.tryParse(normalized) ?? DateTime.now();
}
/// 拆开后端存的天气文本(「晴 25℃」→ weather=晴, temperature=25℃)。
///
/// 存的时候是一个字段(上传时客户端就是这么拼的),所以这里拆回来。
/// 两种情况都要兜住:只有天气没有温度(「晴」)、只有温度没有天气(「25℃」)。
({String weather, String temperature}) splitWeatherText(String combined) {
final text = combined.trim();
if (text.isEmpty) return (weather: '', temperature: '');
final parts = text.split(' ');
if (parts.last.endsWith('℃')) {
return (
weather: parts.sublist(0, parts.length - 1).join(' '),
temperature: parts.last,
);
}
return (weather: text, temperature: '');
}
/// 照片分页
class PhotoPage {
const PhotoPage({
required this.items,
required this.total,
required this.hasMore,
});
final List<PhotoItem> items;
final int total;
final bool hasMore;
static const empty = PhotoPage(items: [], total: 0, hasMore: false);
factory PhotoPage.fromJson(Map<String, dynamic> json) {
return PhotoPage(
items: asMapList(json['list']).map(PhotoItem.fromJson).toList(),
total: asInt(json['total']),
hasMore: json['hasMore'] == true,
);
}
}