import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; import '../../../sample/community_sample_data.dart'; import '../../components/assets/assets.gen.dart'; import '../../components/toast/flushbar.dart'; import '../../router/app_router.gr.dart'; import 'community_ui.dart'; import 'widgets/community_compose_sheet.dart'; import 'widgets/community_event_card.dart'; import 'widgets/community_group_card.dart'; import 'widgets/community_post_card.dart'; enum _CommunityTab { forYou, groups, events } @RoutePage() class CommunityPage extends StatefulWidget { const CommunityPage({super.key}); @override State createState() => _CommunityPageState(); } class _CommunityPageState extends State { final List _posts = [...communityPosts]; final Set _goingEventIds = {}; _CommunityTab _tab = _CommunityTab.forYou; void _openGroup(CommunityGroup group) => context.router.push(CommunityDetailRoute(group: group)); void _openPost(CommunityPost post) => context.router.push(CommunityPostDetailRoute(post: post)); Future _compose() async { final joined = communityGroups .where((g) => joinedCommunityIds.value.contains(g.id)) .toList(); if (joined.isEmpty) { AppFlushbar.showError(context, 'Gabung komunitas dulu untuk posting'); setState(() => _tab = _CommunityTab.groups); return; } final post = await CommunityComposeSheet.show(context, groups: joined); if (post == null || !mounted) return; setState(() { _posts.insert(0, post); _tab = _CommunityTab.forYou; }); AppFlushbar.showSuccess(context, 'Postingan kamu sudah terbit'); } void _toggleEvent(CommunityEvent event) { final going = !_goingEventIds.contains(event.id); setState(() { going ? _goingEventIds.add(event.id) : _goingEventIds.remove(event.id); }); if (going) { AppFlushbar.showSuccess(context, 'Kamu terdaftar di ${event.title}'); } } @override Widget build(BuildContext context) { return ValueListenableBuilder>( valueListenable: joinedCommunityIds, builder: (context, joinedIds, _) { return Scaffold( backgroundColor: CommunityColor.background, floatingActionButton: AnimatedScale( scale: _tab == _CommunityTab.events ? 0 : 1, duration: const Duration(milliseconds: 200), child: FloatingActionButton.extended( onPressed: _compose, elevation: 2, backgroundColor: AppColor.primary, foregroundColor: AppColor.white, shape: const StadiumBorder(), icon: const Icon(Icons.edit_rounded, size: 20), label: Text( 'Tulis', style: AppStyle.md.copyWith( color: AppColor.white, fontWeight: FontWeight.w700, ), ), ), ), body: CustomScrollView( slivers: [ SliverAppBar( pinned: true, automaticallyImplyLeading: false, backgroundColor: CommunityColor.background, title: const Text('EnakCommunity'), actions: [ IconButton( onPressed: () => context.router.push(const NotificationRoute()), icon: const Icon( Icons.notifications_none_rounded, color: AppColor.textPrimary, ), ), const SizedBox(width: 4), ], ), SliverToBoxAdapter(child: _buildHero(joinedIds.length)), SliverPersistentHeader( pinned: true, delegate: _TabHeaderDelegate( selected: _tab, onChanged: (tab) => setState(() => _tab = tab), ), ), ...switch (_tab) { _CommunityTab.forYou => _buildForYou(joinedIds), _CommunityTab.groups => _buildGroups(joinedIds), _CommunityTab.events => _buildEvents(), }, const SliverToBoxAdapter(child: SizedBox(height: 96)), ], ), ); }, ); } Widget _buildHero(int joinedCount) { final myPostCount = _posts .where((p) => p.author.id == currentMember.id) .length; return Container( margin: const EdgeInsets.fromLTRB(16, 4, 16, 8), padding: const EdgeInsets.fromLTRB(20, 20, 16, 16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [CommunityColor.blush, Color(0xFFFFF6EE)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(28), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Halo, Foodie! 👋', style: AppStyle.xxl.copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: 6), Text( 'Berbagi cerita kuliner, ikut event seru, dan temukan ' 'teman makan baru.', style: AppStyle.sm.copyWith( height: 1.45, color: AppColor.textSecondary, fontWeight: FontWeight.w500, ), ), ], ), ), const SizedBox(width: 8), Assets.icons.enakcommunityIcon.image(width: 72, height: 72), ], ), const SizedBox(height: 14), Row( children: [ _HeroStat(value: '$joinedCount', label: 'Komunitas'), const SizedBox(width: 8), _HeroStat(value: '$myPostCount', label: 'Postingan'), const SizedBox(width: 8), _HeroStat(value: '${_goingEventIds.length}', label: 'Event'), ], ), const SizedBox(height: 14), Material( color: AppColor.white, borderRadius: BorderRadius.circular(999), child: InkWell( borderRadius: BorderRadius.circular(999), onTap: _compose, child: Padding( padding: const EdgeInsets.fromLTRB(6, 6, 16, 6), child: Row( children: [ const CommunityAvatar(member: currentMember, size: 32), const SizedBox(width: 10), Expanded( child: Text( 'Apa yang kamu makan hari ini?', style: AppStyle.sm.copyWith( color: CommunityColor.muted, fontWeight: FontWeight.w600, ), ), ), const Icon( Icons.edit_note_rounded, color: AppColor.primary, ), ], ), ), ), ), ], ), ); } List _buildForYou(Set joinedIds) { return [ SliverToBoxAdapter( child: CommunitySectionTitle( title: 'Komunitas Populer', actionLabel: 'Lihat semua', onAction: () => setState(() => _tab = _CommunityTab.groups), ), ), SliverToBoxAdapter( child: SizedBox( height: 176, child: ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: communityGroups.length, separatorBuilder: (_, _) => const SizedBox(width: 12), itemBuilder: (context, index) { final group = communityGroups[index]; return CommunityGroupCard( group: group, isJoined: joinedIds.contains(group.id), onTap: () => _openGroup(group), onJoin: () => toggleJoinCommunity(group.id), ); }, ), ), ), const SliverToBoxAdapter( child: CommunitySectionTitle(title: 'Obrolan Terbaru'), ), SliverList.builder( itemCount: _posts.length, itemBuilder: (context, index) { final post = _posts[index]; return CommunityPostCard( key: ValueKey(post.id), post: post, onTap: () => _openPost(post), onGroupTap: () => _openGroup(post.group), ); }, ), ]; } List _buildGroups(Set joinedIds) { final joined = communityGroups .where((g) => joinedIds.contains(g.id)) .toList(); final discover = communityGroups .where((g) => !joinedIds.contains(g.id)) .toList(); Widget tile(CommunityGroup group) => CommunityGroupTile( key: ValueKey(group.id), group: group, isJoined: joinedIds.contains(group.id), onTap: () => _openGroup(group), onJoin: () => toggleJoinCommunity(group.id), ); return [ const SliverToBoxAdapter( child: CommunitySectionTitle(title: 'Komunitas Saya'), ), if (joined.isEmpty) const SliverToBoxAdapter( child: _SoftEmpty( emoji: '🤝', title: 'Belum gabung komunitas', message: 'Pilih komunitas di bawah yang sesuai seleramu.', ), ) else SliverList.list(children: joined.map(tile).toList()), if (discover.isNotEmpty) ...[ const SliverToBoxAdapter( child: CommunitySectionTitle(title: 'Jelajahi Komunitas'), ), SliverList.list(children: discover.map(tile).toList()), ], ]; } List _buildEvents() { return [ SliverToBoxAdapter( child: Container( margin: const EdgeInsets.fromLTRB(16, 16, 16, 16), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFFFF8E1), borderRadius: BorderRadius.circular(18), ), child: Row( children: [ const Text('🎁', style: TextStyle(fontSize: 24)), const SizedBox(width: 12), Expanded( child: Text( 'Hadiri event komunitas dan kumpulkan EnakPoint & EnakCoin ' 'tambahan!', style: AppStyle.sm.copyWith( height: 1.4, fontWeight: FontWeight.w600, ), ), ), ], ), ), ), SliverList.builder( itemCount: communityEvents.length, itemBuilder: (context, index) { final event = communityEvents[index]; return CommunityEventCard( event: event, isGoing: _goingEventIds.contains(event.id), onToggle: () => _toggleEvent(event), ); }, ), ]; } } class _HeroStat extends StatelessWidget { const _HeroStat({required this.value, required this.label}); final String value; final String label; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: AppColor.white.withOpacity(0.7), borderRadius: BorderRadius.circular(999), ), child: Text.rich( TextSpan( children: [ TextSpan( text: '$value ', style: const TextStyle( color: AppColor.primary, fontWeight: FontWeight.w700, ), ), TextSpan(text: label), ], ), style: AppStyle.xs.copyWith(fontWeight: FontWeight.w600), ), ); } } class _SoftEmpty extends StatelessWidget { const _SoftEmpty({ required this.emoji, required this.title, required this.message, }); final String emoji; final String title; final String message; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), decoration: communityCardDecoration(), child: Column( children: [ Text(emoji, style: const TextStyle(fontSize: 32)), const SizedBox(height: 8), Text(title, style: AppStyle.md.copyWith(fontWeight: FontWeight.w700)), const SizedBox(height: 4), Text( message, textAlign: TextAlign.center, style: AppStyle.sm.copyWith(color: CommunityColor.muted), ), ], ), ); } } /// Tab bergaya pil lembut yang menempel saat di-scroll. class _TabHeaderDelegate extends SliverPersistentHeaderDelegate { _TabHeaderDelegate({required this.selected, required this.onChanged}); final _CommunityTab selected; final ValueChanged<_CommunityTab> onChanged; static const double _height = 64; static const _labels = { _CommunityTab.forYou: 'Untukmu', _CommunityTab.groups: 'Komunitas', _CommunityTab.events: 'Event', }; @override double get minExtent => _height; @override double get maxExtent => _height; @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent, ) { const tabs = _CommunityTab.values; final index = tabs.indexOf(selected); return Container( color: CommunityColor.background, padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: AppColor.white, borderRadius: BorderRadius.circular(999), boxShadow: communitySoftShadow, ), child: Stack( children: [ AnimatedAlign( duration: const Duration(milliseconds: 250), curve: Curves.easeOutCubic, alignment: Alignment(-1 + 2 * index / (tabs.length - 1), 0), child: FractionallySizedBox( widthFactor: 1 / tabs.length, heightFactor: 1, child: Container( decoration: BoxDecoration( color: CommunityColor.blush, borderRadius: BorderRadius.circular(999), ), ), ), ), Row( children: tabs.map((tab) { final isSelected = tab == selected; return Expanded( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => onChanged(tab), child: Center( child: Text( _labels[tab]!, style: AppStyle.sm.copyWith( color: isSelected ? AppColor.primary : CommunityColor.muted, fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, ), ), ), ), ); }).toList(), ), ], ), ), ); } @override bool shouldRebuild(_TabHeaderDelegate oldDelegate) => oldDelegate.selected != selected; }