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"
|
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
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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'],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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: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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
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: () {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
186
pubspec.lock
186
pubspec.lock
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue