From e0b5c274b38a0cd4ae0778ba732a1845cd0d3abe Mon Sep 17 00:00:00 2001 From: vionar3 Date: Tue, 3 Jun 2025 01:24:38 +0700 Subject: [PATCH] feat: add fitur quiz,memperbaiki voice record & integrate api get santri --- android/app/build.gradle.kts | 2 +- lib/core/baseurl/base_url.dart | 2 +- lib/core/navigation/navigation.dart | 11 +- lib/core/navigation/navigation_pengajar.dart | 6 + lib/core/router/route.dart | 48 +- lib/view/auth/login/login.dart | 5 +- lib/view/home/latihan/latihan.dart | 56 ++- lib/view/home/latihan/pelafalan_popup.dart | 177 ++++--- lib/view/home/quiz/detail_quiz.dart | 323 +++++++++++++ lib/view/home/quiz/hasil_quiz.dart | 101 ++++ lib/view/home/quiz/model/model_data_quiz.dart | 0 lib/view/home/quiz/quiz.dart | 236 +++++++++ .../pengajar/data_latihan/data_latihan.dart | 270 +++++++++++ .../data_latihan/detail_data_latihan.dart | 31 ++ .../pengajar/data_santri/data_santri.dart | 449 +++++++++++++----- .../data_santri/detail_data_santri.dart | 242 +++++++--- .../pengajar/data_santri/tambah_santri.dart | 221 +++++++++ .../pengajar/kemajuan/detail_kemajuan.dart | 2 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 186 +++++++- pubspec.yaml | 5 + .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 25 files changed, 2129 insertions(+), 261 deletions(-) create mode 100644 lib/view/home/quiz/detail_quiz.dart create mode 100644 lib/view/home/quiz/hasil_quiz.dart create mode 100644 lib/view/home/quiz/model/model_data_quiz.dart create mode 100644 lib/view/home/quiz/quiz.dart create mode 100644 lib/view/pengajar/data_latihan/data_latihan.dart create mode 100644 lib/view/pengajar/data_latihan/detail_data_latihan.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 119d68e..bdf0f72 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -24,7 +24,7 @@ android { applicationId = "com.example.ta_tahsin" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + minSdk = 24 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/lib/core/baseurl/base_url.dart b/lib/core/baseurl/base_url.dart index 76e5df8..48324f8 100644 --- a/lib/core/baseurl/base_url.dart +++ b/lib/core/baseurl/base_url.dart @@ -1,4 +1,4 @@ // lib/config.dart class BaseUrl { - static const String baseUrl = 'http://192.168.100.13:8000/api'; + static const String baseUrl = 'http://192.168.0.102:8000/api'; } diff --git a/lib/core/navigation/navigation.dart b/lib/core/navigation/navigation.dart index b7ea394..95582b7 100644 --- a/lib/core/navigation/navigation.dart +++ b/lib/core/navigation/navigation.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:ta_tahsin/core/theme.dart'; +import 'package:ta_tahsin/view/home/quiz/quiz.dart'; import '../../view/home/belajar/belajar.dart'; import '../../view/home/profile/profile.dart'; @@ -20,7 +21,7 @@ class _NavigationPageState extends State { final List _pages = [ const BelajarPage(), - // const UjianPage(), + const QuizPage(), const ProfilePage(), ]; @@ -44,10 +45,10 @@ class _NavigationPageState extends State { icon: Icon(Icons.menu_book), label: "Belajar", ), - // BottomNavigationBarItem( - // icon: Icon(Icons.list_alt), - // label: "Ujian", - // ), + BottomNavigationBarItem( + icon: Icon(Icons.quiz), + label: "Quiz", + ), BottomNavigationBarItem( icon: Icon(Icons.person), label: "Profile", diff --git a/lib/core/navigation/navigation_pengajar.dart b/lib/core/navigation/navigation_pengajar.dart index e2c1032..a1d2e2e 100644 --- a/lib/core/navigation/navigation_pengajar.dart +++ b/lib/core/navigation/navigation_pengajar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:ta_tahsin/core/theme.dart'; +import 'package:ta_tahsin/view/pengajar/data_latihan/data_latihan.dart'; import '../../view/pengajar/data_santri/data_santri.dart'; import '../../view/pengajar/kemajuan/kemajuan.dart'; @@ -23,6 +24,7 @@ class _NavigationPengajarPageState extends State { final List _pages = [ const KemajuanPage(), const DataSantriPage(), + const DataLatihanPage(), const PengajarProfilePage(), ]; @@ -50,6 +52,10 @@ class _NavigationPengajarPageState extends State { icon: Icon(Icons.list_alt), label: "Data Santri", ), + BottomNavigationBarItem( + icon: Icon(Icons.list_alt), + label: "Data Latihan", + ), BottomNavigationBarItem( icon: Icon(Icons.person), label: "Profile", diff --git a/lib/core/router/route.dart b/lib/core/router/route.dart index 3d74d85..3619477 100644 --- a/lib/core/router/route.dart +++ b/lib/core/router/route.dart @@ -2,7 +2,11 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:ta_tahsin/view/home/latihan/latihan.dart'; +import 'package:ta_tahsin/view/home/quiz/detail_quiz.dart'; +import 'package:ta_tahsin/view/home/quiz/hasil_quiz.dart'; +import 'package:ta_tahsin/view/pengajar/data_latihan/detail_data_latihan.dart'; import 'package:ta_tahsin/view/pengajar/data_santri/detail_data_santri.dart'; +import 'package:ta_tahsin/view/pengajar/data_santri/tambah_santri.dart'; import 'package:ta_tahsin/view/pengajar/kemajuan/detail_kemajuan.dart'; import '../../view/auth/login/login.dart'; @@ -72,11 +76,13 @@ GoRoute( final Map extra = state.extra as Map; final int currentStep = extra['currentStep']; final List latihanData = extra['latihanData']; + final String recordedFilePath = extra['recordedFilePath']; return PelafalanPage( id: extra['id'], currentStep: currentStep, latihanData: latihanData, + recordedFilePath: recordedFilePath, ); }, ), @@ -104,7 +110,47 @@ GoRoute( ), GoRoute( path: '/detail_user', - builder: (context, state) => DetailDataSantriPage(), + builder: (BuildContext context, GoRouterState state) { + final Map extra = state.extra as Map; + return DetailDataSantriPage( + id: extra['id'], + ); + }, + ), + GoRoute( + path: '/tambah_santri', + builder: (context, state) { + return TambahSantriPage(); // Halaman Data Santri untuk pengajar + }, + ), + GoRoute( + path: '/detail_data_latihan', + builder: (BuildContext context, GoRouterState state) { + final Map extra = state.extra as Map; + return DetailDataLatihanPage( + id: extra['id'], + ); + }, + ), + GoRoute( + path: '/detail_quiz', + builder: (BuildContext context, GoRouterState state) { + final Map extra = state.extra as Map; + return DetailQuizPage( + id: extra['id'], + title: extra['title'], + ); + }, + ), + GoRoute( + path: '/hasil_quiz', + builder: (BuildContext context, GoRouterState state) { + final Map extra = state.extra as Map; + return HasilQuizPage( + totalScore: extra['totalScore'], + title: extra['title'], + ); + }, ), ], ); diff --git a/lib/view/auth/login/login.dart b/lib/view/auth/login/login.dart index d0deda1..3714b4d 100644 --- a/lib/view/auth/login/login.dart +++ b/lib/view/auth/login/login.dart @@ -46,6 +46,7 @@ class _LoginPageState extends State { String peran = data['data']['user']['peran']; + prefs.setString('peran', peran); if (peran == 'santri') { @@ -54,7 +55,7 @@ class _LoginPageState extends State { router.push("/navigasiPengajar"); } } else { - debugPrint("anjing"); + // debugPrint("anjing"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Login Failed: ${response.body}')), @@ -91,7 +92,7 @@ class _LoginPageState extends State { Center(child: Image.asset('assets/logo/sho.jpg', height: 180)), const SizedBox(height: 20), const Text( - "No Telp", + "Email", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), const SizedBox(height: 5), diff --git a/lib/view/home/latihan/latihan.dart b/lib/view/home/latihan/latihan.dart index 039e15d..9cdfc13 100644 --- a/lib/view/home/latihan/latihan.dart +++ b/lib/view/home/latihan/latihan.dart @@ -1,5 +1,10 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:record/record.dart'; import 'package:ta_tahsin/core/baseurl/base_url.dart'; import 'package:ta_tahsin/core/theme.dart'; import 'package:http/http.dart' as http; @@ -18,6 +23,7 @@ class LatihanPage extends StatefulWidget { } class _LatihanPageState extends State { + final _record = AudioRecorder(); late Future> latihanData; bool isRecording = false; int timer = 10; @@ -25,6 +31,7 @@ class _LatihanPageState extends State { String timerText = "10"; final AudioPlayer _audioPlayer = AudioPlayer(); bool isAudioPlaying = false; + String? recordedFilePath; @override void initState() { @@ -54,10 +61,11 @@ class _LatihanPageState extends State { } + // Fungsi untuk memulai timer dan mulai merekam void startTimer() { setState(() { - isRecording = true; - timer = 10; + isRecording = true; + timer = 10; timerText = timer.toString(); }); @@ -69,16 +77,42 @@ class _LatihanPageState extends State { }); } else { countdownTimer.cancel(); - setState(() { - isRecording = false; - }); + stopRecording(); } }); + + // Mulai merekam + _startRecording(); + } + + + Future _startRecording() async { + if (await Permission.microphone.request().isGranted) { + final directory = Directory.systemTemp; + final fileName = DateTime.now().millisecondsSinceEpoch.toString(); + final path = '${directory.path}/record_voice_$fileName.m4a'; + await _record.start( + const RecordConfig(), + path: path, +); + setState(() { + recordedFilePath = path; + }); + } + } + + // Fungsi untuk menghentikan perekaman + Future stopRecording() async { + await _record.stop(); + setState(() { + isRecording = false; + }); } void stopTimer() { countdownTimer.cancel(); + stopRecording(); setState(() { isRecording = false; timer = 10; @@ -211,22 +245,22 @@ class _LatihanPageState extends State { ), child: Column( children: [ - const Padding( + Padding( padding: EdgeInsets.only(bottom: 40.0), child: Text( "Ucapkan potongan kata ini", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.black, + color: secondPrimaryColor, ), ), ), Text( latihan['potongan_ayat'], - style: const TextStyle( - fontSize: 30, - color: Colors.red, + style: TextStyle( + fontSize: 19, + color: blackColor, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, @@ -274,6 +308,7 @@ class _LatihanPageState extends State { 'id': widget.id, 'currentStep': widget.currentStep, 'latihanData': snapshot.data, + 'recordedFilePath': recordedFilePath, }, ); } @@ -304,6 +339,7 @@ class _LatihanPageState extends State { @override void dispose() { + _record.dispose(); _audioPlayer.dispose(); super.dispose(); } diff --git a/lib/view/home/latihan/pelafalan_popup.dart b/lib/view/home/latihan/pelafalan_popup.dart index 35151fc..c0204e2 100644 --- a/lib/view/home/latihan/pelafalan_popup.dart +++ b/lib/view/home/latihan/pelafalan_popup.dart @@ -1,26 +1,72 @@ +import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:ta_tahsin/core/theme.dart'; -class PelafalanPage extends StatelessWidget { +class PelafalanPage extends StatefulWidget { final int id; final int currentStep; - final List latihanData; + final List latihanData; + final String recordedFilePath; const PelafalanPage({ super.key, required this.id, required this.currentStep, required this.latihanData, + required this.recordedFilePath, }); + @override + _PelafalanPageState createState() => _PelafalanPageState(); +} + +class _PelafalanPageState extends State { + late String recordedFilePath; + late int currentStep; + late List latihanData; + late dynamic latihan; + final AudioPlayer _audioPlayer = AudioPlayer(); + bool isAudioPlaying = false; + bool isAudioRecordPlaying = false; + + @override + void initState() { + super.initState(); + recordedFilePath = widget.recordedFilePath; + + currentStep = widget.currentStep; + latihanData = widget.latihanData; + latihan = latihanData[currentStep]; + _audioPlayer.onPlayerStateChanged.listen((PlayerState state) { + if (state == PlayerState.completed) { + setState(() { + isAudioPlaying = false; + isAudioRecordPlaying = false; + }); + } + }); + } + + void playAudio(String audioUrl) async { + + await _audioPlayer.play(AssetSource(audioUrl)); + setState(() { + isAudioPlaying = true; + }); + print("Audio playing..."); + } + + void playRecordedAudio() async { + await _audioPlayer.play(DeviceFileSource(recordedFilePath)); + setState(() { + isAudioRecordPlaying = true; + }); + print("Audio playing..."); + } + @override Widget build(BuildContext context) { - - final latihan = latihanData[currentStep]; - - - return Scaffold( body: Center( child: Card( @@ -57,9 +103,9 @@ class PelafalanPage extends StatelessWidget { Center( child: Text( latihan['potongan_ayat'], - style: const TextStyle( + style: TextStyle( fontSize: 30, - color: Colors.red, + color: blackColor, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, @@ -69,56 +115,68 @@ class PelafalanPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: Colors.grey, + GestureDetector( + onTap: () { + playRecordedAudio(); + print("Pelafalan Kamu tapped!"); + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.grey, + ), ), - ), - child: Column( - children: [ - Text( - 'Pelafalan Kamu', - style: TextStyle( - color: secondPrimaryColor, - fontWeight: FontWeight.bold, + child: Column( + children: [ + Text( + 'Pelafalan Kamu', + style: TextStyle( + color: secondPrimaryColor, + fontWeight: FontWeight.bold, + ), ), - ), - const SizedBox(height: 10), - Icon( - Icons.volume_up, - color: secondPrimaryColor, - size: 30, - ), - ], + const SizedBox(height: 10), + Icon( + isAudioRecordPlaying ? Icons.volume_up : Icons.volume_down, + color: secondPrimaryColor, + size: 30, + ), + ], + ), ), ), - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: Colors.grey, + GestureDetector( + onTap: () { + print("Pelafalan Ustadz tapped!"); + playAudio('audio/${latihan['correct_audio']}'); + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.grey, + ), ), - ), - child: Column( - children: [ - Text( - 'Pelafalan Ustadz', - style: TextStyle( - color: secondPrimaryColor, - fontWeight: FontWeight.bold, + child: Column( + children: [ + Text( + 'Pelafalan Ustadz', + style: TextStyle( + color: secondPrimaryColor, + fontWeight: FontWeight.bold, + ), ), - ), - const SizedBox(height: 10), - Icon( - Icons.volume_up, - color: secondPrimaryColor, - size: 30, - ), - ], + const SizedBox(height: 10), + Icon( + isAudioPlaying ? Icons.volume_up : Icons.volume_down, + color: secondPrimaryColor, + size: 30, + ), + ], + ), ), ), ], @@ -137,7 +195,7 @@ class PelafalanPage extends StatelessWidget { ), TextButton( onPressed: () { - + // Logic untuk melihat video }, child: Text( 'Lihat Video', @@ -162,11 +220,14 @@ class PelafalanPage extends StatelessWidget { ElevatedButton( onPressed: () { if (currentStep < latihanData.length - 1) { + setState(() { + currentStep++; // Update step saat lanjut + }); context.go( '/latihan', extra: { - 'id': id, - 'currentStep': currentStep + 1, + 'id': widget.id, + 'currentStep': currentStep, }, ); } else { @@ -196,9 +257,9 @@ class PelafalanPage extends StatelessWidget { context.go( '/latihan', extra: { - 'id': id, + 'id': widget.id, 'currentStep': currentStep, - 'latihanData': latihanData, + 'latihanData': latihanData, }, ); }, diff --git a/lib/view/home/quiz/detail_quiz.dart b/lib/view/home/quiz/detail_quiz.dart new file mode 100644 index 0000000..3f7e3e5 --- /dev/null +++ b/lib/view/home/quiz/detail_quiz.dart @@ -0,0 +1,323 @@ +import 'dart:convert'; // Untuk JSON parsing +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; // Untuk HTTP request +import 'package:go_router/go_router.dart'; +import 'package:ta_tahsin/core/baseurl/base_url.dart'; +import 'package:ta_tahsin/core/theme.dart'; // Pastikan Anda mengimpor theme.dart +import 'package:shared_preferences/shared_preferences.dart'; // Import shared_preferences + +class DetailQuizPage extends StatefulWidget { + final int id; // Menambahkan materiId yang dipilih + final String title; // Menambahkan materiId yang dipilih + DetailQuizPage({super.key, required this.id, required this.title}); + + @override + _DetailQuizPageState createState() => _DetailQuizPageState(); +} + +class _DetailQuizPageState extends State { + Map selectedAnswers = {}; // Menyimpan jawaban per soal + List quizList = []; // Menyimpan data soal yang diambil dari API + bool isLoading = true; // Untuk menandakan status loading data + String? authToken; // Variabel authToken yang akan diambil dari shared_preferences + + @override + void initState() { + super.initState(); + _getAuthToken(); // Ambil authToken ketika halaman pertama kali dibuka + fetchQuizData(); // Mengambil data soal ketika halaman pertama kali dibuka + } + + Future _getAuthToken() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + + // Mengambil token yang disimpan + String? authToken = prefs.getString('token'); + + if (authToken != null) { + setState(() { + this.authToken = authToken; // Menyimpan token ke variabel authToken + }); + } else { + print("No auth token found"); + } + } + + // Fungsi untuk mengambil data soal dari API + Future fetchQuizData() async { + final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/quiz/${widget.id}')); + + if (response.statusCode == 200) { + setState(() { + quizList = json.decode(response.body)['data']; + isLoading = false; + }); + } else { + setState(() { + isLoading = false; + }); + // Handle error jika API gagal + print('Failed to load quiz data'); + } + } + + Future submitQuizAnswers() async { + if (authToken == null) { + print("No auth token found"); + return; + } + + List> answers = []; + + selectedAnswers.forEach((questionIndex, optionIndex) { + var questionData = quizList[questionIndex]; + var selectedOption = ['a', 'b', 'c', 'd'][optionIndex]; // Menyusun option berdasarkan index + answers.add({ + 'question_id': questionData['id'], + 'selected_option': selectedOption, + }); + }); + + final response = await http.post( + Uri.parse('${BaseUrl.baseUrl}/quiz/${widget.id}/check'), + headers: { + 'Authorization': 'Bearer $authToken', + 'Content-Type': 'application/json', + }, + body: json.encode({'answers': answers}), + ); + + print("Response status: ${response.statusCode}"); + print("Response body: ${response.body}"); + + if (response.statusCode == 200) { + var data = json.decode(response.body); + int totalScore = data['data']['total_score']; + int correctAnswers = data['data']['correct_answers']; + + // Tampilkan dialog hasil quiz + showResultDialog(totalScore, correctAnswers); + } else { + print('Failed to submit answers'); + // Tampilkan pesan error jika gagal + } +} + + + void showResultDialog(int totalScore, int correctAnswers) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), // Membuat sudut lebih melengkung + ), + title: Row( + children: [ + Icon( + Icons.check_circle_outline, + color: Colors.green, + size: 30, + ), + SizedBox(width: 10), + Text( + "Konfirmasi", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ], + ), + content: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Apakah Anda yakin dengan jawaban Anda?", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ), + actions: [ + // Tombol Batal + TextButton( + child: Text( + "Batal", + style: TextStyle(color: Colors.red, fontSize: 16), + ), + onPressed: () { + Navigator.of(context).pop(); // Menutup dialog dan kembali + }, + ), + // Tombol OK + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: secondPrimaryColor, // Warna tombol OK + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + child: Text( + "OK", + style: TextStyle(fontSize: 16, color: Colors.white), + ), + onPressed: () { + // Navigasi ke halaman hasil_quiz dengan data tambahan + Navigator.of(context).pop(); // Tutup dialog terlebih dahulu + context.go('/hasil_quiz', extra: { + 'totalScore': totalScore, + 'title': widget.title, + }); + }, + ), + ], + ); + }, + ); +} + + + + // Fungsi untuk mengecek apakah semua soal sudah dijawab + bool canFinishQuiz() { + return selectedAnswers.length == quizList.length; + } + + // Membuat tampilan tombol pilihan jawaban + Widget _buildOption(String optionText, int questionIndex, int optionIndex) { + bool isSelected = selectedAnswers[questionIndex] == optionIndex; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ElevatedButton( + onPressed: () { + setState(() { + selectedAnswers[questionIndex] = optionIndex; // Menyimpan pilihan yang dipilih untuk soal tertentu + }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: isSelected ? secondPrimaryColor : Colors.grey[300], // Mengubah warna tombol saat dipilih + padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), + elevation: isSelected ? 5 : 2, // Efek bayangan yang lebih halus + shadowColor: isSelected ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.1), + minimumSize: Size(double.infinity, 50), // Tombol mengisi lebar layar + ), + child: Text( + optionText, + style: TextStyle(fontSize: 16, color: isSelected ? Colors.white : Colors.black87), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(50), + child: Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + margin: EdgeInsets.zero, + child: AppBar( + backgroundColor: secondPrimaryColor, + title: Text( + "Detail Quiz", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + color: Colors.white, + onPressed: () { + context.go('/navigasi'); + }, + ), + ), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: isLoading + ? Center(child: CircularProgressIndicator()) // Loading indicator + : Column( + children: [ + Expanded( + child: ListView.builder( + itemCount: quizList.length, + itemBuilder: (context, index) { + var questionData = quizList[index]; + var question = questionData['question']; + var options = [ + questionData['option_a'], + questionData['option_b'], + questionData['option_c'], + questionData['option_d'] + ]; + + return Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Card( + elevation: 5, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15)), + color: Colors.white, + shadowColor: Colors.black.withOpacity(0.1), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${index + 1}. $question", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: blackColor, + ), + ), + const SizedBox(height: 15), + ...options.map((option) { + int optionIndex = options.indexOf(option); + return _buildOption(option, index, optionIndex); + }).toList(), + ], + ), + ), + ), + ); + }, + ), + ), + // Tombol Selesai + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: canFinishQuiz() ? submitQuizAnswers : null, // Tombol selesai hanya aktif jika semua soal dijawab + style: ElevatedButton.styleFrom( + backgroundColor: canFinishQuiz() + ? secondPrimaryColor + : Colors.grey[300], + padding: const EdgeInsets.symmetric(vertical: 10.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0)), + elevation: 5, + ), + child: Text( + "Selesai", + style: TextStyle(fontSize: 16, color: Colors.white), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/view/home/quiz/hasil_quiz.dart b/lib/view/home/quiz/hasil_quiz.dart new file mode 100644 index 0000000..fc71196 --- /dev/null +++ b/lib/view/home/quiz/hasil_quiz.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:ta_tahsin/core/theme.dart'; + +class HasilQuizPage extends StatelessWidget { + final int totalScore; + final String title; + + // Konstruktor untuk menerima nilai dari halaman sebelumnya + HasilQuizPage({super.key, required this.totalScore, required this.title}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, // Background putih + appBar: PreferredSize( + preferredSize: Size.fromHeight(50), + child: Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + margin: EdgeInsets.zero, + child: AppBar( + backgroundColor: secondPrimaryColor, + title: Text( + "Hasil Quiz", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 40.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Header pesan selamat + Text( + 'Selamat anda berhasil menyelesaikan latihan soal\n$title!', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + SizedBox(height: 30), + + // Menampilkan skor + Text( + 'Point kamu:', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.black54, + ), + ), + SizedBox(height: 10), + Text( + '$totalScore', + style: TextStyle( + fontSize: 48, + fontWeight: FontWeight.bold, + color: secondPrimaryColor, + ), + ), + SizedBox(height: 30), + + // Tombol kembali + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: secondPrimaryColor, // Warna tombol + padding: EdgeInsets.symmetric(horizontal: 100, vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + context.go('/navigasi'); + }, + child: Text( + 'Kembali', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/view/home/quiz/model/model_data_quiz.dart b/lib/view/home/quiz/model/model_data_quiz.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/view/home/quiz/quiz.dart b/lib/view/home/quiz/quiz.dart new file mode 100644 index 0000000..15c0204 --- /dev/null +++ b/lib/view/home/quiz/quiz.dart @@ -0,0 +1,236 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:shared_preferences/shared_preferences.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'; + +class QuizPage extends StatefulWidget { + const QuizPage({super.key}); + + @override + _QuizPageState createState() => _QuizPageState(); +} + +class _QuizPageState extends State { + List materiList = []; + bool isLoading = true; + String? authToken; + + // Variabel untuk menyimpan hasil score dan status per materi + Map> quizResults = {}; + + @override + void initState() { + super.initState(); + _getAuthToken(); + _fetchMateri(); // Fetch materi saat halaman dimuat + } + + // Mengambil token auth dari shared_preferences + Future _getAuthToken() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? authToken = prefs.getString('token'); + if (authToken != null) { + setState(() { + this.authToken = authToken; // Menyimpan token + }); + } else { + print("No auth token found"); + } + } + + // Fungsi untuk mengambil data materi + Future _fetchMateri() async { + final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/materi')); + + if (response.statusCode == 200) { + final Map data = json.decode(response.body); + setState(() { + materiList = data['data']; + isLoading = false; + }); + + // Ambil hasil quiz untuk setiap materi yang ada + for (var materi in materiList) { + _fetchQuizResult(materi['id']); + } + } else { + throw Exception('Failed to load materi'); + } + } + + // Fungsi untuk mengambil hasil quiz dari API + Future _fetchQuizResult(int id_materi) async { + if (authToken == null) { + print("No auth token found"); + return; + } + + final response = await http.get( + Uri.parse('${BaseUrl.baseUrl}/quizresult/$id_materi'), + headers: { + 'Authorization': 'Bearer $authToken', // Gunakan token autentikasi yang valid + }, + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + setState(() { + quizResults[id_materi] = { + 'total_score': data['data']['total_score'] ?? 0, + 'status': data['data']['status'] ?? 'Belum Selesai', + }; + }); + } else { + print('Failed to fetch quiz result'); + // Menampilkan nilai default jika gagal + setState(() { + quizResults[id_materi] = { + 'total_score': 0, + 'status': 'Belum Selesai', + }; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Quiz"), + automaticallyImplyLeading: false, + ), + body: Center( + child: isLoading + ? const CircularProgressIndicator() + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Display the materi list with score and status + for (var materi in materiList) + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: GestureDetector( + onTap: () { + // Ambil hasil quiz untuk materi yang dipilih + + context.go('/detail_quiz', extra: { + 'id': materi['id'], + 'title': materi['title'], + }); + }, + child: Center( + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + elevation: 5, // Add shadow for depth + child: Container( + width: 350, // Slightly narrower for a more compact feel + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + colors: [secondPrimaryColor, Colors.blue], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + materi['title'], + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: whiteColor, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox(height: 8), + Text( + materi['subtitle'], + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: whiteColor, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox(height: 16), + // Add score and quiz status inside each card + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Skor:", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: whiteColor, + ), + ), + Text( + "${quizResults[materi['id']]?['total_score'] ?? 0}", // Menampilkan score sesuai materi + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.greenAccent, + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Status:", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: whiteColor, + ), + ), + Text( + "${quizResults[materi['id']]?['status'] ?? 'Belum Selesai'}", // Menampilkan status sesuai materi + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: (quizResults[materi['id']]?['status'] == "Selesai") + ? Colors.greenAccent + : Colors.orangeAccent, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/view/pengajar/data_latihan/data_latihan.dart b/lib/view/pengajar/data_latihan/data_latihan.dart new file mode 100644 index 0000000..200fe48 --- /dev/null +++ b/lib/view/pengajar/data_latihan/data_latihan.dart @@ -0,0 +1,270 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:http/http.dart' as http; +import 'package:ta_tahsin/core/baseurl/base_url.dart'; +import 'package:ta_tahsin/core/theme.dart'; + +class DataLatihanPage extends StatefulWidget { + const DataLatihanPage({super.key}); + + @override + _DataLatihanPageState createState() => _DataLatihanPageState(); +} + +class _DataLatihanPageState extends State { + int _selectedIndex = 0; // To track the selected tab + late Future> kategoriData; + bool isLoading = true; + List materiList = []; + + // Define the content for each "tab" + final List _contentWidgets = [ + Center(child: Text('Konten Materi 1')), + Center(child: Text('Konten Materi 2')), + ]; + + // Function to fetch materi data when a tab is selected + Future _fetchMateri() async { + final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/materi')); + + if (response.statusCode == 200) { + final Map data = json.decode(response.body); + setState(() { + materiList = data['data']; + isLoading = false; + }); + } else { + throw Exception('Failed to load materi'); + } + } + + // Function to fetch kategori data based on the selected materi (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; + }); + return json.decode(response.body)['data']; // Fetch categories based on id_materi + } else { + throw Exception('Failed to load kategori'); + } + } + + // Function to fetch sub-materi data based on selected 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']; + } else { + throw Exception('Failed to load sub-materi'); + } + } + + // Tab selection callback to update the selected index and fetch relevant kategori data + void _onTabTapped(int index) { + setState(() { + _selectedIndex = index; + int idMateri = index == 0 ? 1 : 2; // 1 for "Makhrijul Huruf" and 2 for "Materi 2" + kategoriData = fetchKategoriData(idMateri); // Fetch kategori based on id_materi + }); + } + + @override + void initState() { + super.initState(); + kategoriData = fetchKategoriData(1); // Default to "Makhrijul Huruf" + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Data Latihan'), + automaticallyImplyLeading: false, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Button Bar with full width buttons + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Materi 1 Button + Expanded( + child: TextButton.icon( + onPressed: () { + _onTabTapped(0); + }, + label: const Text( + 'Makhrijul Huruf', + style: TextStyle( + color: Colors.white, + ), + ), + style: TextButton.styleFrom( + backgroundColor: secondPrimaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 7, + ), + textStyle: const TextStyle(fontSize: 16), + ), + ), + ), + const SizedBox(width: 10), + // Materi 2 Button + Expanded( + child: TextButton.icon( + onPressed: () { + _onTabTapped(1); + }, + label: const Text( + 'Sifatul Huruf', + style: TextStyle( + color: Colors.white, + ), + ), + style: TextButton.styleFrom( + backgroundColor: secondPrimaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 7, + ), + textStyle: const TextStyle(fontSize: 16), + ), + ), + ), + ], + ), + const SizedBox(height: 10,), + // FutureBuilder to fetch and display categories based on selected tab + Expanded( + child: FutureBuilder>( + future: kategoriData, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (!snapshot.hasData || snapshot.data == null) { + return Center(child: Text('Tidak ada data tersedia')); + } + + final kategoriList = snapshot.data!; + + return CustomScrollView( + slivers: [ + + // Iterate through categories and fetch corresponding sub-materi + for (var kategori in kategoriList) + FutureBuilder>( + future: fetchSubMateriData(kategori['id']), + builder: (context, subMateriSnapshot) { + if (subMateriSnapshot.connectionState == ConnectionState.waiting) { + return SliverToBoxAdapter(child: SizedBox()); + } else if (subMateriSnapshot.hasError) { + return SliverToBoxAdapter(child: Center(child: Text('Error: ${subMateriSnapshot.error}'))); + } else if (!subMateriSnapshot.hasData || subMateriSnapshot.data == null) { + return SliverToBoxAdapter(child: Center(child: Text('No sub-materi available'))); + } + + final subMateriList = subMateriSnapshot.data!; + + return SliverList( + delegate: SliverChildListDelegate( + [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0), + child: Text( + kategori['nama_kategori'], + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + + for (var submateri in subMateriList) + Padding( + padding: const EdgeInsets.only(bottom: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), + leading: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: secondPrimaryColor, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.menu_book, + color: whiteColor, + size: 24, + ), + ), + title: Row( + children: [ + Expanded( + child: Text( + submateri['title'], + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + ], + ), + subtitle: Text( + submateri['subtitle'], + style: const TextStyle(fontSize: 14, color: Colors.grey), + ), + onTap: () { + context.push( + '/detail_data_latihan', + extra: { + 'id': submateri['id'], + }, + ); + }, + ), + Divider( + color: Colors.grey.withOpacity(0.5), + thickness: 1, + indent: 80, + ), + ], + ), + ), + ], + ), + ); + }, + ), + ], + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/view/pengajar/data_latihan/detail_data_latihan.dart b/lib/view/pengajar/data_latihan/detail_data_latihan.dart new file mode 100644 index 0000000..45b2c29 --- /dev/null +++ b/lib/view/pengajar/data_latihan/detail_data_latihan.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class DetailDataLatihanPage extends StatefulWidget { + final int id; + const DetailDataLatihanPage({super.key,required this.id}); + + @override + _DetailDataLatihanPageState createState() => _DetailDataLatihanPageState(); +} + +class _DetailDataLatihanPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Detail Data Latihan'), + automaticallyImplyLeading: false, + ), + body: Center( + child: Text( + 'Halaman Detail Data Latihan', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + ); + } +} diff --git a/lib/view/pengajar/data_santri/data_santri.dart b/lib/view/pengajar/data_santri/data_santri.dart index 557a99d..322ab8b 100644 --- a/lib/view/pengajar/data_santri/data_santri.dart +++ b/lib/view/pengajar/data_santri/data_santri.dart @@ -1,11 +1,243 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; +import 'package:excel/excel.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:ta_tahsin/core/baseurl/base_url.dart'; import 'package:ta_tahsin/core/router/route.dart'; import 'package:ta_tahsin/core/theme.dart'; -import 'package:ta_tahsin/view/pengajar/data_santri/model/model_data_santri.dart'; +import 'package:file_picker/file_picker.dart'; -class DataSantriPage extends StatelessWidget { +class DataSantriPage extends StatefulWidget { const DataSantriPage({super.key}); + @override + _DataSantriPageState createState() => _DataSantriPageState(); +} + +class _DataSantriPageState extends State { + List santriList = []; + List filteredSantriList = []; + String searchQuery = ""; + bool isLoading = true; + File? file; + + @override + void initState() { + super.initState(); + fetchSantriData(); + } + + Future fetchSantriData() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? authToken = prefs.getString('token'); + debugPrint("Token yang diambil: $authToken"); + + if (authToken == null) { + print("Token tidak ditemukan!"); + return; + } + + final response = await http.get( + Uri.parse('${BaseUrl.baseUrl}/users/santri'), + headers: { + 'Authorization': 'Bearer $authToken', + }, + ); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + setState(() { + santriList = data['data']; + filteredSantriList = santriList; + isLoading = false; + }); + } else { + throw Exception('Gagal memuat data santri'); + } + } + + + void filterSantri(String query) { + final filtered = santriList.where((santri) { + final nama = santri['nama_lengkap'].toLowerCase(); + final search = query.toLowerCase(); + return nama.contains(search); + }).toList(); + + setState(() { + filteredSantriList = filtered; + }); + } + + + + // // Function to handle the file import + // void onTapImportFile() async { + // List fileExt = ["xls", "xlsx"]; + // FilePickerResult? result = await FilePicker.platform.pickFiles( + // allowedExtensions: fileExt, + // type: FileType.custom, + // ); + + // try { + // if (result != null) { + // File file = File(result.files.single.path!); + // var bytes = await file.readAsBytes(); + + // var excel = Excel.decodeBytes(bytes); + // var sheet = excel.tables[excel.tables.keys.first]; + // if (sheet != null) { + // List> santriData = []; + + // // Loop through rows and create the data + // for (var row in sheet.rows) { + // if (row.isNotEmpty) { + // Map santri = { + // 'nama_lengkap': row[0]?.toString(), + // 'alamat': row[1]?.toString(), + // 'usia': row[2]?.toString(), + // 'no_telp_wali': row[3]?.toString(), + // 'email': row[4]?.toString(), + // 'jenis_kelamin': row[5]?.toString(), + // 'jenjang_pendidikan': row[6]?.toString(), + // }; + // santriData.add(santri); + // } + // } + // // Call API to send this data + // // await sendSantriData(santriData); + // } + // } + // } catch (e) { + // print(e.toString()); + // } + // } + +// Future sendSantriData(List> santriData) async { +// SharedPreferences prefs = await SharedPreferences.getInstance(); +// String? authToken = prefs.getString('token'); + +// if (authToken == null) { +// print("Token tidak ditemukan!"); // Debugging log +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar(content: Text('Token tidak ditemukan!')), +// ); +// return; +// } + +// setState(() { +// isLoading = true; +// }); + +// try { +// final response = await http.post( +// Uri.parse('${BaseUrl.baseUrl}/importSantri'), +// headers: { +// 'Authorization': 'Bearer $authToken', +// 'Content-Type': 'application/json', +// }, +// body: jsonEncode({'santri': santriData}), +// ); + +// setState(() { +// isLoading = false; +// }); + +// if (response.statusCode == 200) { +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar(content: Text('Santri data imported successfully')), +// ); +// } else { +// var errorData = jsonDecode(response.body); +// print("Error data: ${errorData.toString()}"); // Debugging log + +// String errorMessage = errorData['meta']['message'] ?? 'Failed to import data'; + +// if (errorData['meta']['status'] == 'error') { +// String validationErrors = ''; +// if (errorData['data'] != null) { +// var errors = errorData['data']; +// validationErrors = errors.toString(); +// } + +// // Show specific error message in SnackBar +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar(content: Text('Error: $errorMessage $validationErrors')), +// ); +// } +// } +// } catch (e) { +// setState(() { +// isLoading = false; +// }); + +// // Handle any other exception (e.g., network error) +// print("Exception: $e"); // Debugging log +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar(content: Text('Failed to import data: $e')), +// ); +// } +// } + + + // Send the parsed Santri data to the API + // Future sendSantriData(List> santriData) async { + // SharedPreferences prefs = await SharedPreferences.getInstance(); + // String? authToken = prefs.getString('token'); + + // if (authToken == null) { + // print("Token tidak ditemukan!"); + // return; + // } + + // setState(() { + // isLoading = true; + // }); + + // final response = await http.post( + // Uri.parse('${BaseUrl.baseUrl}/importSantri'), + // headers: { + // 'Authorization': 'Bearer $authToken', + // }, + // body: jsonEncode({'santri': santriData}), + // ); + + // setState(() { + // isLoading = false; + // }); + + // if (response.statusCode == 200) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text('Santri data imported successfully')), + // ); + // } else { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text('Failed to import data')), + // ); + // } + // } + +// void onTapImportFile() async { +// List fileExt = ["xls", "xlsx"]; +// FilePickerResult? result = await FilePicker.platform.pickFiles( +// allowedExtensions: fileExt, +// type: FileType.custom, +// ); +// try { +// if (result != null) { +// file = File(result.files.single.path!); +// setState(() {}); +// } +// } catch (e) { +// log(e.toString() as num); +// } +// } + + @override Widget build(BuildContext context) { return Scaffold( @@ -18,8 +250,14 @@ class DataSantriPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Search bar + TextField( + onChanged: (query) { + setState(() { + searchQuery = query; + filterSantri(searchQuery); + }); + }, decoration: InputDecoration( prefixIcon: const Icon(Icons.search), hintText: 'Cari Santri...', @@ -31,7 +269,6 @@ class DataSantriPage extends StatelessWidget { ), ), const SizedBox(height: 20), - // Tombol + untuk menambah data santri Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -40,22 +277,20 @@ class DataSantriPage extends StatelessWidget { children: [ TextButton.icon( onPressed: () { - // Tindakan ketika tombol + diklik - print("Menambahkan data santri..."); + context.go('/tambah_santri'); }, icon: const Icon( Icons.add, color: Colors.white, - ), // Ikon dengan warna putih + ), label: const Text( 'Tambah', style: TextStyle( color: Colors.white, - ), // Teks dengan warna putih + ), ), style: TextButton.styleFrom( - backgroundColor: - secondPrimaryColor, // Warna latar belakang tombol + backgroundColor: secondPrimaryColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -67,41 +302,37 @@ class DataSantriPage extends StatelessWidget { ), ), const SizedBox(width: 10), - TextButton.icon( - onPressed: () { - // Tindakan untuk impor - }, - icon: const Icon( - Icons.import_export, - color: Colors.white, - ), // Ikon dengan warna putih - label: const Text( - 'Import', - style: TextStyle( - color: Colors.white, - ), // Teks dengan warna putih - ), - style: TextButton.styleFrom( - backgroundColor: - secondPrimaryColor, // Warna latar belakang tombol - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - textStyle: const TextStyle(fontSize: 16), - ), - ), + // TextButton.icon( + // onPressed: () { + // onTapImportFile(); + // }, + // icon: const Icon( + // Icons.import_export, + // color: Colors.white, + // ), + // label: const Text( + // 'Import', + // style: TextStyle( + // color: Colors.white, + // ), + // ), + // style: TextButton.styleFrom( + // backgroundColor: secondPrimaryColor, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(12), + // ), + // padding: const EdgeInsets.symmetric( + // horizontal: 16, + // vertical: 12, + // ), + // textStyle: const TextStyle(fontSize: 16), + // ), + // ), ], ), ], ), - const SizedBox(height: 20), - - // Judul Pilih Santri Text( 'Pilih Santri', style: TextStyle( @@ -111,83 +342,85 @@ class DataSantriPage extends StatelessWidget { ), ), const SizedBox(height: 10), - - // Menggunakan SliverList dengan santriList Expanded( - child: CustomScrollView( - slivers: [ - SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - var santri = santriList[index]; + child: isLoading + ? Center(child: CircularProgressIndicator()) + : filteredSantriList.isEmpty + ? Center(child: Text('Data tidak ditemukan')) + : CustomScrollView( + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + var santri = filteredSantriList[index]; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - contentPadding: const EdgeInsets.symmetric( - vertical: 5.0, - ), - leading: Container( - width: 60, - height: 60, - decoration: BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - image: AssetImage( - 'assets/icon/${santri['image']}', - ), - fit: BoxFit.cover, - ), - ), - ), - title: Row( - children: [ - Expanded( - child: Text( - santri['nama'], - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric(vertical: 5.0), + leading: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: AssetImage( + 'assets/icon/defaultprofile.jpeg', + ), + fit: BoxFit.cover, + ), + ), + ), + title: Row( + children: [ + Expanded( + child: Text( + santri['nama_lengkap'], + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + ], + ), + subtitle: Text( + santri['email'], + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + onTap: () { + context.go('/detail_user', extra: { + 'id': santri['id'], + }); + // router.push("/detail_user"); + }, ), - ), + Divider( + color: Colors.grey.withOpacity(0.5), + thickness: 1, + indent: 80, + ), + ], ), - ], - ), - subtitle: Text( - santri['jilid'], - style: const TextStyle( - fontSize: 14, - color: Colors.grey, ), - ), - onTap: () { - // context.go('/detail_kemajuan', extra: {'nama': santri['nama']}); - router.push("/detail_user"); - }, + ], ), - Divider( - color: Colors.grey.withOpacity(0.5), - thickness: 1, - indent: 80, - ), - ], - ), + ); + }, childCount: filteredSantriList.length), ), ], ), - ); - }, childCount: santriList.length), - ), - ], - ), ), ], ), diff --git a/lib/view/pengajar/data_santri/detail_data_santri.dart b/lib/view/pengajar/data_santri/detail_data_santri.dart index 5e3e21c..ec687cb 100644 --- a/lib/view/pengajar/data_santri/detail_data_santri.dart +++ b/lib/view/pengajar/data_santri/detail_data_santri.dart @@ -1,20 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:ta_tahsin/core/baseurl/base_url.dart'; import 'package:ta_tahsin/core/theme.dart'; - -void main() { - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - home: DetailDataSantriPage(), - ); - } -} +import 'package:http/http.dart' as http; +import 'dart:convert'; class DetailDataSantriPage extends StatefulWidget { + final int id; + const DetailDataSantriPage({super.key, required this.id}); + @override _DetailDataSantriPageState createState() => _DetailDataSantriPageState(); } @@ -22,12 +17,129 @@ class DetailDataSantriPage extends StatefulWidget { class _DetailDataSantriPageState extends State { final _formKey = GlobalKey(); final TextEditingController _fullNameController = TextEditingController(); + final TextEditingController _addressController = TextEditingController(); final TextEditingController _dobController = TextEditingController(); final TextEditingController _phoneController = TextEditingController(); final TextEditingController _emailController = TextEditingController(); - final TextEditingController _weightController = TextEditingController(); - final TextEditingController _heightController = TextEditingController(); + final TextEditingController _jenjangPendidikanController = TextEditingController(); String _gender = 'Laki-laki'; + bool _isEditing = false; // Track whether we are in edit mode + + @override + void initState() { + super.initState(); + // Panggil API untuk mengambil data pengguna berdasarkan ID + _fetchUserData(); + } + + // Fungsi untuk mengambil data pengguna berdasarkan ID dengan token dari SharedPreferences + Future _fetchUserData() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? authToken = prefs.getString('token'); + + if (authToken == null) { + print("Token tidak ditemukan!"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Token tidak ditemukan!')), + ); + return; + } + + final response = await http.get( + Uri.parse('${BaseUrl.baseUrl}/user/${widget.id}'), + headers: { + 'Authorization': 'Bearer $authToken', + }, + ); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data']; + + setState(() { + _fullNameController.text = data['nama_lengkap']; + _addressController.text = data['alamat']; + _dobController.text = data['usia'].toString(); + _phoneController.text = data['no_telp_wali']; + _emailController.text = data['email']; + _jenjangPendidikanController.text = data['jenjang_pendidikan']; + _gender = data['jenis_kelamin']; + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Gagal memuat data pengguna')), + ); + } + } + + // API function to update user data + Future _updateUserData() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? authToken = prefs.getString('token'); + + if (authToken == null) { + print("Token tidak ditemukan!"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Token tidak ditemukan!')), + ); + return; + } + + final response = await http.put( + Uri.parse('${BaseUrl.baseUrl}/updateUser/${widget.id}'), + headers: { + 'Authorization': 'Bearer $authToken', + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'nama_lengkap': _fullNameController.text, + 'alamat': _addressController.text, + 'usia': _dobController.text, + 'no_telp_wali': _phoneController.text, + 'email': _emailController.text, + 'jenjang_pendidikan': _jenjangPendidikanController.text, + 'jenis_kelamin': _gender, + }), + ); + + if (response.statusCode == 200) { + // Show the success dialog + _showSuccessDialog(); + } else { + // Handle error if the API call fails + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to update user')), + ); + } + } + + // Show a success dialog after user data is updated successfully + void _showSuccessDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Berhasil'), + content: Text('Data santri berhasil diubah.'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () { + setState(() { + _isEditing = false; +}); + + Navigator.of(context).pop(); + // context.go('/navigasiPengajar'); + // context.go('/detail_user', extra: { + // 'id': widget.id, + // }); + }, + ), + ], + ); + }, + ); + } @override Widget build(BuildContext context) { @@ -37,17 +149,26 @@ class _DetailDataSantriPageState extends State { leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () { - Navigator.pop(context); + context.go('/navigasiPengajar'); }, ), + actions: [ + IconButton( + icon: Icon(Icons.edit_note, size: 35), + onPressed: () { + setState(() { + _isEditing = !_isEditing; // Toggle edit mode + }); + }, + ), + ], ), - body: SingleChildScrollView( // Add this to make the screen scrollable + body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Profile picture section Center( child: CircleAvatar( radius: 60, @@ -56,8 +177,6 @@ class _DetailDataSantriPageState extends State { ), ), SizedBox(height: 20), - - // Title section Container( width: double.infinity, padding: EdgeInsets.all(10), @@ -72,41 +191,40 @@ class _DetailDataSantriPageState extends State { ), ), SizedBox(height: 16), - - // Basic details section - _buildTextFormField(_fullNameController, 'Nama Lengkap'), - _buildTextFormField(_dobController, 'Usia'), - _buildTextFormField(_dobController, 'Jilid Tilawati'), - - // Gender selection section + _buildTextFormField(_fullNameController, 'Nama Lengkap', _isEditing), + _buildTextFormField(_addressController, 'Alamat', _isEditing), + _buildTextFormField(_dobController, 'Usia', _isEditing), + _buildTextFormField(_jenjangPendidikanController, 'Jenjang Pendidikan', _isEditing), Row( children: [ Text('Jenis Kelamin', style: TextStyle(fontSize: 16)), Radio( value: 'Laki-laki', groupValue: _gender, - onChanged: (value) { - setState(() { - _gender = value!; - }); - }, + onChanged: _isEditing + ? (value) { + setState(() { + _gender = value!; + }); + } + : null, ), Text('Laki-laki'), Radio( value: 'Perempuan', groupValue: _gender, - onChanged: (value) { - setState(() { - _gender = value!; - }); - }, + onChanged: _isEditing + ? (value) { + setState(() { + _gender = value!; + }); + } + : null, ), Text('Perempuan'), ], ), SizedBox(height: 20), - - // Contact details section Container( width: double.infinity, padding: EdgeInsets.all(10), @@ -121,50 +239,26 @@ class _DetailDataSantriPageState extends State { ), ), SizedBox(height: 16), - - _buildTextFormField(_phoneController, 'No WA Wali'), - _buildTextFormField(_emailController, 'Email'), + _buildTextFormField(_phoneController, 'No WA Wali', _isEditing), + _buildTextFormField(_emailController, 'Email', _isEditing), SizedBox(height: 20), - - // Personal details section - Container( - width: double.infinity, - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - color: secondPrimaryColor, - borderRadius: BorderRadius.circular(12), - boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 10, offset: Offset(0, 4))], - ), - child: Text( - 'Detail Pribadi', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: whiteColor), - ), - ), - SizedBox(height: 16), - - _buildTextFormField(_weightController, 'Berat Badan (kg)'), - _buildTextFormField(_heightController, 'Tinggi Badan (cm)'), - SizedBox(height: 40), - - // Save button section with shadow effect Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20), child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: secondPrimaryColor, // Customize your color + backgroundColor: _isEditing ? secondPrimaryColor : Colors.grey, // Button color based on editing shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), minimumSize: Size(double.infinity, 50), elevation: 5, // Adding shadow to the button ), - onPressed: () { - if (_formKey.currentState!.validate()) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Profil tersimpan')), - ); - } - }, + onPressed: _isEditing + ? () { + // Trigger the update user function when editing + _updateUserData(); + } + : null, child: Text( "Simpan", style: TextStyle(fontSize: 16, color: whiteColor), @@ -178,7 +272,8 @@ class _DetailDataSantriPageState extends State { ); } - Widget _buildTextFormField(TextEditingController controller, String labelText) { + // Helper function to build TextFormField + Widget _buildTextFormField(TextEditingController controller, String labelText, bool isEnabled) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -186,6 +281,7 @@ class _DetailDataSantriPageState extends State { SizedBox(height: 8), TextFormField( controller: controller, + enabled: isEnabled, // Control whether the field is enabled or not decoration: InputDecoration( filled: true, fillColor: Colors.white, diff --git a/lib/view/pengajar/data_santri/tambah_santri.dart b/lib/view/pengajar/data_santri/tambah_santri.dart index e69de29..bc3959c 100644 --- a/lib/view/pengajar/data_santri/tambah_santri.dart +++ b/lib/view/pengajar/data_santri/tambah_santri.dart @@ -0,0 +1,221 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:ta_tahsin/core/baseurl/base_url.dart'; +import 'package:ta_tahsin/core/theme.dart'; + +class TambahSantriPage extends StatefulWidget { + @override + _TambahSantriPageState createState() => _TambahSantriPageState(); +} + +class _TambahSantriPageState extends State { + final _formKey = GlobalKey(); + final TextEditingController _fullNameController = TextEditingController(); + final TextEditingController _addressController = TextEditingController(); + final TextEditingController _dobController = TextEditingController(); + final TextEditingController _phoneController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _jenjangPendidikanController = TextEditingController(); + String _gender = 'Laki-laki'; + + Future _submitForm() async { + if (_formKey.currentState?.validate() ?? false) { + print("Validation Passed"); + + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) => Center(child: CircularProgressIndicator()), + ); + + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? authToken = prefs.getString('token'); + debugPrint("Token yang diambil: $authToken"); + + if (authToken == null) { + Navigator.of(context).pop(); + print("Token tidak ditemukan!"); + return; + } + + final Map data = { + 'nama_lengkap': _fullNameController.text, + 'alamat': _addressController.text, + 'usia': _dobController.text, + 'no_telp_wali': _phoneController.text, + 'email': _emailController.text, + 'jenis_kelamin': _gender, + 'jenjang_pendidikan': _jenjangPendidikanController.text, + }; + + final response = await http.post( + Uri.parse('${BaseUrl.baseUrl}/tambahSantri'), + headers: { + 'Authorization': 'Bearer $authToken', + 'Content-Type': 'application/json', + }, + body: json.encode(data), + ); + + Navigator.of(context).pop(); + + print("API Response Status: ${response.statusCode}"); + + if (response.statusCode == 200) { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text('Sukses'), + content: Text('Santri berhasil ditambahkan'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + context.go('/navigasiPengajar'); + }, + child: Text('OK'), + ), + ], + ), + ); + } else { + print("Failed to submit: ${response.body}"); + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text('Gagal'), + content: Text('Terjadi kesalahan, silakan coba lagi.'), + actions: [ + TextButton( + onPressed: () { + context.go('/navigasiPengajar'); + }, + child: Text('OK'), + ), + ], + ), + ); + } + } else { + print("Validation Failed"); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Tambah Santri'), + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + context.go('/navigasiPengajar'); + }, + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + children: [ + _buildTextFormField(_fullNameController, 'Nama Lengkap'), + _buildTextFormField(_addressController, 'Alamat'), + _buildTextFormField(_dobController, 'Usia'), + _buildTextFormField(_jenjangPendidikanController, 'Jenjang Pendidikan'), + Row( + children: [ + Text('Jenis Kelamin', style: TextStyle(fontSize: 16)), + Radio( + value: 'Laki-laki', + groupValue: _gender, + onChanged: (value) { + setState(() { + _gender = value!; + }); + }, + ), + Text('Laki-laki'), + Radio( + value: 'Perempuan', + groupValue: _gender, + onChanged: (value) { + setState(() { + _gender = value!; + }); + }, + ), + Text('Perempuan'), + ], + ), + SizedBox(height: 20), + _buildTextFormField(_phoneController, 'No WA Wali'), + _buildTextFormField(_emailController, 'Email'), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: secondPrimaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + minimumSize: Size(double.infinity, 50), + elevation: 5, + ), + onPressed: () async { + print("Simpan Taped"); + await _submitForm(); + }, + child: Text( + "Simpan", + style: TextStyle(fontSize: 16, color: whiteColor), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + + // Fungsi pembuat TextFormField + Widget _buildTextFormField(TextEditingController controller, String labelText) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), + SizedBox(height: 8), + TextFormField( + controller: controller, + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.blue), + borderRadius: BorderRadius.circular(8), + ), + contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Harap masukkan $labelText Anda'; + } + return null; + }, + ), + SizedBox(height: 20), + ], + ); + } +} diff --git a/lib/view/pengajar/kemajuan/detail_kemajuan.dart b/lib/view/pengajar/kemajuan/detail_kemajuan.dart index cc34ad7..bc283ce 100644 --- a/lib/view/pengajar/kemajuan/detail_kemajuan.dart +++ b/lib/view/pengajar/kemajuan/detail_kemajuan.dart @@ -12,7 +12,7 @@ final String nama; Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Kemajuan'), + title: const Text('Detail Kemajuan'), leading: IconButton( icon: const Icon(Icons.arrow_back),color: blackColor, onPressed: () { diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index cc10c4d..6867a3e 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,12 +7,16 @@ #include "generated_plugin_registrant.h" #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); + g_autoptr(FlPluginRegistrar) record_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); + record_linux_plugin_register_with_registrar(record_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 8e2a190..8109724 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux + record_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 0641494..1b653e4 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,16 +6,20 @@ import FlutterMacOS import Foundation import audioplayers_darwin +import file_picker import flutter_inappwebview_macos import path_provider_foundation +import record_macos import shared_preferences_foundation import url_launcher_macos import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index acc4eba..09b0a2f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" async: dependency: transitive description: @@ -97,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -121,6 +137,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + excel: + dependency: "direct main" + description: + name: excel + sha256: "1a15327dcad260d5db21d1f6e04f04838109b39a2f6a84ea486ceda36e468780" + url: "https://pub.dev" + source: hosted + version: "4.0.6" fake_async: dependency: transitive description: @@ -145,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "77f8e81d22d2a07d0dee2c62e1dda71dc1da73bf43bb2d45af09727406167964" + url: "https://pub.dev" + source: hosted + version: "10.1.9" fixnum: dependency: transitive description: @@ -246,6 +286,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" flutter_screenutil: dependency: "direct main" description: @@ -385,7 +433,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" @@ -432,6 +480,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f" + url: "https://pub.dev" + source: hosted + version: "12.0.0+1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.dev" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" platform: dependency: transitive description: @@ -448,6 +552,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + record: + dependency: "direct main" + description: + name: record + sha256: daeb3f9b3fea9797094433fe6e49a879d8e4ca4207740bc6dc7e4a58764f0817 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + record_android: + dependency: transitive + description: + name: record_android + sha256: "97d7122455f30de89a01c6c244c839085be6b12abca251fc0e78f67fed73628b" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + record_ios: + dependency: transitive + description: + name: record_ios + sha256: "73706ebbece6150654c9d6f57897cf9b622c581148304132ba85dba15df0fdfb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + record_linux: + dependency: transitive + description: + name: record_linux + sha256: "29e7735b05c1944bb6c9b72a36c08d4a1b24117e712d6a9523c003bde12bf484" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + record_macos: + dependency: transitive + description: + name: record_macos + sha256: "02240833fde16c33fcf2c589f3e08d4394b704761b4a3bb609d872ff3043fbbd" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + record_platform_interface: + dependency: transitive + description: + name: record_platform_interface + sha256: "8a575828733d4c3cb5983c914696f40db8667eab3538d4c41c50cbb79e722ef4" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + record_web: + dependency: transitive + description: + name: record_web + sha256: f8e536a9c927e52f95326d7540898457eaeefbe0b21a84d3cb3d2d7d4645e8cb + url: "https://pub.dev" + source: hosted + version: "1.1.7" + record_windows: + dependency: transitive + description: + name: record_windows + sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99" + url: "https://pub.dev" + source: hosted + version: "1.0.6" shared_preferences: dependency: "direct main" description: @@ -717,6 +885,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + url: "https://pub.dev" + source: hosted + version: "5.13.0" xdg_directories: dependency: transitive description: @@ -725,6 +901,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" youtube_player_flutter: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 7934bdf..303f476 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,11 @@ dependencies: flame_audio: ^2.11.5 shared_preferences: ^2.5.3 http: ^1.4.0 + path_provider: ^2.1.5 + permission_handler: ^12.0.0+1 + file_picker: ^10.1.9 + excel: ^4.0.6 + record: ^6.0.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 90bd64a..b3f2c19 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,8 @@ #include #include +#include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -15,6 +17,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + RecordWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index fea6638..b7c9cc9 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows flutter_inappwebview_windows + permission_handler_windows + record_windows url_launcher_windows )