import 'package:flutter/material.dart'; import '../../services/artikel_service.dart'; import '../dashboard/artikel_detail_screen.dart'; import 'dart:async'; class ArtikelScreen extends StatefulWidget { @override _ArtikelScreenState createState() => _ArtikelScreenState(); } class _ArtikelScreenState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _fadeAnimation; String _selectedCategory = 'Semua'; final List _categories = ['Semua', 'Gizi', 'Imunisasi', 'Kesehatan', 'Tumbuh Kembang', 'Vitamin', 'Stunting']; // Artikel service final ArtikelService _artikelService = ArtikelService(); // Artikel data List _artikelList = []; bool _isLoading = true; String? _error; int _currentPage = 1; int _totalPages = 1; bool _hasMore = false; // Set default ke false untuk mencegah infinite loading // Flag untuk menentukan apakah menggunakan data dummy atau API // Set ke false untuk mencoba menggunakan API dulu final bool _useDummyDataOnly = false; // Flag untuk mencegah multiple loading calls bool _isLoadingMore = false; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 1000), ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Interval(0.1, 1.0, curve: Curves.easeOut), )); _animationController.forward(); // Inisialisasi data _loadArtikels(); } void _initializeData() { if (_useDummyDataOnly) { // Langsung gunakan data dummy jika flag aktif setState(() { _artikelList = _getDummyArtikels(); _isLoading = false; _hasMore = false; }); } else { // Coba load dari API _loadArtikels(); } } Future _loadArtikels({bool refresh = false}) async { if (_isLoadingMore) return; if (refresh) { setState(() { _currentPage = 1; _artikelList = []; _isLoading = true; }); } if (_useDummyDataOnly) { // Langsung gunakan data dummy jika flag aktif setState(() { _artikelList = _getDummyArtikels(); _isLoading = false; _hasMore = false; }); return; } setState(() { _isLoadingMore = true; if (_artikelList.isEmpty) { _isLoading = true; } }); try { print('Memulai request artikel dari API untuk halaman $_currentPage'); final result = await _artikelService.getArtikels( page: _currentPage, perPage: 10, ).timeout(Duration(seconds: 5), onTimeout: () { throw TimeoutException('Timeout saat mengambil data artikel'); }); if (!mounted) return; setState(() { if (refresh || _currentPage == 1) { _artikelList = result.data; } else { _artikelList.addAll(result.data); } _currentPage = result.currentPage + 1; _totalPages = result.lastPage; _hasMore = result.currentPage < result.lastPage; _isLoading = false; _isLoadingMore = false; _error = null; }); print('Berhasil mendapatkan ${result.data.length} artikel. Halaman: ${result.currentPage}/$_totalPages'); } catch (e) { print('Error saat memuat artikel: $e'); if (!mounted) return; // Gunakan data dummy sebagai fallback jika API gagal setState(() { if (_artikelList.isEmpty) { // Hanya gunakan dummy jika belum ada data sama sekali _artikelList = _getDummyArtikels(); } _isLoading = false; _isLoadingMore = false; _error = 'Gagal memuat data dari server. Menampilkan data lokal.'; }); } } List _getDummyArtikels() { // Konversi data dummy yang sudah ada ke ArtikelModel print('Menggunakan data dummy pada ArtikelScreen'); return artikelList.map((artikel) => ArtikelModel( id: int.parse(artikel['views'] ?? '1'), judul: artikel['title'] ?? '', isiArtikel: artikel['description'] ?? '', tanggal: DateTime.now().subtract(Duration(days: int.parse(artikel['views'] ?? '1'))), gambarUrl: artikel['imageUrl'], )).toList(); } @override void dispose() { _animationController.dispose(); super.dispose(); } List _filteredArticles() { if (_selectedCategory == 'Semua') { return _artikelList; } else { return _artikelList.where((artikel) { // Tentukan kategori berdasarkan judul artikel String kategori = _detectArticleCategory(artikel.judul); return kategori == _selectedCategory; }).toList(); } } // Fungsi untuk mendeteksi kategori berdasarkan judul artikel String _detectArticleCategory(String judul) { judul = judul.toLowerCase(); // Cek kata kunci imunisasi terlebih dahulu if (judul.contains('imunisasi') || judul.contains('vaksin') || judul.contains('vaksinasi') || judul.contains('suntik') || judul.contains('kekebalan') || judul.contains('campak') || judul.contains('polio') || judul.contains('bcg') || judul.contains('dpt') || judul.contains('booster') || judul.contains('pengebalan') || judul.contains('mmr') || judul.contains('dpt-hb-hib') || judul.contains('hib')) { return 'Imunisasi'; } // Cek kata kunci stunting else if (judul.contains('stunting') || judul.contains('pendek') || judul.contains('mencegah stunting') || judul.contains('perawakan pendek') || judul.contains('kerdil')) { return 'Stunting'; } // Cek kata kunci vitamin else if (judul.contains('vitamin') || judul.contains('suplemen') || judul.contains('vit a') || judul.contains('vitamin a') || judul.contains('vitamin d') || judul.contains('multivitamin') || judul.contains('mineral')) { return 'Vitamin'; } // Cek kata kunci gizi else if (judul.contains('gizi') || judul.contains('nutrisi') || judul.contains('makanan') || judul.contains('makan') || judul.contains('asi') || judul.contains('mpasi') || judul.contains('bergizi') || judul.contains('menu') || judul.contains('porsi')) { return 'Gizi'; } // Cek kata kunci tumbuh kembang else if (judul.contains('tumbuh') || judul.contains('kembang') || judul.contains('perkembangan') || judul.contains('usia') || judul.contains('tahapan') || judul.contains('motorik') || judul.contains('balita') || judul.contains('bayi') || judul.contains('milestones') || judul.contains('kemampuan') || judul.contains('pertumbuhan')) { return 'Tumbuh Kembang'; } // Cek kata kunci kesehatan else if (judul.contains('sehat') || judul.contains('penyakit') || judul.contains('obat') || judul.contains('virus') || judul.contains('bakteri') || judul.contains('demam') || judul.contains('flu') || judul.contains('batuk') || judul.contains('diare') || judul.contains('kesehatan') || judul.contains('infeksi') || judul.contains('kebersihan') || judul.contains('rumah sakit') || judul.contains('dokter') || judul.contains('perawatan') || judul.contains('puskesmas') || judul.contains('klinik')) { return 'Kesehatan'; } // Default kategori else { return 'Kesehatan'; } } // Fungsi untuk navigasi ke detail artikel void _navigateToArticleDetail(ArtikelModel artikel) { Navigator.push( context, MaterialPageRoute( builder: (context) => ArtikelDetailScreen( artikelId: artikel.id, initialData: artikel, // Pass artikel sebagai initialData ), ), ); } // Fungsi untuk mendapatkan info kategori dari artikel Map getArticleCategory(ArtikelModel article) { // Deteksi kategori berdasarkan judul final category = _detectArticleCategory(article.judul); return { 'name': category, 'color': getCategoryColor(category), 'icon': getCategoryIcon(category), }; } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; final filteredArticles = _filteredArticles(); return Scaffold( body: RefreshIndicator( onRefresh: () { // Jika menggunakan dummy, cukup refresh state if (_useDummyDataOnly) { setState(() { _artikelList = _getDummyArtikels(); }); return Future.value(); } return _loadArtikels(refresh: true); }, child: CustomScrollView( slivers: [ // Custom App Bar SliverAppBar( expandedHeight: screenSize.height * 0.25, pinned: true, backgroundColor: Colors.teal.shade700, flexibleSpace: FlexibleSpaceBar( title: Text( 'Artikel Kesehatan', style: TextStyle( fontWeight: FontWeight.bold, shadows: [ Shadow( color: Colors.black.withOpacity(0.3), blurRadius: 5, offset: Offset(0, 2), ), ], ), ), background: Stack( fit: StackFit.expand, children: [ Image.network( 'https://images.unsplash.com/photo-1505751172876-fa1923c5c528?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', fit: BoxFit.cover, ), Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withOpacity(0.7), ], ), ), ), ], ), ), ), // Categories SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, child: Padding( padding: EdgeInsets.only(top: 16, bottom: 8), child: SizedBox( height: 40, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _categories.length, padding: EdgeInsets.symmetric(horizontal: 16), itemBuilder: (context, index) { final category = _categories[index]; final isSelected = category == _selectedCategory; return Padding( padding: EdgeInsets.only(right: 8), child: GestureDetector( onTap: () { setState(() { _selectedCategory = category; }); }, child: AnimatedContainer( duration: Duration(milliseconds: 300), padding: EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: isSelected ? Colors.teal.shade700 : Colors.grey.shade200, borderRadius: BorderRadius.circular(20), ), child: Center( child: Text( category, style: TextStyle( color: isSelected ? Colors.white : Colors.grey.shade800, fontWeight: FontWeight.bold, ), ), ), ), ), ); }, ), ), ), ), ), if (_isLoading && _artikelList.isEmpty) SliverFillRemaining( child: Center( child: CircularProgressIndicator( color: Colors.teal.shade700, ), ), ) else if (_error != null && _artikelList.isEmpty) SliverFillRemaining( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, color: Colors.red, size: 48, ), SizedBox(height: 16), Text( 'Gagal memuat artikel', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, ), ), SizedBox(height: 8), Text(_error!), SizedBox(height: 16), ElevatedButton( onPressed: () => _loadArtikels(refresh: true), child: Text('Coba Lagi'), style: ElevatedButton.styleFrom( backgroundColor: Colors.teal.shade700, foregroundColor: Colors.white, ), ), ], ), ), ) else ...[ // Featured Article if available if (filteredArticles.isNotEmpty) SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Artikel Unggulan', style: TextStyle( fontSize: screenSize.width * 0.05, fontWeight: FontWeight.bold, color: Colors.grey.shade800, ), ), SizedBox(height: 16), GestureDetector( onTap: () => _navigateToArticleDetail(filteredArticles.first), child: _buildFeaturedArticleCard(filteredArticles.first), ), ], ), ), ), ), // Article List SliverToBoxAdapter( child: Padding( padding: EdgeInsets.fromLTRB(16, 8, 16, 0), child: Text( filteredArticles.length > 1 ? 'Artikel Lainnya' : 'Artikel', style: TextStyle( fontSize: screenSize.width * 0.05, fontWeight: FontWeight.bold, color: Colors.grey.shade800, ), ), ), ), if (filteredArticles.length <= 1) SliverToBoxAdapter( child: Padding( padding: EdgeInsets.all(16), child: Text( 'Tidak ada artikel lainnya dalam kategori ini', textAlign: TextAlign.center, style: TextStyle( color: Colors.grey.shade600, ), ), ), ) else SliverList( delegate: SliverChildBuilderDelegate( (context, index) { // Skip the first item since it's featured if (index == 0) return SizedBox.shrink(); final articleIndex = index - 1; if (articleIndex >= filteredArticles.length - 1) { // Load more if we're at the end and there are more articles if (_hasMore && !_isLoading) { _loadArtikels(); } if (articleIndex < filteredArticles.length) { return Padding( padding: EdgeInsets.fromLTRB(16, 0, 16, 16), child: ArticleListItem( article: filteredArticles[articleIndex], index: articleIndex, animation: _animationController, onTap: () => _navigateToArticleDetail(filteredArticles[articleIndex]), detectCategory: _detectArticleCategory, ), ); } else { // Show loading indicator at the end of the list return Padding( padding: EdgeInsets.all(16), child: Center( child: _hasMore ? Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: Colors.teal.shade700), SizedBox(height: 8), Text('Memuat artikel lainnya...'), ], ) : Text(_error != null ? _error! : 'Tidak ada artikel lagi'), ), ); } } return Padding( padding: EdgeInsets.fromLTRB(16, 0, 16, 16), child: ArticleListItem( article: filteredArticles[articleIndex], index: articleIndex, animation: _animationController, onTap: () => _navigateToArticleDetail(filteredArticles[articleIndex]), detectCategory: _detectArticleCategory, ), ); }, childCount: filteredArticles.length > 1 ? filteredArticles.length + 1 : 1, ), ), ], ], ), ), ); } Widget _buildFeaturedArticleCard(ArtikelModel article) { // Deteksi kategori dari judul artikel final categoryInfo = getArticleCategory(article); final category = categoryInfo['name']; final categoryColor = categoryInfo['color']; final categoryIcon = categoryInfo['icon']; return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Article image ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), child: Container( width: double.infinity, height: 200, color: categoryColor.withOpacity(0.2), child: article.gambarUrl != null ? Image.network( article.gambarUrl!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Center( child: Icon( categoryIcon, size: 64, color: categoryColor, ), ); }, ) : Center( child: Icon( categoryIcon, size: 64, color: categoryColor, ), ), ), ), // Article details Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Category and date Row( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: categoryColor, borderRadius: BorderRadius.circular(4), ), child: Text( category, style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), Spacer(), Text( _formatDate(article.tanggal), style: TextStyle( color: Colors.grey, fontSize: 14, ), ), ], ), SizedBox(height: 12), // Title Text( article.judul, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), SizedBox(height: 8), // Description Text( article.isiArtikel, style: TextStyle( color: Colors.grey.shade700, fontSize: 14, ), maxLines: 3, overflow: TextOverflow.ellipsis, ), SizedBox(height: 16), // Read more button Row( mainAxisAlignment: MainAxisAlignment.end, children: [ ElevatedButton( onPressed: () => _navigateToArticleDetail(article), style: ElevatedButton.styleFrom( backgroundColor: Colors.teal.shade700, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text('Baca Selengkapnya'), ), ], ), ], ), ), ], ), ); } String _formatDate(DateTime date) { final months = [ '', 'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nov', 'Des' ]; return '${date.day} ${months[date.month]} ${date.year}'; } } class ArticleListItem extends StatelessWidget { final ArtikelModel article; final int index; final Animation animation; final VoidCallback onTap; final Function(String) detectCategory; const ArticleListItem({ Key? key, required this.article, required this.index, required this.animation, required this.onTap, required this.detectCategory, }) : super(key: key); @override Widget build(BuildContext context) { final delay = 0.2 + (index * 0.1); final slideAnimation = Tween( begin: Offset(0, 0.2), end: Offset.zero, ).animate( CurvedAnimation( parent: animation, curve: Interval(delay, delay + 0.2, curve: Curves.easeOut), ), ); // Deteksi kategori artikel berdasarkan judul final category = detectCategory(article.judul); final categoryColor = getCategoryColor(category); final categoryIcon = getCategoryIcon(category); return AnimatedBuilder( animation: animation, builder: (context, child) { return Transform.translate( offset: slideAnimation.value, child: Opacity( opacity: animation.value, child: child, ), ); }, child: GestureDetector( onTap: onTap, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 5, offset: Offset(0, 2), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Article image ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.circular(12), bottomLeft: Radius.circular(12), ), child: Container( width: 100, height: 100, color: categoryColor.withOpacity(0.2), child: article.gambarUrl != null ? Image.network( article.gambarUrl!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Center( child: Icon( categoryIcon, size: 40, color: categoryColor, ), ); }, ) : Center( child: Icon( categoryIcon, size: 40, color: categoryColor, ), ), ), ), // Article details Expanded( child: Padding( padding: EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Category and date Row( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3), decoration: BoxDecoration( color: categoryColor, borderRadius: BorderRadius.circular(4), ), child: Text( category, style: TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), Spacer(), Text( _formatDate(article.tanggal), style: TextStyle( color: Colors.grey, fontSize: 12, ), ), ], ), SizedBox(height: 8), // Title Text( article.judul, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), SizedBox(height: 4), // Description Text( article.isiArtikel, style: TextStyle( color: Colors.grey.shade600, fontSize: 12, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), ), ], ), ), ), ); } String _formatDate(DateTime date) { final months = [ '', 'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nov', 'Des' ]; return '${date.day} ${months[date.month]} ${date.year}'; } } // Helper functions IconData getCategoryIcon(String category) { switch (category) { case 'Gizi': return Icons.restaurant; case 'Imunisasi': return Icons.vaccines; case 'Tumbuh Kembang': return Icons.child_care; case 'Vitamin': return Icons.medication; case 'Stunting': return Icons.height; default: return Icons.healing; } } Color getCategoryColor(String category) { switch (category) { case 'Gizi': return Colors.orange.shade700; case 'Imunisasi': return Colors.purple.shade700; case 'Tumbuh Kembang': return Colors.blue.shade700; case 'Vitamin': return Colors.amber.shade700; case 'Stunting': return Colors.red.shade700; default: return Colors.teal.shade700; } } // Sample data untuk fallback dan kategori sementara final List> artikelList = [ { 'title': 'Pentingnya ASI Eksklusif untuk Perkembangan Bayi', 'description': 'ASI eksklusif adalah pemberian ASI saja pada bayi sampai usia 6 bulan. Manfaatnya sangat banyak untuk tumbuh kembang dan kekebalan tubuh bayi.', 'imageUrl': 'https://img.freepik.com/free-photo/young-mother-showing-breastfeeding-her-baby_23-2149046913.jpg', 'date': '20 Feb 2025', 'views': '325', }, { 'title': 'Mencegah Stunting Sejak Dini pada Anak', 'description': 'Stunting dapat dicegah dengan memperhatikan asupan gizi sejak masa kehamilan dan memberikan makanan bergizi seimbang pada anak.', 'imageUrl': 'https://img.freepik.com/free-photo/doctor-check-up-little-boy-office_23-2148982292.jpg', 'date': '18 Feb 2025', 'views': '198', }, { 'title': 'Panduan Makanan Bergizi untuk Balita', 'description': 'Makanan bergizi seimbang sangat penting untuk pertumbuhan optimal balita. Pelajari menu-menu sehat yang bisa diberikan sesuai usia anak.', 'imageUrl': 'https://img.freepik.com/free-photo/close-up-young-attractive-smiling-mother-feeding-her-cute-baby-son-with-spoon-organic-healthy-baby-food-white-kitchen-with-big-window_8353-12056.jpg', 'date': '15 Feb 2025', 'views': '276', }, { 'title': 'Jadwal Imunisasi Lengkap untuk Anak', 'description': 'Imunisasi adalah cara efektif untuk melindungi anak dari berbagai penyakit berbahaya. Ketahui jadwal imunisasi yang tepat untuk anak.', 'imageUrl': 'https://img.freepik.com/free-photo/doctor-vaccinating-little-girl_23-2148982283.jpg', 'date': '10 Feb 2025', 'views': '214', }, { 'title': 'Tips Merawat Kesehatan Anak selama Musim Hujan', 'description': 'Musim hujan meningkatkan risiko beberapa penyakit pada anak. Ikuti tips ini untuk menjaga kesehatan anak tetap optimal.', 'imageUrl': 'https://img.freepik.com/free-photo/cute-child-with-umbrella_1149-537.jpg', 'date': '05 Feb 2025', 'views': '187', }, { 'title': 'Manfaat Vitamin A untuk Penglihatan Anak', 'description': 'Vitamin A sangat penting untuk perkembangan penglihatan anak. Ketahui sumber-sumber makanan kaya vitamin A dan jadwal pemberian vitamin A di Posyandu.', 'imageUrl': 'https://img.freepik.com/free-photo/doctor-examining-little-boy_23-2148982280.jpg', 'date': '01 Feb 2025', 'views': '165', }, { 'title': 'Mengenal Tahapan Perkembangan Anak di Usia 1-5 Tahun', 'description': 'Setiap anak memiliki tahapan perkembangan yang berbeda. Kenali perkembangan normal anak sesuai usianya untuk memastikan pertumbuhan yang optimal.', 'imageUrl': 'https://img.freepik.com/free-photo/mother-helping-her-daughter-draw_23-2148797344.jpg', 'date': '25 Jan 2025', }, ];