import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:client_proxy_framework/client_proxy_framework.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../design/pencil_theme.dart'; /// WBRp4 单张卡片:171×182 比例,圆角 20,Download pill。 class HistoryGridCard extends StatelessWidget { const HistoryGridCard({ super.key, required this.item, this.localCoverPath, this.onDownload, }); final MyTaskItem item; final String? localCoverPath; final VoidCallback? onDownload; @override Widget build(BuildContext context) { final url = item.resultUrl?.trim() ?? ''; final created = item.createTime ?? '—'; final remainder = _remainderLabel(item.createTime); return LayoutBuilder( builder: (context, c) { final w = c.maxWidth; final h = w * (182 / 171); return SizedBox( width: w, height: h, child: Stack( clipBehavior: Clip.none, children: [ Positioned( left: 0, top: 0, width: w, height: h, child: ClipRRect( borderRadius: BorderRadius.circular(20), child: url.isNotEmpty ? CachedNetworkImage(imageUrl: url, fit: BoxFit.cover) : localCoverPath != null ? Image.file(File(localCoverPath!), fit: BoxFit.cover) : Container(color: PencilTheme.cardThumbBg), ), ), Positioned( left: 8, top: 10, right: 8, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( created, maxLines: 1, overflow: TextOverflow.ellipsis, style: GoogleFonts.inter( fontSize: 9, fontWeight: FontWeight.w500, color: PencilTheme.inkMuted, ), ), Text( remainder, style: GoogleFonts.inter( fontSize: 9, fontWeight: FontWeight.w600, color: PencilTheme.underlineGold, ), ), ], ), ), Positioned( right: 0, bottom: 0, child: Material( color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(21), side: const BorderSide(color: PencilTheme.downloadPillBorder), ), child: InkWell( onTap: onDownload, borderRadius: BorderRadius.circular(21), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'Download', style: TextStyle( fontFamily: 'BonheurRoyale', fontSize: 12, color: PencilTheme.downloadPillInk, ), ), const SizedBox(width: 4), Icon(Icons.download_rounded, size: 10, color: PencilTheme.downloadPillInk), ], ), ), ), ), ), ], ), ); }, ); } } String _remainderLabel(String? createTimeRaw) { if (createTimeRaw == null || createTimeRaw.isEmpty) return '—'; DateTime? created; final asInt = int.tryParse(createTimeRaw); if (asInt != null) { var ms = asInt; if (asInt < 2000000000) ms = asInt * 1000; created = DateTime.fromMillisecondsSinceEpoch(ms); } else { created = DateTime.tryParse(createTimeRaw); } if (created == null) return '—'; final deadline = created.add(const Duration(hours: 24)); final left = deadline.difference(DateTime.now()); if (left.isNegative) return 'Expired'; final h = left.inHours; final m = left.inMinutes.remainder(60); return '${h}h ${m.toString().padLeft(2, '0')}m left'; }