328 lines
11 KiB
Dart
328 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:posyandu/services/artikel_service.dart';
|
|
import 'package:share_plus/share_plus.dart';
|
|
import 'dart:async';
|
|
|
|
class ArtikelDetailScreen extends StatefulWidget {
|
|
final int artikelId;
|
|
final ArtikelModel? initialData;
|
|
|
|
const ArtikelDetailScreen({
|
|
Key? key,
|
|
required this.artikelId,
|
|
this.initialData,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
_ArtikelDetailScreenState createState() => _ArtikelDetailScreenState();
|
|
}
|
|
|
|
class _ArtikelDetailScreenState extends State<ArtikelDetailScreen> {
|
|
final ArtikelService _artikelService = ArtikelService();
|
|
late Future<ArtikelModel> _artikelFuture;
|
|
bool _isLoading = true;
|
|
String? _error;
|
|
|
|
// Flag untuk selalu menggunakan data initial
|
|
final bool _useInitialDataOnly = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadArtikelDetail();
|
|
}
|
|
|
|
void _loadArtikelDetail() {
|
|
// Jika ada initial data, prioritaskan initial data
|
|
if (widget.initialData != null) {
|
|
_artikelFuture = Future.value(widget.initialData!);
|
|
return;
|
|
}
|
|
|
|
// Jika tidak ada initial data tapi flag aktif, buat dummy
|
|
if (_useInitialDataOnly) {
|
|
_artikelFuture = Future.value(_createDummyDetail(widget.artikelId));
|
|
return;
|
|
}
|
|
|
|
// Jika masih bisa ambil dari API, coba dengan timeout 5 detik
|
|
_artikelFuture = _artikelService.getArtikelDetail(widget.artikelId)
|
|
.timeout(
|
|
Duration(seconds: 5),
|
|
onTimeout: () {
|
|
print('Timeout saat mengambil detail artikel');
|
|
throw TimeoutException('Waktu habis saat mengambil detail artikel');
|
|
},
|
|
);
|
|
}
|
|
|
|
// Fungsi lokal untuk membuat model dummy sebagai fallback
|
|
ArtikelModel _createDummyDetail(int id) {
|
|
return ArtikelModel(
|
|
id: id,
|
|
judul: 'Artikel Tidak Tersedia',
|
|
isiArtikel: 'Maaf, artikel yang Anda cari tidak dapat diakses saat ini. Silakan coba lagi nanti.',
|
|
tanggal: DateTime.now(),
|
|
gambarUrl: null,
|
|
);
|
|
}
|
|
|
|
String _formatDate(DateTime date) {
|
|
final months = [
|
|
'', 'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
|
|
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
|
|
];
|
|
return '${date.day} ${months[date.month]} ${date.year}';
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: FutureBuilder<ArtikelModel>(
|
|
future: _artikelFuture,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting && widget.initialData == null) {
|
|
return Center(
|
|
child: CircularProgressIndicator(
|
|
color: Colors.teal.shade700,
|
|
),
|
|
);
|
|
} else if (snapshot.hasError) {
|
|
// Jika error tapi ada initial data, gunakan initial data
|
|
if (widget.initialData != null) {
|
|
return _buildArtikelDetailBody(widget.initialData!);
|
|
}
|
|
|
|
return 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('${snapshot.error}'),
|
|
SizedBox(height: 16),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
_loadArtikelDetail();
|
|
});
|
|
},
|
|
child: Text('Coba Lagi'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.teal.shade700,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
} else if (!snapshot.hasData) {
|
|
// Jika tidak ada data tapi ada initial data, gunakan initial data
|
|
if (widget.initialData != null) {
|
|
return _buildArtikelDetailBody(widget.initialData!);
|
|
}
|
|
|
|
return Center(
|
|
child: Text('Artikel tidak ditemukan'),
|
|
);
|
|
}
|
|
|
|
return _buildArtikelDetailBody(snapshot.data!);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildArtikelDetailBody(ArtikelModel artikel) {
|
|
final screenSize = MediaQuery.of(context).size;
|
|
final gambarUrl = artikel.gambarUrl;
|
|
|
|
return CustomScrollView(
|
|
slivers: [
|
|
// Artikel header with image
|
|
SliverAppBar(
|
|
expandedHeight: screenSize.height * 0.3,
|
|
pinned: true,
|
|
backgroundColor: Colors.teal.shade700,
|
|
leading: IconButton(
|
|
icon: Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black26,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(Icons.arrow_back),
|
|
),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
icon: Container(
|
|
padding: EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black26,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(Icons.share),
|
|
),
|
|
onPressed: () {
|
|
Share.share(
|
|
'Baca artikel "${artikel.judul}" di aplikasi Posyandu',
|
|
subject: artikel.judul,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
flexibleSpace: FlexibleSpaceBar(
|
|
background: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
// Image or placeholder
|
|
gambarUrl != null
|
|
? Image.network(
|
|
gambarUrl,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Image.network(
|
|
'https://img.freepik.com/free-photo/stethoscope-copy-space-medical-stilllife_23-2148854308.jpg',
|
|
fit: BoxFit.cover,
|
|
);
|
|
},
|
|
)
|
|
: Image.network(
|
|
'https://img.freepik.com/free-photo/stethoscope-copy-space-medical-stilllife_23-2148854308.jpg',
|
|
fit: BoxFit.cover,
|
|
),
|
|
// Gradient overlay
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [
|
|
Colors.transparent,
|
|
Colors.black.withOpacity(0.7),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Content
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Title
|
|
Text(
|
|
artikel.judul,
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
// Date
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.calendar_today,
|
|
size: 16,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
SizedBox(width: 4),
|
|
Text(
|
|
_formatDate(artikel.tanggal),
|
|
style: TextStyle(
|
|
color: Colors.grey.shade600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 24),
|
|
// Content
|
|
Text(
|
|
artikel.isiArtikel,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
height: 1.6,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 32),
|
|
// Footer
|
|
Container(
|
|
padding: EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade100,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Bagikan Artikel',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'Informasi kesehatan ini penting untuk dibagikan kepada keluarga dan teman Anda.',
|
|
style: TextStyle(
|
|
color: Colors.grey.shade700,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
SizedBox(height: 16),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
ElevatedButton.icon(
|
|
onPressed: () {
|
|
Share.share(
|
|
'Baca artikel: ${artikel.judul}\n\nBaca selengkapnya di aplikasi Posyandu.',
|
|
);
|
|
},
|
|
icon: Icon(Icons.share),
|
|
label: Text('Bagikan'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.teal.shade700,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 50),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |