Files
xianyan/lib/shared/widgets/media/watermarked_copyright_image.dart
Developer ae1df22732 feat: v6.10.3 多语言翻译补全 + 17项功能修复
- 引导页协议多语言支持(languageId传递)
- 登录页双书名号修复 + 注册页协议勾选
- 个人中心页面多语言(18个翻译键)
- 网络断开提示增加关闭/刷新按钮
- 了解我们:新增秋叶qy开发者 + ayk签名修改 + 贡献者精简 + 微风暴微信搜索
- iOS快捷按钮重复修复(删除Info.plist静态定义)
- 测试账号123456警告提示
- 扫码登录自动跳转(HTTP轮询+WebSocket双通道)
- 登录页老用户按钮改次要色
- Syncfusion图表崩溃修复(DeferredBuilder+animationDuration:0)
- macOS标题栏跟随软件夜间模式
- 平台兼容分发渠道弹窗
- 软件著作权图片+交叉水印
- 桌面小部件平台兼容说明默认收起
- iOS/macOS图标更新+名称确认为闲言
- 12个语言文件补全roleNative+7个分发渠道翻译字段
2026-06-02 04:50:32 +08:00

272 lines
8.6 KiB
Dart

/// ============================================================
/// 闲言APP — 带水印的版权证书图片组件
/// 创建时间: 2026-06-02
/// 更新时间: 2026-06-02
/// 作用: 展示软件著作权证书图片,带对角线"闲言"水印,点击全屏查看
/// 上次更新: 初始创建
/// ============================================================
import 'dart:math';
import 'package:flutter/cupertino.dart';
import '../../../core/theme/app_radius.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_typography.dart';
class WatermarkedCopyrightImage extends StatelessWidget {
const WatermarkedCopyrightImage({super.key});
static const _assetPath = 'assets/images/empty/rz.png';
static const _watermarkText = '闲言';
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
return GestureDetector(
onTap: () => _showFullScreen(context),
child: Container(
margin: const EdgeInsets.symmetric(vertical: AppSpacing.sm),
decoration: BoxDecoration(
borderRadius: AppRadius.lgBorder,
border: Border.all(
color: ext.textHint.withValues(alpha: 0.15),
width: 0.5,
),
boxShadow: [
BoxShadow(
color: ext.textPrimary.withValues(alpha: 0.06),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
children: [
ClipRRect(
borderRadius: AppRadius.lgBorder,
child: Image.asset(
_assetPath,
fit: BoxFit.fitWidth,
width: double.infinity,
errorBuilder: (_, __, ___) => _buildErrorPlaceholder(ext),
),
),
Positioned.fill(
child: IgnorePointer(
child: CustomPaint(
painter: _DiagonalWatermarkPainter(
text: _watermarkText,
color: ext.textPrimary.withValues(alpha: 0.08),
),
),
),
),
Positioned(
right: AppSpacing.sm,
top: AppSpacing.sm,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.xs,
vertical: 2,
),
decoration: BoxDecoration(
color: ext.bgPrimary.withValues(alpha: 0.7),
borderRadius: AppRadius.smBorder,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
CupertinoIcons.fullscreen,
size: 12,
color: ext.textSecondary,
),
const SizedBox(width: 3),
Text(
'🔍',
style: AppTypography.caption2.copyWith(
color: ext.textSecondary,
fontSize: 10,
),
),
],
),
),
),
],
),
),
);
}
Widget _buildErrorPlaceholder(AppThemeExtension ext) {
return Container(
height: 200,
decoration: BoxDecoration(
color: ext.bgSecondary,
borderRadius: AppRadius.lgBorder,
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(CupertinoIcons.doc_text_search, size: 36, color: ext.textHint),
const SizedBox(height: AppSpacing.xs),
Text(
'证书图片加载失败',
style: AppTypography.caption1.copyWith(color: ext.textHint),
),
],
),
),
);
}
void _showFullScreen(BuildContext context) {
final ext = AppTheme.ext(context);
showCupertinoDialog<void>(
context: context,
barrierDismissible: true,
builder: (dialogContext) => CupertinoPageScaffold(
backgroundColor: ext.bgPrimary.withValues(alpha: 0.95),
child: SafeArea(
child: Column(
children: [
CupertinoNavigationBar(
backgroundColor: ext.bgPrimary.withValues(alpha: 0.8),
border: null,
leading: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () => Navigator.of(dialogContext).pop(),
child: Icon(
CupertinoIcons.xmark_circle_fill,
color: ext.textHint,
size: 28,
),
),
middle: Text(
'📜 软件著作权证书',
style: AppTypography.subhead.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
),
Expanded(
child: InteractiveViewer(
minScale: 0.5,
maxScale: 4.0,
child: Center(
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: Image.asset(
_assetPath,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
CupertinoIcons.doc_text_search,
size: 48,
color: ext.textHint,
),
const SizedBox(height: AppSpacing.sm),
Text(
'图片加载失败',
style: AppTypography.subhead.copyWith(
color: ext.textHint,
),
),
],
),
),
),
),
Positioned.fill(
child: IgnorePointer(
child: CustomPaint(
painter: _DiagonalWatermarkPainter(
text: _watermarkText,
color: ext.textPrimary.withValues(alpha: 0.06),
fontSize: 20,
),
),
),
),
],
),
),
),
),
],
),
),
),
);
}
}
class _DiagonalWatermarkPainter extends CustomPainter {
_DiagonalWatermarkPainter({
required this.text,
required this.color,
this.fontSize = 14,
});
final String text;
final Color color;
final double fontSize;
@override
void paint(Canvas canvas, Size size) {
final textStyle = TextStyle(
color: color,
fontSize: fontSize,
fontWeight: FontWeight.w600,
letterSpacing: 4,
);
final textSpan = TextSpan(text: text, style: textStyle);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout();
final stepX = textPainter.width + 60;
final stepY = textPainter.height + 80;
const angle = -pi / 6;
canvas.save();
canvas.translate(size.width / 2, size.height / 2);
canvas.rotate(angle);
canvas.translate(-size.width, -size.height);
final cols = (size.width * 3 / stepX).ceil() + 1;
final rows = (size.height * 3 / stepY).ceil() + 1;
for (var row = 0; row < rows; row++) {
for (var col = 0; col < cols; col++) {
final x = col * stepX.toDouble();
final y = row * stepY.toDouble();
textPainter.paint(canvas, Offset(x, y));
}
}
canvas.restore();
}
@override
bool shouldRepaint(covariant _DiagonalWatermarkPainter oldDelegate) {
return oldDelegate.text != text ||
oldDelegate.color != color ||
oldDelegate.fontSize != fontSize;
}
}