madari-oss/lib/features/collection/widgets/collection_card.dart
Madari Developers 16fe4a653f Project import generated by Copybara.
GitOrigin-RevId: 829626e92d5dba6a4586d1e7c4bd1615ec396e88
2025-01-02 18:46:26 +00:00

313 lines
9.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:pocketbase/pocketbase.dart';
import '../../../engine/engine.dart';
import '../container/collection_list_item_list.dart';
class CollectionListModel {
final String id;
final String collectionId;
final String name;
final String? description;
final int order;
final String? background;
final DateTime created;
final DateTime updated;
final String userId;
final bool isPublic;
CollectionListModel({
required this.id,
required this.collectionId,
required this.name,
this.description,
required this.order,
this.background,
required this.created,
required this.updated,
required this.userId,
required this.isPublic,
});
factory CollectionListModel.fromRecord(RecordModel record) {
return CollectionListModel(
id: record.id,
collectionId: record.collectionId,
name: record.data['name'],
description: record.data['description'],
order: record.data['order'],
background: record.data['background'],
created: DateTime.parse(record.get("created")),
updated: DateTime.parse(record.get("updated")),
userId: record.data['user'],
isPublic: record.data['isPublic'],
);
}
}
class CollectionCard extends StatefulWidget {
final CollectionListModel collection;
final double width;
const CollectionCard({
super.key,
required this.collection,
this.width = 480,
});
@override
State<CollectionCard> createState() => _CollectionCardState();
}
class _CollectionCardState extends State<CollectionCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.05).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => _controller.forward(),
onExit: (_) => _controller.reverse(),
child: AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) => Transform.scale(
scale: _scaleAnimation.value,
child: child,
),
child: Container(
width: widget.width,
height: widget.width * .6,
margin: const EdgeInsets.all(12),
child: Card(
elevation: 8,
shadowColor: Colors.black26,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
clipBehavior: Clip.antiAlias,
child: Stack(
fit: StackFit.expand,
children: [
// Background Image or Gradient
_buildBackground(),
// Gradient Overlay
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.3),
Colors.black.withOpacity(0.8),
],
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Spacer(),
// Collection Name
Text(
widget.collection.name,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 3,
color: Colors.black45,
),
],
),
),
const SizedBox(height: 8),
// Description
if (widget.collection.description != null)
Text(
widget.collection.description!,
style: const TextStyle(
color: Colors.white70,
fontSize: 16,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 2,
color: Colors.black45,
),
],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 16),
// Metadata Row
Row(
children: [
_buildMetadataChip(
Icons.calendar_today,
_formatDate(widget.collection.updated),
),
const Spacer(),
_buildInteractionButton(),
],
),
],
),
),
// Optional: Add a subtle overlay pattern
CustomPaint(
painter: PatternPainter(),
),
// Material Ink Effect for Ripple
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => CollectionListItemsScreen(
listId: widget.collection.id,
title: widget.collection.name,
isPublic: widget.collection.isPublic,
),
),
);
},
),
),
],
),
),
),
),
);
}
Widget _buildBackground() {
if (widget.collection.background != null) {
return Hero(
tag: 'collection-${widget.collection.id}',
child: Image.network(
'${AppEngine.engine.pb.baseURL}/api/files/${widget.collection.collectionId}/${widget.collection.id}/${widget.collection.background}',
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
_buildFallbackBackground(),
),
);
}
return _buildFallbackBackground();
}
Widget _buildFallbackBackground() {
// Generate a unique but consistent color based on collection ID
final color = Color(
(widget.collection.id.hashCode & 0xFFFFFF) | 0xFF000000,
);
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
color,
Color.lerp(color, Colors.white, 0.2)!,
],
),
),
child: const Icon(
Icons.collections,
size: 80,
color: Colors.white24,
),
);
}
Widget _buildMetadataChip(IconData icon, String label) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 16, color: Colors.white70),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(color: Colors.white70),
),
],
),
);
}
Widget _buildInteractionButton() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: IconButton(
icon: const Icon(Icons.arrow_forward, color: Colors.black87),
visualDensity: VisualDensity.compact,
onPressed: () {
// Handle interaction
},
),
);
}
String _formatDate(DateTime date) {
return '${date.day}/${date.month}/${date.year}';
}
}
// Custom Pattern Painter for subtle overlay
class PatternPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.05)
..strokeWidth = 1;
for (var i = 0; i < size.width; i += 20) {
for (var j = 0; j < size.height; j += 20) {
canvas.drawCircle(Offset(i.toDouble(), j.toDouble()), 1, paint);
}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}