feat: add fitur quiz,memperbaiki voice record & integrate api get santri
This commit is contained in:
parent
b2bde219ae
commit
e0b5c274b3
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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<NavigationPage> {
|
|||
|
||||
final List<Widget> _pages = [
|
||||
const BelajarPage(),
|
||||
// const UjianPage(),
|
||||
const QuizPage(),
|
||||
const ProfilePage(),
|
||||
];
|
||||
|
||||
|
@ -44,10 +45,10 @@ class _NavigationPageState extends State<NavigationPage> {
|
|||
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",
|
||||
|
|
|
@ -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<NavigationPengajarPage> {
|
|||
final List<Widget> _pages = [
|
||||
const KemajuanPage(),
|
||||
const DataSantriPage(),
|
||||
const DataLatihanPage(),
|
||||
const PengajarProfilePage(),
|
||||
];
|
||||
|
||||
|
@ -50,6 +52,10 @@ class _NavigationPengajarPageState extends State<NavigationPengajarPage> {
|
|||
icon: Icon(Icons.list_alt),
|
||||
label: "Data Santri",
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.list_alt),
|
||||
label: "Data Latihan",
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: "Profile",
|
||||
|
|
|
@ -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<String, dynamic> extra = state.extra as Map<String, dynamic>;
|
||||
final int currentStep = extra['currentStep'];
|
||||
final List<dynamic> 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<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'],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -46,6 +46,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
|
||||
|
||||
String peran = data['data']['user']['peran'];
|
||||
prefs.setString('peran', peran);
|
||||
|
||||
|
||||
if (peran == 'santri') {
|
||||
|
@ -54,7 +55,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
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<LoginPage> {
|
|||
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),
|
||||
|
|
|
@ -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<LatihanPage> {
|
||||
final _record = AudioRecorder();
|
||||
late Future<List<dynamic>> latihanData;
|
||||
bool isRecording = false;
|
||||
int timer = 10;
|
||||
|
@ -25,6 +31,7 @@ class _LatihanPageState extends State<LatihanPage> {
|
|||
String timerText = "10";
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
bool isAudioPlaying = false;
|
||||
String? recordedFilePath;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -54,6 +61,7 @@ class _LatihanPageState extends State<LatihanPage> {
|
|||
}
|
||||
|
||||
|
||||
// Fungsi untuk memulai timer dan mulai merekam
|
||||
void startTimer() {
|
||||
setState(() {
|
||||
isRecording = true;
|
||||
|
@ -69,16 +77,42 @@ class _LatihanPageState extends State<LatihanPage> {
|
|||
});
|
||||
} else {
|
||||
countdownTimer.cancel();
|
||||
setState(() {
|
||||
isRecording = false;
|
||||
});
|
||||
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(() {
|
||||
recordedFilePath = path;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk menghentikan perekaman
|
||||
Future<void> 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<LatihanPage> {
|
|||
),
|
||||
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<LatihanPage> {
|
|||
'id': widget.id,
|
||||
'currentStep': widget.currentStep,
|
||||
'latihanData': snapshot.data,
|
||||
'recordedFilePath': recordedFilePath,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -304,6 +339,7 @@ class _LatihanPageState extends State<LatihanPage> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_record.dispose();
|
||||
_audioPlayer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
@ -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<dynamic> 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<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
|
||||
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,7 +257,7 @@ class PelafalanPage extends StatelessWidget {
|
|||
context.go(
|
||||
'/latihan',
|
||||
extra: {
|
||||
'id': id,
|
||||
'id': widget.id,
|
||||
'currentStep': currentStep,
|
||||
'latihanData': latihanData,
|
||||
},
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<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
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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<DetailDataSantriPage> {
|
||||
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 _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<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
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -37,17 +149,26 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
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<DetailDataSantriPage> {
|
|||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
|
||||
// Title section
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(10),
|
||||
|
@ -72,41 +191,40 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
),
|
||||
),
|
||||
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<String>(
|
||||
value: 'Laki-laki',
|
||||
groupValue: _gender,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_gender = value!;
|
||||
});
|
||||
},
|
||||
onChanged: _isEditing
|
||||
? (value) {
|
||||
setState(() {
|
||||
_gender = value!;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
Text('Laki-laki'),
|
||||
Radio<String>(
|
||||
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<DetailDataSantriPage> {
|
|||
),
|
||||
),
|
||||
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<DetailDataSantriPage> {
|
|||
);
|
||||
}
|
||||
|
||||
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<DetailDataSantriPage> {
|
|||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
enabled: isEnabled, // Control whether the field is enabled or not
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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: () {
|
||||
|
|
|
@ -7,12 +7,16 @@
|
|||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||
#include <record_linux/record_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
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);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
audioplayers_linux
|
||||
record_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
|
|
@ -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"))
|
||||
|
|
186
pubspec.lock
186
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <audioplayers_windows/audioplayers_windows_plugin.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>
|
||||
|
||||
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"));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
audioplayers_windows
|
||||
flutter_inappwebview_windows
|
||||
permission_handler_windows
|
||||
record_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue