MIF_E31231027/smart_cycling/lib/screens/profile_screen.dart

415 lines
18 KiB
Dart

import 'dart:ui';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/foundation.dart'; // import kIsWeb
import 'package:http/http.dart' as http;
import '../utils/session_manager.dart';
import '../utils/api_config.dart';
import '../main.dart'; // import themeNotifier
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
bool _isUploading = false;
// Fungsi untuk mengunggah foto profil
Future<void> _pickAndUploadPhoto() async {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery, imageQuality: 70);
if (pickedFile != null && mounted) {
setState(() => _isUploading = true);
try {
var request = http.MultipartRequest('POST', Uri.parse('${ApiConfig.baseUrl}/upload_photo.php'));
request.fields['id_users'] = SessionManager.userId.toString();
if (kIsWeb) {
// Web needs readAsBytes since path is just a blob URL
final bytes = await pickedFile.readAsBytes();
request.files.add(http.MultipartFile.fromBytes(
'photo',
bytes,
filename: pickedFile.name,
));
} else {
// Android/Mobile can use path
request.files.add(await http.MultipartFile.fromPath('photo', pickedFile.path));
}
var response = await request.send();
if (response.statusCode == 200) {
var responseData = await response.stream.bytesToString();
var data = jsonDecode(responseData);
// Check for 'status' instead of 'success' to match backend
if (data['status'] == 'success') {
setState(() {
SessionManager.userPhoto = data['photo_url']; // Save url photo
});
if(mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Foto profil diunggah!')));
} else {
if(mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Gagal: ${data['message']}')));
}
}
} catch (e) {
if(mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Gagal unggah foto: $e')));
}
setState(() => _isUploading = false);
}
}
// Fungsi Hapus Akun
void _deleteAccount() {
showDialog(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
title: const Text("Hapus Akun Permanen", style: TextStyle(color: Colors.red)),
content: const Text("Apakah Anda yakin ingin menghapus akun ini secara permanen? Data yang dihapus tidak dapat dipulihkan."),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text("Batal", style: TextStyle(color: Colors.grey)),
),
ElevatedButton(
onPressed: () async {
Navigator.pop(ctx); // Tutup dialog konfirmasi
try {
final response = await http.post(
Uri.parse('${ApiConfig.baseUrl}/delete_account.php'),
body: {'id_users': SessionManager.userId.toString()},
);
final data = jsonDecode(response.body);
if (data['status'] == 'success' && mounted) {
// Kosongkan sesi lokal
SessionManager.userId = null;
SessionManager.userName = null;
SessionManager.userEmail = null;
SessionManager.userPhoto = null;
// Tampilkan pop-up keberhasilan
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Column(
children: [
Icon(Icons.check_circle_outline, color: Colors.greenAccent[400], size: 70),
const SizedBox(height: 16),
const Text("Akun Berhasil Dihapus", style: TextStyle(fontWeight: FontWeight.bold)),
],
),
content: const Text(
"Akun Anda telah dihapus secara permanen.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 15),
),
);
},
);
// Berikan jeda 2.5 detik agar user sempat membaca popup
await Future.delayed(const Duration(milliseconds: 2500));
if (mounted) {
Navigator.pushNamedAndRemoveUntil(context, '/welcome', (route) => false);
}
} else {
if(mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Gagal menghapus: ${data['message']}')));
}
} catch(e) {
if(mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Terjadi kesalahan koneksi saat menghapus akun')));
}
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
child: const Text("Hapus", style: TextStyle(color: Colors.white)),
),
],
);
}
);
}
// Fungsi untuk dummy dialog pusat bantuan
void _showInfoDialog(String title, String content) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
content: Text(content),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text("Tutup")),
],
)
);
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: SingleChildScrollView(
child: Column(
children: [
// --- KARTU HEADER PROFIL DENGAN GRADIENT ---
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 70, left: 24, right: 24, bottom: 40),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF2196F3), Color(0xFF00BCD4)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
),
child: Row(
children: [
// Avatar dengan GestureDetector untuk Edit Photo
GestureDetector(
onTap: _isUploading ? null : _pickAndUploadPhoto,
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: Stack(
alignment: Alignment.center,
children: [
CircleAvatar(
radius: 38,
backgroundColor: Colors.blueAccent,
backgroundImage: (SessionManager.userPhoto != null && SessionManager.userPhoto!.isNotEmpty)
? NetworkImage("${ApiConfig.baseUrl}/${SessionManager.userPhoto}?t=${DateTime.now().millisecondsSinceEpoch}")
: null,
child: (SessionManager.userPhoto == null || SessionManager.userPhoto!.isEmpty)
? const Icon(Icons.person, size: 45, color: Colors.white)
: null,
),
if (_isUploading)
const CircularProgressIndicator(color: Colors.white)
else
Positioned(
bottom: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(color: Colors.black54, shape: BoxShape.circle),
child: const Icon(Icons.camera_alt, color: Colors.white, size: 14),
),
)
],
),
),
),
const SizedBox(width: 20),
// Info nama dan email
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
SessionManager.userName ?? "Pengguna",
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
SessionManager.userEmail ?? "Tidak ada data",
style: const TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
],
),
),
],
),
),
// --- KONTEN MENU PROFIL ---
Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
// --- BAGIAN PENGATURAN AKUN ---
_buildSectionHeader("Akun", isDark),
_buildGlassMenuTile(Icons.person_outline, "Edit Profil", () async {
final updated = await Navigator.pushNamed(context, '/edit_profile');
// Jika data kembali bernilai true, refresh halaman profil untuk menampilkan nama baru
if (updated == true) {
setState(() {});
}
}, isDark),
// Dark Mode Switch ditaruh di Akun
_buildGlassMenuTile(
themeNotifier.value == ThemeMode.dark ? Icons.dark_mode : Icons.light_mode,
"Mode Gelap",
() {
setState(() {
if(themeNotifier.value == ThemeMode.dark) {
themeNotifier.value = ThemeMode.light;
} else {
themeNotifier.value = ThemeMode.dark;
}
});
},
isDark,
trailingWidget: Switch(
value: themeNotifier.value == ThemeMode.dark,
onChanged: (val) {
setState(() {
themeNotifier.value = val ? ThemeMode.dark : ThemeMode.light;
});
},
activeColor: Colors.blueAccent,
),
),
const SizedBox(height: 24),
// --- BAGIAN DUKUNGAN & LAINNYA ---
_buildSectionHeader("Dukungan & Lainnya", isDark),
_buildGlassMenuTile(Icons.help_outline, "Pusat Bantuan", () => _showInfoDialog("Pusat Bantuan", "Jika Anda memiliki kendala teknis atau saran perbaikan, hubungi tim kami di cs@smartcycling.com atau hubungi layanan Hotline bebas pulsa kami 1500-099."), isDark),
_buildGlassMenuTile(Icons.info_outline, "Tentang Aplikasi", () => _showInfoDialog("Tentang Aplikasi", "Smart Cycling App Versi 1.0.\nDikembangkan secara khusus untuk memberikan rekomendasi rute bagi pesepeda secara efisien."), isDark),
_buildGlassMenuTile(Icons.privacy_tip_outlined, "Kebijakan Privasi", () => _showInfoDialog("Kebijakan Privasi", "Informasi lokasi dan preferensi rute Anda tersimpan dengan aman hanya pada sesi aktif. Kami tidak memperjualbelikan rekam jejak pengguna."), isDark),
const SizedBox(height: 32),
// --- FITUR HAPUS AKUN & KELUAR ---
_buildGlassMenuTile(
Icons.delete_forever,
"Hapus Akun",
_deleteAccount,
isDark,
iconColor: Colors.orangeAccent,
textColor: Colors.orangeAccent,
showChevron: false,
),
_buildGlassMenuTile(
Icons.logout,
"Keluar",
() {
// Tampilkan pop-up konfirmasi
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Konfirmasi Keluar", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.red)),
content: const Text("Apakah anda yakin ingin keluar?"),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(), // Tutup dialog
child: const Text("Batal", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold)),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Tutup dialog
// Kosongkan sesi lokal
SessionManager.userId = null;
SessionManager.userName = null;
SessionManager.userEmail = null;
SessionManager.userPhoto = null;
Navigator.pushNamedAndRemoveUntil(context, '/welcome', (route) => false); // Arahkan ke welcome & bersihkan stack
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text("Keluar", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
],
);
},
);
},
isDark,
iconColor: Colors.redAccent,
textColor: Colors.redAccent,
showChevron: false, // Hilangkan panah khusus untuk tombol keluar
),
const SizedBox(height: 24),
],
),
),
],
),
),
);
}
// --- HELPER WIDGETS ---
Widget _buildSectionHeader(String title, bool isDark) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, bottom: 8.0, top: 4.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white70 : Colors.black87,
),
),
),
);
}
Widget _buildGlassMenuTile(IconData icon, String title, VoidCallback onTap, bool isDark, {Color? iconColor, Color? textColor, bool showChevron = true, Widget? trailingWidget}) {
Color defaultTxColor = isDark ? Colors.white : Colors.black87;
Color defaultIcColor = isDark ? Colors.white70 : Colors.black54;
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: isDark ? Colors.white.withOpacity(0.1) : Colors.white.withOpacity(0.55), // Transparansi efek kaca yang disesuaikan tema
borderRadius: BorderRadius.circular(15),
border: Border.all(color: isDark ? Colors.white.withOpacity(0.2) : Colors.white.withOpacity(0.9), width: 1.5),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.02),
blurRadius: 10,
spreadRadius: 1,
offset: const Offset(0, 4),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(15),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), // Fitur blur khas glassmorphism
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 4.0),
leading: Icon(icon, color: iconColor ?? defaultIcColor, size: 26),
title: Text(
title,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: textColor ?? defaultTxColor),
),
trailing: trailingWidget ?? (showChevron ? Icon(Icons.chevron_right, color: isDark ? Colors.white38 : Colors.black38, size: 20) : null),
onTap: onTap,
),
),
),
);
}
}