23/06/25 fitur validasi pelafalan santri belum
This commit is contained in:
parent
e0b5c274b3
commit
4d896d6628
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 261 KiB |
|
@ -1,4 +1,4 @@
|
|||
// lib/config.dart
|
||||
class BaseUrl {
|
||||
static const String baseUrl = 'http://192.168.0.102:8000/api';
|
||||
static const String baseUrl = 'https://legal-marginally-macaque.ngrok-free.app/api';
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class _NavigationPengajarPageState extends State<NavigationPengajarPage> {
|
|||
final List<Widget> _pages = [
|
||||
const KemajuanPage(),
|
||||
const DataSantriPage(),
|
||||
const DataLatihanPage(),
|
||||
// const DataLatihanPage(),
|
||||
const PengajarProfilePage(),
|
||||
];
|
||||
|
||||
|
@ -52,10 +52,10 @@ class _NavigationPengajarPageState extends State<NavigationPengajarPage> {
|
|||
icon: Icon(Icons.list_alt),
|
||||
label: "Data Santri",
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.list_alt),
|
||||
label: "Data Latihan",
|
||||
),
|
||||
// BottomNavigationBarItem(
|
||||
// icon: Icon(Icons.list_alt),
|
||||
// label: "Data Latihan",
|
||||
// ),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: "Profile",
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:ta_tahsin/view/auth/changePass/ubah_password.dart';
|
||||
import 'package:ta_tahsin/view/home/latihan/latihan.dart';
|
||||
import 'package:ta_tahsin/view/home/profile/edit_profile.dart';
|
||||
import 'package:ta_tahsin/view/home/progres/progres.dart';
|
||||
import 'package:ta_tahsin/view/home/quiz/detail_quiz.dart';
|
||||
import 'package:ta_tahsin/view/home/quiz/hasil_quiz.dart';
|
||||
import 'package:ta_tahsin/view/pengajar/data_latihan/detail_data_latihan.dart';
|
||||
import 'package:ta_tahsin/view/pengajar/data_santri/detail_data_santri.dart';
|
||||
import 'package:ta_tahsin/view/pengajar/data_santri/tambah_santri.dart';
|
||||
import 'package:ta_tahsin/view/pengajar/kemajuan/detail_kemajuan.dart';
|
||||
import 'package:ta_tahsin/view/pengajar/profile/edit_profile_pengajar.dart';
|
||||
import 'package:ta_tahsin/view/pengajar/profile/ubah_password_pengajar.dart';
|
||||
|
||||
import '../../view/auth/login/login.dart';
|
||||
import '../../view/home/latihan/pelafalan_popup.dart';
|
||||
|
@ -19,10 +24,10 @@ import '../navigation/navigation.dart';
|
|||
import '../navigation/navigation_pengajar.dart';
|
||||
|
||||
final router = GoRouter(
|
||||
initialLocation: '/login',
|
||||
// initialLocation: '/login',
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/login',
|
||||
path: '/',
|
||||
builder: (context, state) => const LoginPage(),
|
||||
),
|
||||
GoRoute(
|
||||
|
@ -94,14 +99,22 @@ GoRoute(
|
|||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/detail_kemajuan',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final Map<String, dynamic> extra = state.extra as Map<String, dynamic>;
|
||||
return DetailKemajuanPage(
|
||||
nama: extra['nama'],
|
||||
);
|
||||
},
|
||||
),
|
||||
path: '/detail_kemajuan',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final Map<String, dynamic> extra = state.extra as Map<String, dynamic>;
|
||||
|
||||
// Mengambil data yang diteruskan (nama dan user_id)
|
||||
final String nama = extra['nama'];
|
||||
final int userId = extra['user_id'];
|
||||
|
||||
// Mengirimkan data ke halaman DetailKemajuanPage
|
||||
return DetailKemajuanPage(
|
||||
nama: nama,
|
||||
userId: userId,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
path: '/data_santri',
|
||||
builder: (context, state) {
|
||||
|
@ -152,5 +165,40 @@ GoRoute(
|
|||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/progres_belajar',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return ProgresBelajarPage(
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/ubah_password',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return ChangePasswordPage(
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/ubah_password_pengajar',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return ChangePasswordPengajarPage(
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/edit_profile',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return EditProfile(
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/edit_profile_pengajar',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return EditProfilePengajar(
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
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 ChangePasswordPage extends StatefulWidget {
|
||||
const ChangePasswordPage({super.key});
|
||||
|
||||
@override
|
||||
_ChangePasswordPageState createState() => _ChangePasswordPageState();
|
||||
}
|
||||
|
||||
class _ChangePasswordPageState extends State<ChangePasswordPage> {
|
||||
final _newPasswordController = TextEditingController();
|
||||
final _confirmPasswordController = TextEditingController();
|
||||
String? _errorMessage;
|
||||
|
||||
bool showNewPassword = true;
|
||||
bool showConfirmPassword = true;
|
||||
|
||||
// Update visibility for New Password field
|
||||
void updateNewPasswordVisibility() {
|
||||
setState(() {
|
||||
showNewPassword = !showNewPassword;
|
||||
});
|
||||
}
|
||||
|
||||
// Update visibility for Confirm Password field
|
||||
void updateConfirmPasswordVisibility() {
|
||||
setState(() {
|
||||
showConfirmPassword = !showConfirmPassword;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> handleChangePassword() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
debugPrint("Token yang diambil: $authToken");
|
||||
|
||||
// Validasi panjang password minimal 8 karakter
|
||||
if (_newPasswordController.text.length < 8) {
|
||||
setState(() {
|
||||
_errorMessage = 'Password harus minimal 8 karakter';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validasi jika password baru dan konfirmasi password tidak cocok
|
||||
if (_newPasswordController.text != _confirmPasswordController.text) {
|
||||
setState(() {
|
||||
_errorMessage = 'Password baru dan konfirmasi password tidak cocok';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_errorMessage = null; // Reset error message
|
||||
});
|
||||
|
||||
// URL API untuk mengubah password
|
||||
String url = '${BaseUrl.baseUrl}/change_password'; // Ganti dengan URL API Anda
|
||||
debugPrint("URL yang digunakan: $url");
|
||||
|
||||
// Data yang akan dikirim
|
||||
Map<String, dynamic> requestBody = {
|
||||
'new_password': _newPasswordController.text,
|
||||
'new_password_confirmation': _confirmPasswordController.text,
|
||||
};
|
||||
|
||||
debugPrint("Request Body: $requestBody");
|
||||
|
||||
// Kirim request ke API
|
||||
final response = await http.post(
|
||||
Uri.parse(url),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $authToken',
|
||||
'Content-Type': 'application/json', // Pastikan header ini ada
|
||||
},
|
||||
body: jsonEncode(requestBody),
|
||||
);
|
||||
|
||||
debugPrint("Status code: ${response.statusCode}");
|
||||
debugPrint("Response body: ${response.body}");
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Jika berhasil, tampilkan pesan sukses
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Password berhasil diubah')),
|
||||
);
|
||||
context.go('/navigasi'); // Kembali ke halaman sebelumnya
|
||||
} else {
|
||||
// Jika gagal, tampilkan pesan error dari response body
|
||||
final responseBody = json.decode(response.body);
|
||||
setState(() {
|
||||
// Menampilkan pesan error jika ada
|
||||
_errorMessage = responseBody['message']['new_password']?.first ??
|
||||
'Terjadi kesalahan, coba lagi';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@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(
|
||||
"Ubah Password",
|
||||
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.symmetric(horizontal: 20.0, vertical: 50.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Password Input Field
|
||||
Text(
|
||||
'Password Baru',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _newPasswordController,
|
||||
obscureText: showNewPassword,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
showNewPassword ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: updateNewPasswordVisibility,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200], // Lighter background for the field
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none, // Remove default border
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Confirm Password Input Field
|
||||
Text(
|
||||
'Konfirmasi Password',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _confirmPasswordController,
|
||||
obscureText: showConfirmPassword,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
showConfirmPassword ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: updateConfirmPasswordVisibility,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200], // Lighter background for the field
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none, // Remove default border
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Error Message
|
||||
if (_errorMessage != null)
|
||||
Text(
|
||||
_errorMessage!,
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
// Send Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
handleChangePassword();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 50,
|
||||
vertical: 15,
|
||||
),
|
||||
minimumSize: Size(double.infinity, 40),
|
||||
),
|
||||
child: Text(
|
||||
'Selesai',
|
||||
style: TextStyle(
|
||||
color: whiteColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:ta_tahsin/core/baseurl/base_url.dart';
|
||||
|
@ -21,6 +19,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
bool showPass = true;
|
||||
bool isLoading = false; // Menambahkan variabel untuk status loading
|
||||
bool isEmailEmpty = false; // Validasi untuk email
|
||||
bool isPasswordEmpty = false; // Validasi untuk password
|
||||
|
||||
void updateObsecure() {
|
||||
setState(() {
|
||||
|
@ -29,152 +30,214 @@ class _LoginPageState extends State<LoginPage> {
|
|||
}
|
||||
|
||||
Future<void> login() async {
|
||||
setState(() {
|
||||
isEmailEmpty = emailController.text.isEmpty;
|
||||
isPasswordEmpty = passwordController.text.isEmpty;
|
||||
});
|
||||
|
||||
// Cek jika ada input yang kosong
|
||||
if (isEmailEmpty || isPasswordEmpty) {
|
||||
return; // Jika ada input yang kosong, hentikan proses login
|
||||
}
|
||||
|
||||
setState(() {
|
||||
isLoading = true; // Menandakan bahwa login sedang diproses
|
||||
});
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('${BaseUrl.baseUrl}/login'),
|
||||
Uri.parse('${BaseUrl.baseUrl}/loginWithTelp'),
|
||||
body: {
|
||||
'email': emailController.text,
|
||||
'no_telp_wali': emailController.text,
|
||||
'password': passwordController.text,
|
||||
},
|
||||
);
|
||||
|
||||
setState(() {
|
||||
isLoading = false; // Menandakan bahwa login telah selesai
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var data = json.decode(response.body);
|
||||
|
||||
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('token', data['data']['access_token']);
|
||||
|
||||
|
||||
String peran = data['data']['user']['peran'];
|
||||
prefs.setString('peran', peran);
|
||||
|
||||
|
||||
if (peran == 'santri') {
|
||||
router.push("/navigasi");
|
||||
router.push("/navigasi");
|
||||
} else if (peran == 'pengajar') {
|
||||
router.push("/navigasiPengajar");
|
||||
router.push("/navigasiPengajar");
|
||||
}
|
||||
} else {
|
||||
// debugPrint("anjing");
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Login Failed: ${response.body}')),
|
||||
);
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.white), // Menambahkan ikon error
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${json.decode(response.body)['data']['message']}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: alertTextColor, // Mengubah warna latar belakang menjadi merah
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10), // Menambahkan radius hanya di atas kiri
|
||||
topRight: Radius.circular(10), // Menambahkan radius hanya di atas kanan
|
||||
bottomLeft: Radius.zero, // Tidak ada radius di bawah kiri
|
||||
bottomRight: Radius.zero, // Tidak ada radius di bawah kanan
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showErrorBottomSheet(BuildContext context, String message) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent, // Menjadikan background transparan
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red, // Mengubah warna latar belakang menjadi merah
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.white), // Menambahkan ikon error
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
message, // Menampilkan pesan error
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: PaddingCustom().paddingHorizontal(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
"Selamat Datang,",
|
||||
style: TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: bold,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
const Text(
|
||||
"Masuk Untuk Melanjutkan",
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Center(child: Image.asset('assets/logo/sho.jpg', height: 180)),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
"Email",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: emailController,
|
||||
keyboardType:
|
||||
TextInputType
|
||||
.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.email,
|
||||
color: Colors.grey,
|
||||
),
|
||||
hintText:
|
||||
"Masukkan Email",
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
"Password",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
obscureText: showPass,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock, color: Colors.grey),
|
||||
hintText: "Enter your password",
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
showPass ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: updateObsecure,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
|
||||
},
|
||||
child: Text(
|
||||
"Lupa password?",
|
||||
style: TextStyle(color: secondPrimaryColor, fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
login();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
foregroundColor: whiteColor,
|
||||
padding: PaddingCustom().paddingVertical(15),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: const Text("Masuk"),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
body: Column(
|
||||
children: [
|
||||
// Top half: Background image
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.5, // Half the screen height
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/images/muhajirin4.jpg'), // Your image
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom half: Form section
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
|
||||
color: Colors.white, // White background for the form
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"No Telp",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: emailController, // Ubah menjadi controller untuk no_telp jika diperlukan
|
||||
keyboardType: TextInputType.phone, // Gunakan TextInputType.phone untuk nomor telepon
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.phone, // Ganti icon dengan ikon telepon
|
||||
color: Colors.grey,
|
||||
),
|
||||
hintText: "Masukkan No. Telepon", // Ubah hint text sesuai kebutuhan
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
errorText: isEmailEmpty ? 'No Telepon tidak boleh kosong' : null, // Menambahkan pesan error
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: isEmailEmpty ? Colors.red : Colors.blue), // Ganti warna border saat error
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
"Password",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
obscureText: showPass,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock, color: Colors.grey),
|
||||
hintText: "Masukkan Password",
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
showPass ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: updateObsecure,
|
||||
),
|
||||
errorText: isPasswordEmpty ? 'Password tidak boleh kosong' : null, // Menambahkan pesan error
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: isPasswordEmpty ? Colors.red : Colors.blue), // Ganti warna border saat error
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading ? null : () { // Disable button saat loading
|
||||
login();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
foregroundColor: whiteColor,
|
||||
padding: PaddingCustom().paddingVertical(15),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? CircularProgressIndicator(color: whiteColor) // Menampilkan indikator loading
|
||||
: const Text("Masuk"), // Menampilkan teks "Masuk" ketika tidak loading
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@ 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: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';
|
||||
import 'dart:async';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
class LatihanPage extends StatefulWidget {
|
||||
final int id;
|
||||
|
@ -101,14 +103,134 @@ class _LatihanPageState extends State<LatihanPage> {
|
|||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk menghentikan perekaman
|
||||
Future<void> stopRecording() async {
|
||||
await _record.stop();
|
||||
// // Fungsi untuk menghentikan perekaman
|
||||
// Future<void> stopRecording() async {
|
||||
// await _record.stop();
|
||||
// setState(() {
|
||||
// isRecording = false;
|
||||
// });
|
||||
// }
|
||||
|
||||
// // In stopRecording(), save the recorded audio file path to the database
|
||||
// Future<void> stopRecording() async {
|
||||
// await _record.stop();
|
||||
// setState(() {
|
||||
// isRecording = false;
|
||||
// });
|
||||
|
||||
// debugPrint('ID Latihan: ${widget.id}');
|
||||
// debugPrint('File Path: $recordedFilePath');
|
||||
// // After stopping the recording, save the audio file name/path to the backend
|
||||
// if (recordedFilePath != null) {
|
||||
// saveRecordedAudioName(recordedFilePath!); // Call the save function to API
|
||||
// }
|
||||
// }
|
||||
Future<void> uploadRecording(String filePath) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
var uri = Uri.parse('${BaseUrl.baseUrl}/upload_audio');
|
||||
var request = http.MultipartRequest('POST', uri);
|
||||
|
||||
// Menambahkan file rekaman ke dalam request
|
||||
var file = await http.MultipartFile.fromPath(
|
||||
'recorded_audio', // Nama field yang akan diterima di Laravel
|
||||
filePath,
|
||||
// contentType: MediaType('file', 'm4a'), // Sesuaikan dengan jenis file
|
||||
);
|
||||
|
||||
request.files.add(file);
|
||||
|
||||
// Kirimkan request
|
||||
// Menambahkan header Authorization
|
||||
request.headers.addAll({
|
||||
'Authorization': 'Bearer $authToken', // Menambahkan token ke dalam header
|
||||
});
|
||||
var response = await request.send();
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
print('Upload berhasil!');
|
||||
} else {
|
||||
print('Gagal mengupload file: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stopRecording() async {
|
||||
await _record.stop();
|
||||
await uploadRecording(recordedFilePath!);
|
||||
|
||||
// Check if the widget is still mounted before calling setState
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isRecording = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Get the latihan id from the latihanData list using the current step
|
||||
final latihanList = await latihanData; // Fetch the data again if not already fetched
|
||||
final latihan = latihanList[widget.currentStep]; // Get the latihan at current step
|
||||
final idLatihan = latihan['id']; // Use the 'id' from latihan data
|
||||
|
||||
await storeLatihanId(idLatihan);
|
||||
|
||||
// Debug print to show the file path and latihan ID
|
||||
debugPrint('ID Latihan: $idLatihan');
|
||||
debugPrint('File Path: $recordedFilePath');
|
||||
|
||||
// After stopping the recording, save the audio file name/path to the backend
|
||||
if (recordedFilePath != null) {
|
||||
saveRecordedAudioName(idLatihan, recordedFilePath!); // Pass the latihan ID to the save function
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveRecordedAudioName(int idLatihan, String filePath) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
debugPrint("Token yang diambil: $authToken");
|
||||
|
||||
// Check if filePath is valid
|
||||
if (filePath.isEmpty) {
|
||||
debugPrint("Error: File path is empty.");
|
||||
return; // Exit if the file path is invalid
|
||||
}
|
||||
|
||||
final String apiUrl = '${BaseUrl.baseUrl}/latihan/$idLatihan/saverecord'; // API endpoint with the latihan ID
|
||||
|
||||
try {
|
||||
final response = await http.put(
|
||||
Uri.parse(apiUrl),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $authToken', // Authentication token
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: jsonEncode({
|
||||
'recorded_audio': filePath, // Send the file path as parameter
|
||||
}),
|
||||
);
|
||||
|
||||
// Check if the response status is successful
|
||||
if (response.statusCode == 200) {
|
||||
debugPrint('Audio file saved successfully');
|
||||
} else {
|
||||
// Log more detailed error information
|
||||
debugPrint('Failed to save audio file');
|
||||
debugPrint('Status Code: ${response.statusCode}');
|
||||
debugPrint('Response Body: ${response.body}');
|
||||
}
|
||||
} catch (e) {
|
||||
// Catch any error that occurs during the request and log it
|
||||
debugPrint("Error saving audio file: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> storeLatihanId(int idLatihan) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
List<int> latihanIds = prefs.getStringList('latihanIds')?.map((e) => int.parse(e)).toList() ?? [];
|
||||
latihanIds.add(idLatihan); // Add the new id to the list
|
||||
|
||||
// Store the list as a string (in SharedPreferences)
|
||||
await prefs.setStringList('latihanIds', latihanIds.map((e) => e.toString()).toList());
|
||||
debugPrint("Stored Latihan IDs: $latihanIds");
|
||||
}
|
||||
|
||||
void stopTimer() {
|
||||
countdownTimer.cancel();
|
||||
|
@ -340,7 +462,10 @@ class _LatihanPageState extends State<LatihanPage> {
|
|||
@override
|
||||
void dispose() {
|
||||
_record.dispose();
|
||||
_audioPlayer.dispose();
|
||||
_audioPlayer.dispose();
|
||||
if (countdownTimer.isActive) {
|
||||
countdownTimer.cancel(); // Cancel the timer
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:audioplayers/audioplayers.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/theme.dart';
|
||||
|
||||
class PelafalanPage extends StatefulWidget {
|
||||
|
@ -65,6 +70,101 @@ class _PelafalanPageState extends State<PelafalanPage> {
|
|||
print("Audio playing...");
|
||||
}
|
||||
|
||||
// Future<void> updateProgress(int subMateriId) async {
|
||||
// SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// String? authToken = prefs.getString('token');
|
||||
// debugPrint("Token yang diambil: $authToken");
|
||||
// final String apiUrl = '${BaseUrl.baseUrl}/progress/$subMateriId/update'; // Ganti dengan URL API yang sesuai
|
||||
|
||||
// final response = await http.post(
|
||||
// Uri.parse(apiUrl),
|
||||
// headers: {
|
||||
// 'Authorization': 'Bearer $authToken',
|
||||
// },
|
||||
// body: jsonEncode({
|
||||
// 'sub_materi_id': subMateriId, // ID submateri yang sedang dikerjakan
|
||||
// }),
|
||||
// );
|
||||
|
||||
// if (response.statusCode == 200) {
|
||||
// // Progres berhasil diupdate
|
||||
// print('Progress updated successfully');
|
||||
// // ignore: use_build_context_synchronously
|
||||
// _showCompletionDialog(context);
|
||||
// } else {
|
||||
// // Handle error
|
||||
// print('Failed to update progress');
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<void> updateProgress(int submateriId) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token'); // Get the auth token from shared preferences
|
||||
final String apiUrl = '${BaseUrl.baseUrl}/progress/$submateriId/save'; // Include submateri_id in URL
|
||||
|
||||
// Retrieve stored latihan ids from SharedPreferences
|
||||
List<int> latihanIds = prefs.getStringList('latihanIds')?.map((e) => int.parse(e)).toList() ?? [];
|
||||
|
||||
// If there are no latihan IDs, show an error and return
|
||||
if (latihanIds.isEmpty) {
|
||||
debugPrint('No latihan IDs found to update progress.');
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(apiUrl),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $authToken', // Send the auth token for authorization
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: jsonEncode({
|
||||
'latihan_ids': latihanIds, // Pass the array of latihan IDs
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// If successful, show completion dialog
|
||||
_showCompletionDialog(context);
|
||||
|
||||
// After updating, remove the stored latihan IDs from SharedPreferences
|
||||
await clearLatihanIds();
|
||||
} else {
|
||||
// Handle failure response
|
||||
print('Failed to update progress');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to remove latihan ids from SharedPreferences after the action
|
||||
Future<void> clearLatihanIds() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove('latihanIds');
|
||||
debugPrint("Latihan IDs cleared from SharedPreferences.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
void _showCompletionDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Selamat!'),
|
||||
content: Text('Kamu telah menyelesaikan latihan ini.'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // Menutup dialog
|
||||
context.go('/navigasi');
|
||||
},
|
||||
child: Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -163,7 +263,7 @@ class _PelafalanPageState extends State<PelafalanPage> {
|
|||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Pelafalan Ustadz',
|
||||
'Pelafalan Benar',
|
||||
style: TextStyle(
|
||||
color: secondPrimaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -231,7 +331,9 @@ class _PelafalanPageState extends State<PelafalanPage> {
|
|||
},
|
||||
);
|
||||
} else {
|
||||
context.go('/navigasi');
|
||||
// updateProgress(widget.id);
|
||||
|
||||
updateProgress(widget.id); // Pass submateri_id (widget.id) and latihan_ids
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
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';
|
||||
|
||||
|
@ -23,52 +24,70 @@ class MateriPage extends StatefulWidget {
|
|||
|
||||
class _MateriPageState extends State<MateriPage> {
|
||||
late Future<List<dynamic>> kategoriData;
|
||||
bool isLoading = true;
|
||||
bool isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
kategoriData = fetchKategoriData(widget.id);
|
||||
kategoriData = fetchKategoriData(widget.id);
|
||||
}
|
||||
|
||||
|
||||
Future<List<dynamic>> fetchKategoriData(int id_materi) async {
|
||||
final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/kategori/$id_materi'));
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/kategori/$id_materi'),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
isLoading = false;
|
||||
});
|
||||
return json.decode(response.body)['data'];
|
||||
return json.decode(response.body)['data'];
|
||||
} else {
|
||||
throw Exception('Failed to load kategori');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<List<dynamic>> fetchSubMateriData(int id_kategori) async {
|
||||
final response = await http.get(Uri.parse('${BaseUrl.baseUrl}/sub_materi/$id_kategori'));
|
||||
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'];
|
||||
return json.decode(response.body)['data']['sub_materi'];
|
||||
} else {
|
||||
throw Exception('Failed to load sub-materi');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> fetchProgressBySubMateri(int id_submateri) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
debugPrint("Token yang diambil: $authToken");
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/progress/$id_submateri/status'),
|
||||
headers: {'Authorization': 'Bearer $authToken'},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
return data['status']; // Pastikan status yang diterima adalah string
|
||||
} else {
|
||||
throw Exception('Failed to load progress');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(50),
|
||||
preferredSize: Size.fromHeight(50),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
margin: EdgeInsets.zero,
|
||||
child: AppBar(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
backgroundColor: secondPrimaryColor,
|
||||
title: Text(
|
||||
widget.title,
|
||||
style: TextStyle(
|
||||
|
@ -87,26 +106,26 @@ class _MateriPageState extends State<MateriPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
body: isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: 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'));
|
||||
}
|
||||
body:
|
||||
isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: 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!;
|
||||
final kategoriList = snapshot.data!;
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
@ -123,32 +142,44 @@ class _MateriPageState extends State<MateriPage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
||||
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: 16.0, vertical: 5.0),
|
||||
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(
|
||||
kategori['nama_kategori'],
|
||||
'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: 16.0,
|
||||
vertical: 5.0,
|
||||
),
|
||||
child: Text(
|
||||
kategori['nama_kategori'],
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -156,21 +187,26 @@ class _MateriPageState extends State<MateriPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
|
||||
for (var submateri in subMateriList)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 12.0,
|
||||
),
|
||||
leading: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: secondPrimaryColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius:
|
||||
BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.menu_book,
|
||||
|
@ -190,20 +226,208 @@ class _MateriPageState extends State<MateriPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
FutureBuilder<String>(
|
||||
future:
|
||||
fetchProgressBySubMateri(
|
||||
submateri['id'],
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot
|
||||
.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
return CircularProgressIndicator();
|
||||
} else if (snapshot
|
||||
.hasError) {
|
||||
return Icon(
|
||||
Icons.error,
|
||||
color: Colors.red,
|
||||
);
|
||||
} else if (snapshot.hasData) {
|
||||
final status =
|
||||
snapshot.data;
|
||||
debugPrint(
|
||||
"Status: $status",
|
||||
);
|
||||
|
||||
// return Row(
|
||||
// mainAxisSize:
|
||||
// MainAxisSize.min,
|
||||
// children: [
|
||||
// if (status == 'selesai')
|
||||
// Icon(
|
||||
// Icons
|
||||
// .check_circle_outline,
|
||||
// color: Colors.green,
|
||||
// size: 29,
|
||||
// ),
|
||||
// if (status ==
|
||||
// 'menunggu')
|
||||
// Icon(
|
||||
// Icons.pending,
|
||||
// color:
|
||||
// Colors.orange,
|
||||
// size: 29,
|
||||
// ),
|
||||
// if (status == 'gagal')
|
||||
// Icon(
|
||||
// Icons
|
||||
// .cancel_outlined,
|
||||
// color: Colors.red,
|
||||
// size: 29,
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
return Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
if (status == 'selesai')
|
||||
Icon(
|
||||
Icons
|
||||
.check_circle_outline,
|
||||
color: Colors.green,
|
||||
size: 29,
|
||||
),
|
||||
if (status ==
|
||||
'menunggu')
|
||||
Icon(
|
||||
Icons.pending,
|
||||
color:
|
||||
Colors.orange,
|
||||
size: 29,
|
||||
),
|
||||
if (status == 'gagal')
|
||||
Icon(
|
||||
Icons
|
||||
.cancel_outlined,
|
||||
color: Colors.red,
|
||||
size: 29,
|
||||
),
|
||||
// Add "Detail" button for selesai or gagal status
|
||||
if (status ==
|
||||
'selesai' ||
|
||||
status == 'gagal')
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
left: 10,
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
debugPrint(
|
||||
'tapped detail hasil penilaian',
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor:
|
||||
Colors
|
||||
.white,
|
||||
backgroundColor: secondPrimaryColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 8), // Set text color to white
|
||||
),
|
||||
child: Text(
|
||||
'Hasil',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(); // No icon if status is not 'selesai', 'menunggu', or 'gagal'
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
submateri['subtitle'],
|
||||
style: const TextStyle(fontSize: 14, color: Colors.grey),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
submateri['subtitle'],
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
FutureBuilder<String>(
|
||||
future:
|
||||
fetchProgressBySubMateri(
|
||||
submateri['id'],
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot
|
||||
.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
return SizedBox();
|
||||
} else if (snapshot
|
||||
.hasError) {
|
||||
return SizedBox();
|
||||
} else if (snapshot.hasData) {
|
||||
final status =
|
||||
snapshot.data;
|
||||
String keterangan = '';
|
||||
if (status == 'selesai') {
|
||||
keterangan =
|
||||
'Telah menyelesaikan latihan';
|
||||
} else if (status ==
|
||||
'menunggu') {
|
||||
keterangan =
|
||||
'Menunggu dikoreksi oleh guru';
|
||||
} else if (status ==
|
||||
'belum selesai') {
|
||||
keterangan =
|
||||
'Belum dikerjakan';
|
||||
} else if (status ==
|
||||
'gagal') {
|
||||
keterangan =
|
||||
'Silahkan diperbaiki';
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
top: 8.0,
|
||||
),
|
||||
child: Text(
|
||||
keterangan,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color:
|
||||
status ==
|
||||
'selesai'
|
||||
? Colors.green
|
||||
: (status ==
|
||||
'menunggu'
|
||||
? Colors
|
||||
.orange
|
||||
: (status ==
|
||||
'gagal'
|
||||
? Colors
|
||||
.red
|
||||
: secondPrimaryColor)),
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(); // Default return if there's no data
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
context.push(
|
||||
'/submateri',
|
||||
'/submateri',
|
||||
extra: {
|
||||
'id': submateri['id'],
|
||||
'title': submateri['title'],
|
||||
'description': submateri['subtitle'],
|
||||
'videoLink': submateri['video_url'],
|
||||
'description':
|
||||
submateri['subtitle'],
|
||||
'videoLink':
|
||||
submateri['video_url'],
|
||||
'intro': submateri['intro'],
|
||||
},
|
||||
);
|
||||
|
@ -217,15 +441,14 @@ class _MateriPageState extends State<MateriPage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
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 EditProfile extends StatefulWidget {
|
||||
const EditProfile({super.key});
|
||||
|
||||
@override
|
||||
_EditProfileState createState() => _EditProfileState();
|
||||
}
|
||||
|
||||
class _EditProfileState extends State<EditProfile> {
|
||||
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';
|
||||
bool _isEditing = false; // Track whether we are in edit mode
|
||||
bool _isLoading = true; // Track whether data is loading
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchUserData();
|
||||
}
|
||||
|
||||
// Fetch user data from API
|
||||
Future<void> _fetchUserData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
if (token == null) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/user'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
_fullNameController.text = data['data']['nama_lengkap'];
|
||||
_addressController.text = data['data']['alamat'];
|
||||
_dobController.text = data['data']['usia'];
|
||||
_phoneController.text = data['data']['no_telp_wali'];
|
||||
_emailController.text = data['data']['email'];
|
||||
_jenjangPendidikanController.text = data['data']['jenjang_pendidikan'];
|
||||
_gender = data['data']['jenis_kelamin'];
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update user data using API
|
||||
Future<void> _updateUserData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
if (token == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Token tidak ditemukan')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('${BaseUrl.baseUrl}/user/updateBytoken'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'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,
|
||||
}),
|
||||
);
|
||||
|
||||
// Print response status and body for debugging
|
||||
print("Response status: ${response.statusCode}");
|
||||
print("Response body: ${response.body}");
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_showSuccessDialog();
|
||||
setState(() {
|
||||
_isEditing = false;
|
||||
});
|
||||
// Optionally navigate to another page or refresh profile
|
||||
} else {
|
||||
// Handle error and print response body
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Gagal mengubah data: ${response.body}')),
|
||||
);
|
||||
print("Error response: ${response.body}"); // Print error response here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Edit Profile'),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
context.go('/navigasi');
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit_note, size: 35),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isEditing = !_isEditing; // Toggle edit mode
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 60,
|
||||
backgroundImage: AssetImage('assets/icon/defaultprofile.jpeg'),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
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 Dasar',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: whiteColor),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
_buildTextFormField(_fullNameController, 'Nama Lengkap', _isEditing),
|
||||
_buildTextFormField(_addressController, 'Alamat', _isEditing),
|
||||
_buildTextFormField(_dobController, 'Usia', _isEditing),
|
||||
_buildTextFormField(_jenjangPendidikanController, 'Jenjang Pendidikan', _isEditing),
|
||||
Row(
|
||||
children: [
|
||||
Text('Jenis Kelamin', style: TextStyle(fontSize: 16)),
|
||||
Radio<String>(
|
||||
value: 'Laki-laki',
|
||||
groupValue: _gender,
|
||||
onChanged: _isEditing
|
||||
? (value) {
|
||||
setState(() {
|
||||
_gender = value!;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
Text('Laki-laki'),
|
||||
Radio<String>(
|
||||
value: 'Perempuan',
|
||||
groupValue: _gender,
|
||||
onChanged: _isEditing
|
||||
? (value) {
|
||||
setState(() {
|
||||
_gender = value!;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
Text('Perempuan'),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
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 Kontak',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: whiteColor),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
_buildTextFormField(_phoneController, 'No WA Wali', _isEditing),
|
||||
_buildTextFormField(_emailController, 'Email', _isEditing),
|
||||
SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _isEditing ? secondPrimaryColor : Colors.grey, // Button color based on editing
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
minimumSize: Size(double.infinity, 50),
|
||||
elevation: 5, // Adding shadow to the button
|
||||
),
|
||||
onPressed: _isEditing
|
||||
? () {
|
||||
// Trigger the update user function when editing
|
||||
_updateUserData();
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
"Simpan",
|
||||
style: TextStyle(fontSize: 16, color: whiteColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to build TextFormField
|
||||
Widget _buildTextFormField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
enabled: isEnabled, // Control whether the field is enabled or not
|
||||
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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,45 +2,103 @@ 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 'dart:convert'; // Untuk parsing JSON
|
||||
import 'package:ta_tahsin/core/baseurl/base_url.dart';
|
||||
import 'package:ta_tahsin/core/theme.dart';
|
||||
|
||||
class ProfilePage extends StatelessWidget {
|
||||
class ProfilePage extends StatefulWidget {
|
||||
const ProfilePage({super.key});
|
||||
|
||||
@override
|
||||
_ProfilePageState createState() => _ProfilePageState();
|
||||
}
|
||||
|
||||
class _ProfilePageState extends State<ProfilePage> {
|
||||
String? _name;
|
||||
String? _email;
|
||||
String? _age;
|
||||
String? _phone;
|
||||
String? _guardianName;
|
||||
String? _guardianPhone;
|
||||
String? _address;
|
||||
String? _gender;
|
||||
String? _educationLevel;
|
||||
bool _isLoading = true;
|
||||
String? _errorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchUserData();
|
||||
}
|
||||
|
||||
// Fetch user data from API
|
||||
Future<void> _fetchUserData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
if (token == null) {
|
||||
setState(() {
|
||||
_errorMessage = "Token tidak ditemukan.";
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/user'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
_name = data['data']['nama_lengkap'];
|
||||
_email = data['data']['email'];
|
||||
_age = data['data']['usia'];
|
||||
_phone = data['data']['no_telp_wali'];
|
||||
_guardianName = data['data']['nama_wali'];
|
||||
_guardianPhone = data['data']['no_telp_wali'];
|
||||
_address = data['data']['alamat'];
|
||||
_gender = data['data']['jenis_kelamin'];
|
||||
_educationLevel = data['data']['jenjang_pendidikan'];
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage = "Gagal mengambil data pengguna: ${response.body}";
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Logout Function
|
||||
Future<void> logout(BuildContext context) async {
|
||||
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
if (token != null) {
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('${BaseUrl.baseUrl}/logout'),
|
||||
Uri.parse('${BaseUrl.baseUrl}/logout'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
prefs.remove('token');
|
||||
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Berhasil logout!')),
|
||||
);
|
||||
|
||||
|
||||
context.go('/login');
|
||||
context.go('/');
|
||||
} else {
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Gagal logout: ${response.body}')),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Token tidak ditemukan')),
|
||||
);
|
||||
|
@ -52,132 +110,173 @@ class ProfilePage extends StatelessWidget {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: Text(
|
||||
"Profile",
|
||||
),
|
||||
title: Text("Profile"),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
secondPrimaryColor,
|
||||
Colors.blue,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Colors.white,
|
||||
backgroundImage: AssetImage('assets/logo/sho.jpg'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"irfan",
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white),
|
||||
body: _isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: _errorMessage != null
|
||||
? Center(child: Text(_errorMessage!))
|
||||
: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
"083166408735",
|
||||
style: TextStyle(
|
||||
fontSize: 16, color: Colors.white.withOpacity(0.8)),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [secondPrimaryColor, Colors.blue],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 40,
|
||||
backgroundColor: Colors.white,
|
||||
backgroundImage: AssetImage(
|
||||
'assets/icon/defaultprofile.jpeg'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_name ?? "Nama Tidak Ditemukan",
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
_phone ?? "Nomor Telepon Tidak Ditemukan",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [secondPrimaryColor, Colors.blue],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTestResultSection("Usia", _age ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection("Email", _email ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection("Nama Wali",
|
||||
_guardianName ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection(
|
||||
"Alamat", _address ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection(
|
||||
"Jenis Kelamin", _gender ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection(
|
||||
"Jenjang Pendidikan", _educationLevel ?? "-"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
secondPrimaryColor,
|
||||
Colors.blue,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTestResultSection("Tgl Lahir", "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection("Alamat", "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection("Nama Orang Tua", "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection("email", "-"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
minimumSize: Size(double.infinity, 50),
|
||||
),
|
||||
onPressed: () {
|
||||
logout(context);
|
||||
},
|
||||
child: Text(
|
||||
"Logout",
|
||||
style: TextStyle(fontSize: 16,color: whiteColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
secondPrimaryColor,
|
||||
Colors.blue,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoLink("Edit Profile", () {
|
||||
|
||||
context.go('/edit_profile');
|
||||
}),
|
||||
Divider(color: Colors.white),
|
||||
_buildInfoLink("Progres Belajar", () {
|
||||
context.go('/progres_belajar');
|
||||
}),
|
||||
Divider(color: Colors.white),
|
||||
_buildInfoLink("Ubah Password", () {
|
||||
context.go('/ubah_password');
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
minimumSize: Size(double.infinity, 50),
|
||||
),
|
||||
onPressed: () {
|
||||
logout(context);
|
||||
},
|
||||
child: Text(
|
||||
"Logout",
|
||||
style: TextStyle(fontSize: 16, color: whiteColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildTestResultSection(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
|
@ -188,7 +287,7 @@ class ProfilePage extends StatelessWidget {
|
|||
label,
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
SizedBox(height: 6),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
|
@ -197,22 +296,25 @@ class ProfilePage extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildInfoLink(String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
Widget _buildInfoLink(String label, VoidCallback onTap) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: GestureDetector(
|
||||
onTap: onTap, // Menambahkan aksi saat item ditekan
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, // Mengatur posisi label dan ikon
|
||||
children: [
|
||||
Icon(Icons.arrow_forward, color: secondPrimaryColor),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 16, color: secondPrimaryColor),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward,
|
||||
color: Colors.white, // Anda bisa menyesuaikan warna ikon sesuai dengan desain Anda
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:http/http.dart' as http; // Import the http package
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:ta_tahsin/core/baseurl/base_url.dart';
|
||||
import 'dart:convert'; // To parse the JSON data
|
||||
import 'package:ta_tahsin/core/theme.dart';
|
||||
|
||||
class ProgresBelajarPage extends StatefulWidget {
|
||||
const ProgresBelajarPage({super.key});
|
||||
|
||||
@override
|
||||
_ProgresBelajarPageState createState() => _ProgresBelajarPageState();
|
||||
}
|
||||
|
||||
class _ProgresBelajarPageState extends State<ProgresBelajarPage> {
|
||||
bool isLoading = true;
|
||||
double progress = 0.0;
|
||||
String userName = 'Nama Pengguna'; // Default user name
|
||||
String userProfileImage = 'assets/icon/defaultprofile.jpeg'; // Default profile image
|
||||
|
||||
// Fetch the progress percentage from the API
|
||||
Future<void> fetchProgressData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
debugPrint("Token yang diambil: $authToken");
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/progres/presentase'), // Update with actual API URL
|
||||
headers: {
|
||||
'Authorization': 'Bearer $authToken', // If you need to add authentication token
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
progress = data['progress_percentage'] / 100; // Update progress as a fraction
|
||||
userName = data['nama_lengkap']; // Set user name from API response
|
||||
isLoading = false; // Set loading to false once data is fetched
|
||||
});
|
||||
} else {
|
||||
// Handle error if the API call fails
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
print('Failed to load progress data');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fetchProgressData(); // Call the function to fetch data when the page is loaded
|
||||
}
|
||||
|
||||
@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(
|
||||
"Progres Belajar",
|
||||
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.symmetric(horizontal: 16.0, vertical: 50.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Loading Spinner while fetching data
|
||||
if (isLoading)
|
||||
Center(child: CircularProgressIndicator())
|
||||
else ...[
|
||||
// Foto Profil Pengguna
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundImage: AssetImage('assets/icon/defaultprofile.jpeg'), // Use dynamic profile image
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Nama Pengguna
|
||||
Center(
|
||||
child: Text(
|
||||
userName, // Use dynamic user name
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Judul Progres Belajar
|
||||
Text(
|
||||
'Progres Belajar',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Progress Bar untuk materi
|
||||
ProgressBar(title: 'Jumlah Latihan Selesai', progress: progress),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressBar extends StatelessWidget {
|
||||
final String title;
|
||||
final double progress;
|
||||
|
||||
const ProgressBar({super.key, required this.title, required this.progress});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Linear Progress Bar dengan sudut membulat
|
||||
SizedBox(
|
||||
height: 30, // Set height of the progress bar
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15), // Membuat sudut membulat
|
||||
child: LinearProgressIndicator(
|
||||
value: progress, // The current progress value
|
||||
backgroundColor: Colors.grey[300], // Background color
|
||||
color: secondPrimaryColor, // Progress color
|
||||
minHeight: 10, // Height of the progress indicator
|
||||
),
|
||||
),
|
||||
),
|
||||
// Percentage text on top of progress bar
|
||||
Positioned(
|
||||
child: Text(
|
||||
'${(progress * 100).toStringAsFixed(1)}%', // Display percentage
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -277,7 +277,7 @@ class _DataSantriPageState extends State<DataSantriPage> {
|
|||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
context.go('/tambah_santri');
|
||||
context.push('/tambah_santri');
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
|
@ -400,7 +400,7 @@ class _DataSantriPageState extends State<DataSantriPage> {
|
|||
),
|
||||
),
|
||||
onTap: () {
|
||||
context.go('/detail_user', extra: {
|
||||
context.push('/detail_user', extra: {
|
||||
'id': santri['id'],
|
||||
});
|
||||
// router.push("/detail_user");
|
||||
|
|
|
@ -24,6 +24,10 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
final TextEditingController _jenjangPendidikanController = TextEditingController();
|
||||
String _gender = 'Laki-laki';
|
||||
bool _isEditing = false; // Track whether we are in edit mode
|
||||
String _selectedJenjangPendidikan = 'SD'; // Default value
|
||||
|
||||
// List for dropdown values
|
||||
List<String> jenjangPendidikanOptions = ['SD', 'SMP', 'SMA', 'Perguruan Tinggi'];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -141,6 +145,22 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
);
|
||||
}
|
||||
|
||||
// Function to show DatePicker
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? selectedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(), // Default current date
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
if (selectedDate != null) {
|
||||
setState(() {
|
||||
// Format selected date as 'dd/MM/yyyy'
|
||||
_dobController.text = "${selectedDate.day}/${selectedDate.month}/${selectedDate.year}";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -149,7 +169,8 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
context.go('/navigasiPengajar');
|
||||
// context.go('/navigasiPengajar');
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
|
@ -192,9 +213,9 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
),
|
||||
SizedBox(height: 16),
|
||||
_buildTextFormField(_fullNameController, 'Nama Lengkap', _isEditing),
|
||||
_buildTextFormField(_addressController, 'Alamat', _isEditing),
|
||||
_buildTextFormField(_dobController, 'Usia', _isEditing),
|
||||
_buildTextFormField(_jenjangPendidikanController, 'Jenjang Pendidikan', _isEditing),
|
||||
_buildAddressField(_addressController, 'Alamat', _isEditing),
|
||||
_buildDateOfBirthField(_dobController, 'Tanggal Lahir', _isEditing),
|
||||
_buildDropdownJenjangPendidikan( 'Jenjang Pendidikan', _isEditing),
|
||||
Row(
|
||||
children: [
|
||||
Text('Jenis Kelamin', style: TextStyle(fontSize: 16)),
|
||||
|
@ -239,32 +260,33 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
_buildTextFormField(_phoneController, 'No WA Wali', _isEditing),
|
||||
_buildTextFormField(_emailController, 'Email', _isEditing),
|
||||
_buildPhoneField(_phoneController, 'No WA Wali', _isEditing),
|
||||
_buildEmailField(_emailController, 'Email', _isEditing),
|
||||
SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _isEditing ? secondPrimaryColor : Colors.grey, // Button color based on editing
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
minimumSize: Size(double.infinity, 50),
|
||||
elevation: 5, // Adding shadow to the button
|
||||
),
|
||||
onPressed: _isEditing
|
||||
ElevatedButton(
|
||||
onPressed: _isEditing
|
||||
? () {
|
||||
// Trigger the update user function when editing
|
||||
_updateUserData();
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
"Simpan",
|
||||
style: TextStyle(fontSize: 16, color: whiteColor),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 50,
|
||||
vertical: 15,
|
||||
),
|
||||
minimumSize: Size(double.infinity, 40),
|
||||
),
|
||||
child: Text(
|
||||
'Simpan',
|
||||
style: TextStyle(
|
||||
color: whiteColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -272,7 +294,6 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
);
|
||||
}
|
||||
|
||||
// Helper function to build TextFormField
|
||||
Widget _buildTextFormField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -281,19 +302,16 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
enabled: isEnabled, // Control whether the field is enabled or not
|
||||
enabled: isEnabled,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
fillColor: Colors.grey[200], // Warna latar belakang lebih terang
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(40), // Sudut melengkung
|
||||
borderSide: BorderSide.none, // Menghilangkan border default
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.blue),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
|
@ -306,4 +324,198 @@ class _DetailDataSantriPageState extends State<DetailDataSantriPage> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAddressField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _addressController,
|
||||
keyboardType: TextInputType.multiline, // Membuka multiline pada keyboard
|
||||
maxLines: 3, // Menentukan tinggi area input, bisa lebih panjang jika diperlukan
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
enabled: isEnabled,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none, // Menghilangkan border default
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan Alamat';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Fungsi untuk input tanggal lahir
|
||||
Widget _buildDateOfBirthField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () => _selectDate(context),
|
||||
child: AbsorbPointer(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
enabled: isEnabled,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
hintText: 'Pilih Tanggal Lahir',
|
||||
suffixIcon: Icon(Icons.calendar_today, color: Colors.grey), // Menambahkan ikon kalender
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap pilih Tanggal Lahir';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownJenjangPendidikan(String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Label untuk dropdown
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
|
||||
// Dropdown untuk Jenjang Pendidikan
|
||||
DropdownButtonFormField<String>(
|
||||
value: _selectedJenjangPendidikan, // Nilai yang dipilih, diambil dari API
|
||||
onChanged: isEnabled
|
||||
? (String? newValue) {
|
||||
setState(() {
|
||||
_selectedJenjangPendidikan = newValue!;
|
||||
});
|
||||
}
|
||||
: null, // Hanya bisa diubah jika dalam mode edit
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
items: jenjangPendidikanOptions.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap pilih Jenjang Pendidikan';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Fungsi untuk input No WA Wali dengan tipe nomor dan ikon di kanan
|
||||
Widget _buildPhoneField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.phone,
|
||||
enabled: isEnabled, // Set keyboard type to phone
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
suffixIcon: Icon(Icons.phone, color: Colors.grey), // Icon for phone number on the right
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan No WA Wali';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk input Email dengan tipe email dan ikon di kanan
|
||||
Widget _buildEmailField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.emailAddress, // Set keyboard type to email
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
enabled: isEnabled,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
suffixIcon: Icon(Icons.email, color: Colors.grey), // Icon for email on the right
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan Email';
|
||||
}
|
||||
// Validasi untuk memastikan email berakhiran @gmail.com
|
||||
final regex = RegExp(r'^[a-zA-Z0-9._%+-]+@gmail\.com$');
|
||||
if (!regex.hasMatch(value)) {
|
||||
return 'Harap masukkan @gmail.com';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,14 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _fullNameController = TextEditingController();
|
||||
final TextEditingController _addressController = TextEditingController();
|
||||
final TextEditingController _dobController = TextEditingController();
|
||||
final TextEditingController _dobController = TextEditingController(); // For DOB
|
||||
final TextEditingController _phoneController = TextEditingController();
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final TextEditingController _jenjangPendidikanController = TextEditingController();
|
||||
String _gender = 'Laki-laki';
|
||||
String _selectedJenjangPendidikan = 'SD'; // Default value
|
||||
|
||||
// List for dropdown values
|
||||
List<String> jenjangPendidikanOptions = ['SD', 'SMP', 'SMA', 'Perguruan Tinggi'];
|
||||
|
||||
Future<void> _submitForm() async {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
|
@ -44,11 +47,11 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
final Map<String, String> data = {
|
||||
'nama_lengkap': _fullNameController.text,
|
||||
'alamat': _addressController.text,
|
||||
'usia': _dobController.text,
|
||||
'usia': _dobController.text, // Use formatted DOB
|
||||
'no_telp_wali': _phoneController.text,
|
||||
'email': _emailController.text,
|
||||
'jenis_kelamin': _gender,
|
||||
'jenjang_pendidikan': _jenjangPendidikanController.text,
|
||||
'jenjang_pendidikan': _selectedJenjangPendidikan, // Send selected value
|
||||
};
|
||||
|
||||
final response = await http.post(
|
||||
|
@ -74,7 +77,7 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
context.go('/navigasiPengajar');
|
||||
context.pop();
|
||||
},
|
||||
child: Text('OK'),
|
||||
),
|
||||
|
@ -91,7 +94,7 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.go('/navigasiPengajar');
|
||||
context.pop();
|
||||
},
|
||||
child: Text('OK'),
|
||||
),
|
||||
|
@ -104,16 +107,51 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
}
|
||||
}
|
||||
|
||||
// Function to show DatePicker
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? selectedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(), // Default current date
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
if (selectedDate != null) {
|
||||
setState(() {
|
||||
// Format selected date as 'dd/MM/yyyy'
|
||||
_dobController.text = "${selectedDate.day}/${selectedDate.month}/${selectedDate.year}";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Tambah Santri'),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
context.go('/navigasiPengajar');
|
||||
},
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(50),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
child: AppBar(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
title: Text(
|
||||
"Tambah Santri",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
color: Colors.white,
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
|
@ -124,9 +162,9 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
child: Column(
|
||||
children: [
|
||||
_buildTextFormField(_fullNameController, 'Nama Lengkap'),
|
||||
_buildTextFormField(_addressController, 'Alamat'),
|
||||
_buildTextFormField(_dobController, 'Usia'),
|
||||
_buildTextFormField(_jenjangPendidikanController, 'Jenjang Pendidikan'),
|
||||
_buildAddressField(_addressController,'Alamat'),
|
||||
_buildDateOfBirthField(), // Date of Birth Picker
|
||||
_buildDropdownJenjangPendidikan(), // Jenjang Pendidikan Dropdown
|
||||
Row(
|
||||
children: [
|
||||
Text('Jenis Kelamin', style: TextStyle(fontSize: 16)),
|
||||
|
@ -153,30 +191,31 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
_buildTextFormField(_phoneController, 'No WA Wali'),
|
||||
_buildTextFormField(_emailController, 'Email'),
|
||||
_buildPhoneField(), // No WA Wali input with icon on right
|
||||
_buildEmailField(), // Email input with icon on right
|
||||
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 {
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
print("Simpan Taped");
|
||||
await _submitForm();
|
||||
},
|
||||
child: Text(
|
||||
"Simpan",
|
||||
style: TextStyle(fontSize: 16, color: whiteColor),
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 50,
|
||||
vertical: 15,
|
||||
),
|
||||
minimumSize: Size(double.infinity, 40),
|
||||
),
|
||||
child: Text(
|
||||
'Simpan',
|
||||
style: TextStyle(
|
||||
color: whiteColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -185,7 +224,41 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
);
|
||||
}
|
||||
|
||||
// Fungsi pembuat TextFormField
|
||||
// Fungsi untuk input Alamat dengan tipe longtext (area teks lebih besar)
|
||||
Widget _buildAddressField(TextEditingController controller, String labelText) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _addressController,
|
||||
keyboardType: TextInputType.multiline, // Membuka multiline pada keyboard
|
||||
maxLines: 3, // Menentukan tinggi area input, bisa lebih panjang jika diperlukan
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none, // Menghilangkan border default
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan Alamat';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Fungsi pembuat TextFormField dengan desain seperti pada ubah_password.dart
|
||||
Widget _buildTextFormField(TextEditingController controller, String labelText) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -194,18 +267,15 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
fillColor: Colors.grey[200], // Warna latar belakang lebih terang
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(40), // Sudut melengkung
|
||||
borderSide: BorderSide.none, // Menghilangkan border default
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.blue),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
|
@ -218,4 +288,155 @@ class _TambahSantriPageState extends State<TambahSantriPage> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk input tanggal lahir
|
||||
Widget _buildDateOfBirthField() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Tanggal Lahir', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () => _selectDate(context),
|
||||
child: AbsorbPointer(
|
||||
child: TextFormField(
|
||||
controller: _dobController,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
hintText: 'Pilih Tanggal Lahir',
|
||||
suffixIcon: Icon(Icons.calendar_today, color: Colors.grey), // Menambahkan ikon kalender
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap pilih Tanggal Lahir';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk dropdown Jenjang Pendidikan
|
||||
Widget _buildDropdownJenjangPendidikan() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Jenjang Pendidikan', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: _selectedJenjangPendidikan,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_selectedJenjangPendidikan = newValue!;
|
||||
});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200], // Warna latar belakang lebih terang
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40), // Sudut melengkung
|
||||
borderSide: BorderSide.none, // Menghilangkan border default
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
items: jenjangPendidikanOptions.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap pilih Jenjang Pendidikan';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk input No WA Wali dengan tipe nomor dan ikon di kanan
|
||||
Widget _buildPhoneField() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('No WA Wali', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _phoneController,
|
||||
keyboardType: TextInputType.phone, // Set keyboard type to phone
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
suffixIcon: Icon(Icons.phone, color: Colors.grey), // Icon for phone number on the right
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan No WA Wali';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk input Email dengan tipe email dan ikon di kanan
|
||||
Widget _buildEmailField() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Email', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
keyboardType: TextInputType.emailAddress, // Set keyboard type to email
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
suffixIcon: Icon(Icons.email, color: Colors.grey), // Icon for email on the right
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan Email';
|
||||
}
|
||||
// Validasi untuk memastikan email berakhiran @gmail.com
|
||||
final regex = RegExp(r'^[a-zA-Z0-9._%+-]+@gmail\.com$');
|
||||
if (!regex.hasMatch(value)) {
|
||||
return 'Harap masukkan @gmail.com';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,69 +1,212 @@
|
|||
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 'dart:convert';
|
||||
import 'package:ta_tahsin/core/theme.dart';
|
||||
|
||||
class DetailKemajuanPage extends StatelessWidget {
|
||||
const DetailKemajuanPage({super.key, required this.nama});
|
||||
class DetailKemajuanPage extends StatefulWidget {
|
||||
final String nama;
|
||||
final int userId;
|
||||
const DetailKemajuanPage({super.key, required this.nama, required this.userId});
|
||||
|
||||
final String nama;
|
||||
@override
|
||||
_DetailKemajuanPageState createState() => _DetailKemajuanPageState();
|
||||
}
|
||||
|
||||
class _DetailKemajuanPageState extends State<DetailKemajuanPage> {
|
||||
bool isLoading = true;
|
||||
double progress = 0.0;
|
||||
String userProfileImage = 'assets/icon/defaultprofile.jpeg'; // Default profile image
|
||||
|
||||
// Variables to store submateri and completed submateri
|
||||
int totalSubmateri = 0;
|
||||
int completedSubmateri = 0;
|
||||
|
||||
// Fetch the progress percentage and submateri details from the API
|
||||
Future<void> fetchProgressData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
debugPrint("Token yang diambil: $authToken");
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/progres/${widget.userId}'), // Update with actual API URL
|
||||
headers: {
|
||||
'Authorization': 'Bearer $authToken', // If you need to add authentication token
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
progress = data['progress_percentage'] / 100; // Update progress as a fraction
|
||||
totalSubmateri = data['total_submateri']; // Update total submateri
|
||||
completedSubmateri = data['completed_submateri']; // Update completed submateri
|
||||
isLoading = false; // Set loading to false once data is fetched
|
||||
});
|
||||
} else {
|
||||
// Handle error if the API call fails
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
print('Failed to load progress data');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fetchProgressData(); // Call the function to fetch data when the page is loaded
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Detail Kemajuan'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),color: blackColor,
|
||||
onPressed: () {
|
||||
if (context.canPop()) {
|
||||
context.pop();
|
||||
} else {
|
||||
context.go('/navigasiPengajar');
|
||||
}
|
||||
},
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(60),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
child: AppBar(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
title: Text(
|
||||
"Progres Belajar",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
color: Colors.white,
|
||||
onPressed: () {
|
||||
context.go('/navigasiPengajar');
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Foto Profil Pengguna
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 70,
|
||||
backgroundImage: AssetImage('assets/icon/defaultprofile.jpeg'), // Ganti dengan gambar profil
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Nama Pengguna
|
||||
Center(
|
||||
child: Text(
|
||||
nama, // Ganti dengan nama pengguna
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 50.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Loading Spinner while fetching data
|
||||
if (isLoading)
|
||||
Center(child: CircularProgressIndicator())
|
||||
else ...[
|
||||
// Profile Section with CircleAvatar and Shadow
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 60,
|
||||
backgroundImage: AssetImage(userProfileImage), // Use dynamic profile image
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
spreadRadius: 3,
|
||||
blurRadius: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
// Judul Progres Belajar
|
||||
Text(
|
||||
'Progres Belajar',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Progress Bar untuk materi
|
||||
ProgressBar(title: 'Jumlah Materi 1 Selesai', progress: 0.6), // Progress 60%
|
||||
const SizedBox(height: 20),
|
||||
ProgressBar(title: 'Jumlah Materi 2 Selesai', progress: 0.4), // Progress 40%
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
// Name Section
|
||||
Center(
|
||||
child: Text(
|
||||
widget.nama, // Use dynamic user name
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Progres Belajar Title
|
||||
Text(
|
||||
'Progres Belajar',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Display total_submateri and completed_submateri in a Card
|
||||
Card(
|
||||
elevation: 4,
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Total Submateri',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$totalSubmateri',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Divider(),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Completed Submateri',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'$completedSubmateri',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Progress Bar for learning progress
|
||||
ProgressBar(title: 'Jumlah Latihan Selesai', progress: progress),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -85,16 +228,38 @@ class ProgressBar extends StatelessWidget {
|
|||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.grey[300],
|
||||
color: secondPrimaryColor, // Ganti dengan warna yang sesuai
|
||||
minHeight: 20,
|
||||
const SizedBox(height: 10),
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Linear Progress Bar with rounded corners
|
||||
SizedBox(
|
||||
height: 30,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.grey[300],
|
||||
color: secondPrimaryColor,
|
||||
minHeight: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Percentage text overlaying the progress bar
|
||||
Positioned(
|
||||
child: Text(
|
||||
'${(progress * 100).toStringAsFixed(1)}%',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -1,13 +1,167 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
// Pastikan untuk menyesuaikan import theme.dart sesuai lokasi file theme.dart Anda
|
||||
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/view/pengajar/kemajuan/model/model_data_kemajuan.dart'; // Import kemajuanList
|
||||
|
||||
class KemajuanPage extends StatelessWidget {
|
||||
class ProgressBar extends StatelessWidget {
|
||||
|
||||
final double progress;
|
||||
|
||||
const ProgressBar({super.key, required this.progress});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
const SizedBox(height: 5),
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Linear Progress Bar with rounded corners
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: LinearProgressIndicator(
|
||||
value: progress,
|
||||
backgroundColor: Colors.grey[300],
|
||||
color: secondPrimaryColor,
|
||||
minHeight: 5,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Percentage text overlaying the progress bar
|
||||
Positioned(
|
||||
child: Text(
|
||||
'${(progress * 100).toStringAsFixed(1)}%',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KemajuanPage extends StatefulWidget {
|
||||
const KemajuanPage({super.key});
|
||||
|
||||
@override
|
||||
State<KemajuanPage> createState() => _KemajuanPageState();
|
||||
}
|
||||
|
||||
class _KemajuanPageState extends State<KemajuanPage> {
|
||||
List<dynamic> kemajuanList = []; // Menyimpan list kemajuan
|
||||
List<dynamic> filteredKemajuanList = []; // Menyimpan list hasil filter
|
||||
bool isLoading = true; // Menandakan apakah data sedang dimuat
|
||||
String searchQuery = ""; // Menyimpan query pencarian
|
||||
|
||||
// Menyimpan status progres yang sudah dimuat
|
||||
Set<int> loadedUserIds = Set();
|
||||
|
||||
// Fungsi untuk mengambil data kemajuan
|
||||
Future<void> fetchKemajuanData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/users/progres'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var data = json.decode(response.body);
|
||||
setState(() {
|
||||
kemajuanList = data['data']; // Menyimpan data kemajuan
|
||||
filteredKemajuanList = kemajuanList;
|
||||
isLoading = false;
|
||||
});
|
||||
|
||||
// Setelah data kemajuan berhasil dimuat, panggil fetchProgressData untuk setiap user yang belum dimuat
|
||||
for (var kemajuan in kemajuanList) {
|
||||
if (!loadedUserIds.contains(kemajuan['user_id'])) {
|
||||
fetchProgressData(kemajuan['user_id']);
|
||||
loadedUserIds.add(kemajuan['user_id']); // Tandai user_id yang sudah dimuat
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to fetch data: ${response.body}')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
print('Error occurred: $e');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Error occurred: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk mengambil data progres berdasarkan userId
|
||||
Future<void> fetchProgressData(int userId) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
debugPrint("Token yang diambil: $authToken");
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/progres/$userId'), // API URL yang sesuai
|
||||
headers: {
|
||||
'Authorization': 'Bearer $authToken',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
// Menyimpan progress untuk setiap user
|
||||
final index = kemajuanList.indexWhere((item) => item['user_id'] == userId);
|
||||
if (index != -1) {
|
||||
kemajuanList[index]['progress_percentage'] = data['progress_percentage']; // Menambahkan progres
|
||||
}
|
||||
});
|
||||
} else {
|
||||
print('Failed to load progress data');
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk memfilter data berdasarkan nama
|
||||
void filterKemajuan(String query) {
|
||||
final filtered = kemajuanList.where((kemajuan) {
|
||||
final nama = kemajuan['nama_lengkap'].toLowerCase();
|
||||
final search = query.toLowerCase();
|
||||
return nama.contains(search); // Pencarian berdasarkan nama lengkap
|
||||
}).toList();
|
||||
|
||||
setState(() {
|
||||
filteredKemajuanList = filtered; // Update list yang ditampilkan
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fetchKemajuanData(); // Memanggil fungsi saat halaman pertama kali dibuka
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -22,6 +176,12 @@ class KemajuanPage extends StatelessWidget {
|
|||
children: [
|
||||
// Search bar
|
||||
TextField(
|
||||
onChanged: (query) {
|
||||
setState(() {
|
||||
searchQuery = query;
|
||||
filterKemajuan(searchQuery);
|
||||
});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
hintText: 'Cari Santri...',
|
||||
|
@ -40,102 +200,108 @@ class KemajuanPage extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
secondPrimaryColor, // Gunakan secondPrimaryColor dari theme.dart
|
||||
color: secondPrimaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Menggunakan SliverList dengan kemajuanList
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
// Mengambil data kemajuan dari kemajuanList
|
||||
var kemajuan = kemajuanList[index];
|
||||
// Jika loading, tampilkan loading indicator
|
||||
if (isLoading)
|
||||
Center(child: CircularProgressIndicator())
|
||||
else if (filteredKemajuanList.isEmpty)
|
||||
Center(child: Text('Tidak ada progres latihan yang ditemukan.'))
|
||||
else
|
||||
// Menggunakan SliverList dengan kemajuanList yang sudah difilter
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
var kemajuan = filteredKemajuanList[index];
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Menampilkan detail subMateri, menggunakan subMateri yang terkait
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(
|
||||
vertical: 5.0,
|
||||
),
|
||||
leading: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
shape:
|
||||
BoxShape
|
||||
.circle, // Membuat kontainer berbentuk bulat
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/icon/${kemajuan['image']}',
|
||||
), // Gambar asset lokal
|
||||
fit:
|
||||
BoxFit
|
||||
.cover, // Gambar akan menyesuaikan dengan ukuran kontainer
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
kemajuan['nama'], // Menampilkan nama
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 5.0),
|
||||
leading: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/icon/defaultprofile.jpeg',
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
kemajuan['jilid'], // Menampilkan jilid
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
kemajuan['nama_lengkap'],
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
kemajuan['no_telp_wali'],
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
// Menggunakan widget ProgressBar
|
||||
ProgressBar(
|
||||
progress: kemajuan['progress_percentage'] != null
|
||||
? kemajuan['progress_percentage'] / 100
|
||||
: 0.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
context.go('/detail_kemajuan', extra: {
|
||||
'nama': kemajuan['nama_lengkap'],
|
||||
'user_id': kemajuan['user_id'],
|
||||
});
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
context.go('/detail_kemajuan',
|
||||
extra: {'nama': kemajuan['nama']},
|
||||
);
|
||||
},
|
||||
),
|
||||
Divider(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
thickness: 1,
|
||||
indent: 80,
|
||||
),
|
||||
],
|
||||
Divider(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
thickness: 1,
|
||||
indent: 40,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount:
|
||||
kemajuanList
|
||||
.length, // Menyesuaikan jumlah item yang ada pada kemajuanList
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: filteredKemajuanList.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,522 @@
|
|||
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 EditProfilePengajar extends StatefulWidget {
|
||||
const EditProfilePengajar({super.key});
|
||||
|
||||
@override
|
||||
_EditProfilePengajarState createState() => _EditProfilePengajarState();
|
||||
}
|
||||
|
||||
class _EditProfilePengajarState extends State<EditProfilePengajar> {
|
||||
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';
|
||||
bool _isEditing = false; // Track whether we are in edit mode
|
||||
bool _isLoading = true; // Track whether data is loading
|
||||
String _selectedJenjangPendidikan = 'SD'; // Default value
|
||||
|
||||
// List for dropdown values
|
||||
List<String> jenjangPendidikanOptions = ['SD', 'SMP', 'SMA', 'Perguruan Tinggi'];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchUserData();
|
||||
}
|
||||
|
||||
// Fetch user data from API
|
||||
Future<void> _fetchUserData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
if (token == null) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/user'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
_fullNameController.text = data['data']['nama_lengkap'];
|
||||
_addressController.text = data['data']['alamat'];
|
||||
_dobController.text = data['data']['usia'];
|
||||
_phoneController.text = data['data']['no_telp_wali'];
|
||||
_emailController.text = data['data']['email'];
|
||||
_jenjangPendidikanController.text = data['data']['jenjang_pendidikan'];
|
||||
_gender = data['data']['jenis_kelamin'];
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update user data using API
|
||||
Future<void> _updateUserData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
if (token == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Token tidak ditemukan')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('${BaseUrl.baseUrl}/user/updateBytoken'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'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,
|
||||
}),
|
||||
);
|
||||
|
||||
// Print response status and body for debugging
|
||||
print("Response status: ${response.statusCode}");
|
||||
print("Response body: ${response.body}");
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_showSuccessDialog();
|
||||
setState(() {
|
||||
_isEditing = false;
|
||||
});
|
||||
// Optionally navigate to another page or refresh profile
|
||||
} else {
|
||||
// Handle error and print response body
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Gagal mengubah data: ${response.body}')),
|
||||
);
|
||||
print("Error response: ${response.body}"); // Print error response here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? selectedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(), // Default current date
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
if (selectedDate != null) {
|
||||
setState(() {
|
||||
// Format selected date as 'dd/MM/yyyy'
|
||||
_dobController.text = "${selectedDate.day}/${selectedDate.month}/${selectedDate.year}";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Edit Profile Pengajar'),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit_note, size: 35),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isEditing = !_isEditing; // Toggle edit mode
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 60,
|
||||
backgroundImage: AssetImage('assets/icon/defaultprofile.jpeg'),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
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 Dasar',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: whiteColor),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
_buildTextFormField(_fullNameController, 'Nama Lengkap', _isEditing),
|
||||
_buildAddressField(_addressController, 'Alamat', _isEditing),
|
||||
_buildDateOfBirthField(_dobController, 'Tanggal Lahir', _isEditing),
|
||||
_buildDropdownJenjangPendidikan( 'Jenjang Pendidikan', _isEditing),
|
||||
Row(
|
||||
children: [
|
||||
Text('Jenis Kelamin', style: TextStyle(fontSize: 16)),
|
||||
Radio<String>(
|
||||
value: 'Laki-laki',
|
||||
groupValue: _gender,
|
||||
onChanged: _isEditing
|
||||
? (value) {
|
||||
setState(() {
|
||||
_gender = value!;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
Text('Laki-laki'),
|
||||
Radio<String>(
|
||||
value: 'Perempuan',
|
||||
groupValue: _gender,
|
||||
onChanged: _isEditing
|
||||
? (value) {
|
||||
setState(() {
|
||||
_gender = value!;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
Text('Perempuan'),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
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 Kontak',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: whiteColor),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
_buildPhoneField(_phoneController, 'No WA Wali', _isEditing),
|
||||
_buildEmailField(_emailController, 'Email', _isEditing),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _isEditing
|
||||
? () {
|
||||
// Trigger the update user function when editing
|
||||
_updateUserData();
|
||||
}
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 50,
|
||||
vertical: 15,
|
||||
),
|
||||
minimumSize: Size(double.infinity, 40),
|
||||
),
|
||||
child: Text(
|
||||
'Simpan',
|
||||
style: TextStyle(
|
||||
color: whiteColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextFormField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
enabled: isEnabled,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200], // Warna latar belakang lebih terang
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40), // Sudut melengkung
|
||||
borderSide: BorderSide.none, // Menghilangkan border default
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan $labelText Anda';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAddressField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _addressController,
|
||||
keyboardType: TextInputType.multiline, // Membuka multiline pada keyboard
|
||||
maxLines: 3, // Menentukan tinggi area input, bisa lebih panjang jika diperlukan
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
enabled: isEnabled,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none, // Menghilangkan border default
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan Alamat';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Fungsi untuk input tanggal lahir
|
||||
Widget _buildDateOfBirthField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () => _selectDate(context),
|
||||
child: AbsorbPointer(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
enabled: isEnabled,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
hintText: 'Pilih Tanggal Lahir',
|
||||
suffixIcon: Icon(Icons.calendar_today, color: Colors.grey), // Menambahkan ikon kalender
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap pilih Tanggal Lahir';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownJenjangPendidikan(String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Label untuk dropdown
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
|
||||
// Dropdown untuk Jenjang Pendidikan
|
||||
DropdownButtonFormField<String>(
|
||||
value: _selectedJenjangPendidikan, // Nilai yang dipilih, diambil dari API
|
||||
onChanged: isEnabled
|
||||
? (String? newValue) {
|
||||
setState(() {
|
||||
_selectedJenjangPendidikan = newValue!;
|
||||
});
|
||||
}
|
||||
: null, // Hanya bisa diubah jika dalam mode edit
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
items: jenjangPendidikanOptions.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap pilih Jenjang Pendidikan';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Fungsi untuk input No WA Wali dengan tipe nomor dan ikon di kanan
|
||||
Widget _buildPhoneField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.phone,
|
||||
enabled: isEnabled, // Set keyboard type to phone
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
suffixIcon: Icon(Icons.phone, color: Colors.grey), // Icon for phone number on the right
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan No WA Wali';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk input Email dengan tipe email dan ikon di kanan
|
||||
Widget _buildEmailField(TextEditingController controller, String labelText, bool isEnabled) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(labelText, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.emailAddress, // Set keyboard type to email
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction, // Validasi otomatis saat input
|
||||
enabled: isEnabled,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
suffixIcon: Icon(Icons.email, color: Colors.grey), // Icon for email on the right
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Harap masukkan Email';
|
||||
}
|
||||
// Validasi untuk memastikan email berakhiran @gmail.com
|
||||
final regex = RegExp(r'^[a-zA-Z0-9._%+-]+@gmail\.com$');
|
||||
if (!regex.hasMatch(value)) {
|
||||
return 'Harap masukkan @gmail.com';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
@ -5,9 +7,74 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
import 'package:ta_tahsin/core/baseurl/base_url.dart';
|
||||
import 'package:ta_tahsin/core/theme.dart';
|
||||
|
||||
class PengajarProfilePage extends StatelessWidget {
|
||||
class PengajarProfilePage extends StatefulWidget {
|
||||
const PengajarProfilePage({super.key});
|
||||
|
||||
@override
|
||||
State<PengajarProfilePage> createState() => _PengajarProfilePageState();
|
||||
}
|
||||
|
||||
class _PengajarProfilePageState extends State<PengajarProfilePage> {
|
||||
String? _name;
|
||||
String? _email;
|
||||
String? _age;
|
||||
String? _phone;
|
||||
String? _guardianName;
|
||||
String? _guardianPhone;
|
||||
String? _address;
|
||||
String? _gender;
|
||||
String? _educationLevel;
|
||||
bool _isLoading = true;
|
||||
String? _errorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchUserData();
|
||||
}
|
||||
|
||||
Future<void> _fetchUserData() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? token = prefs.getString('token');
|
||||
|
||||
if (token == null) {
|
||||
setState(() {
|
||||
_errorMessage = "Token tidak ditemukan.";
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('${BaseUrl.baseUrl}/user'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
setState(() {
|
||||
_name = data['data']['nama_lengkap'];
|
||||
_email = data['data']['email'];
|
||||
_age = data['data']['usia'];
|
||||
_phone = data['data']['no_telp_wali'];
|
||||
_guardianName = data['data']['nama_wali'];
|
||||
_guardianPhone = data['data']['no_telp_wali'];
|
||||
_address = data['data']['alamat'];
|
||||
_gender = data['data']['jenis_kelamin'];
|
||||
_educationLevel = data['data']['jenjang_pendidikan'];
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage = "Gagal mengambil data pengguna: ${response.body}";
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> logout(BuildContext context) async {
|
||||
// Ambil token yang ada di SharedPreferences
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
@ -23,22 +90,68 @@ class PengajarProfilePage extends StatelessWidget {
|
|||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Jika berhasil logout, hapus token dari SharedPreferences
|
||||
prefs.remove('token');
|
||||
|
||||
// Arahkan pengguna ke halaman login atau halaman lain setelah logout
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Berhasil logout!')),
|
||||
);
|
||||
// If logout is successful, remove the token from SharedPreferences
|
||||
prefs.remove('token');
|
||||
|
||||
// Show success SnackBar
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: Colors.white), // Success icon
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Berhasil logout!',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.green, // Background color for success
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
topRight: Radius.circular(10),
|
||||
bottomLeft: Radius.zero,
|
||||
bottomRight: Radius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Navigate to the login page after logout
|
||||
// ignore: use_build_context_synchronously
|
||||
context.go('/');
|
||||
} else {
|
||||
// If there’s an error from the server, show an error SnackBar
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.white), // Error icon
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Gagal logout: ${response.body}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.red, // Background color for error
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
topRight: Radius.circular(10),
|
||||
bottomLeft: Radius.zero,
|
||||
bottomRight: Radius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
context.go('/login');
|
||||
} else {
|
||||
// Jika ada error dari server
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Gagal logout: ${response.body}')),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Jika tidak ada token yang tersimpan
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
@ -52,132 +165,169 @@ class PengajarProfilePage extends StatelessWidget {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: Text(
|
||||
"Profile",
|
||||
),
|
||||
title: Text("Profile"),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Profile Section with Gradient Card
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
secondPrimaryColor,
|
||||
Colors.blue,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
// Profile Avatar
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Colors.white,
|
||||
backgroundImage: AssetImage('assets/logo/sho.jpg'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Name and Phone Number to the right
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"irfan",
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white),
|
||||
body: _isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: _errorMessage != null
|
||||
? Center(child: Text(_errorMessage!))
|
||||
: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
"083166408735",
|
||||
style: TextStyle(
|
||||
fontSize: 16, color: Colors.white.withOpacity(0.8)),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [secondPrimaryColor, Colors.blue],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 40,
|
||||
backgroundColor: Colors.white,
|
||||
backgroundImage: AssetImage(
|
||||
'assets/icon/defaultprofile.jpeg'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_name ?? "Nama Tidak Ditemukan",
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
_phone ?? "Nomor Telepon Tidak Ditemukan",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [secondPrimaryColor, Colors.blue],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTestResultSection("Tanggal Lahir", _age ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection("Email", _email ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection("Nama Wali",
|
||||
_guardianName ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection(
|
||||
"Alamat", _address ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection(
|
||||
"Jenis Kelamin", _gender ?? "-"),
|
||||
Divider(color: Colors.white),
|
||||
_buildTestResultSection(
|
||||
"Jenjang Pendidikan", _educationLevel ?? "-"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// "Hasil Placement Test" Section with Gradient Card
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
secondPrimaryColor,
|
||||
Colors.blue,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTestResultSection("Tgl Lahir", "-"),
|
||||
Divider(color: Colors.white), // White Divider for contrast
|
||||
_buildTestResultSection("Alamat", "-"),
|
||||
Divider(color: Colors.white), // White Divider for contrast
|
||||
_buildTestResultSection("Nama Orang Tua", "-"),
|
||||
Divider(color: Colors.white), // White Divider for contrast
|
||||
_buildTestResultSection("email", "-"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
minimumSize: Size(double.infinity, 50),
|
||||
),
|
||||
onPressed: () {
|
||||
logout(context);
|
||||
},
|
||||
child: Text(
|
||||
"Logout",
|
||||
style: TextStyle(fontSize: 16,color: whiteColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
secondPrimaryColor,
|
||||
Colors.blue,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoLink("Edit Profile", () {
|
||||
|
||||
context.push('/edit_profile_pengajar');
|
||||
}),
|
||||
Divider(color: Colors.white),
|
||||
_buildInfoLink("Ubah Password", () {
|
||||
context.push('/ubah_password_pengajar');
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
minimumSize: Size(double.infinity, 50),
|
||||
),
|
||||
onPressed: () {
|
||||
logout(context);
|
||||
},
|
||||
child: Text(
|
||||
"Logout",
|
||||
style: TextStyle(fontSize: 16, color: whiteColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Method to build test result sections
|
||||
Widget _buildTestResultSection(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
|
@ -188,7 +338,7 @@ class PengajarProfilePage extends StatelessWidget {
|
|||
label,
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 6), // Space between label and value
|
||||
SizedBox(height: 6),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
|
@ -197,22 +347,25 @@ class PengajarProfilePage extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Method to create info links
|
||||
Widget _buildInfoLink(String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
Widget _buildInfoLink(String label, VoidCallback onTap) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: GestureDetector(
|
||||
onTap: onTap, // Menambahkan aksi saat item ditekan
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, // Mengatur posisi label dan ikon
|
||||
children: [
|
||||
Icon(Icons.arrow_forward, color: secondPrimaryColor),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 16, color: secondPrimaryColor),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward,
|
||||
color: Colors.white, // Anda bisa menyesuaikan warna ikon sesuai dengan desain Anda
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,281 @@
|
|||
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 ChangePasswordPengajarPage extends StatefulWidget {
|
||||
const ChangePasswordPengajarPage({super.key});
|
||||
|
||||
@override
|
||||
_ChangePasswordPengajarPageState createState() => _ChangePasswordPengajarPageState();
|
||||
}
|
||||
|
||||
class _ChangePasswordPengajarPageState extends State<ChangePasswordPengajarPage> {
|
||||
final _newPasswordController = TextEditingController();
|
||||
final _confirmPasswordController = TextEditingController();
|
||||
String? _errorMessage;
|
||||
|
||||
bool showNewPassword = true;
|
||||
bool showConfirmPassword = true;
|
||||
|
||||
// Update visibility for New Password field
|
||||
void updateNewPasswordVisibility() {
|
||||
setState(() {
|
||||
showNewPassword = !showNewPassword;
|
||||
});
|
||||
}
|
||||
|
||||
// Update visibility for Confirm Password field
|
||||
void updateConfirmPasswordVisibility() {
|
||||
setState(() {
|
||||
showConfirmPassword = !showConfirmPassword;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> handleChangePassword() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? authToken = prefs.getString('token');
|
||||
debugPrint("Token yang diambil: $authToken");
|
||||
|
||||
// Validasi panjang password minimal 8 karakter
|
||||
if (_newPasswordController.text.length < 8) {
|
||||
setState(() {
|
||||
_errorMessage = 'Password harus minimal 8 karakter';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validasi jika password baru dan konfirmasi password tidak cocok
|
||||
if (_newPasswordController.text != _confirmPasswordController.text) {
|
||||
setState(() {
|
||||
_errorMessage = 'Password baru dan konfirmasi password tidak cocok';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_errorMessage = null; // Reset error message
|
||||
});
|
||||
|
||||
// URL API untuk mengubah password
|
||||
String url = '${BaseUrl.baseUrl}/change_password'; // Ganti dengan URL API Anda
|
||||
debugPrint("URL yang digunakan: $url");
|
||||
|
||||
// Data yang akan dikirim
|
||||
Map<String, dynamic> requestBody = {
|
||||
'new_password': _newPasswordController.text,
|
||||
'new_password_confirmation': _confirmPasswordController.text,
|
||||
};
|
||||
|
||||
debugPrint("Request Body: $requestBody");
|
||||
|
||||
// Kirim request ke API
|
||||
final response = await http.post(
|
||||
Uri.parse(url),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $authToken',
|
||||
'Content-Type': 'application/json', // Pastikan header ini ada
|
||||
},
|
||||
body: jsonEncode(requestBody),
|
||||
);
|
||||
|
||||
debugPrint("Status code: ${response.statusCode}");
|
||||
debugPrint("Response body: ${response.body}");
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// If the password change is successful
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: Colors.white), // Success icon
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Password berhasil diubah',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.green, // Background color for success
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
topRight: Radius.circular(10),
|
||||
bottomLeft: Radius.zero,
|
||||
bottomRight: Radius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
context.pop();
|
||||
} else {
|
||||
final responseBody = json.decode(response.body);
|
||||
setState(() {
|
||||
// Displaying error message if available
|
||||
_errorMessage = responseBody['message']['new_password']?.first ?? 'Terjadi kesalahan, coba lagi';
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.white), // Failure icon
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${json.decode(response.body)['data']['message']}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.red, // Background color for failure
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
topRight: Radius.circular(10),
|
||||
bottomLeft: Radius.zero,
|
||||
bottomRight: Radius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@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(
|
||||
"Ubah Password",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
color: Colors.white,
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 50.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Password Input Field
|
||||
Text(
|
||||
'Password Baru',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _newPasswordController,
|
||||
obscureText: showNewPassword,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
showNewPassword ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: updateNewPasswordVisibility,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200], // Lighter background for the field
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none, // Remove default border
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Confirm Password Input Field
|
||||
Text(
|
||||
'Konfirmasi Password',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _confirmPasswordController,
|
||||
obscureText: showConfirmPassword,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
showConfirmPassword ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: updateConfirmPasswordVisibility,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey[200], // Lighter background for the field
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
borderSide: BorderSide.none, // Remove default border
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Error Message
|
||||
if (_errorMessage != null)
|
||||
Text(
|
||||
_errorMessage!,
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
// Send Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
handleChangePassword();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: secondPrimaryColor,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 50,
|
||||
vertical: 15,
|
||||
),
|
||||
minimumSize: Size(double.infinity, 40),
|
||||
),
|
||||
child: Text(
|
||||
'Selesai',
|
||||
style: TextStyle(
|
||||
color: whiteColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -345,7 +345,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.4.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
|
|
|
@ -49,6 +49,7 @@ dependencies:
|
|||
file_picker: ^10.1.9
|
||||
excel: ^4.0.6
|
||||
record: ^6.0.0
|
||||
http_parser: ^4.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -77,6 +78,7 @@ flutter:
|
|||
- assets/logo/
|
||||
- assets/audio/
|
||||
- assets/icon/
|
||||
- assets/images/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
|
Loading…
Reference in New Issue