Perbarui model dan tampilan untuk menambahkan properti fotoProfil di DonaturModel, PetugasDesaModel, dan WargaModel. Modifikasi controller dan tampilan untuk mendukung pengambilan dan penampilan foto profil pengguna. Tambahkan fungsionalitas baru untuk menampilkan foto profil di berbagai tampilan, termasuk detail penerima dan dashboard warga. Perbarui rute aplikasi untuk mencakup halaman profil pengguna.

This commit is contained in:
Khafidh Fuadi
2025-03-25 12:21:37 +07:00
parent 8e9553d1fc
commit 32736be867
19 changed files with 2138 additions and 752 deletions

View File

@ -3,6 +3,7 @@ import 'package:penyaluran_app/app/data/models/penerima_penyaluran_model.dart';
import 'package:penyaluran_app/app/data/models/pengaduan_model.dart';
import 'package:penyaluran_app/app/data/models/pengajuan_kelayakan_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/warga_model.dart';
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
import 'package:penyaluran_app/app/services/supabase_service.dart';
import 'package:flutter/material.dart';
@ -13,6 +14,9 @@ class WargaDashboardController extends GetxController {
final Rx<BaseUserModel?> currentUser = Rx<BaseUserModel?>(null);
// Variabel untuk foto profil
final RxString fotoProfil = ''.obs;
// Indeks tab yang aktif di bottom navigation bar
final RxInt activeTabIndex = 0.obs;
@ -55,6 +59,48 @@ class WargaDashboardController extends GetxController {
String? get desa => user?.desa?.nama;
// Getter untuk alamat dan noHp
String? get alamat {
if (_authController.isWarga && _authController.roleData != null) {
return (_authController.roleData as WargaModel).alamat;
}
return null;
}
String? get noHp {
if (_authController.isWarga && _authController.roleData != null) {
return (_authController.roleData as WargaModel).noHp;
}
return null;
}
// Getter untuk foto profil
String? get profilePhotoUrl {
// 1. Coba ambil dari fotoProfil yang sudah disimpan
if (fotoProfil.isNotEmpty) {
return fotoProfil.value;
}
// 2. Coba ambil dari roleData jika merupakan WargaModel
if (_authController.isWarga && _authController.roleData != null) {
final wargaData = _authController.roleData as WargaModel;
if (wargaData.fotoProfil != null && wargaData.fotoProfil!.isNotEmpty) {
return wargaData.fotoProfil;
}
}
// 3. Coba ambil dari userData.roleData.fotoProfil
final userData = _authController.userData;
if (userData != null && userData.roleData is WargaModel) {
final wargaData = userData.roleData as WargaModel;
if (wargaData.fotoProfil != null && wargaData.fotoProfil!.isNotEmpty) {
return wargaData.fotoProfil;
}
}
return null;
}
@override
void onInit() {
super.onInit();
@ -81,12 +127,45 @@ class WargaDashboardController extends GetxController {
print('DEBUG WARGA: User adalah warga');
var wargaData = _authController.roleData;
print('DEBUG WARGA: Data warga: ${wargaData?.namaLengkap}');
// Ambil foto profil dari wargaData jika ada
if (wargaData != null &&
wargaData.fotoProfil != null &&
wargaData.fotoProfil!.isNotEmpty) {
fotoProfil.value = wargaData.fotoProfil!;
print('DEBUG WARGA: Foto profil: ${fotoProfil.value}');
}
} else {
print('DEBUG WARGA: User bukan warga');
}
} else {
print('DEBUG WARGA: userData null');
}
// Cek dan ambil foto profil jika belum ada
if (fotoProfil.isEmpty) {
_fetchProfilePhoto();
}
}
// Metode untuk mengambil foto profil
Future<void> _fetchProfilePhoto() async {
try {
if (user?.id == null) return;
final wargaData = await _supabaseService.client
.from('warga')
.select('foto_profil')
.eq('user_id', user!.id)
.single();
if (wargaData != null && wargaData['foto_profil'] != null) {
fotoProfil.value = wargaData['foto_profil'];
print('DEBUG WARGA: Foto profil dari API: ${fotoProfil.value}');
}
} catch (e) {
print('Error fetching profile photo: $e');
}
}
void fetchData() async {

View File

@ -41,111 +41,256 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
}
Widget _buildWelcomeSection() {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
return Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.1),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: Colors.blue.shade100,
child: Icon(
Icons.person,
color: Colors.blue.shade700,
size: 28,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Selamat Datang,',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
Text(
controller.nama,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [Colors.white, Colors.blue.shade50],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
Row(
children: [
Icon(
Icons.home,
size: 16,
color: Colors.grey.shade600,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'Alamat tidak tersedia',
style: TextStyle(
color: Colors.grey.shade700,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.blue.shade200, width: 2),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Hero(
tag: 'warga-profile',
child: CircleAvatar(
radius: 30,
backgroundColor: Colors.blue.shade100,
backgroundImage: controller.profilePhotoUrl != null
? NetworkImage(controller.profilePhotoUrl!)
: null,
child: controller.profilePhotoUrl == null
? Icon(
Icons.person,
color: Colors.blue.shade700,
size: 30,
)
: null,
),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Selamat Datang,',
style: TextStyle(
fontSize: 14,
color: Colors.blue.shade700,
fontWeight: FontWeight.w500,
),
),
Text(
controller.nama,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue.shade900,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.phone,
size: 16,
child: Column(
children: [
_buildInfoRow(
icon: Icons.home_rounded,
iconColor: Colors.blue.shade300,
label: 'Alamat',
value: controller.alamat ?? 'Alamat tidak tersedia',
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Divider(height: 1),
),
_buildInfoRow(
icon: Icons.phone_rounded,
iconColor: Colors.green.shade300,
label: 'No. HP',
value: controller.noHp ?? 'No. HP tidak tersedia',
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Divider(height: 1),
),
_buildInfoRow(
icon: Icons.location_city_rounded,
iconColor: Colors.amber.shade300,
label: 'Desa',
value: controller.desa ?? 'Desa tidak tersedia',
),
],
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildActionButton(
icon: Icons.edit_rounded,
label: 'Edit Profil',
color: Colors.blue.shade700,
onTap: () => Get.toNamed(Routes.PROFILE),
),
),
const SizedBox(width: 10),
Expanded(
child: _buildActionButton(
icon: Icons.notifications_rounded,
label: 'Notifikasi',
color: Colors.amber.shade700,
onTap: () => Get.toNamed(Routes.NOTIFIKASI),
),
),
],
),
],
),
),
),
);
}
Widget _buildInfoRow({
required IconData icon,
required Color iconColor,
required String label,
required String value,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
size: 16,
color: iconColor,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
const SizedBox(width: 8),
Text(
'No. HP tidak tersedia',
style: TextStyle(
color: Colors.grey.shade700,
),
),
const SizedBox(height: 2),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.location_city,
size: 16,
color: Colors.grey.shade600,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
);
}
Widget _buildActionButton({
required IconData icon,
required String label,
required Color color,
required VoidCallback onTap,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Ink(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 18,
color: color,
),
const SizedBox(width: 8),
Text(
label,
style: TextStyle(
color: color,
fontWeight: FontWeight.w600,
),
const SizedBox(width: 8),
Text(
controller.desa ?? 'Desa tidak tersedia',
style: TextStyle(
color: Colors.grey.shade700,
),
),
],
),
],
),
],
),
),
),
);