diff --git a/lib/view/auth/login/login.dart b/lib/view/auth/login/login.dart index b42f9a1..d0deda1 100644 --- a/lib/view/auth/login/login.dart +++ b/lib/view/auth/login/login.dart @@ -49,13 +49,13 @@ class _LoginPageState extends State { if (peran == 'santri') { - router.push("/navigasi"); // Arahkan ke route santri + router.push("/navigasi"); } else if (peran == 'pengajar') { - router.push("/navigasiPengajar"); // Arahkan ke route pengajar + router.push("/navigasiPengajar"); } } else { debugPrint("anjing"); - // Tampilkan error jika login gagal + ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Login Failed: ${response.body}')), ); @@ -95,30 +95,18 @@ class _LoginPageState extends State { style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), const SizedBox(height: 5), - // TextField( - // controller: emailController, - // keyboardType: TextInputType.number, - // inputFormatters: [FilteringTextInputFormatter.digitsOnly], - // decoration: InputDecoration( - // prefixIcon: const Icon(Icons.phone, color: Colors.grey), - // hintText: "Masukan No Telp", - // border: OutlineInputBorder( - // borderRadius: BorderRadius.circular(10), - // ), - // ), - // ), TextField( controller: emailController, keyboardType: TextInputType - .emailAddress, // Mengubah keyboard untuk email + .emailAddress, decoration: InputDecoration( prefixIcon: const Icon( Icons.email, color: Colors.grey, - ), // Ikon email + ), hintText: - "Masukkan Email", // Ganti hint text menjadi Masukkan Email + "Masukkan Email", border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), @@ -154,7 +142,7 @@ class _LoginPageState extends State { alignment: Alignment.centerRight, child: TextButton( onPressed: () { - // Navigasi ke halaman lupa password + }, child: Text( "Lupa password?", diff --git a/lib/view/home/belajar/belajar.dart b/lib/view/home/belajar/belajar.dart index a51d786..187f60f 100644 --- a/lib/view/home/belajar/belajar.dart +++ b/lib/view/home/belajar/belajar.dart @@ -22,15 +22,15 @@ class _BelajarPageState extends State { _fetchMateri(); } - // Fungsi untuk mengambil data materi dari API + Future _fetchMateri() async { final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/materi')); if (response.statusCode == 200) { - // Mengubah format response JSON yang berisi objek dengan data dalam properti 'data' + final Map data = json.decode(response.body); setState(() { - materiList = data['data']; // Mengambil data dari properti 'data' + materiList = data['data']; isLoading = false; }); } else { @@ -47,7 +47,7 @@ class _BelajarPageState extends State { ), body: Center( child: isLoading - ? const CircularProgressIndicator() // Menampilkan loading saat data masih dimuat + ? const CircularProgressIndicator() : SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16), @@ -91,7 +91,7 @@ class _BelajarPageState extends State { children: [ Expanded(child: Container()), Text( - materi['title'], // Menampilkan judul materi + materi['title'], style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -99,7 +99,7 @@ class _BelajarPageState extends State { ), ), Text( - materi['subtitle'], // Menampilkan subtitle materi + materi['subtitle'], style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, diff --git a/lib/view/home/latihan/latihan.dart b/lib/view/home/latihan/latihan.dart index 6e8d991..039e15d 100644 --- a/lib/view/home/latihan/latihan.dart +++ b/lib/view/home/latihan/latihan.dart @@ -3,13 +3,13 @@ import 'package:go_router/go_router.dart'; import 'package:ta_tahsin/core/baseurl/base_url.dart'; import 'package:ta_tahsin/core/theme.dart'; import 'package:http/http.dart' as http; -import 'dart:convert'; // Untuk parsing JSON -import 'dart:async'; // Untuk timer -import 'package:audioplayers/audioplayers.dart'; // Tambahkan audioplayers +import 'dart:convert'; +import 'dart:async'; +import 'package:audioplayers/audioplayers.dart'; class LatihanPage extends StatefulWidget { - final int id; // ID yang diteruskan dari halaman sebelumnya - final int currentStep; // Menambahkan parameter currentStep + final int id; + final int currentStep; const LatihanPage({super.key, required this.id, required this.currentStep}); @@ -18,37 +18,46 @@ class LatihanPage extends StatefulWidget { } class _LatihanPageState extends State { - late Future> latihanData; // Data latihan berdasarkan id_submateri - bool isRecording = false; // Menandakan apakah sedang merekam - int timer = 10; // Timer dimulai dari 10 detik - late Timer countdownTimer; // Timer untuk countdown - String timerText = "10"; // Menampilkan countdown timer dalam teks - final AudioPlayer _audioPlayer = AudioPlayer(); // Inisialisasi Audioplayer + late Future> latihanData; + bool isRecording = false; + int timer = 10; + late Timer countdownTimer; + String timerText = "10"; + final AudioPlayer _audioPlayer = AudioPlayer(); + bool isAudioPlaying = false; @override void initState() { super.initState(); - latihanData = fetchLatihanData(widget.id); // Ambil data latihan berdasarkan id_submateri yang diteruskan + latihanData = fetchLatihanData(widget.id); + + _audioPlayer.onPlayerStateChanged.listen((PlayerState state) { + if (state == PlayerState.completed) { + setState(() { + isAudioPlaying = false; + }); + } + }); } - // Fungsi untuk mengambil data latihan dari API + Future> fetchLatihanData(int id_submateri) async { final response = await http.get( - Uri.parse('${BaseUrl.baseUrl}/latihan/$id_submateri'), // Gantilah dengan URL API yang sesuai + Uri.parse('${BaseUrl.baseUrl}/latihan/$id_submateri'), ); if (response.statusCode == 200) { - return json.decode(response.body)['data']; // Parse data latihan dari API + return json.decode(response.body)['data']; } else { throw Exception('Failed to load latihan'); } } - // Fungsi untuk mulai countdown timer + void startTimer() { setState(() { - isRecording = true; // Set to true when starting to record - timer = 10; // Reset timer to 10 seconds + isRecording = true; + timer = 10; timerText = timer.toString(); }); @@ -61,28 +70,31 @@ class _LatihanPageState extends State { } else { countdownTimer.cancel(); setState(() { - isRecording = false; // Stop recording after timer ends + isRecording = false; }); } }); } - // Fungsi untuk menghentikan timer dan reset ke kondisi semula + void stopTimer() { countdownTimer.cancel(); setState(() { - isRecording = false; // Stop recording - timer = 10; // Reset timer + isRecording = false; + timer = 10; timerText = timer.toString(); }); } - // Fungsi untuk memutar audio dari asset berdasarkan URL yang diterima -void playAudio(String audioUrl) async { - // Menggunakan AssetSource untuk memutar audio dari asset - await _audioPlayer.play(AssetSource(audioUrl)); // Gunakan AssetSource untuk audio lokal - print("Audio playing..."); -} + + void playAudio(String audioUrl) async { + + await _audioPlayer.play(AssetSource(audioUrl)); + setState(() { + isAudioPlaying = true; + }); + print("Audio playing..."); + } @override @@ -92,7 +104,7 @@ void playAudio(String audioUrl) async { leading: IconButton( icon: const Icon(Icons.close), onPressed: () { - context.go('/navigasi'); // Navigasi kembali ke halaman utama + context.go('/navigasi'); }, ), actions: [ @@ -120,16 +132,16 @@ void playAudio(String audioUrl) async { future: latihanData, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CircularProgressIndicator()); // Tampilkan loading selama data dimuat + return Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); // Menampilkan pesan error + return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data == null || snapshot.data!.isEmpty) { - return Center(child: Text('Tidak ada latihan tersedia')); // Menampilkan pesan jika tidak ada data latihan + return Center(child: Text('Tidak ada latihan tersedia')); } - // Ambil data latihan berdasarkan currentStep + final latihanList = snapshot.data!; - final latihan = latihanList[widget.currentStep]; // Ambil data latihan untuk currentStep + final latihan = latihanList[widget.currentStep]; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), @@ -211,7 +223,7 @@ void playAudio(String audioUrl) async { ), ), Text( - latihan['potongan_ayat'], // Menampilkan potongan ayat dari data latihan + latihan['potongan_ayat'], style: const TextStyle( fontSize: 30, color: Colors.red, @@ -221,7 +233,7 @@ void playAudio(String audioUrl) async { ), const SizedBox(height: 20), Text( - latihan['latin_text'], // Menampilkan teks latin dari data latihan + latihan['latin_text'], style: TextStyle( fontSize: 15, color: Colors.black, @@ -230,14 +242,14 @@ void playAudio(String audioUrl) async { ), const SizedBox(height: 30), IconButton( - onPressed: () { - // Memutar audio dari asset sesuai dengan URL yang ada pada `correct_audio` + onPressed: isRecording ? null : () { + playAudio('audio/${latihan['correct_audio']}'); }, icon: Icon( - Icons.volume_up, + isAudioPlaying ? Icons.volume_up : Icons.volume_down, size: 50, - color: secondPrimaryColor, + color: isRecording ? greysColor : secondPrimaryColor, ), padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0), ) @@ -250,29 +262,29 @@ void playAudio(String audioUrl) async { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: ElevatedButton( - onPressed: () { + onPressed: isAudioPlaying ? null : () { if (!isRecording) { - startTimer(); // Start timer and change to stop icon + startTimer(); } else { - stopTimer(); // Stop timer and reset the button - // Pass data to PelafalanPage after recording + stopTimer(); + context.go( '/pelafalan', extra: { 'id': widget.id, 'currentStep': widget.currentStep, - 'latihanData': snapshot.data, // Mengirimkan data latihan yang sudah di-fetch + 'latihanData': snapshot.data, }, ); } }, style: ElevatedButton.styleFrom( - backgroundColor: secondPrimaryColor, + backgroundColor: isAudioPlaying ? greysColor : secondPrimaryColor, shape: const CircleBorder(), - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(10), ), child: Icon( - isRecording ? Icons.stop : Icons.mic, // Toggle between mic and stop icon + isRecording ? Icons.stop : Icons.mic, size: 40, color: Colors.white, ), @@ -280,6 +292,7 @@ void playAudio(String audioUrl) async { ), ), ), + const SizedBox(height: 8), ], ), ), @@ -291,7 +304,7 @@ void playAudio(String audioUrl) async { @override void dispose() { - _audioPlayer.dispose(); // Jangan lupa untuk melepaskan player + _audioPlayer.dispose(); super.dispose(); } } diff --git a/lib/view/home/latihan/pelafalan_popup.dart b/lib/view/home/latihan/pelafalan_popup.dart index 8dad5c4..35151fc 100644 --- a/lib/view/home/latihan/pelafalan_popup.dart +++ b/lib/view/home/latihan/pelafalan_popup.dart @@ -5,7 +5,7 @@ import 'package:ta_tahsin/core/theme.dart'; class PelafalanPage extends StatelessWidget { final int id; final int currentStep; - final List latihanData; // Menerima semua data latihan + final List latihanData; const PelafalanPage({ super.key, @@ -16,7 +16,7 @@ class PelafalanPage extends StatelessWidget { @override Widget build(BuildContext context) { - // Ambil data latihan berdasarkan currentStep + final latihan = latihanData[currentStep]; @@ -137,7 +137,7 @@ class PelafalanPage extends StatelessWidget { ), TextButton( onPressed: () { - // Logic to view video + }, child: Text( 'Lihat Video', @@ -165,7 +165,7 @@ class PelafalanPage extends StatelessWidget { context.go( '/latihan', extra: { - 'id': id, // ganti sesuai kebutuhan + 'id': id, 'currentStep': currentStep + 1, }, ); @@ -196,9 +196,9 @@ class PelafalanPage extends StatelessWidget { context.go( '/latihan', extra: { - 'id': 1, // Ganti sesuai kebutuhan + 'id': id, 'currentStep': currentStep, - 'latihanData': latihanData, // Mengirimkan data latihan yang sesuai + 'latihanData': latihanData, }, ); }, diff --git a/lib/view/home/materi/materi.dart b/lib/view/home/materi/materi.dart index 61b010b..048aa59 100644 --- a/lib/view/home/materi/materi.dart +++ b/lib/view/home/materi/materi.dart @@ -23,34 +23,34 @@ class MateriPage extends StatefulWidget { class _MateriPageState extends State { late Future> kategoriData; - bool isLoading = true; // Variabel untuk mengatur status loading + bool isLoading = true; @override void initState() { super.initState(); - kategoriData = fetchKategoriData(widget.id); // Ambil kategori berdasarkan id materi + kategoriData = fetchKategoriData(widget.id); } - // Fungsi untuk mengambil kategori berdasarkan id_materi + Future> fetchKategoriData(int id_materi) async { final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/kategori/$id_materi')); if (response.statusCode == 200) { setState(() { - isLoading = false; // Set isLoading ke false setelah data kategori berhasil diambil + isLoading = false; }); - return json.decode(response.body)['data']; // Parse JSON response ke List kategori + return json.decode(response.body)['data']; } else { throw Exception('Failed to load kategori'); } } - // Fungsi untuk mengambil sub-materi berdasarkan id_kategori + Future> fetchSubMateriData(int id_kategori) async { final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/sub_materi/$id_kategori')); if (response.statusCode == 200) { - return json.decode(response.body)['data']['sub_materi']; // Parse JSON response ke List sub-materi + return json.decode(response.body)['data']['sub_materi']; } else { throw Exception('Failed to load sub-materi'); } @@ -60,15 +60,15 @@ class _MateriPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: PreferredSize( - preferredSize: Size.fromHeight(50), // Ukuran tinggi AppBar + preferredSize: Size.fromHeight(50), child: Card( - elevation: 4, // Menambahkan shadow + elevation: 4, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.zero, // Tidak ada radius, sudut tajam + borderRadius: BorderRadius.zero, ), - margin: EdgeInsets.zero, // Menghilangkan margin Card + margin: EdgeInsets.zero, child: AppBar( - backgroundColor: secondPrimaryColor, // Warna AppBar (sesuaikan dengan secondPrimaryColor) + backgroundColor: secondPrimaryColor, title: Text( widget.title, style: TextStyle( @@ -88,16 +88,16 @@ class _MateriPageState extends State { ), ), body: isLoading - ? Center(child: CircularProgressIndicator()) // Tampilkan loading spinner selama proses loading + ? Center(child: CircularProgressIndicator()) : FutureBuilder>( future: kategoriData, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CircularProgressIndicator()); // Tampilkan loading saat menunggu + return Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); // Tampilkan error jika ada masalah + return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data == null) { - return Center(child: Text('Tidak ada data tersedia')); // Tampilkan jika data kosong + return Center(child: Text('Tidak ada data tersedia')); } final kategoriList = snapshot.data!; @@ -126,13 +126,13 @@ class _MateriPageState extends State { ], ), ), - // SliverList untuk kategori dan sub-materi + for (var kategori in kategoriList) FutureBuilder>( - future: fetchSubMateriData(kategori['id']), // Ambil submateri untuk kategori ini + future: fetchSubMateriData(kategori['id']), builder: (context, subMateriSnapshot) { if (subMateriSnapshot.connectionState == ConnectionState.waiting) { - return SliverToBoxAdapter(child: SizedBox()); // Tidak ada loading spinner lagi + return SliverToBoxAdapter(child: SizedBox()); } else if (subMateriSnapshot.hasError) { return SliverToBoxAdapter(child: Center(child: Text('Error: ${subMateriSnapshot.error}'))); } else if (!subMateriSnapshot.hasData || subMateriSnapshot.data == null) { @@ -144,11 +144,11 @@ class _MateriPageState extends State { return SliverList( delegate: SliverChildListDelegate( [ - // Menampilkan nama kategori sekali + Padding( padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0), child: Text( - kategori['nama_kategori'], // Menampilkan nama kategori + kategori['nama_kategori'], style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -156,7 +156,7 @@ class _MateriPageState extends State { ), ), ), - // Menampilkan sub-materi yang terkait dengan kategori ini + for (var submateri in subMateriList) Padding( padding: const EdgeInsets.only(bottom: 0), @@ -198,7 +198,7 @@ class _MateriPageState extends State { ), onTap: () { context.push( - '/submateri', // Gantilah dengan rute yang sesuai + '/submateri', extra: { 'id': submateri['id'], 'title': submateri['title'], diff --git a/lib/view/home/profile/profile.dart b/lib/view/home/profile/profile.dart index b05ad11..ca66f99 100644 --- a/lib/view/home/profile/profile.dart +++ b/lib/view/home/profile/profile.dart @@ -9,38 +9,38 @@ class ProfilePage extends StatelessWidget { const ProfilePage({super.key}); Future logout(BuildContext context) async { - // Ambil token yang ada di SharedPreferences + SharedPreferences prefs = await SharedPreferences.getInstance(); String? token = prefs.getString('token'); if (token != null) { - // Panggil API logout untuk menghapus token di server + final response = await http.post( - Uri.parse('${BaseUrl.baseUrl}/logout'), // Ganti dengan URL API logout + Uri.parse('${BaseUrl.baseUrl}/logout'), headers: { - 'Authorization': 'Bearer $token', // Kirim token di header + 'Authorization': 'Bearer $token', }, ); if (response.statusCode == 200) { - // Jika berhasil logout, hapus token dari SharedPreferences + prefs.remove('token'); - // Arahkan pengguna ke halaman login atau halaman lain setelah logout + ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Berhasil logout!')), ); - // ignore: use_build_context_synchronously + context.go('/login'); } else { - // Jika ada error dari server + ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal logout: ${response.body}')), ); } } else { - // Jika tidak ada token yang tersimpan + ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Token tidak ditemukan')), ); @@ -64,7 +64,7 @@ class ProfilePage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Profile Section with Gradient Card + Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), @@ -85,7 +85,7 @@ class ProfilePage extends StatelessWidget { padding: const EdgeInsets.all(16), child: Row( children: [ - // Profile Avatar + CircleAvatar( radius: 50, backgroundColor: Colors.white, @@ -93,7 +93,7 @@ class ProfilePage extends StatelessWidget { ), const SizedBox(width: 16), - // Name and Phone Number to the right + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -118,7 +118,7 @@ class ProfilePage extends StatelessWidget { ), const SizedBox(height: 20), - // "Hasil Placement Test" Section with Gradient Card + Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), @@ -141,11 +141,11 @@ class ProfilePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTestResultSection("Tgl Lahir", "-"), - Divider(color: Colors.white), // White Divider for contrast + Divider(color: Colors.white), _buildTestResultSection("Alamat", "-"), - Divider(color: Colors.white), // White Divider for contrast + Divider(color: Colors.white), _buildTestResultSection("Nama Orang Tua", "-"), - Divider(color: Colors.white), // White Divider for contrast + Divider(color: Colors.white), _buildTestResultSection("email", "-"), ], ), @@ -177,7 +177,7 @@ class ProfilePage extends StatelessWidget { ); } - // Method to build test result sections + Widget _buildTestResultSection(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 5), @@ -188,7 +188,7 @@ class ProfilePage extends StatelessWidget { label, style: TextStyle(fontSize: 16, color: Colors.white), ), - SizedBox(height: 6), // Space between label and value + SizedBox(height: 6), Text( value, style: TextStyle(fontSize: 16, color: Colors.white), @@ -198,7 +198,7 @@ class ProfilePage extends StatelessWidget { ); } - // Method to create info links + Widget _buildInfoLink(String label) { return Padding( padding: const EdgeInsets.symmetric(vertical: 5), diff --git a/lib/view/home/submateri/submateri.dart b/lib/view/home/submateri/submateri.dart index 493c48c..9f59a49 100644 --- a/lib/view/home/submateri/submateri.dart +++ b/lib/view/home/submateri/submateri.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:ta_tahsin/core/theme.dart'; -import 'package:youtube_player_flutter/youtube_player_flutter.dart'; // Import youtube_player_flutter +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; class SubMateriPage extends StatefulWidget { final int id; @@ -39,16 +39,16 @@ class _SubMateriPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: PreferredSize( - preferredSize: Size.fromHeight(50), // Ukuran tinggi AppBar + preferredSize: Size.fromHeight(50), child: Card( - elevation: 4, // Menambahkan shadow + elevation: 4, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.zero, // Tidak ada radius, sudut tajam + borderRadius: BorderRadius.zero, ), - margin: EdgeInsets.zero, // Menghilangkan margin Card + margin: EdgeInsets.zero, child: AppBar( backgroundColor: - secondPrimaryColor, // Warna AppBar (sesuaikan dengan secondPrimaryColor) + secondPrimaryColor, title: Text( widget.title, style: TextStyle( @@ -61,7 +61,7 @@ class _SubMateriPageState extends State { icon: const Icon(Icons.arrow_back), color: Colors.white, onPressed: () { - Navigator.pop(context); // Aksi kembali + Navigator.pop(context); }, ), ), @@ -129,8 +129,8 @@ class _SubMateriPageState extends State { child: TextButton( onPressed: () { context.go( - '/latihan', // Gantilah dengan rute yang sesuai - extra: {'id': widget.id}, // Mengirimkan id dari halaman sebelumnya + '/latihan', + extra: {'id': widget.id}, ); }, child: Text(