143 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			143 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:cached_network_image/cached_network_image.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:go_router/go_router.dart';
 | 
						|
import 'package:island/models/webfeed.dart';
 | 
						|
import 'package:island/services/time.dart';
 | 
						|
 | 
						|
class WebArticleCard extends StatelessWidget {
 | 
						|
  final SnWebArticle article;
 | 
						|
  final double? maxWidth;
 | 
						|
  final bool showDetails;
 | 
						|
 | 
						|
  const WebArticleCard({
 | 
						|
    super.key,
 | 
						|
    required this.article,
 | 
						|
    this.maxWidth,
 | 
						|
    this.showDetails = false,
 | 
						|
  });
 | 
						|
 | 
						|
  void _onTap(BuildContext context) {
 | 
						|
    context.pushNamed('articleDetail', pathParameters: {'id': article.id});
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    final theme = Theme.of(context);
 | 
						|
    final colorScheme = theme.colorScheme;
 | 
						|
 | 
						|
    return ConstrainedBox(
 | 
						|
      constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
 | 
						|
      child: Card(
 | 
						|
        margin: EdgeInsets.zero,
 | 
						|
        clipBehavior: Clip.antiAlias,
 | 
						|
        child: InkWell(
 | 
						|
          onTap: () => _onTap(context),
 | 
						|
          child: AspectRatio(
 | 
						|
            aspectRatio: 16 / 9,
 | 
						|
            child: Stack(
 | 
						|
              fit: StackFit.expand,
 | 
						|
              children: [
 | 
						|
                // Image or fallback
 | 
						|
                article.preview?.imageUrl != null
 | 
						|
                    ? CachedNetworkImage(
 | 
						|
                      imageUrl: article.preview!.imageUrl!,
 | 
						|
                      fit: BoxFit.cover,
 | 
						|
                      width: double.infinity,
 | 
						|
                      height: double.infinity,
 | 
						|
                    )
 | 
						|
                    : ColoredBox(
 | 
						|
                      color: colorScheme.secondaryContainer,
 | 
						|
                      child: const Center(
 | 
						|
                        child: Icon(
 | 
						|
                          Icons.article_outlined,
 | 
						|
                          size: 48,
 | 
						|
                          color: Colors.white,
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                    ),
 | 
						|
                // Gradient overlay
 | 
						|
                Container(
 | 
						|
                  decoration: BoxDecoration(
 | 
						|
                    gradient: LinearGradient(
 | 
						|
                      begin: Alignment.topCenter,
 | 
						|
                      end: Alignment.bottomCenter,
 | 
						|
                      colors: [
 | 
						|
                        Colors.transparent,
 | 
						|
                        Colors.black.withOpacity(0.7),
 | 
						|
                      ],
 | 
						|
                    ),
 | 
						|
                  ),
 | 
						|
                ),
 | 
						|
                // Title
 | 
						|
                Align(
 | 
						|
                  alignment: Alignment.bottomLeft,
 | 
						|
                  child: Container(
 | 
						|
                    padding: const EdgeInsets.only(
 | 
						|
                      left: 12,
 | 
						|
                      right: 12,
 | 
						|
                      bottom: 8,
 | 
						|
                    ),
 | 
						|
                    child: Column(
 | 
						|
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                      mainAxisAlignment: MainAxisAlignment.end,
 | 
						|
                      mainAxisSize: MainAxisSize.min,
 | 
						|
                      children: [
 | 
						|
                        if (showDetails)
 | 
						|
                          const SizedBox(height: 8)
 | 
						|
                        else
 | 
						|
                          Spacer(),
 | 
						|
                        Text(
 | 
						|
                          article.title,
 | 
						|
                          style: theme.textTheme.titleSmall?.copyWith(
 | 
						|
                            color: Colors.white,
 | 
						|
                            fontWeight: FontWeight.bold,
 | 
						|
                            height: 1.3,
 | 
						|
                          ),
 | 
						|
                          maxLines: showDetails ? 3 : 1,
 | 
						|
                          overflow: TextOverflow.ellipsis,
 | 
						|
                        ),
 | 
						|
                        if (showDetails &&
 | 
						|
                            article.author?.isNotEmpty == true) ...[
 | 
						|
                          const SizedBox(height: 4),
 | 
						|
                          Text(
 | 
						|
                            article.author!,
 | 
						|
                            style: TextStyle(
 | 
						|
                              fontSize: 10,
 | 
						|
                              color: Colors.white.withOpacity(0.9),
 | 
						|
                              fontWeight: FontWeight.w500,
 | 
						|
                            ),
 | 
						|
                          ),
 | 
						|
                        ],
 | 
						|
                        if (showDetails) const Spacer(),
 | 
						|
                        if (showDetails && article.publishedAt != null) ...[
 | 
						|
                          Text(
 | 
						|
                            '${article.publishedAt!.formatSystem()} · ${article.publishedAt!.formatRelative(context)}',
 | 
						|
                            style: const TextStyle(
 | 
						|
                              fontSize: 9,
 | 
						|
                              color: Colors.white70,
 | 
						|
                            ),
 | 
						|
                          ),
 | 
						|
                          const SizedBox(height: 2),
 | 
						|
                        ],
 | 
						|
                        Text(
 | 
						|
                          article.feed?.title ?? 'Unknown Source',
 | 
						|
                          style: const TextStyle(
 | 
						|
                            fontSize: 9,
 | 
						|
                            color: Colors.white70,
 | 
						|
                          ),
 | 
						|
                          maxLines: 1,
 | 
						|
                          overflow: TextOverflow.ellipsis,
 | 
						|
                        ),
 | 
						|
                      ],
 | 
						|
                    ),
 | 
						|
                  ),
 | 
						|
                ),
 | 
						|
              ],
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |