鸿蒙提交
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -171,6 +171,20 @@
|
|||||||
- `lib/app/services/desktop_services_manager.dart` — 延迟首次 applyEffect
|
- `lib/app/services/desktop_services_manager.dart` — 延迟首次 applyEffect
|
||||||
- `lib/app/services/theme_sync_service.dart` — 跳过首次窗口特效同步
|
- `lib/app/services/theme_sync_service.dart` — 跳过首次窗口特效同步
|
||||||
|
|
||||||
|
### 🔧 维护(pubspec.yaml 同步鸿蒙端模板)
|
||||||
|
|
||||||
|
#### 1. pubspec.yaml 版本号与依赖同步鸿蒙端模板
|
||||||
|
- **Issue**: `pubspec.yaml` 版本落后(6.6.22+2606213),且头部注释错误标注为"鸿蒙端模板",与 `pubspec.ohos.yaml` 模板(6.6.25+2606241)不同步
|
||||||
|
- **原因**: 拉取 git 代码后未重新生成 `pubspec.yaml`,导致主文件与鸿蒙端模板版本号不一致
|
||||||
|
- **修复**:
|
||||||
|
- 基于 `pubspec.ohos.yaml` 模板重新生成 `pubspec.yaml`
|
||||||
|
- 版本号升级:6.6.22+2606213 → 6.6.25+2606241
|
||||||
|
- 头部注释修正为"主 pubspec.yaml(基于鸿蒙端模板生成)"
|
||||||
|
- 所有依赖项、本地包引用、dependency_overrides 完全对齐 `pubspec.ohos.yaml`
|
||||||
|
- 保留 `msix` dev_dependency 和 `msix_config` 配置(Windows 端 Microsoft Store 打包专用)
|
||||||
|
- **涉及文件**: `pubspec.yaml`
|
||||||
|
- **验证**: `git diff --no-index pubspec.ohos.yaml pubspec.yaml` 确认差异仅为 msix 配置和头部注释
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [v6.118.0] - 2026-06-24
|
## [v6.118.0] - 2026-06-24
|
||||||
|
|||||||
479
docs/google_play_feature_graphic.html
Normal file
479
docs/google_play_feature_graphic.html
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>闲言 Xianyan — Google Play 置顶大图</title>
|
||||||
|
<style>
|
||||||
|
/* ============================================================
|
||||||
|
* 闲言 APP — Google Play Feature Graphic
|
||||||
|
* 尺寸: 1024 x 500 px (PNG/JPEG, ≤15MB)
|
||||||
|
* 设计: 白色主题 + iOS 风格 + 毛玻璃效果
|
||||||
|
* 创建时间: 2026-06-24
|
||||||
|
* 更新时间: 2026-06-24
|
||||||
|
* 上次更新: 主题色由紫色渐变改为白色主题,紫色作为点缀色
|
||||||
|
* ============================================================ */
|
||||||
|
|
||||||
|
/* ---- 设计令牌 (与项目 app_colors.dart 对齐) ---- */
|
||||||
|
:root {
|
||||||
|
--primary: #6C63FF; /* 主色 (点缀用) */
|
||||||
|
--primary-light: #8B83FF;
|
||||||
|
--primary-dark: #4A42E0;
|
||||||
|
--secondary: #FF6B6B; /* 辅助色 珊瑚红 */
|
||||||
|
--accent: #4ECDC4; /* 强调色 青色 */
|
||||||
|
--ios-blue: #007AFF;
|
||||||
|
--ios-pink: #FF2D55;
|
||||||
|
--ios-orange: #FF9500;
|
||||||
|
--ios-yellow: #FFCC00;
|
||||||
|
--ios-mint: #30D158;
|
||||||
|
|
||||||
|
--text-primary: #1A1A2E; /* 深色文字 */
|
||||||
|
--text-secondary: #6B7280;
|
||||||
|
--text-tertiary: #9CA3AF;
|
||||||
|
--text-inverse: #FFFFFF;
|
||||||
|
|
||||||
|
--bg-primary: #FFFFFF;
|
||||||
|
--bg-secondary: #FAFAFA;
|
||||||
|
--bg-card: #FFFFFF;
|
||||||
|
|
||||||
|
--radius-sm: 8px;
|
||||||
|
--radius-md: 12px;
|
||||||
|
--radius-lg: 20px;
|
||||||
|
--radius-xl: 28px;
|
||||||
|
|
||||||
|
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
--shadow-md: 0 8px 24px rgba(108, 99, 255, 0.10);
|
||||||
|
--shadow-lg: 0 16px 40px rgba(74, 66, 224, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 1024px;
|
||||||
|
height: 500px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: 'Inter', -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 画布容器 (白色主题) ---- */
|
||||||
|
.canvas {
|
||||||
|
position: relative;
|
||||||
|
width: 1024px;
|
||||||
|
height: 500px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 12% 18%, rgba(139, 131, 255, 0.18) 0%, transparent 40%),
|
||||||
|
radial-gradient(circle at 88% 82%, rgba(78, 205, 196, 0.15) 0%, transparent 45%),
|
||||||
|
radial-gradient(circle at 50% 50%, rgba(255, 107, 107, 0.06) 0%, transparent 55%),
|
||||||
|
linear-gradient(135deg, #FFFFFF 0%, #FAFAFF 50%, #F5F5FF 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 装饰光斑 (淡彩氛围) ---- */
|
||||||
|
.blob {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(80px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.blob-1 {
|
||||||
|
width: 340px; height: 340px;
|
||||||
|
background: radial-gradient(circle, rgba(108, 99, 255, 0.35) 0%, transparent 70%);
|
||||||
|
top: -100px; right: -80px;
|
||||||
|
}
|
||||||
|
.blob-2 {
|
||||||
|
width: 280px; height: 280px;
|
||||||
|
background: radial-gradient(circle, rgba(78, 205, 196, 0.30) 0%, transparent 70%);
|
||||||
|
bottom: -120px; left: 28%;
|
||||||
|
}
|
||||||
|
.blob-3 {
|
||||||
|
width: 220px; height: 220px;
|
||||||
|
background: radial-gradient(circle, rgba(255, 204, 0, 0.20) 0%, transparent 70%);
|
||||||
|
top: 35%; right: 22%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 网格纹理 (细微质感) ---- */
|
||||||
|
.grid-texture {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(108, 99, 255, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(108, 99, 255, 0.03) 1px, transparent 1px);
|
||||||
|
background-size: 40px 40px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 主内容布局 ---- */
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 48px 56px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 左侧: 品牌区 ---- */
|
||||||
|
.brand {
|
||||||
|
flex: 0 0 440px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- App 图标 (使用项目实际图标) ---- */
|
||||||
|
.app-icon {
|
||||||
|
width: 88px;
|
||||||
|
height: 88px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
box-shadow:
|
||||||
|
0 10px 28px rgba(108, 99, 255, 0.40),
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.app-icon-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 标题区 ---- */
|
||||||
|
.title-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.title-cn {
|
||||||
|
font-size: 44px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-primary);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
.title-en {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--primary);
|
||||||
|
letter-spacing: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 标语 ---- */
|
||||||
|
.tagline {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.tagline-cn {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.tagline-cn .accent {
|
||||||
|
background: linear-gradient(135deg, #6C63FF 0%, #FF6B6B 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.tagline-en {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 功能标签条 ---- */
|
||||||
|
.feature-pills {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.pill {
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 100px;
|
||||||
|
background: rgba(108, 99, 255, 0.08);
|
||||||
|
border: 1px solid rgba(108, 99, 255, 0.20);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 右侧: 功能展示卡片群 ---- */
|
||||||
|
.showcase {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 白色毛玻璃功能卡 ---- */
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.90);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 24px rgba(108, 99, 255, 0.10),
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.04),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||||||
|
padding: 18px 18px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
height: 50%;
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,0.5), transparent);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 22px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.card-icon.purple { background: linear-gradient(135deg, #6C63FF, #8B83FF); }
|
||||||
|
.card-icon.coral { background: linear-gradient(135deg, #FF6B6B, #FF8E8E); }
|
||||||
|
.card-icon.cyan { background: linear-gradient(135deg, #4ECDC4, #6FE5DC); }
|
||||||
|
.card-icon.yellow { background: linear-gradient(135deg, #FF9500, #FFB84D); }
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.card-title-cn {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.card-title-en {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 卡片装饰小元素 ---- */
|
||||||
|
.card-deco {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -20px;
|
||||||
|
right: -20px;
|
||||||
|
font-size: 80px;
|
||||||
|
opacity: 0.10;
|
||||||
|
transform: rotate(-15deg);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 底部信息条 ---- */
|
||||||
|
.footer-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 36px;
|
||||||
|
background: linear-gradient(180deg, transparent, rgba(108, 99, 255, 0.06));
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 56px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.footer-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.footer-dot {
|
||||||
|
width: 4px; height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.footer-right {
|
||||||
|
color: var(--primary);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 角标: 多语言 ---- */
|
||||||
|
.badge-lang {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.lang-label {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 100px;
|
||||||
|
background: rgba(255, 255, 255, 0.80);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(108, 99, 255, 0.20);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
box-shadow: 0 2px 8px rgba(108, 99, 255, 0.08);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.lang-label.more {
|
||||||
|
background: linear-gradient(135deg, #6C63FF, #8B83FF);
|
||||||
|
color: #FFFFFF;
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: 0 2px 8px rgba(108, 99, 255, 0.30);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="canvas">
|
||||||
|
<!-- 装饰光斑 -->
|
||||||
|
<div class="blob blob-1"></div>
|
||||||
|
<div class="blob blob-2"></div>
|
||||||
|
<div class="blob blob-3"></div>
|
||||||
|
<div class="grid-texture"></div>
|
||||||
|
|
||||||
|
<!-- 多语言角标 -->
|
||||||
|
<div class="badge-lang">
|
||||||
|
<span class="lang-label">🌐 简体中文</span>
|
||||||
|
<span class="lang-label">繁體中文</span>
|
||||||
|
<span class="lang-label">English</span>
|
||||||
|
<span class="lang-label">日本語</span>
|
||||||
|
<span class="lang-label more">+10 种语言</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主内容 -->
|
||||||
|
<div class="content">
|
||||||
|
<!-- 左侧品牌区 -->
|
||||||
|
<div class="brand">
|
||||||
|
<div class="brand-head">
|
||||||
|
<div class="app-icon">
|
||||||
|
<img class="app-icon-img" src="icon_88x88.png" alt="闲言">
|
||||||
|
</div>
|
||||||
|
<div class="title-block">
|
||||||
|
<div class="title-cn">闲言</div>
|
||||||
|
<div class="title-en">Xianyan</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tagline">
|
||||||
|
<div class="tagline-cn">灵感语录 · <span class="accent">更纯粹</span></div>
|
||||||
|
<div class="tagline-en">Pure inspiration · Daily quotes & wallpaper creation</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-pills">
|
||||||
|
<span class="pill">✨ 每日拾句</span>
|
||||||
|
<span class="pill">🎨 壁纸创作</span>
|
||||||
|
<span class="pill">📖 灵感语录</span>
|
||||||
|
<span class="pill">🔮 每日运势</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧功能展示 -->
|
||||||
|
<div class="showcase">
|
||||||
|
<!-- 每日卡片 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-icon purple">📅</div>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="card-title-cn">每日拾句</div>
|
||||||
|
<div class="card-title-en">Daily Quote</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-deco">📖</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 壁纸创作 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-icon coral">🎨</div>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="card-title-cn">壁纸创作</div>
|
||||||
|
<div class="card-title-en">Wallpaper Studio</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-deco">🖼️</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 发现工具 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-icon cyan">🔍</div>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="card-title-cn">发现工具</div>
|
||||||
|
<div class="card-title-en">Discover & Tools</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-deco">🧭</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 每日运势 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-icon yellow">🔮</div>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="card-title-cn">每日运势</div>
|
||||||
|
<div class="card-title-en">Daily Fortune</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-deco">⭐</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部信息条 -->
|
||||||
|
<div class="footer-bar">
|
||||||
|
<div class="footer-left">
|
||||||
|
<span>每日拾句</span>
|
||||||
|
<span class="footer-dot"></span>
|
||||||
|
<span>壁纸创作</span>
|
||||||
|
<span class="footer-dot"></span>
|
||||||
|
<span>灵感语录</span>
|
||||||
|
<span class="footer-dot"></span>
|
||||||
|
<span>笔记收藏</span>
|
||||||
|
<span class="footer-dot"></span>
|
||||||
|
<span>跨端同步</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-right">v6.6 · 微风暴出品</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
docs/google_play_feature_graphic.png
Normal file
BIN
docs/google_play_feature_graphic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
BIN
docs/icon_88x88.png
Normal file
BIN
docs/icon_88x88.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
@@ -3,7 +3,7 @@
|
|||||||
/// 创建时间: 2026-06-12
|
/// 创建时间: 2026-06-12
|
||||||
/// 更新时间: 2026-06-12
|
/// 更新时间: 2026-06-12
|
||||||
/// 作用: CustomPainter自绘天气图标,支持动态主题色和动画效果
|
/// 作用: CustomPainter自绘天气图标,支持动态主题色和动画效果
|
||||||
/// 上次更新: 小尺寸优化 — size<16 自动回退 CupertinoIcons 避免自绘细节丢失
|
/// 上次更新: 修复 SingleTickerProviderStateMixin 多 ticker 崩溃 → 改用 TickerProviderStateMixin
|
||||||
/// ============================================================
|
/// ============================================================
|
||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
@@ -48,22 +48,29 @@ class WeatherIcon extends StatefulWidget {
|
|||||||
|
|
||||||
/// 获取天气对应的图标颜色
|
/// 获取天气对应的图标颜色
|
||||||
static Color moodColor(WeatherPoetryMood mood, {bool isDark = false}) =>
|
static Color moodColor(WeatherPoetryMood mood, {bool isDark = false}) =>
|
||||||
switch (mood) {
|
switch (mood) {
|
||||||
WeatherPoetryMood.sunny => isDark ? const Color(0xFFFFD60A) : const Color(0xFFFFCC00),
|
WeatherPoetryMood.sunny =>
|
||||||
WeatherPoetryMood.cloudy => isDark ? const Color(0xFF98989D) : const Color(0xFF8E8E93),
|
isDark ? const Color(0xFFFFD60A) : const Color(0xFFFFCC00),
|
||||||
WeatherPoetryMood.rainy => isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
|
WeatherPoetryMood.cloudy =>
|
||||||
WeatherPoetryMood.thunder => isDark ? const Color(0xFFBF5AF2) : const Color(0xFFAF52DE),
|
isDark ? const Color(0xFF98989D) : const Color(0xFF8E8E93),
|
||||||
WeatherPoetryMood.snowy => isDark ? const Color(0xFFD1E9FF) : const Color(0xFFC7DFF0),
|
WeatherPoetryMood.rainy =>
|
||||||
WeatherPoetryMood.foggy => isDark ? const Color(0xFFC7C7CC) : const Color(0xFFAEAEB2),
|
isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
|
||||||
WeatherPoetryMood.windy => isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
|
WeatherPoetryMood.thunder =>
|
||||||
};
|
isDark ? const Color(0xFFBF5AF2) : const Color(0xFFAF52DE),
|
||||||
|
WeatherPoetryMood.snowy =>
|
||||||
|
isDark ? const Color(0xFFD1E9FF) : const Color(0xFFC7DFF0),
|
||||||
|
WeatherPoetryMood.foggy =>
|
||||||
|
isDark ? const Color(0xFFC7C7CC) : const Color(0xFFAEAEB2),
|
||||||
|
WeatherPoetryMood.windy =>
|
||||||
|
isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<WeatherIcon> createState() => _WeatherIconState();
|
State<WeatherIcon> createState() => _WeatherIconState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WeatherIconState extends State<WeatherIcon>
|
class _WeatherIconState extends State<WeatherIcon>
|
||||||
with SingleTickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
AnimationController? _controller;
|
AnimationController? _controller;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -109,7 +116,8 @@ class _WeatherIconState extends State<WeatherIcon>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isDark = AppTheme.isDarkMode(context);
|
final isDark = AppTheme.isDarkMode(context);
|
||||||
final color = widget.color ?? WeatherIcon.moodColor(widget.mood, isDark: isDark);
|
final color =
|
||||||
|
widget.color ?? WeatherIcon.moodColor(widget.mood, isDark: isDark);
|
||||||
|
|
||||||
// 小尺寸优化:16px以下回退到CupertinoIcons,避免自绘细节丢失
|
// 小尺寸优化:16px以下回退到CupertinoIcons,避免自绘细节丢失
|
||||||
if (widget.size < 16) {
|
if (widget.size < 16) {
|
||||||
@@ -127,14 +135,14 @@ class _WeatherIconState extends State<WeatherIcon>
|
|||||||
|
|
||||||
/// 小尺寸回退图标 — 将天气类型映射到 CupertinoIcons
|
/// 小尺寸回退图标 — 将天气类型映射到 CupertinoIcons
|
||||||
static IconData _fallbackIcon(WeatherPoetryMood mood) => switch (mood) {
|
static IconData _fallbackIcon(WeatherPoetryMood mood) => switch (mood) {
|
||||||
WeatherPoetryMood.sunny => CupertinoIcons.sun_max,
|
WeatherPoetryMood.sunny => CupertinoIcons.sun_max,
|
||||||
WeatherPoetryMood.cloudy => CupertinoIcons.cloud_fill,
|
WeatherPoetryMood.cloudy => CupertinoIcons.cloud_fill,
|
||||||
WeatherPoetryMood.rainy => CupertinoIcons.cloud_fill,
|
WeatherPoetryMood.rainy => CupertinoIcons.cloud_fill,
|
||||||
WeatherPoetryMood.thunder => CupertinoIcons.bolt_fill,
|
WeatherPoetryMood.thunder => CupertinoIcons.bolt_fill,
|
||||||
WeatherPoetryMood.snowy => CupertinoIcons.star_fill,
|
WeatherPoetryMood.snowy => CupertinoIcons.star_fill,
|
||||||
WeatherPoetryMood.foggy => CupertinoIcons.cloud_fill,
|
WeatherPoetryMood.foggy => CupertinoIcons.cloud_fill,
|
||||||
WeatherPoetryMood.windy => CupertinoIcons.wind,
|
WeatherPoetryMood.windy => CupertinoIcons.wind,
|
||||||
};
|
};
|
||||||
|
|
||||||
Widget _buildPainter(Color color, double progress) => SizedBox(
|
Widget _buildPainter(Color color, double progress) => SizedBox(
|
||||||
width: widget.size,
|
width: widget.size,
|
||||||
@@ -143,15 +151,15 @@ class _WeatherIconState extends State<WeatherIcon>
|
|||||||
);
|
);
|
||||||
|
|
||||||
CustomPainter _createPainter(WeatherPoetryMood mood, Color color, double p) =>
|
CustomPainter _createPainter(WeatherPoetryMood mood, Color color, double p) =>
|
||||||
switch (mood) {
|
switch (mood) {
|
||||||
WeatherPoetryMood.sunny => SunnyPainter(color: color, progress: p),
|
WeatherPoetryMood.sunny => SunnyPainter(color: color, progress: p),
|
||||||
WeatherPoetryMood.cloudy => CloudyPainter(color: color, progress: p),
|
WeatherPoetryMood.cloudy => CloudyPainter(color: color, progress: p),
|
||||||
WeatherPoetryMood.rainy => RainyPainter(color: color, progress: p),
|
WeatherPoetryMood.rainy => RainyPainter(color: color, progress: p),
|
||||||
WeatherPoetryMood.thunder => ThunderPainter(color: color, progress: p),
|
WeatherPoetryMood.thunder => ThunderPainter(color: color, progress: p),
|
||||||
WeatherPoetryMood.snowy => SnowyPainter(color: color, progress: p),
|
WeatherPoetryMood.snowy => SnowyPainter(color: color, progress: p),
|
||||||
WeatherPoetryMood.foggy => FoggyPainter(color: color, progress: p),
|
WeatherPoetryMood.foggy => FoggyPainter(color: color, progress: p),
|
||||||
WeatherPoetryMood.windy => WindyPainter(color: color, progress: p),
|
WeatherPoetryMood.windy => WindyPainter(color: color, progress: p),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -177,10 +185,38 @@ Path _cloudPath(double left, double top, double w, double h) {
|
|||||||
final p = Path();
|
final p = Path();
|
||||||
final cx = left + w / 2, cy = top + h / 2;
|
final cx = left + w / 2, cy = top + h / 2;
|
||||||
p.moveTo(cx - w / 2, cy);
|
p.moveTo(cx - w / 2, cy);
|
||||||
p.cubicTo(cx - w / 2, cy - h * 0.65, cx - w * 0.25, cy - h * 0.85, cx, cy - h * 0.5);
|
p.cubicTo(
|
||||||
p.cubicTo(cx + w * 0.25, cy - h * 0.85, cx + w / 2, cy - h * 0.65, cx + w / 2, cy);
|
cx - w / 2,
|
||||||
p.cubicTo(cx + w / 2, cy + h * 0.35, cx + w * 0.25, cy + h * 0.45, cx, cy + h * 0.35);
|
cy - h * 0.65,
|
||||||
p.cubicTo(cx - w * 0.25, cy + h * 0.45, cx - w / 2, cy + h * 0.35, cx - w / 2, cy);
|
cx - w * 0.25,
|
||||||
|
cy - h * 0.85,
|
||||||
|
cx,
|
||||||
|
cy - h * 0.5,
|
||||||
|
);
|
||||||
|
p.cubicTo(
|
||||||
|
cx + w * 0.25,
|
||||||
|
cy - h * 0.85,
|
||||||
|
cx + w / 2,
|
||||||
|
cy - h * 0.65,
|
||||||
|
cx + w / 2,
|
||||||
|
cy,
|
||||||
|
);
|
||||||
|
p.cubicTo(
|
||||||
|
cx + w / 2,
|
||||||
|
cy + h * 0.35,
|
||||||
|
cx + w * 0.25,
|
||||||
|
cy + h * 0.45,
|
||||||
|
cx,
|
||||||
|
cy + h * 0.35,
|
||||||
|
);
|
||||||
|
p.cubicTo(
|
||||||
|
cx - w * 0.25,
|
||||||
|
cy + h * 0.45,
|
||||||
|
cx - w / 2,
|
||||||
|
cy + h * 0.35,
|
||||||
|
cx - w / 2,
|
||||||
|
cy,
|
||||||
|
);
|
||||||
p.close();
|
p.close();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@@ -222,15 +258,24 @@ class SunnyPainter extends CustomPainter {
|
|||||||
final dx2 = outerR * math.cos(a), dy2 = outerR * math.sin(a);
|
final dx2 = outerR * math.cos(a), dy2 = outerR * math.sin(a);
|
||||||
final ray = Path()
|
final ray = Path()
|
||||||
..moveTo(dx1, dy1)
|
..moveTo(dx1, dy1)
|
||||||
..cubicTo(dx1 + (dx2 - dx1) * 0.4, dy1 + (dy2 - dy1) * 0.2,
|
..cubicTo(
|
||||||
dx1 + (dx2 - dx1) * 0.6, dy1 + (dy2 - dy1) * 0.8, dx2, dy2);
|
dx1 + (dx2 - dx1) * 0.4,
|
||||||
|
dy1 + (dy2 - dy1) * 0.2,
|
||||||
|
dx1 + (dx2 - dx1) * 0.6,
|
||||||
|
dy1 + (dy2 - dy1) * 0.8,
|
||||||
|
dx2,
|
||||||
|
dy2,
|
||||||
|
);
|
||||||
canvas.drawPath(ray, rayStroke);
|
canvas.drawPath(ray, rayStroke);
|
||||||
}
|
}
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
|
|
||||||
// 中心高光
|
// 中心高光
|
||||||
canvas.drawCircle(const Offset(22, 22), 3,
|
canvas.drawCircle(
|
||||||
_fillPaint(const Color(0xFFFFFFFF), alpha: 0.4));
|
const Offset(22, 22),
|
||||||
|
3,
|
||||||
|
_fillPaint(const Color(0xFFFFFFFF), alpha: 0.4),
|
||||||
|
);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +341,10 @@ class RainyPainter extends CustomPainter {
|
|||||||
final drop = Path()
|
final drop = Path()
|
||||||
..moveTo(x, dropY - 4)
|
..moveTo(x, dropY - 4)
|
||||||
..cubicTo(x - 0.5, dropY - 2, x - 0.3, dropY, x, dropY + 2);
|
..cubicTo(x - 0.5, dropY - 2, x - 0.3, dropY, x, dropY + 2);
|
||||||
canvas.drawPath(drop, _strokePaint(color, 1.8, alpha: alpha.clamp(0.0, 1.0)));
|
canvas.drawPath(
|
||||||
|
drop,
|
||||||
|
_strokePaint(color, 1.8, alpha: alpha.clamp(0.0, 1.0)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
@@ -329,17 +377,25 @@ class ThunderPainter extends CustomPainter {
|
|||||||
final flashAlpha = _flashOpacity(progress);
|
final flashAlpha = _flashOpacity(progress);
|
||||||
// 闪电锯齿形状
|
// 闪电锯齿形状
|
||||||
final bolt = Path()
|
final bolt = Path()
|
||||||
..moveTo(22, 20)..lineTo(17, 30)..lineTo(21, 30)
|
..moveTo(22, 20)
|
||||||
..lineTo(16, 42)..lineTo(27, 27)..lineTo(22, 27)
|
..lineTo(17, 30)
|
||||||
..lineTo(27, 20)..close();
|
..lineTo(21, 30)
|
||||||
|
..lineTo(16, 42)
|
||||||
|
..lineTo(27, 27)
|
||||||
|
..lineTo(22, 27)
|
||||||
|
..lineTo(27, 20)
|
||||||
|
..close();
|
||||||
canvas.drawPath(bolt, _fillPaint(color, alpha: flashAlpha));
|
canvas.drawPath(bolt, _fillPaint(color, alpha: flashAlpha));
|
||||||
|
|
||||||
// 闪电高光
|
// 闪电高光
|
||||||
if (flashAlpha > 0.5) {
|
if (flashAlpha > 0.5) {
|
||||||
canvas.drawPath(bolt, Paint()
|
canvas.drawPath(
|
||||||
..color = color.withValues(alpha: (flashAlpha * 0.3).clamp(0.0, 1.0))
|
bolt,
|
||||||
..style = PaintingStyle.fill
|
Paint()
|
||||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4));
|
..color = color.withValues(alpha: (flashAlpha * 0.3).clamp(0.0, 1.0))
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
@@ -400,11 +456,18 @@ class SnowyPainter extends CustomPainter {
|
|||||||
final end = Offset(c.dx + r * math.cos(a), c.dy + r * math.sin(a));
|
final end = Offset(c.dx + r * math.cos(a), c.dy + r * math.sin(a));
|
||||||
canvas.drawLine(c, end, paint);
|
canvas.drawLine(c, end, paint);
|
||||||
// 小分支
|
// 小分支
|
||||||
final mid = Offset(c.dx + r * 0.6 * math.cos(a), c.dy + r * 0.6 * math.sin(a));
|
final mid = Offset(
|
||||||
|
c.dx + r * 0.6 * math.cos(a),
|
||||||
|
c.dy + r * 0.6 * math.sin(a),
|
||||||
|
);
|
||||||
final br = r * 0.3;
|
final br = r * 0.3;
|
||||||
for (final sign in [1, -1]) {
|
for (final sign in [1, -1]) {
|
||||||
final ba = a + sign * math.pi / 6;
|
final ba = a + sign * math.pi / 6;
|
||||||
canvas.drawLine(mid, Offset(mid.dx + br * math.cos(ba), mid.dy + br * math.sin(ba)), paint);
|
canvas.drawLine(
|
||||||
|
mid,
|
||||||
|
Offset(mid.dx + br * math.cos(ba), mid.dy + br * math.sin(ba)),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,7 +497,11 @@ class FoggyPainter extends CustomPainter {
|
|||||||
..strokeCap = StrokeCap.round
|
..strokeCap = StrokeCap.round
|
||||||
..isAntiAlias = true;
|
..isAntiAlias = true;
|
||||||
|
|
||||||
const lines = [(16.0, 0.9, 14.0, 34.0), (24.0, 0.7, 10.0, 38.0), (32.0, 0.5, 16.0, 32.0)];
|
const lines = [
|
||||||
|
(16.0, 0.9, 14.0, 34.0),
|
||||||
|
(24.0, 0.7, 10.0, 38.0),
|
||||||
|
(32.0, 0.5, 16.0, 32.0),
|
||||||
|
];
|
||||||
for (var i = 0; i < 3; i++) {
|
for (var i = 0; i < 3; i++) {
|
||||||
final (y, alpha, startX, endX) = lines[i];
|
final (y, alpha, startX, endX) = lines[i];
|
||||||
final dx = math.sin(progress * 2 * math.pi + i * 1.2) * 3.0;
|
final dx = math.sin(progress * 2 * math.pi + i * 1.2) * 3.0;
|
||||||
@@ -442,8 +509,14 @@ class FoggyPainter extends CustomPainter {
|
|||||||
stroke.strokeWidth = 2.5 - i * 0.5;
|
stroke.strokeWidth = 2.5 - i * 0.5;
|
||||||
final path = Path()
|
final path = Path()
|
||||||
..moveTo(startX + dx, y)
|
..moveTo(startX + dx, y)
|
||||||
..cubicTo(startX + (endX - startX) * 0.25 + dx, y - 1.5,
|
..cubicTo(
|
||||||
startX + (endX - startX) * 0.75 + dx, y + 1.5, endX + dx, y);
|
startX + (endX - startX) * 0.25 + dx,
|
||||||
|
y - 1.5,
|
||||||
|
startX + (endX - startX) * 0.75 + dx,
|
||||||
|
y + 1.5,
|
||||||
|
endX + dx,
|
||||||
|
y,
|
||||||
|
);
|
||||||
canvas.drawPath(path, stroke);
|
canvas.drawPath(path, stroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +548,11 @@ class WindyPainter extends CustomPainter {
|
|||||||
..strokeCap = StrokeCap.round
|
..strokeCap = StrokeCap.round
|
||||||
..isAntiAlias = true;
|
..isAntiAlias = true;
|
||||||
|
|
||||||
const windLines = [(14.0, 18.0, 2.0, 0.9), (10.0, 26.0, 2.2, 0.7), (16.0, 34.0, 1.8, 0.5)];
|
const windLines = [
|
||||||
|
(14.0, 18.0, 2.0, 0.9),
|
||||||
|
(10.0, 26.0, 2.2, 0.7),
|
||||||
|
(16.0, 34.0, 1.8, 0.5),
|
||||||
|
];
|
||||||
for (var i = 0; i < 3; i++) {
|
for (var i = 0; i < 3; i++) {
|
||||||
final (startX, y, sw, alpha) = windLines[i];
|
final (startX, y, sw, alpha) = windLines[i];
|
||||||
final lineLen = 20.0 + i * 4.0;
|
final lineLen = 20.0 + i * 4.0;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import flutter_app_group_directory
|
|||||||
import flutter_image_compress_macos
|
import flutter_image_compress_macos
|
||||||
import flutter_inappwebview_macos
|
import flutter_inappwebview_macos
|
||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import flutter_secure_storage_darwin
|
import flutter_secure_storage_macos
|
||||||
import flutter_tts
|
import flutter_tts
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import gal
|
import gal
|
||||||
@@ -56,7 +56,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
|
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
|
||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||||
|
|||||||
Reference in New Issue
Block a user