143 lines
4.7 KiB
Dart
143 lines
4.7 KiB
Dart
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';
|
||
}
|