feat: add fitur quiz,memperbaiki voice record & integrate api get santri

This commit is contained in:
vionar3 2025-06-03 01:24:38 +07:00
parent b2bde219ae
commit e0b5c274b3
25 changed files with 2129 additions and 261 deletions

View File

@ -24,7 +24,7 @@ android {
applicationId = "com.example.ta_tahsin" applicationId = "com.example.ta_tahsin"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = 24
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName

View File

@ -1,4 +1,4 @@
// lib/config.dart // lib/config.dart
class BaseUrl { class BaseUrl {
static const String baseUrl = 'http://192.168.100.13:8000/api'; static const String baseUrl = 'http://192.168.0.102:8000/api';
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ta_tahsin/core/theme.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/belajar/belajar.dart';
import '../../view/home/profile/profile.dart'; import '../../view/home/profile/profile.dart';
@ -20,7 +21,7 @@ class _NavigationPageState extends State<NavigationPage> {
final List<Widget> _pages = [ final List<Widget> _pages = [
const BelajarPage(), const BelajarPage(),
// const UjianPage(), const QuizPage(),
const ProfilePage(), const ProfilePage(),
]; ];
@ -44,10 +45,10 @@ class _NavigationPageState extends State<NavigationPage> {
icon: Icon(Icons.menu_book), icon: Icon(Icons.menu_book),
label: "Belajar", label: "Belajar",
), ),
// BottomNavigationBarItem( BottomNavigationBarItem(
// icon: Icon(Icons.list_alt), icon: Icon(Icons.quiz),
// label: "Ujian", label: "Quiz",
// ), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.person), icon: Icon(Icons.person),
label: "Profile", label: "Profile",

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ta_tahsin/core/theme.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/data_santri/data_santri.dart';
import '../../view/pengajar/kemajuan/kemajuan.dart'; import '../../view/pengajar/kemajuan/kemajuan.dart';
@ -23,6 +24,7 @@ class _NavigationPengajarPageState extends State<NavigationPengajarPage> {
final List<Widget> _pages = [ final List<Widget> _pages = [
const KemajuanPage(), const KemajuanPage(),
const DataSantriPage(), const DataSantriPage(),
const DataLatihanPage(),
const PengajarProfilePage(), const PengajarProfilePage(),
]; ];
@ -50,6 +52,10 @@ class _NavigationPengajarPageState extends State<NavigationPengajarPage> {
icon: Icon(Icons.list_alt), icon: Icon(Icons.list_alt),
label: "Data Santri", label: "Data Santri",
), ),
BottomNavigationBarItem(
icon: Icon(Icons.list_alt),
label: "Data Latihan",
),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.person), icon: Icon(Icons.person),
label: "Profile", label: "Profile",

View File

@ -2,7 +2,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:ta_tahsin/view/home/latihan/latihan.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/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 'package:ta_tahsin/view/pengajar/kemajuan/detail_kemajuan.dart';
import '../../view/auth/login/login.dart'; import '../../view/auth/login/login.dart';
@ -72,11 +76,13 @@ GoRoute(
final Map<String, dynamic> extra = state.extra as Map<String, dynamic>; final Map<String, dynamic> extra = state.extra as Map<String, dynamic>;
final int currentStep = extra['currentStep']; final int currentStep = extra['currentStep'];
final List<dynamic> latihanData = extra['latihanData']; final List<dynamic> latihanData = extra['latihanData'];
final String recordedFilePath = extra['recordedFilePath'];
return PelafalanPage( return PelafalanPage(
id: extra['id'], id: extra['id'],
currentStep: currentStep, currentStep: currentStep,
latihanData: latihanData, latihanData: latihanData,
recordedFilePath: recordedFilePath,
); );
}, },
), ),
@ -104,7 +110,47 @@ GoRoute(
), ),
GoRoute( GoRoute(
path: '/detail_user', path: '/detail_user',
builder: (context, state) => DetailDataSantriPage(), builder: (BuildContext context, GoRouterState state) {
final Map<String, dynamic> extra = state.extra as Map<String, dynamic>;
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<String, dynamic> extra = state.extra as Map<String, dynamic>;
return DetailDataLatihanPage(
id: extra['id'],
);
},
),
GoRoute(
path: '/detail_quiz',
builder: (BuildContext context, GoRouterState state) {
final Map<String, dynamic> extra = state.extra as Map<String, dynamic>;
return DetailQuizPage(
id: extra['id'],
title: extra['title'],
);
},
),
GoRoute(
path: '/hasil_quiz',
builder: (BuildContext context, GoRouterState state) {
final Map<String, dynamic> extra = state.extra as Map<String, dynamic>;
return HasilQuizPage(
totalScore: extra['totalScore'],
title: extra['title'],
);
},
), ),
], ],
); );

View File

@ -46,6 +46,7 @@ class _LoginPageState extends State<LoginPage> {
String peran = data['data']['user']['peran']; String peran = data['data']['user']['peran'];
prefs.setString('peran', peran);
if (peran == 'santri') { if (peran == 'santri') {
@ -54,7 +55,7 @@ class _LoginPageState extends State<LoginPage> {
router.push("/navigasiPengajar"); router.push("/navigasiPengajar");
} }
} else { } else {
debugPrint("anjing"); // debugPrint("anjing");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login Failed: ${response.body}')), SnackBar(content: Text('Login Failed: ${response.body}')),
@ -91,7 +92,7 @@ class _LoginPageState extends State<LoginPage> {
Center(child: Image.asset('assets/logo/sho.jpg', height: 180)), Center(child: Image.asset('assets/logo/sho.jpg', height: 180)),
const SizedBox(height: 20), const SizedBox(height: 20),
const Text( const Text(
"No Telp", "Email",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
), ),
const SizedBox(height: 5), const SizedBox(height: 5),

View File

@ -1,5 +1,10 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.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/baseurl/base_url.dart';
import 'package:ta_tahsin/core/theme.dart'; import 'package:ta_tahsin/core/theme.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -18,6 +23,7 @@ class LatihanPage extends StatefulWidget {
} }
class _LatihanPageState extends State<LatihanPage> { class _LatihanPageState extends State<LatihanPage> {
final _record = AudioRecorder();
late Future<List<dynamic>> latihanData; late Future<List<dynamic>> latihanData;
bool isRecording = false; bool isRecording = false;
int timer = 10; int timer = 10;
@ -25,6 +31,7 @@ class _LatihanPageState extends State<LatihanPage> {
String timerText = "10"; String timerText = "10";
final AudioPlayer _audioPlayer = AudioPlayer(); final AudioPlayer _audioPlayer = AudioPlayer();
bool isAudioPlaying = false; bool isAudioPlaying = false;
String? recordedFilePath;
@override @override
void initState() { void initState() {
@ -54,6 +61,7 @@ class _LatihanPageState extends State<LatihanPage> {
} }
// Fungsi untuk memulai timer dan mulai merekam
void startTimer() { void startTimer() {
setState(() { setState(() {
isRecording = true; isRecording = true;
@ -69,16 +77,42 @@ class _LatihanPageState extends State<LatihanPage> {
}); });
} else { } else {
countdownTimer.cancel(); countdownTimer.cancel();
stopRecording();
}
});
// Mulai merekam
_startRecording();
}
Future<void> _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(() { setState(() {
isRecording = false; recordedFilePath = path;
}); });
} }
}
// Fungsi untuk menghentikan perekaman
Future<void> stopRecording() async {
await _record.stop();
setState(() {
isRecording = false;
}); });
} }
void stopTimer() { void stopTimer() {
countdownTimer.cancel(); countdownTimer.cancel();
stopRecording();
setState(() { setState(() {
isRecording = false; isRecording = false;
timer = 10; timer = 10;
@ -211,22 +245,22 @@ class _LatihanPageState extends State<LatihanPage> {
), ),
child: Column( child: Column(
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.only(bottom: 40.0), padding: EdgeInsets.only(bottom: 40.0),
child: Text( child: Text(
"Ucapkan potongan kata ini", "Ucapkan potongan kata ini",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black, color: secondPrimaryColor,
), ),
), ),
), ),
Text( Text(
latihan['potongan_ayat'], latihan['potongan_ayat'],
style: const TextStyle( style: TextStyle(
fontSize: 30, fontSize: 19,
color: Colors.red, color: blackColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -274,6 +308,7 @@ class _LatihanPageState extends State<LatihanPage> {
'id': widget.id, 'id': widget.id,
'currentStep': widget.currentStep, 'currentStep': widget.currentStep,
'latihanData': snapshot.data, 'latihanData': snapshot.data,
'recordedFilePath': recordedFilePath,
}, },
); );
} }
@ -304,6 +339,7 @@ class _LatihanPageState extends State<LatihanPage> {
@override @override
void dispose() { void dispose() {
_record.dispose();
_audioPlayer.dispose(); _audioPlayer.dispose();
super.dispose(); super.dispose();
} }

View File

@ -1,26 +1,72 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:ta_tahsin/core/theme.dart'; import 'package:ta_tahsin/core/theme.dart';
class PelafalanPage extends StatelessWidget { class PelafalanPage extends StatefulWidget {
final int id; final int id;
final int currentStep; final int currentStep;
final List<dynamic> latihanData; final List<dynamic> latihanData;
final String recordedFilePath;
const PelafalanPage({ const PelafalanPage({
super.key, super.key,
required this.id, required this.id,
required this.currentStep, required this.currentStep,
required this.latihanData, required this.latihanData,
required this.recordedFilePath,
}); });
@override
_PelafalanPageState createState() => _PelafalanPageState();
}
class _PelafalanPageState extends State<PelafalanPage> {
late String recordedFilePath;
late int currentStep;
late List<dynamic> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final latihan = latihanData[currentStep];
return Scaffold( return Scaffold(
body: Center( body: Center(
child: Card( child: Card(
@ -57,9 +103,9 @@ class PelafalanPage extends StatelessWidget {
Center( Center(
child: Text( child: Text(
latihan['potongan_ayat'], latihan['potongan_ayat'],
style: const TextStyle( style: TextStyle(
fontSize: 30, fontSize: 30,
color: Colors.red, color: blackColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -69,7 +115,12 @@ class PelafalanPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Container( GestureDetector(
onTap: () {
playRecordedAudio();
print("Pelafalan Kamu tapped!");
},
child: Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -88,14 +139,20 @@ class PelafalanPage extends StatelessWidget {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Icon( Icon(
Icons.volume_up, isAudioRecordPlaying ? Icons.volume_up : Icons.volume_down,
color: secondPrimaryColor, color: secondPrimaryColor,
size: 30, size: 30,
), ),
], ],
), ),
), ),
Container( ),
GestureDetector(
onTap: () {
print("Pelafalan Ustadz tapped!");
playAudio('audio/${latihan['correct_audio']}');
},
child: Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -114,13 +171,14 @@ class PelafalanPage extends StatelessWidget {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Icon( Icon(
Icons.volume_up, isAudioPlaying ? Icons.volume_up : Icons.volume_down,
color: secondPrimaryColor, color: secondPrimaryColor,
size: 30, size: 30,
), ),
], ],
), ),
), ),
),
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -137,7 +195,7 @@ class PelafalanPage extends StatelessWidget {
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
// Logic untuk melihat video
}, },
child: Text( child: Text(
'Lihat Video', 'Lihat Video',
@ -162,11 +220,14 @@ class PelafalanPage extends StatelessWidget {
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
if (currentStep < latihanData.length - 1) { if (currentStep < latihanData.length - 1) {
setState(() {
currentStep++; // Update step saat lanjut
});
context.go( context.go(
'/latihan', '/latihan',
extra: { extra: {
'id': id, 'id': widget.id,
'currentStep': currentStep + 1, 'currentStep': currentStep,
}, },
); );
} else { } else {
@ -196,7 +257,7 @@ class PelafalanPage extends StatelessWidget {
context.go( context.go(
'/latihan', '/latihan',
extra: { extra: {
'id': id, 'id': widget.id,
'currentStep': currentStep, 'currentStep': currentStep,
'latihanData': latihanData, 'latihanData': latihanData,
}, },

View File

@ -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<DetailQuizPage> {
Map<int, int> selectedAnswers = {}; // Menyimpan jawaban per soal
List<dynamic> 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<void> _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<void> 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<void> submitQuizAnswers() async {
if (authToken == null) {
print("No auth token found");
return;
}
List<Map<String, dynamic>> 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: <Widget>[
// 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),
),
),
),
),
],
),
),
);
}
}

View File

@ -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,
),
),
),
],
),
),
);
}
}

View File

@ -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<QuizPage> {
List<dynamic> materiList = [];
bool isLoading = true;
String? authToken;
// Variabel untuk menyimpan hasil score dan status per materi
Map<int, Map<String, dynamic>> quizResults = {};
@override
void initState() {
super.initState();
_getAuthToken();
_fetchMateri(); // Fetch materi saat halaman dimuat
}
// Mengambil token auth dari shared_preferences
Future<void> _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<void> _fetchMateri() async {
final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/materi'));
if (response.statusCode == 200) {
final Map<String, dynamic> 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<void> _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,
),
),
],
),
],
),
],
),
),
),
),
),
),
),
],
),
),
),
),
);
}
}

View File

@ -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<DataLatihanPage> {
int _selectedIndex = 0; // To track the selected tab
late Future<List<dynamic>> kategoriData;
bool isLoading = true;
List<dynamic> materiList = [];
// Define the content for each "tab"
final List<Widget> _contentWidgets = [
Center(child: Text('Konten Materi 1')),
Center(child: Text('Konten Materi 2')),
];
// Function to fetch materi data when a tab is selected
Future<void> _fetchMateri() async {
final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/materi'));
if (response.statusCode == 200) {
final Map<String, dynamic> 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<List<dynamic>> 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<List<dynamic>> 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<List<dynamic>>(
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<List<dynamic>>(
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,
),
],
),
),
],
),
);
},
),
],
);
},
),
),
],
),
),
);
}
}

View File

@ -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<DetailDataLatihanPage> {
@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,
),
),
),
);
}
}

View File

@ -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: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/router/route.dart';
import 'package:ta_tahsin/core/theme.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}); const DataSantriPage({super.key});
@override
_DataSantriPageState createState() => _DataSantriPageState();
}
class _DataSantriPageState extends State<DataSantriPage> {
List<dynamic> santriList = [];
List<dynamic> filteredSantriList = [];
String searchQuery = "";
bool isLoading = true;
File? file;
@override
void initState() {
super.initState();
fetchSantriData();
}
Future<void> 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<String> 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<Map<String, dynamic>> santriData = [];
// // Loop through rows and create the data
// for (var row in sheet.rows) {
// if (row.isNotEmpty) {
// Map<String, dynamic> 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<void> sendSantriData(List<Map<String, dynamic>> 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<void> sendSantriData(List<Map<String, dynamic>> 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<String> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -18,8 +250,14 @@ class DataSantriPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Search bar
TextField( TextField(
onChanged: (query) {
setState(() {
searchQuery = query;
filterSantri(searchQuery);
});
},
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(Icons.search),
hintText: 'Cari Santri...', hintText: 'Cari Santri...',
@ -31,7 +269,6 @@ class DataSantriPage extends StatelessWidget {
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Tombol + untuk menambah data santri
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -40,22 +277,20 @@ class DataSantriPage extends StatelessWidget {
children: [ children: [
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// Tindakan ketika tombol + diklik context.go('/tambah_santri');
print("Menambahkan data santri...");
}, },
icon: const Icon( icon: const Icon(
Icons.add, Icons.add,
color: Colors.white, color: Colors.white,
), // Ikon dengan warna putih ),
label: const Text( label: const Text(
'Tambah', 'Tambah',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
), // Teks dengan warna putih ),
), ),
style: TextButton.styleFrom( style: TextButton.styleFrom(
backgroundColor: backgroundColor: secondPrimaryColor,
secondPrimaryColor, // Warna latar belakang tombol
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -67,41 +302,37 @@ class DataSantriPage extends StatelessWidget {
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
TextButton.icon( // TextButton.icon(
onPressed: () { // onPressed: () {
// Tindakan untuk impor // onTapImportFile();
}, // },
icon: const Icon( // icon: const Icon(
Icons.import_export, // Icons.import_export,
color: Colors.white, // color: Colors.white,
), // Ikon dengan warna putih // ),
label: const Text( // label: const Text(
'Import', // 'Import',
style: TextStyle( // style: TextStyle(
color: Colors.white, // color: Colors.white,
), // Teks dengan warna putih // ),
), // ),
style: TextButton.styleFrom( // style: TextButton.styleFrom(
backgroundColor: // backgroundColor: secondPrimaryColor,
secondPrimaryColor, // Warna latar belakang tombol // shape: RoundedRectangleBorder(
shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12), // ),
), // padding: const EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric( // horizontal: 16,
horizontal: 16, // vertical: 12,
vertical: 12, // ),
), // textStyle: const TextStyle(fontSize: 16),
textStyle: const TextStyle(fontSize: 16), // ),
), // ),
),
], ],
), ),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Judul Pilih Santri
Text( Text(
'Pilih Santri', 'Pilih Santri',
style: TextStyle( style: TextStyle(
@ -111,14 +342,16 @@ class DataSantriPage extends StatelessWidget {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
// Menggunakan SliverList dengan santriList
Expanded( Expanded(
child: CustomScrollView( child: isLoading
? Center(child: CircularProgressIndicator())
: filteredSantriList.isEmpty
? Center(child: Text('Data tidak ditemukan'))
: CustomScrollView(
slivers: [ slivers: [
SliverList( SliverList(
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
var santri = santriList[index]; var santri = filteredSantriList[index];
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
@ -131,9 +364,7 @@ class DataSantriPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTile( ListTile(
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(vertical: 5.0),
vertical: 5.0,
),
leading: Container( leading: Container(
width: 60, width: 60,
height: 60, height: 60,
@ -141,7 +372,7 @@ class DataSantriPage extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
image: DecorationImage( image: DecorationImage(
image: AssetImage( image: AssetImage(
'assets/icon/${santri['image']}', 'assets/icon/defaultprofile.jpeg',
), ),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
@ -151,7 +382,7 @@ class DataSantriPage extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
santri['nama'], santri['nama_lengkap'],
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -162,15 +393,17 @@ class DataSantriPage extends StatelessWidget {
], ],
), ),
subtitle: Text( subtitle: Text(
santri['jilid'], santri['email'],
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.grey, color: Colors.grey,
), ),
), ),
onTap: () { onTap: () {
// context.go('/detail_kemajuan', extra: {'nama': santri['nama']}); context.go('/detail_user', extra: {
router.push("/detail_user"); 'id': santri['id'],
});
// router.push("/detail_user");
}, },
), ),
Divider( Divider(
@ -184,7 +417,7 @@ class DataSantriPage extends StatelessWidget {
], ],
), ),
); );
}, childCount: santriList.length), }, childCount: filteredSantriList.length),
), ),
], ],
), ),

View File

@ -1,20 +1,15 @@
import 'package:flutter/material.dart'; 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:ta_tahsin/core/theme.dart';
import 'package:http/http.dart' as http;
void main() { import 'dart:convert';
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DetailDataSantriPage(),
);
}
}
class DetailDataSantriPage extends StatefulWidget { class DetailDataSantriPage extends StatefulWidget {
final int id;
const DetailDataSantriPage({super.key, required this.id});
@override @override
_DetailDataSantriPageState createState() => _DetailDataSantriPageState(); _DetailDataSantriPageState createState() => _DetailDataSantriPageState();
} }
@ -22,12 +17,129 @@ class DetailDataSantriPage extends StatefulWidget {
class _DetailDataSantriPageState extends State<DetailDataSantriPage> { class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final TextEditingController _fullNameController = TextEditingController(); final TextEditingController _fullNameController = TextEditingController();
final TextEditingController _addressController = TextEditingController();
final TextEditingController _dobController = TextEditingController(); final TextEditingController _dobController = TextEditingController();
final TextEditingController _phoneController = TextEditingController(); final TextEditingController _phoneController = TextEditingController();
final TextEditingController _emailController = TextEditingController(); final TextEditingController _emailController = TextEditingController();
final TextEditingController _weightController = TextEditingController(); final TextEditingController _jenjangPendidikanController = TextEditingController();
final TextEditingController _heightController = TextEditingController();
String _gender = 'Laki-laki'; 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<void> _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<void> _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: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
_isEditing = false;
});
Navigator.of(context).pop();
// context.go('/navigasiPengajar');
// context.go('/detail_user', extra: {
// 'id': widget.id,
// });
},
),
],
);
},
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -37,17 +149,26 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back), icon: Icon(Icons.arrow_back),
onPressed: () { 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( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Profile picture section
Center( Center(
child: CircleAvatar( child: CircleAvatar(
radius: 60, radius: 60,
@ -56,8 +177,6 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
), ),
), ),
SizedBox(height: 20), SizedBox(height: 20),
// Title section
Container( Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
@ -72,41 +191,40 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
), ),
), ),
SizedBox(height: 16), SizedBox(height: 16),
_buildTextFormField(_fullNameController, 'Nama Lengkap', _isEditing),
// Basic details section _buildTextFormField(_addressController, 'Alamat', _isEditing),
_buildTextFormField(_fullNameController, 'Nama Lengkap'), _buildTextFormField(_dobController, 'Usia', _isEditing),
_buildTextFormField(_dobController, 'Usia'), _buildTextFormField(_jenjangPendidikanController, 'Jenjang Pendidikan', _isEditing),
_buildTextFormField(_dobController, 'Jilid Tilawati'),
// Gender selection section
Row( Row(
children: [ children: [
Text('Jenis Kelamin', style: TextStyle(fontSize: 16)), Text('Jenis Kelamin', style: TextStyle(fontSize: 16)),
Radio<String>( Radio<String>(
value: 'Laki-laki', value: 'Laki-laki',
groupValue: _gender, groupValue: _gender,
onChanged: (value) { onChanged: _isEditing
? (value) {
setState(() { setState(() {
_gender = value!; _gender = value!;
}); });
}, }
: null,
), ),
Text('Laki-laki'), Text('Laki-laki'),
Radio<String>( Radio<String>(
value: 'Perempuan', value: 'Perempuan',
groupValue: _gender, groupValue: _gender,
onChanged: (value) { onChanged: _isEditing
? (value) {
setState(() { setState(() {
_gender = value!; _gender = value!;
}); });
}, }
: null,
), ),
Text('Perempuan'), Text('Perempuan'),
], ],
), ),
SizedBox(height: 20), SizedBox(height: 20),
// Contact details section
Container( Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
@ -121,50 +239,26 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
), ),
), ),
SizedBox(height: 16), SizedBox(height: 16),
_buildTextFormField(_phoneController, 'No WA Wali', _isEditing),
_buildTextFormField(_phoneController, 'No WA Wali'), _buildTextFormField(_emailController, 'Email', _isEditing),
_buildTextFormField(_emailController, 'Email'),
SizedBox(height: 20), 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(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20), padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20),
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: secondPrimaryColor, // Customize your color backgroundColor: _isEditing ? secondPrimaryColor : Colors.grey, // Button color based on editing
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
minimumSize: Size(double.infinity, 50), minimumSize: Size(double.infinity, 50),
elevation: 5, // Adding shadow to the button elevation: 5, // Adding shadow to the button
), ),
onPressed: () { onPressed: _isEditing
if (_formKey.currentState!.validate()) { ? () {
ScaffoldMessenger.of(context).showSnackBar( // Trigger the update user function when editing
SnackBar(content: Text('Profil tersimpan')), _updateUserData();
);
} }
}, : null,
child: Text( child: Text(
"Simpan", "Simpan",
style: TextStyle(fontSize: 16, color: whiteColor), style: TextStyle(fontSize: 16, color: whiteColor),
@ -178,7 +272,8 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
); );
} }
Widget _buildTextFormField(TextEditingController controller, String labelText) { // Helper function to build TextFormField
Widget _buildTextFormField(TextEditingController controller, String labelText, bool isEnabled) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -186,6 +281,7 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
SizedBox(height: 8), SizedBox(height: 8),
TextFormField( TextFormField(
controller: controller, controller: controller,
enabled: isEnabled, // Control whether the field is enabled or not
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.white, fillColor: Colors.white,

View File

@ -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<TambahSantriPage> {
final _formKey = GlobalKey<FormState>();
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<void> _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<String, String> 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: <Widget>[
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: <Widget>[
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<String>(
value: 'Laki-laki',
groupValue: _gender,
onChanged: (value) {
setState(() {
_gender = value!;
});
},
),
Text('Laki-laki'),
Radio<String>(
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),
],
);
}
}

View File

@ -12,7 +12,7 @@ final String nama;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Kemajuan'), title: const Text('Detail Kemajuan'),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back),color: blackColor, icon: const Icon(Icons.arrow_back),color: blackColor,
onPressed: () { onPressed: () {

View File

@ -7,12 +7,16 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h> #include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <record_linux/record_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); 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 = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux audioplayers_linux
record_linux
url_launcher_linux url_launcher_linux
) )

View File

@ -6,16 +6,20 @@ import FlutterMacOS
import Foundation import Foundation
import audioplayers_darwin import audioplayers_darwin
import file_picker
import flutter_inappwebview_macos import flutter_inappwebview_macos
import path_provider_foundation import path_provider_foundation
import record_macos
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
import video_player_avfoundation import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))

View File

@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -97,6 +105,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" 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: crypto:
dependency: transitive dependency: transitive
description: description:
@ -121,6 +137,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -145,6 +177,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.1" 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: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -246,6 +286,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" 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: flutter_screenutil:
dependency: "direct main" dependency: "direct main"
description: description:
@ -385,7 +433,7 @@ packages:
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_provider: path_provider:
dependency: transitive dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
@ -432,6 +480,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" 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: platform:
dependency: transitive dependency: transitive
description: description:
@ -448,6 +552,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" 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: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -717,6 +885,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
url: "https://pub.dev"
source: hosted
version: "5.13.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -725,6 +901,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" 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: youtube_player_flutter:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -44,6 +44,11 @@ dependencies:
flame_audio: ^2.11.5 flame_audio: ^2.11.5
shared_preferences: ^2.5.3 shared_preferences: ^2.5.3
http: ^1.4.0 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: dev_dependencies:
flutter_test: flutter_test:

View File

@ -8,6 +8,8 @@
#include <audioplayers_windows/audioplayers_windows_plugin.h> #include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h> #include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <record_windows/record_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -15,6 +17,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
RecordWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@ -5,6 +5,8 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows audioplayers_windows
flutter_inappwebview_windows flutter_inappwebview_windows
permission_handler_windows
record_windows
url_launcher_windows url_launcher_windows
) )