339 lines
11 KiB
Dart
339 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:timeago/timeago.dart' as timeago;
|
|
import '../models/post.dart';
|
|
import '../theme/app_theme.dart';
|
|
import 'media/signed_media_image.dart';
|
|
import '../routes/app_routes.dart';
|
|
|
|
class ReadingPostCard extends StatefulWidget {
|
|
final Post post;
|
|
final VoidCallback? onTap;
|
|
final VoidCallback? onSave;
|
|
final bool isSaved;
|
|
final bool showDivider;
|
|
|
|
const ReadingPostCard({
|
|
super.key,
|
|
required this.post,
|
|
this.onTap,
|
|
this.onSave,
|
|
this.isSaved = false,
|
|
this.showDivider = true,
|
|
});
|
|
|
|
@override
|
|
State<ReadingPostCard> createState() => _ReadingPostCardState();
|
|
}
|
|
|
|
class _ReadingPostCardState extends State<ReadingPostCard> {
|
|
bool _isPressed = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildPostCard(),
|
|
if (widget.showDivider) _buildDivider(),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildPostCard() {
|
|
return Container(
|
|
constraints: const BoxConstraints(maxWidth: 680),
|
|
margin: _getMargin(),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.white,
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusSm),
|
|
border: Border(
|
|
left: BorderSide(
|
|
color: _isPressed ? AppTheme.brightNavy : AppTheme.egyptianBlue,
|
|
width: AppTheme.flowLineWidth,
|
|
),
|
|
right: BorderSide(color: AppTheme.egyptianBlue, width: 1),
|
|
top: BorderSide(color: AppTheme.egyptianBlue, width: 1),
|
|
bottom: BorderSide(color: AppTheme.egyptianBlue, width: 1),
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.03),
|
|
blurRadius: 8,
|
|
offset: Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(
|
|
AppTheme.spacingMd,
|
|
AppTheme.spacingSm,
|
|
AppTheme.spacingLg,
|
|
AppTheme.spacingMd,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildAuthorRow(),
|
|
const SizedBox(height: AppTheme.spacingMd),
|
|
// White space area - clickable for post detail with full background coverage
|
|
InkWell(
|
|
onTap: widget.onTap,
|
|
onTapDown: (_) => setState(() => _isPressed = true),
|
|
onTapUp: (_) => setState(() => _isPressed = false),
|
|
onTapCancel: () => setState(() => _isPressed = false),
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusSm),
|
|
splashColor: AppTheme.queenPink.withValues(alpha: 0.3),
|
|
highlightColor: Colors.transparent,
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: _buildBodyText(),
|
|
),
|
|
),
|
|
const SizedBox(height: AppTheme.spacingLg),
|
|
_buildActionRow(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDivider() {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppTheme.spacingMd),
|
|
child: Container(
|
|
height: AppTheme.dividerThickness,
|
|
decoration: BoxDecoration(color: AppTheme.egyptianBlue),
|
|
),
|
|
);
|
|
}
|
|
|
|
EdgeInsets _getMargin() {
|
|
final charCount = widget.post.body.length;
|
|
if (charCount < 100) {
|
|
return EdgeInsets.only(
|
|
left: AppTheme.spacingMd,
|
|
right: AppTheme.spacingMd,
|
|
top: AppTheme.spacingPostShort);
|
|
} else if (charCount < 300) {
|
|
return EdgeInsets.only(
|
|
left: AppTheme.spacingMd,
|
|
right: AppTheme.spacingMd,
|
|
top: AppTheme.spacingPostMedium);
|
|
}
|
|
return EdgeInsets.only(
|
|
left: AppTheme.spacingMd,
|
|
right: AppTheme.spacingMd,
|
|
top: AppTheme.spacingPostLong);
|
|
}
|
|
|
|
Widget _buildAuthorRow() {
|
|
final avatarUrl = widget.post.author?.avatarUrl;
|
|
final handle = widget.post.author?.handle ?? '';
|
|
final fallbackColor = _getAvatarColor(handle);
|
|
|
|
return InkWell(
|
|
onTap: () {
|
|
if (handle.isNotEmpty && handle != 'unknown') {
|
|
AppRoutes.navigateToProfile(context, handle);
|
|
}
|
|
},
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusSm),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: fallbackColor,
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: avatarUrl != null && avatarUrl.isNotEmpty
|
|
? ClipRRect(
|
|
borderRadius: BorderRadius.circular(9),
|
|
child: SignedMediaImage(
|
|
url: avatarUrl,
|
|
width: 36,
|
|
height: 36,
|
|
fit: BoxFit.cover,
|
|
),
|
|
)
|
|
: Center(
|
|
child: Text(
|
|
handle.isNotEmpty ? handle[0].toUpperCase() : '?',
|
|
style: AppTheme.textTheme.labelMedium?.copyWith(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppTheme.spacingSm),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Flexible(
|
|
child: Text(
|
|
widget.post.author?.displayName ?? 'Unknown',
|
|
style: AppTheme.textTheme.labelMedium?.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
color: AppTheme.navyBlue,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
if (widget.post.author?.isOfficial == true) ...[
|
|
const SizedBox(width: AppTheme.spacingXs),
|
|
_buildOfficialBadge(),
|
|
],
|
|
if (widget.post.author?.isOfficial != true &&
|
|
widget.post.author?.trustState != null) ...[
|
|
const SizedBox(width: AppTheme.spacingXs),
|
|
_buildTrustBadge(),
|
|
],
|
|
],
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
timeago.format(widget.post.createdAt),
|
|
style: AppTheme.textTheme.labelSmall
|
|
?.copyWith(color: AppTheme.egyptianBlue),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBodyText() {
|
|
final charCount = widget.post.body.length;
|
|
final style = charCount >= 10 ? AppTheme.postBodyLong : AppTheme.postBody;
|
|
return Text(widget.post.body, style: style);
|
|
}
|
|
|
|
Widget _buildActionRow() {
|
|
return Row(
|
|
children: [
|
|
if (widget.post.contentIntegrityScore < 1.0) ...[
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppTheme.spacingSm, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: _getCISColor(widget.post.contentIntegrityScore)
|
|
.withValues(alpha: 0.08),
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusXs),
|
|
),
|
|
child: Text(
|
|
'CIS ${(widget.post.contentIntegrityScore * 100).toInt()}%',
|
|
style: AppTheme.textTheme.labelSmall?.copyWith(
|
|
color: _getCISColor(widget.post.contentIntegrityScore),
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppTheme.spacingMd),
|
|
],
|
|
const Spacer(),
|
|
_buildActionButton(
|
|
icon: widget.isSaved ? Icons.bookmark : Icons.bookmark_border,
|
|
isActive: widget.isSaved,
|
|
onPressed: widget.onSave,
|
|
color: AppTheme.brightNavy,
|
|
),
|
|
const SizedBox(width: AppTheme.spacingMd),
|
|
_buildActionButton(
|
|
icon: Icons.share_outlined,
|
|
isActive: false,
|
|
onPressed: null, // TODO: Implement share functionality
|
|
color: AppTheme.brightNavy,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildActionButton({
|
|
required IconData icon,
|
|
required bool isActive,
|
|
VoidCallback? onPressed,
|
|
required Color color,
|
|
}) {
|
|
return InkWell(
|
|
onTap: onPressed,
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusSm),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppTheme.spacingSm, vertical: AppTheme.spacingXs),
|
|
child: Icon(icon,
|
|
size: 18, color: isActive ? color : AppTheme.royalPurple),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildOfficialBadge() {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.info.withValues(alpha: 0.12),
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusXs),
|
|
),
|
|
child: Text('sojorn',
|
|
style: AppTheme.textTheme.labelSmall?.copyWith(
|
|
fontSize: 8,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppTheme.info,
|
|
letterSpacing: 0.4,
|
|
)),
|
|
);
|
|
}
|
|
|
|
Widget _buildTrustBadge() {
|
|
final tier = widget.post.author!.trustState!.tier;
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
|
decoration: BoxDecoration(
|
|
color: _getTierColor(tier.value).withValues(alpha: 0.12),
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusXs),
|
|
),
|
|
child: Text(tier.displayName.toUpperCase(),
|
|
style: AppTheme.textTheme.labelSmall?.copyWith(
|
|
fontSize: 8,
|
|
fontWeight: FontWeight.w600,
|
|
color: _getTierColor(tier.value),
|
|
letterSpacing: 0.4,
|
|
)),
|
|
);
|
|
}
|
|
|
|
Color _getAvatarColor(String handle) {
|
|
final hash = handle.hashCode;
|
|
final hue = (hash % 360).toDouble();
|
|
return HSLColor.fromAHSL(1.0, hue, 0.45, 0.55).toColor();
|
|
}
|
|
|
|
Color _getTierColor(String tier) {
|
|
switch (tier) {
|
|
case 'established':
|
|
return AppTheme.tierEstablished;
|
|
case 'trusted':
|
|
return AppTheme.tierTrusted;
|
|
default:
|
|
return AppTheme.tierNew;
|
|
}
|
|
}
|
|
|
|
Color _getCISColor(double cis) {
|
|
if (cis >= 0.8) return AppTheme.success;
|
|
if (cis >= 0.6) return AppTheme.warning;
|
|
return AppTheme.error;
|
|
}
|
|
}
|