FunyMeeAI/lib/features/history/widgets/history_grid_card.dart

143 lines
4.7 KiB
Dart
Raw 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.

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 比例,圆角 20Download 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';
}