Files
xianyan/lib/features/template/wallpaper_preview_sheet.dart
Developer 6119918185 release: bump version to 6.6.25+2606241
主要变更:
1. 新增桌面端托盘图标支持深色/浅色主题切换
2. 重构应用锁、动画配置、小组件导航服务职责
3. 修复Riverpod初始化断言、防重复点击、工作台模式残留选中态问题
4. 优化诗词服务、阅读进度、搜索结果空状态体验
5. 完善macOS打包配置与错误静默处理逻辑
6. 新增快速卡片多语言适配与动画退出队列管理
2026-06-24 04:26:50 +08:00

414 lines
14 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.
/// ============================================================
/// 闲言APP — 壁纸预览底部面板
/// 创建时间: 2026-05-01
/// 更新时间: 2026-06-23
/// 作用: 壁纸大图预览 + 信息 + 下载/设为壁纸/编辑器
/// 上次更新: 下载toast改"版权未知"+增加截图保存tips+图片点击全屏预览(可缩放/无UI干扰截图)
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_typography.dart';
import '../../../shared/widgets/feedback/app_toast.dart';
import '../../../shared/widgets/media/safe_cached_image.dart';
import 'models/template_models.dart';
class WallpaperPreviewSheet extends StatelessWidget {
const WallpaperPreviewSheet({super.key, required this.item});
final WallpaperItem item;
static Future<void> show(BuildContext context, WallpaperItem item) {
final screenHeight = MediaQuery.of(context).size.height;
final sheetHeight = screenHeight * 0.7;
return showCupertinoModalPopup<void>(
context: context,
barrierColor: Colors.black54,
builder: (_) => _WallpaperSheetContent(
item: item,
sheetHeight: sheetHeight,
screenHeight: screenHeight,
),
);
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final sheetHeight = screenHeight * 0.7;
return _WallpaperSheetContent(
item: item,
sheetHeight: sheetHeight,
screenHeight: screenHeight,
);
}
}
class _WallpaperSheetContent extends StatelessWidget {
const _WallpaperSheetContent({
required this.item,
required this.sheetHeight,
required this.screenHeight,
});
final WallpaperItem item;
final double sheetHeight;
final double screenHeight;
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
final dismissAreaHeight = screenHeight - sheetHeight;
return Stack(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: SizedBox(width: double.infinity, height: dismissAreaHeight),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: sheetHeight,
decoration: BoxDecoration(
color: ext.bgPrimary,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(20),
),
),
child: Column(
children: [
_buildHandle(context),
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildImage(context),
_buildInfo(ext),
_buildActions(context),
const SizedBox(height: 24),
],
),
),
),
],
),
),
),
],
);
}
Widget _buildHandle(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.pop(context),
child: Padding(
padding: const EdgeInsets.only(top: 10, bottom: 6),
child: Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: CupertinoColors.systemGrey4,
borderRadius: BorderRadius.circular(2),
),
),
),
);
}
Widget _buildImage(BuildContext context) {
final imageUrl = item.previewUrl.isNotEmpty
? item.previewUrl
: item.imageUrl;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: GestureDetector(
onTap: () => _openFullScreenPreview(context, imageUrl),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Stack(
children: [
ConstrainedBox(
constraints: BoxConstraints(maxHeight: sheetHeight * 0.55),
child: SafeCachedImage(
url: imageUrl,
fit: BoxFit.contain,
placeholder: (_, __) => const SizedBox(
height: 200,
child: Center(child: CupertinoActivityIndicator()),
),
errorWidget: (_, __, ___) => const SizedBox(
height: 200,
child: Center(
child: Icon(CupertinoIcons.photo, size: 48, color: Colors.grey),
),
),
),
),
// 右上角放大提示角标
Positioned(
top: 8,
right: 8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.45),
borderRadius: BorderRadius.circular(8),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(CupertinoIcons.zoom_in, size: 12, color: Colors.white),
SizedBox(width: 3),
Text('点击放大', style: TextStyle(fontSize: 10, color: Colors.white)),
],
),
),
),
],
),
),
),
);
}
/// 打开全屏预览(黑色背景+可缩放+点击切换UI显隐便于截图无干扰
void _openFullScreenPreview(BuildContext context, String imageUrl) {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => _CleanFullScreenImageViewer(imageUrl: imageUrl),
),
);
}
Widget _buildInfo(AppThemeExtension ext) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.title.isNotEmpty)
Text(
item.title,
style: TextStyle(
color: ext.textPrimary,
fontSize: 17,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Row(
children: [
_buildInfoChip('📐 ${item.resolution}'),
const SizedBox(width: 8),
if (item.source.isNotEmpty) _buildInfoChip('🏷 ${item.source}'),
const SizedBox(width: 8),
if (item.views > 0) _buildInfoChip('👁 ${item.views}'),
],
),
if (item.tags.isNotEmpty) ...[
const SizedBox(height: 8),
Wrap(
spacing: 6,
runSpacing: 4,
children: item.tags
.take(5)
.map((t) => _buildTagChip(t, ext))
.toList(),
),
],
],
),
);
}
Widget _buildInfoChip(String text) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: CupertinoColors.systemGrey5,
borderRadius: BorderRadius.circular(6),
),
child: Text(text, style: const TextStyle(fontSize: 11)),
);
}
Widget _buildTagChip(String tag, AppThemeExtension ext) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(tag, style: TextStyle(fontSize: 11, color: ext.accent)),
);
}
Widget _buildActions(BuildContext context) {
final ext = AppTheme.ext(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
Row(
children: [
Expanded(
child: CupertinoButton(
onPressed: () {
AppToast.show('版权未知');
},
color: ext.accent,
borderRadius: BorderRadius.circular(12),
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(CupertinoIcons.arrow_down_circle, size: 18, color: ext.textOnAccent),
const SizedBox(width: 6),
Text('下载', style: TextStyle(fontSize: 15, color: ext.textOnAccent)),
],
),
),
),
const SizedBox(width: 10),
Expanded(
child: CupertinoButton(
onPressed: () {
AppToast.show('版权未知');
},
color: ext.accent.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(CupertinoIcons.pencil_ellipsis_rectangle, size: 18, color: ext.accent),
const SizedBox(width: 6),
Text('编辑', style: TextStyle(fontSize: 15, color: ext.accent)),
],
),
),
),
],
),
const SizedBox(height: 10),
// 截图保存提示
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
Icon(CupertinoIcons.info_circle, size: 14, color: ext.accent),
const SizedBox(width: 6),
Expanded(
child: Text(
'点击图片可放大预览,自行截图保存',
style: AppTypography.caption1.copyWith(color: ext.textSecondary),
),
),
],
),
),
],
),
);
}
}
/// ============================================================
/// 全屏图片查看器(纯净版)
/// 作用: 黑色背景+可缩放+点击切换UI显隐便于截图无其他视图干扰
/// ============================================================
class _CleanFullScreenImageViewer extends StatefulWidget {
const _CleanFullScreenImageViewer({required this.imageUrl});
final String imageUrl;
@override
State<_CleanFullScreenImageViewer> createState() =>
_CleanFullScreenImageViewerState();
}
class _CleanFullScreenImageViewerState extends State<_CleanFullScreenImageViewer> {
bool _showUi = true;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
backgroundColor: Colors.black,
child: SafeArea(
child: GestureDetector(
onTap: () => setState(() => _showUi = !_showUi),
child: Stack(
children: [
PhotoView(
imageProvider: CachedNetworkImageProvider(widget.imageUrl),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 4.0,
backgroundDecoration: const BoxDecoration(color: Colors.black),
loadingBuilder: (_, __) =>
const Center(child: CupertinoActivityIndicator(radius: 16, color: Colors.white)),
errorBuilder: (_, __, ___) => const Center(
child: Icon(CupertinoIcons.photo, size: 48, color: Colors.white54),
),
),
// 顶部关闭按钮(可切换显隐)
if (_showUi)
Positioned(
top: 8,
left: 8,
child: SafeArea(
child: CupertinoButton(
padding: const EdgeInsets.all(8),
color: Colors.black.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(20),
minimumSize: const Size(36, 36),
onPressed: () => Navigator.of(context).pop(),
child: const Icon(CupertinoIcons.xmark, size: 18, color: Colors.white),
),
),
),
// 底部提示(可切换显隐)
if (_showUi)
Positioned(
bottom: 16,
left: 0,
right: 0,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'双指缩放 · 单击隐藏按钮后可截图',
style: TextStyle(fontSize: 12, color: Colors.white70),
),
),
),
),
],
),
),
),
);
}
}