/// ============================================================ /// 闲言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( 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; } }