Files
enaklo-flutter/lib/presentation/pages/community/widgets/community_post_card.dart
T
efrilmandClaude Opus 5.5 f5e3075205 EnakPoint & EnakCoin wallet, PIN, outlet, order, community, reward
- Wallet EnakPoint & EnakCoin: beranda, riwayat, saldo kedaluwarsa, kode
  bayar, tukar EnakCoin, transfer
- PIN (buat, ganti, lupa), outlet, riwayat order, game spin
- Halaman community, webview, coming soon, dan ikon menu beranda baru
- Redesign Reward Saya: kartu tiket, filter chip, detail kode + QR
- Hapus customer point loader, menu, dan checkout lama
- Base URL ke enaklo-pos-api.altru.id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:43:22 +07:00

285 lines
8.1 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../common/theme/theme.dart';
import '../../../../sample/community_sample_data.dart';
import '../community_ui.dart';
/// Kartu postingan. [expanded] menampilkan teks penuh (halaman detail).
class CommunityPostCard extends StatefulWidget {
const CommunityPostCard({
super.key,
required this.post,
this.onTap,
this.onGroupTap,
this.expanded = false,
});
final CommunityPost post;
final VoidCallback? onTap;
final VoidCallback? onGroupTap;
final bool expanded;
@override
State<CommunityPostCard> createState() => _CommunityPostCardState();
}
class _CommunityPostCardState extends State<CommunityPostCard> {
late bool _isLiked = widget.post.isLiked;
late int _likeCount = widget.post.likeCount;
bool _isSaved = false;
void _toggleLike() {
setState(() {
_isLiked = !_isLiked;
_likeCount += _isLiked ? 1 : -1;
});
}
@override
Widget build(BuildContext context) {
final post = widget.post;
return Container(
margin: const EdgeInsets.fromLTRB(16, 0, 16, 14),
decoration: communityCardDecoration(),
child: Material(
type: MaterialType.transparency,
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: widget.onTap,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(post),
const SizedBox(height: 12),
Text(
post.content,
maxLines: widget.expanded ? null : 4,
overflow: widget.expanded ? null : TextOverflow.ellipsis,
style: AppStyle.md.copyWith(
height: 1.5,
fontWeight: FontWeight.w500,
),
),
if (post.image != null) ...[
const SizedBox(height: 12),
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AspectRatio(
aspectRatio: 16 / 10,
child: Image.asset(post.image!, fit: BoxFit.cover),
),
),
],
if (post.merchantName != null) ...[
const SizedBox(height: 10),
_MerchantTag(name: post.merchantName!),
],
const SizedBox(height: 6),
_buildActions(post),
],
),
),
),
),
);
}
Widget _buildHeader(CommunityPost post) {
return Row(
children: [
CommunityAvatar(member: post.author, size: 42),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
post.author.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppStyle.md.copyWith(fontWeight: FontWeight.w700),
),
),
if (post.author.badge != null) ...[
const SizedBox(width: 6),
_AuthorBadge(label: post.author.badge!),
],
],
),
const SizedBox(height: 2),
GestureDetector(
onTap: widget.onGroupTap,
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${post.group.emoji} ${post.group.name}',
style: const TextStyle(
color: AppColor.textPrimary,
fontWeight: FontWeight.w600,
),
),
TextSpan(text: ' ยท ${formatTimeAgo(post.createdAt)}'),
],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppStyle.xs.copyWith(color: CommunityColor.muted),
),
),
],
),
),
IconButton(
onPressed: () {},
visualDensity: VisualDensity.compact,
icon: const Icon(
Icons.more_horiz_rounded,
color: CommunityColor.muted,
),
),
],
);
}
Widget _buildActions(CommunityPost post) {
return Row(
children: [
_ActionButton(
icon: _isLiked
? Icons.favorite_rounded
: Icons.favorite_border_rounded,
label: formatCompact(_likeCount),
color: _isLiked ? AppColor.primary : CommunityColor.muted,
onTap: _toggleLike,
),
_ActionButton(
icon: Icons.chat_bubble_outline_rounded,
label: formatCompact(post.commentCount),
color: CommunityColor.muted,
onTap: widget.onTap,
),
_ActionButton(
icon: Icons.ios_share_rounded,
label: 'Bagikan',
color: CommunityColor.muted,
onTap: () {},
),
const Spacer(),
IconButton(
onPressed: () => setState(() => _isSaved = !_isSaved),
visualDensity: VisualDensity.compact,
icon: Icon(
_isSaved ? Icons.bookmark_rounded : Icons.bookmark_border_rounded,
size: 22,
color: _isSaved ? AppColor.primary : CommunityColor.muted,
),
),
],
);
}
}
class _AuthorBadge extends StatelessWidget {
const _AuthorBadge({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: CommunityColor.blush,
borderRadius: BorderRadius.circular(999),
),
child: Text(
label,
style: AppStyle.xs.copyWith(
fontSize: 10,
color: AppColor.primary,
fontWeight: FontWeight.w700,
),
),
);
}
}
class _MerchantTag extends StatelessWidget {
const _MerchantTag({required this.name});
final String name;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: CommunityColor.background,
borderRadius: BorderRadius.circular(999),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.storefront_rounded,
size: 14,
color: AppColor.primary,
),
const SizedBox(width: 6),
Text(name, style: AppStyle.xs.copyWith(fontWeight: FontWeight.w700)),
],
),
);
}
}
class _ActionButton extends StatelessWidget {
const _ActionButton({
required this.icon,
required this.label,
required this.color,
this.onTap,
});
final IconData icon;
final String label;
final Color color;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(999),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Row(
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation) =>
ScaleTransition(scale: animation, child: child),
child: Icon(icon, key: ValueKey(icon), size: 20, color: color),
),
const SizedBox(width: 6),
Text(
label,
style: AppStyle.sm.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
),
],
),
),
);
}
}