MIF_E31222888/lib/pages/n5/materi/detail_materi.dart

416 lines
17 KiB
Dart

import 'package:bahasajepang/pages/n5/materi/materi_service.dart';
import 'package:bahasajepang/pages/n5/materi/isi_materi.dart';
import 'package:bahasajepang/theme.dart';
import 'package:flutter/material.dart';
class DetailMateriN5Page extends StatefulWidget {
final int materiId;
const DetailMateriN5Page({super.key, required this.materiId});
@override
State<DetailMateriN5Page> createState() => _DetailMateriN5PageState();
}
class _DetailMateriN5PageState extends State<DetailMateriN5Page>
with SingleTickerProviderStateMixin {
final MateriService _materiService = MateriService();
late Future<dynamic> _materiDetailFuture;
bool _isLoading = true;
dynamic _materiData;
bool _isExpanded = false;
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
_materiDetailFuture = _loadMateriDetails();
}
Future<dynamic> _loadMateriDetails() async {
try {
final data = await _materiService.getMateriDetails(widget.materiId);
if (data == null) {
throw Exception('Data materi tidak ditemukan');
}
setState(() {
_materiData = data;
_isLoading = false;
});
return data;
} catch (e) {
setState(() {
_isLoading = false;
});
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: const Text('Error',
style: TextStyle(fontWeight: FontWeight.bold)),
content: Text('Gagal memuat materi: ${e.toString()}'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
style: TextButton.styleFrom(
foregroundColor: bgColor2,
),
),
],
),
);
throw e;
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggleExpand() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_animationController.forward();
} else {
_animationController.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: bgColor1.withValues(alpha: 0.95),
appBar: AppBar(
title: Text(
_isLoading ? 'Loading...' : _materiData['judul'] ?? 'Materi N5',
style: const TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
backgroundColor: bgColor3,
elevation: 4,
shadowColor: bgColor2.withValues(alpha: 0.5),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(15),
),
),
iconTheme: const IconThemeData(color: Colors.black),
),
body: Builder(
builder: (context) {
if (_isLoading) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(bgColor2),
),
);
}
return RefreshIndicator(
color: bgColor2,
backgroundColor: bgColor1,
onRefresh: _loadMateriDetails,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: bgColor2.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
// Header clickable
InkWell(
onTap: _toggleExpand,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Daftar Sub Materi',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
RotationTransition(
turns: _animation,
child: Icon(
Icons.expand_more,
color: Colors.white,
size: 28,
),
),
],
),
),
),
// Sub materi list
SizeTransition(
sizeFactor: _animation,
child: Column(
children: _materiData['details'] != null
? _materiData['details']
.asMap()
.entries
.map<Widget>((entry) {
final index = entry.key;
final detail = entry.value;
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 4.0),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius:
BorderRadius.circular(12),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
IsiMateriN5Page(
items: _materiData['details'],
initialIndex: index,
),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: bgColor1.withValues(
alpha: 0.8),
borderRadius:
BorderRadius.circular(12),
),
child: Row(
children: [
Container(
width: 28,
height: 28,
alignment: Alignment.center,
decoration: BoxDecoration(
color: bgColor2.withValues(
alpha: 0.2),
shape: BoxShape.circle,
border: Border.all(
color:
bgColor2.withValues(
alpha: 0.5),
width: 1.5,
),
),
child: Text(
'${index + 1}',
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.bold,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
detail['judul'] ??
'Sub Materi',
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight:
FontWeight.w500,
),
),
),
Icon(
Icons
.arrow_forward_ios_rounded,
size: 16,
color: Colors.white
.withValues(alpha: 0.7),
),
],
),
),
),
),
);
}).toList()
: [],
),
),
],
),
),
// Informasi tambahan tentang level N5
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: bgColor2.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.info_outline_rounded,
color: Colors.white.withValues(alpha: 0.9),
size: 24,
),
const SizedBox(width: 8),
Text(
'Tentang JLPT N5',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
const SizedBox(height: 12),
Text(
'JLPT (Japanese-Language Proficiency Test) atau dalam bahasa Jepang disebut 日本語能力試験 (Nihongo Nōryoku Shiken) adalah ujian standarisasi kemampuan bahasa Jepang bagi penutur asing. Ujian ini diselenggarakan oleh Japan Foundation dan Japan Educational Exchanges and Services (JEES). Tujuan dari ujian ini adalah untuk mengukur dan mengakui tingkat penguasaan bahasa Jepang seseorang, baik untuk keperluan studi, kerja, maupun imigrasi ke Jepang.'
'Dapat dikatakan level N5 ketika sudah mengetahui lebih dari 800 kosakata dan 100 kanji.',
style: TextStyle(
fontSize: 14,
color: Colors.white.withValues(alpha: 0.9),
height: 1.5,
),
),
],
),
),
// Tombol navigasi cepat
if (_materiData['details'] != null &&
_materiData['details'].isNotEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: bgColor2.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mulai Belajar',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 12),
SizedBox(
height: 48,
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IsiMateriN5Page(
items: _materiData['details'],
initialIndex: 0,
),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: bgColor2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 2,
),
child: const Text(
'Mulai dari Sub Materi Pertama',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
],
),
),
);
},
),
);
}
}