MIF_E31230549/lib/ibu/profile_ibu.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)),
);
}
}