589 lines
22 KiB
Dart
589 lines
22 KiB
Dart
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import '../pages/login_page.dart';
|
|
import '../ibu/dashboard_ibu.dart';
|
|
|
|
class ProfileIbuPage extends StatefulWidget {
|
|
const ProfileIbuPage({super.key});
|
|
|
|
@override
|
|
State<ProfileIbuPage> createState() => _ProfileIbuPageState();
|
|
}
|
|
|
|
class _ProfileIbuPageState extends State<ProfileIbuPage> {
|
|
// --- CONTROLLER TABEL USERS ---
|
|
final TextEditingController namaC = TextEditingController();
|
|
final TextEditingController emailC = TextEditingController();
|
|
final TextEditingController passwordC = TextEditingController();
|
|
final TextEditingController noHpC = TextEditingController();
|
|
final TextEditingController roleC = TextEditingController();
|
|
final TextEditingController statusC = TextEditingController();
|
|
|
|
// --- CONTROLLER TABEL IBU ---
|
|
final TextEditingController nikC = TextEditingController();
|
|
final TextEditingController noKkC = TextEditingController();
|
|
final TextEditingController namaSuamiC = TextEditingController();
|
|
final TextEditingController tempatLahirC = TextEditingController();
|
|
final TextEditingController tanggalLahirC = TextEditingController();
|
|
final TextEditingController pendidikanC = TextEditingController();
|
|
final TextEditingController pekerjaanC = TextEditingController();
|
|
final TextEditingController alamatDetailC = TextEditingController();
|
|
final TextEditingController desaC = TextEditingController();
|
|
final TextEditingController dusunC = TextEditingController();
|
|
|
|
// Variabel State
|
|
String? idUser;
|
|
String? fotoUser;
|
|
String? namaKader;
|
|
XFile? _pickedFile;
|
|
bool _obscurePassword = true;
|
|
bool isEditMode = false;
|
|
|
|
// Variabel Dropdown
|
|
String? selectedAgama;
|
|
String? selectedGoldar;
|
|
|
|
final List<String> listAgama = [
|
|
'Islam',
|
|
'Kristen',
|
|
'Katolik',
|
|
'Hindu',
|
|
'Budha',
|
|
'Khonghucu'
|
|
];
|
|
final List<String> listGoldar = ['A', 'B', 'AB', 'O', '-'];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadUserData();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
namaC.dispose();
|
|
emailC.dispose();
|
|
passwordC.dispose();
|
|
noHpC.dispose();
|
|
roleC.dispose();
|
|
statusC.dispose();
|
|
nikC.dispose();
|
|
noKkC.dispose();
|
|
namaSuamiC.dispose();
|
|
tempatLahirC.dispose();
|
|
tanggalLahirC.dispose();
|
|
pendidikanC.dispose();
|
|
pekerjaanC.dispose();
|
|
alamatDetailC.dispose();
|
|
desaC.dispose();
|
|
dusunC.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadUserData() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final isLogin = prefs.getBool('isLogin') ?? false;
|
|
|
|
if (!isLogin) {
|
|
if (!mounted) return;
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const LoginPage(fromGuard: true)),
|
|
(route) => false,
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
idUser = prefs.getString('id_user') ?? "";
|
|
fotoUser = prefs.getString('foto');
|
|
namaKader = prefs.getString('nama_kader') ?? "Kader";
|
|
|
|
namaC.text = prefs.getString('nama') ?? "";
|
|
emailC.text = prefs.getString('email') ?? "";
|
|
passwordC.text = prefs.getString('password') ?? "";
|
|
noHpC.text = prefs.getString('no_hp') ?? "";
|
|
roleC.text = "Ibu";
|
|
statusC.text = prefs.getString('status_aktif') ?? "Aktif";
|
|
|
|
nikC.text = prefs.getString('nik') ?? "-";
|
|
noKkC.text = prefs.getString('no_kk') ?? "-";
|
|
namaSuamiC.text = prefs.getString('nama_suami') ?? "";
|
|
tempatLahirC.text = prefs.getString('tempat_lahir') ?? "-";
|
|
tanggalLahirC.text = prefs.getString('tanggal_lahir') ?? "-";
|
|
pendidikanC.text = prefs.getString('pendidikan') ?? "";
|
|
pekerjaanC.text = prefs.getString('pekerjaan') ?? "";
|
|
alamatDetailC.text = prefs.getString('alamat_detail') ?? "";
|
|
desaC.text = prefs.getString('nama_desa') ?? "-";
|
|
dusunC.text = prefs.getString('nama_dusun') ?? "-";
|
|
|
|
String? dbAgama = prefs.getString('agama');
|
|
if (listAgama.contains(dbAgama)) selectedAgama = dbAgama;
|
|
|
|
String? dbGoldar = prefs.getString('golongan_darah');
|
|
if (listGoldar.contains(dbGoldar)) selectedGoldar = dbGoldar;
|
|
});
|
|
}
|
|
|
|
void _showError(String msg) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(msg), backgroundColor: Colors.red),
|
|
);
|
|
}
|
|
|
|
// Fungsi untuk menangani klik foto/hapus saat belum edit
|
|
void _warnEditFirst() {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text(
|
|
"Klik tombol edit di bawah untuk mengubah atau menghapus foto"),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _pickImage() async {
|
|
if (!isEditMode) {
|
|
_warnEditFirst();
|
|
return;
|
|
}
|
|
final ImagePicker picker = ImagePicker();
|
|
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
|
|
if (image != null) setState(() => _pickedFile = image);
|
|
}
|
|
|
|
void _removeImage() {
|
|
if (!isEditMode) {
|
|
_warnEditFirst();
|
|
return;
|
|
}
|
|
setState(() {
|
|
_pickedFile = null;
|
|
fotoUser = "";
|
|
});
|
|
}
|
|
|
|
void _saveProfile() async {
|
|
if (!isEditMode) return;
|
|
if (namaC.text.isEmpty || emailC.text.isEmpty) {
|
|
_showError("Nama dan Email tidak boleh kosong");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var request = http.MultipartRequest(
|
|
'POST',
|
|
Uri.parse(
|
|
"http://ta.myhost.id/E31230549/mposyandu_api/users/update_profile_ibu.php"),
|
|
);
|
|
|
|
request.fields['id_user'] = idUser ?? "";
|
|
request.fields['nama'] = namaC.text;
|
|
request.fields['email'] = emailC.text;
|
|
request.fields['password'] = passwordC.text;
|
|
request.fields['no_hp'] = noHpC.text;
|
|
request.fields['foto_lama'] = fotoUser ?? "";
|
|
request.fields['nama_suami'] = namaSuamiC.text;
|
|
request.fields['golongan_darah'] = selectedGoldar ?? "";
|
|
request.fields['pendidikan'] = pendidikanC.text;
|
|
request.fields['pekerjaan'] = pekerjaanC.text;
|
|
request.fields['agama'] = selectedAgama ?? "";
|
|
request.fields['alamat_detail'] = alamatDetailC.text;
|
|
|
|
if (_pickedFile != null) {
|
|
Uint8List data = await _pickedFile!.readAsBytes();
|
|
request.files.add(http.MultipartFile.fromBytes('foto', data,
|
|
filename: _pickedFile!.name));
|
|
}
|
|
|
|
var response = await request.send();
|
|
var responseData = await response.stream.bytesToString();
|
|
var result = json.decode(responseData);
|
|
|
|
if (result['status'] == 'success') {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString('nama', namaC.text);
|
|
await prefs.setString('email', emailC.text);
|
|
await prefs.setString('no_hp', noHpC.text);
|
|
await prefs.setString('nama_suami', namaSuamiC.text);
|
|
await prefs.setString('golongan_darah', selectedGoldar ?? "");
|
|
await prefs.setString('agama', selectedAgama ?? "");
|
|
|
|
if (result['foto'] != null)
|
|
await prefs.setString('foto', result['foto']);
|
|
|
|
setState(() {
|
|
isEditMode = false;
|
|
_pickedFile = null;
|
|
if (result['foto'] != null) fotoUser = result['foto'];
|
|
});
|
|
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Profil berhasil diperbarui")),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
_showError("Gagal menyimpan: $e");
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool hasPhoto =
|
|
_pickedFile != null || (fotoUser != null && fotoUser!.isNotEmpty);
|
|
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvokedWithResult: (didPop, result) async {
|
|
if (didPop) return;
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const DashboardIbuPage(),
|
|
),
|
|
(route) => false,
|
|
);
|
|
},
|
|
child: Scaffold(
|
|
backgroundColor: const Color(0xfff4f6fb),
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.blue,
|
|
elevation: 0,
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|
onPressed: () {
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => const DashboardIbuPage()),
|
|
(route) => false,
|
|
);
|
|
},
|
|
),
|
|
title: Text("",
|
|
style: GoogleFonts.poppins(color: Colors.white, fontSize: 18)),
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
decoration: const BoxDecoration(
|
|
color: Colors.blue,
|
|
borderRadius:
|
|
BorderRadius.vertical(bottom: Radius.circular(30)),
|
|
),
|
|
padding: const EdgeInsets.only(bottom: 30),
|
|
child: Column(
|
|
children: [
|
|
Stack(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 65,
|
|
backgroundColor: Colors.white,
|
|
child: CircleAvatar(
|
|
radius: 60,
|
|
backgroundColor: Colors.blue.shade100,
|
|
backgroundImage: _pickedFile != null
|
|
? (kIsWeb
|
|
? NetworkImage(_pickedFile!.path)
|
|
: FileImage(File(_pickedFile!.path))
|
|
as ImageProvider)
|
|
: (fotoUser != null && fotoUser!.isNotEmpty)
|
|
? NetworkImage(
|
|
"http://ta.myhost.id/E31230549/mposyandu_api/uploads/$fotoUser")
|
|
: null,
|
|
child: !hasPhoto
|
|
? const Icon(Icons.person,
|
|
size: 60, color: Colors.blue)
|
|
: null,
|
|
),
|
|
),
|
|
// Tombol Kamera
|
|
Positioned(
|
|
bottom: 0,
|
|
right: hasPhoto ? 40 : 0,
|
|
child: GestureDetector(
|
|
onTap: _pickImage,
|
|
child: CircleAvatar(
|
|
radius: 18,
|
|
backgroundColor:
|
|
isEditMode ? Colors.black : Colors.grey,
|
|
child: const Icon(Icons.camera_alt,
|
|
color: Colors.white, size: 18),
|
|
),
|
|
),
|
|
),
|
|
// Tombol Hapus
|
|
if (hasPhoto)
|
|
Positioned(
|
|
bottom: 0,
|
|
right: 0,
|
|
child: GestureDetector(
|
|
onTap: _removeImage,
|
|
child: CircleAvatar(
|
|
radius: 18,
|
|
backgroundColor:
|
|
isEditMode ? Colors.red : Colors.grey,
|
|
child: const Icon(Icons.delete,
|
|
color: Colors.white, size: 18),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
namaC.text,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
children: [
|
|
Card(
|
|
color: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionTitle("Informasi Akun & Kontak"),
|
|
_buildField("Nama Lengkap", namaC),
|
|
_buildField("Email", emailC),
|
|
_buildPasswordField("Password", passwordC),
|
|
_buildField("No. HP", noHpC, isNumber: true),
|
|
const Divider(height: 30),
|
|
_buildSectionTitle("Informasi Resmi"),
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
margin: const EdgeInsets.only(bottom: 15),
|
|
decoration: BoxDecoration(
|
|
color: Colors.amber.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border:
|
|
Border.all(color: Colors.amber.shade200),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const Icon(Icons.info_outline,
|
|
color: Colors.amber, size: 20),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(
|
|
"Jika ada kesalahan data pada kolom di bawah ini, silahkan hubungi kader: $namaKader",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 11,
|
|
color: Colors.brown.shade700,
|
|
fontStyle: FontStyle.italic),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
_buildField("NIK", nikC, isReadOnly: true),
|
|
_buildField("No. KK", noKkC, isReadOnly: true),
|
|
_buildField("Tempat Lahir", tempatLahirC,
|
|
isReadOnly: true),
|
|
_buildField("Tanggal Lahir", tanggalLahirC,
|
|
isReadOnly: true),
|
|
_buildField("Desa", desaC, isReadOnly: true),
|
|
_buildField("Dusun", dusunC, isReadOnly: true),
|
|
const Divider(height: 30),
|
|
_buildSectionTitle("Data Tambahan"),
|
|
_buildField("Nama Suami", namaSuamiC),
|
|
_buildDropdown(
|
|
"Golongan Darah",
|
|
selectedGoldar,
|
|
listGoldar,
|
|
(val) => setState(() => selectedGoldar = val)),
|
|
_buildField("Pendidikan", pendidikanC),
|
|
_buildField("Pekerjaan", pekerjaanC),
|
|
_buildDropdown("Agama", selectedAgama, listAgama,
|
|
(val) => setState(() => selectedAgama = val)),
|
|
_buildField("Alamat Detail", alamatDetailC),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 25),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildActionButton(
|
|
label: "Edit Profil",
|
|
color: Colors.orange,
|
|
onTap: () =>
|
|
setState(() => isEditMode = true))),
|
|
const SizedBox(width: 15),
|
|
Expanded(
|
|
child: _buildActionButton(
|
|
label: "Simpan",
|
|
color: Colors.blue,
|
|
onTap: _saveProfile)),
|
|
],
|
|
),
|
|
const SizedBox(height: 40),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionTitle(String title) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 10),
|
|
child: Text(title,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blueGrey)),
|
|
);
|
|
}
|
|
|
|
Widget _buildDropdown(String label, String? value, List<String> items,
|
|
Function(String?) onChanged) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 15),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey[600])),
|
|
const SizedBox(height: 5),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
decoration: BoxDecoration(
|
|
color: !isEditMode ? Colors.grey[100] : Colors.white,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<String>(
|
|
value: value,
|
|
isExpanded: true,
|
|
items: items
|
|
.map((s) => DropdownMenuItem(
|
|
value: s,
|
|
child:
|
|
Text(s, style: GoogleFonts.poppins(fontSize: 14))))
|
|
.toList(),
|
|
onChanged: isEditMode ? onChanged : null,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildField(String label, TextEditingController controller,
|
|
{bool isReadOnly = false, bool isNumber = false}) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 15),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey[600])),
|
|
const SizedBox(height: 5),
|
|
TextField(
|
|
controller: controller,
|
|
readOnly: isReadOnly || !isEditMode,
|
|
keyboardType: isNumber ? TextInputType.phone : TextInputType.text,
|
|
style: GoogleFonts.poppins(fontSize: 14),
|
|
decoration: InputDecoration(
|
|
filled: true,
|
|
fillColor:
|
|
(isReadOnly || !isEditMode) ? Colors.grey[100] : Colors.white,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: Colors.grey.shade300)),
|
|
contentPadding:
|
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPasswordField(String label, TextEditingController controller) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 15),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey[600])),
|
|
const SizedBox(height: 5),
|
|
TextField(
|
|
controller: controller,
|
|
obscureText: _obscurePassword,
|
|
readOnly: !isEditMode,
|
|
style: GoogleFonts.poppins(fontSize: 14),
|
|
decoration: InputDecoration(
|
|
filled: true,
|
|
fillColor: !isEditMode ? Colors.grey[100] : Colors.white,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: Colors.grey.shade300)),
|
|
suffixIcon: IconButton(
|
|
icon: Icon(
|
|
_obscurePassword ? Icons.visibility_off : Icons.visibility),
|
|
onPressed: () =>
|
|
setState(() => _obscurePassword = !_obscurePassword),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildActionButton(
|
|
{required String label,
|
|
required Color color,
|
|
required VoidCallback onTap}) {
|
|
return ElevatedButton(
|
|
onPressed: onTap,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: color,
|
|
padding: const EdgeInsets.symmetric(vertical: 15),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
),
|
|
child: Text(label,
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.white, fontWeight: FontWeight.bold)),
|
|
);
|
|
}
|
|
}
|