AI 接入 V1.0
UI更新
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
/// 双色图标组件。
|
||||
///
|
||||
/// 图标是 SVG 资源(`assets/icons/*.svg`),由 `tools/make_icons.py` 生成 ——
|
||||
/// 改形状或配色请改那个脚本再重跑,不要手工编辑 SVG 文件。
|
||||
///
|
||||
/// 每个图标自带一套配色(浅色填充 + 深色描边)。需要单色时传 [tint],
|
||||
/// 它会把整个图标压成一种颜色 —— 比如放在焦糖色主按钮上的白色图标。
|
||||
class CuteIcon extends StatelessWidget {
|
||||
const CuteIcon(this.name, {super.key, this.size = 22, this.tint});
|
||||
|
||||
/// 图标名,见 [cuteIconNames]
|
||||
final String name;
|
||||
|
||||
final double size;
|
||||
|
||||
/// 传了就压成单色(丢失双色效果,但能适配任意背景)
|
||||
final Color? tint;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final resolved = resolveIconName(name);
|
||||
return SvgPicture.asset(
|
||||
'assets/icons/$resolved.svg',
|
||||
width: size,
|
||||
height: size,
|
||||
colorFilter:
|
||||
tint == null ? null : ColorFilter.mode(tint!, BlendMode.srcIn),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 所有合法的图标名(和 tools/make_icons.py 里的 ICONS 一一对应)
|
||||
const cuteIconNames = <String>{
|
||||
// 记账分类
|
||||
'food', 'transport', 'shopping', 'entertainment', 'medical', 'education',
|
||||
'housing', 'utilities', 'phone', 'other', 'salary', 'bonus', 'invest',
|
||||
'parttime',
|
||||
// 模块
|
||||
'bill', 'note', 'assistant',
|
||||
// 界面
|
||||
'expense', 'income', 'camera', 'search', 'user', 'lock', 'eye', 'image',
|
||||
'file', 'folder', 'folder-open', 'check', 'pin', 'target', 'inbox', 'error',
|
||||
};
|
||||
|
||||
/// 老数据的兼容映射:emoji → 图标名。
|
||||
///
|
||||
/// 分类图标以前是 emoji 存在数据库里(`category.icon`),现在换成了图标名。
|
||||
/// 这份映射有两个作用:
|
||||
/// ① 迁移还没跑的时候,老数据照样能显示
|
||||
/// ② 迁移跑完之后,用户自己建分类时随手挑个 emoji 也能兜住
|
||||
/// 都不认识就退到 `other`,不会出现空白图标。
|
||||
///
|
||||
/// 公开出来是为了能写测试(映射表里写错一个名字会静默退化成兜底图标,
|
||||
/// 不测的话很难发现)。
|
||||
const cuteEmojiAliases = <String, String>{
|
||||
'🍔': 'food', '🍜': 'food', '🍚': 'food', '🍕': 'food', '☕': 'food',
|
||||
'🚗': 'transport', '🚌': 'transport', '🚇': 'transport', '✈': 'transport',
|
||||
'🛍': 'shopping', '🛒': 'shopping', '👕': 'shopping', '🎁': 'bonus',
|
||||
'🎬': 'entertainment', '🎮': 'entertainment', '🎵': 'entertainment',
|
||||
'🏥': 'medical', '💊': 'medical', '💉': 'medical',
|
||||
'📚': 'education', '🎓': 'education', '📖': 'education', '✏': 'education',
|
||||
'🏠': 'housing', '🏡': 'housing', '🛏': 'housing',
|
||||
'💡': 'utilities', '💧': 'utilities', '⚡': 'utilities', '🔌': 'utilities',
|
||||
'📱': 'phone', '☎': 'phone', '💻': 'phone',
|
||||
'💰': 'salary', '💵': 'salary', '🏦': 'salary', '💴': 'salary',
|
||||
'📈': 'invest', '📊': 'invest', '💹': 'invest', '🪙': 'invest',
|
||||
'💼': 'parttime', '🧑💻': 'parttime', '🔧': 'parttime',
|
||||
'📦': 'other', '📌': 'other', '🏷': 'other', '❓': 'other',
|
||||
};
|
||||
|
||||
/// 把任意来源的图标值(图标名 / emoji / 空)解析成一个合法的图标名
|
||||
String resolveIconName(String? raw) {
|
||||
final value = (raw ?? '').trim();
|
||||
if (value.isEmpty) return 'other';
|
||||
if (cuteIconNames.contains(value)) return value;
|
||||
|
||||
// emoji 里可能带变体选择符(U+FE0F),去掉再查一次
|
||||
final stripped = value.replaceAll('\uFE0F', '');
|
||||
return cuteEmojiAliases[value] ?? cuteEmojiAliases[stripped] ?? 'other';
|
||||
}
|
||||
Reference in New Issue
Block a user