MIF_E31230549/lib/kader/profile_kader.dart

483 lines
16 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 '../kader/dashboard_kader.dart';
class ProfileKaderPage extends StatefulWidget {
const ProfileKaderPage({super.key});
@override
State<ProfileKaderPage> createState() => _ProfileKaderPageState();
}
class _ProfileKaderPageState extends State<ProfileKaderPage> {
final TextEditingController namaC = TextEditingController();
final TextEditingController emailC = TextEditingController();
final TextEditingController passwordC = TextEditingController();
final TextEditingController roleC = TextEditingController();
final TextEditingController desaIdC = TextEditingController();
final TextEditingController dusuIdC = TextEditingController();
final TextEditingController noHpC = TextEditingController();
final TextEditingController statusC = TextEditingController();
String? idUser;
String? fotoUser;
XFile? _pickedFile;
bool _obscurePassword = true;
bool isEditMode = false;
@override
void initState() {
super.initState();
_loadUserData();
}
@override
void dispose() {
namaC.dispose();
emailC.dispose();
passwordC.dispose();
roleC.dispose();
desaIdC.dispose();
dusuIdC.dispose();
noHpC.dispose();
statusC.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') ?? "";
namaC.text = prefs.getString('nama') ?? "";
emailC.text = prefs.getString('email') ?? "";
passwordC.text = prefs.getString('password') ?? "";
roleC.text = (prefs.getString('role') ?? "Kader").toLowerCase();
desaIdC.text = prefs.getString('nama_desa') ?? "-";
dusuIdC.text = prefs.getString('nama_dusun') ?? "-";
String savedNoHp = prefs.getString('no_hp') ?? "";
noHpC.text = (savedNoHp.isEmpty || savedNoHp == "-") ? "-" : savedNoHp;
statusC.text = prefs.getString('status_aktif') ?? "Aktif";
fotoUser = prefs.getString('foto');
});
}
void _backToDashboard() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const DashboardKaderPage()),
(route) => false,
);
}
void _showEditWarning() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Klik tombol edit di bawah untuk mengubah foto."),
backgroundColor: Colors.red,
),
);
}
Future<void> _pickImage() async {
if (!isEditMode) {
_showEditWarning();
return;
}
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_pickedFile = image;
});
}
}
void _removeImage() {
if (!isEditMode) {
_showEditWarning();
return;
}
setState(() {
_pickedFile = null;
fotoUser = "";
});
}
void _enableEdit() {
setState(() {
isEditMode = true;
});
}
bool _validatePassword(String value) {
String pattern = r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6}$';
RegExp regExp = RegExp(pattern);
return regExp.hasMatch(value);
}
bool _validateNoHp(String value) {
String pattern = r'^[0-9]{10,13}$';
RegExp regExp = RegExp(pattern);
return regExp.hasMatch(value);
}
void _saveProfile() async {
if (!isEditMode) return;
if (!_validateNoHp(noHpC.text)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("No. HP harus berupa angka dan berjumlah 10-13 digit!"),
backgroundColor: Colors.red,
),
);
return;
}
if (!_validatePassword(passwordC.text)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Password harus 6 digit (kombinasi huruf & angka)!"),
backgroundColor: Colors.red,
),
);
return;
}
try {
var request = http.MultipartRequest(
'POST',
Uri.parse(
"http://ta.myhost.id/E31230549/mposyandu_api/users/update_profile_kader.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 ?? "";
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('password', passwordC.text);
await prefs.setString('no_hp', noHpC.text);
String newFoto = result['foto'] ?? fotoUser;
await prefs.setString('foto', newFoto);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Profil berhasil diperbarui")),
);
setState(() {
fotoUser = newFoto;
isEditMode = false;
_pickedFile = null;
});
} else {
throw result['message'];
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Gagal menyimpan: $e")),
);
}
}
@override
Widget build(BuildContext context) {
bool hasPhoto =
_pickedFile != null || (fotoUser != null && fotoUser!.isNotEmpty);
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
_backToDashboard();
},
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: _backToDashboard,
),
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.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
),
padding: const EdgeInsets.only(bottom: 30),
child: Column(
children: [
Stack(
children: [
CircleAvatar(
key: ValueKey(fotoUser), // Mengatasi lag update foto
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?t=${DateTime.now().millisecondsSinceEpoch}")
: null,
child: !hasPhoto
? const Icon(Icons.person,
size: 60, color: Colors.blue)
: null,
),
),
Positioned(
bottom: 0,
right: hasPhoto ? 40 : 0,
child: GestureDetector(
onTap: _pickImage,
child: const CircleAvatar(
radius: 18,
backgroundColor: Colors.black,
child: Icon(Icons.camera_alt,
color: Colors.white, size: 18),
),
),
),
if (hasPhoto)
Positioned(
bottom: 0,
right: 0,
child: GestureDetector(
onTap: _removeImage,
child: const CircleAvatar(
radius: 18,
backgroundColor: Colors.red,
child: 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,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
_buildField("Nama Lengkap", namaC),
_buildField("Email", emailC),
_buildPasswordField(
"Password (6 Digit Huruf & Angka)", passwordC),
_buildField("Role User", roleC, isReadOnly: true),
Row(
children: [
Expanded(
child: _buildField("Nama Desa", desaIdC,
isReadOnly: true)),
const SizedBox(width: 10),
Expanded(
child: _buildField("Nama Dusun", dusuIdC,
isReadOnly: true)),
],
),
_buildField("No.HP", noHpC, isNumber: true),
_buildField("Status", statusC, isReadOnly: true),
],
),
),
),
const SizedBox(height: 30),
Row(
children: [
Expanded(
child: _buildActionButton(
label: "Edit Profil",
color: Colors.orange,
onTap: _enableEdit,
),
),
const SizedBox(width: 15),
Expanded(
child: _buildActionButton(
label: "Simpan",
color: Colors.blue,
onTap: _saveProfile,
),
),
],
),
],
),
),
],
),
),
),
);
}
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,
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
),
],
),
);
}
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,
maxLength: 6,
style: GoogleFonts.poppins(fontSize: 14),
decoration: InputDecoration(
counterText: "",
filled: true,
fillColor: !isEditMode ? Colors.grey[100] : Colors.white,
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility),
onPressed: () =>
setState(() => _obscurePassword = !_obscurePassword),
),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
),
],
),
);
}
Widget _buildActionButton(
{required String label,
required Color color,
required VoidCallback onTap}) {
return OutlinedButton(
onPressed: onTap,
style: OutlinedButton.styleFrom(
foregroundColor: color,
side: BorderSide(color: color, width: 1.5),
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
backgroundColor: Colors.white,
),
child: Text(label,
style:
GoogleFonts.poppins(fontWeight: FontWeight.bold, fontSize: 14)),
);
}
}