From b1665307c51d8b750e87254f1f857f682b4285c0 Mon Sep 17 00:00:00 2001 From: Khafidh Fuadi Date: Sat, 8 Mar 2025 10:20:57 +0700 Subject: [PATCH] Tambahkan dependensi dan konfigurasi awal proyek - Tambahkan dependensi utama: GetX, Supabase, SharedPreferences - Konfigurasi struktur awal aplikasi dengan GetX - Inisialisasi layanan Supabase - Perbarui konfigurasi plugin untuk berbagai platform - Ganti template default dengan struktur aplikasi baru --- .env | 2 + lib/app/data/models/user_model.dart | 81 +++ lib/app/data/providers/auth_provider.dart | 147 +++++ .../modules/auth/bindings/auth_binding.dart | 11 + .../auth/controllers/auth_controller.dart | 512 ++++++++++++++++++ .../auth/views/complete_profile_view.dart | 270 +++++++++ lib/app/modules/auth/views/login_view.dart | 133 +++++ lib/app/modules/auth/views/register_view.dart | 140 +++++ .../dashboard/bindings/dashboard_binding.dart | 11 + .../controllers/dashboard_controller.dart | 39 ++ .../views/donatur_dashboard_view.dart | 229 ++++++++ .../views/petugas_desa_dashboard_view.dart | 220 ++++++++ .../petugas_verifikasi_dashboard_view.dart | 216 ++++++++ .../dashboard/views/warga_dashboard_view.dart | 303 +++++++++++ .../modules/home/bindings/home_binding.dart | 11 + .../home/controllers/home_controller.dart | 14 + lib/app/modules/home/views/home_view.dart | 63 +++ lib/app/routes/app_pages.dart | 63 +++ lib/app/routes/app_routes.dart | 26 + lib/app/services/supabase_service.dart | 268 +++++++++ lib/main.dart | 129 +---- linux/flutter/generated_plugin_registrant.cc | 8 + linux/flutter/generated_plugins.cmake | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 8 + pubspec.lock | 439 ++++++++++++++- pubspec.yaml | 18 +- .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 28 files changed, 3259 insertions(+), 112 deletions(-) create mode 100644 .env create mode 100644 lib/app/data/models/user_model.dart create mode 100644 lib/app/data/providers/auth_provider.dart create mode 100644 lib/app/modules/auth/bindings/auth_binding.dart create mode 100644 lib/app/modules/auth/controllers/auth_controller.dart create mode 100644 lib/app/modules/auth/views/complete_profile_view.dart create mode 100644 lib/app/modules/auth/views/login_view.dart create mode 100644 lib/app/modules/auth/views/register_view.dart create mode 100644 lib/app/modules/dashboard/bindings/dashboard_binding.dart create mode 100644 lib/app/modules/dashboard/controllers/dashboard_controller.dart create mode 100644 lib/app/modules/dashboard/views/donatur_dashboard_view.dart create mode 100644 lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart create mode 100644 lib/app/modules/dashboard/views/petugas_verifikasi_dashboard_view.dart create mode 100644 lib/app/modules/dashboard/views/warga_dashboard_view.dart create mode 100644 lib/app/modules/home/bindings/home_binding.dart create mode 100644 lib/app/modules/home/controllers/home_controller.dart create mode 100644 lib/app/modules/home/views/home_view.dart create mode 100644 lib/app/routes/app_pages.dart create mode 100644 lib/app/routes/app_routes.dart create mode 100644 lib/app/services/supabase_service.dart diff --git a/.env b/.env new file mode 100644 index 0000000..f6529e4 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +SUPABASE_URL=http://labulabs.net:8000 +SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzMxODYyODAwLAogICJleHAiOiAxODg5NjI5MjAwCn0.4IpwhwCVbfYXxb8JlZOLSBzCt6kQmypkvuso7N8Aicc \ No newline at end of file diff --git a/lib/app/data/models/user_model.dart b/lib/app/data/models/user_model.dart new file mode 100644 index 0000000..f987642 --- /dev/null +++ b/lib/app/data/models/user_model.dart @@ -0,0 +1,81 @@ +class UserModel { + final String id; + final String email; + final String? name; + final String? avatar; + final String role; + final bool isActive; + final DateTime? lastLogin; + final DateTime? createdAt; + final DateTime? updatedAt; + + UserModel({ + required this.id, + required this.email, + this.name, + this.avatar, + required this.role, + this.isActive = true, + this.lastLogin, + this.createdAt, + this.updatedAt, + }); + + factory UserModel.fromJson(Map json) { + return UserModel( + id: json['id'], + email: json['email'], + name: json['name'], + avatar: json['avatar'], + role: json['role'] ?? 'WARGA', + isActive: json['is_active'] ?? true, + lastLogin: json['last_login'] != null + ? DateTime.parse(json['last_login']) + : null, + createdAt: json['CREATED_AT'] != null + ? DateTime.parse(json['CREATED_AT']) + : null, + updatedAt: json['UPDATED_AT'] != null + ? DateTime.parse(json['UPDATED_AT']) + : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'email': email, + 'name': name, + 'avatar': avatar, + 'role': role, + 'is_active': isActive, + 'last_login': lastLogin?.toIso8601String(), + 'CREATED_AT': createdAt?.toIso8601String(), + 'UPDATED_AT': updatedAt?.toIso8601String(), + }; + } + + UserModel copyWith({ + String? id, + String? email, + String? name, + String? avatar, + String? role, + bool? isActive, + DateTime? lastLogin, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return UserModel( + id: id ?? this.id, + email: email ?? this.email, + name: name ?? this.name, + avatar: avatar ?? this.avatar, + role: role ?? this.role, + isActive: isActive ?? this.isActive, + lastLogin: lastLogin ?? this.lastLogin, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } +} diff --git a/lib/app/data/providers/auth_provider.dart b/lib/app/data/providers/auth_provider.dart new file mode 100644 index 0000000..0014fa0 --- /dev/null +++ b/lib/app/data/providers/auth_provider.dart @@ -0,0 +1,147 @@ +import 'package:penyaluran_app/app/services/supabase_service.dart'; +import 'package:penyaluran_app/app/data/models/user_model.dart'; + +class AuthProvider { + final SupabaseService _supabaseService = SupabaseService.to; + + // Metode untuk mendaftar pengguna baru + Future signUp(String email, String password) async { + try { + final response = await _supabaseService.signUp(email, password); + + if (response.user != null) { + // Tunggu beberapa saat agar trigger di database berjalan + await Future.delayed(const Duration(seconds: 1)); + + // Ambil profil pengguna dari database + final profileData = await _supabaseService.getUserProfile(); + + if (profileData != null) { + return UserModel.fromJson({ + ...profileData, + 'id': response.user!.id, + 'email': response.user!.email!, + }); + } + + // Jika profil belum tersedia, gunakan data default + return UserModel( + id: response.user!.id, + email: response.user!.email!, + role: 'WARGA', // Default role + ); + } + return null; + } catch (e) { + rethrow; + } + } + + // Metode untuk login + Future signIn(String email, String password) async { + try { + final response = await _supabaseService.signIn(email, password); + + if (response.user != null) { + // Ambil profil pengguna dari database + final profileData = await _supabaseService.getUserProfile(); + + if (profileData != null) { + return UserModel.fromJson({ + ...profileData, + 'id': response.user!.id, + 'email': response.user!.email!, + }); + } + + // Jika profil belum tersedia, gunakan data default + return UserModel( + id: response.user!.id, + email: response.user!.email!, + role: 'WARGA', // Default role + ); + } + return null; + } catch (e) { + rethrow; + } + } + + // Metode untuk logout + Future signOut() async { + try { + await _supabaseService.signOut(); + } catch (e) { + rethrow; + } + } + + // Metode untuk mendapatkan user saat ini + Future getCurrentUser() async { + final user = _supabaseService.currentUser; + if (user != null) { + // Ambil profil pengguna dari database + final profileData = await _supabaseService.getUserProfile(); + + if (profileData != null) { + return UserModel.fromJson({ + ...profileData, + 'id': user.id, + 'email': user.email!, + }); + } + + // Jika profil belum tersedia, gunakan data default + return UserModel( + id: user.id, + email: user.email!, + role: 'WARGA', // Default role + ); + } + return null; + } + + // Metode untuk memeriksa apakah user sudah login + bool isAuthenticated() { + return _supabaseService.isAuthenticated; + } + + // Metode untuk mendapatkan data warga + Future?> getWargaData() async { + return await _supabaseService.getWargaByUserId(); + } + + // Metode untuk membuat profil warga + Future createWargaProfile({ + required String nik, + required String namaLengkap, + required String jenisKelamin, + String? noHp, + String? alamat, + String? tempatLahir, + DateTime? tanggalLahir, + String? agama, + }) async { + await _supabaseService.createWargaProfile( + nik: nik, + namaLengkap: namaLengkap, + jenisKelamin: jenisKelamin, + noHp: noHp, + alamat: alamat, + tempatLahir: tempatLahir, + tanggalLahir: tanggalLahir, + agama: agama, + ); + } + + // Metode untuk mendapatkan notifikasi pengguna + Future>> getUserNotifications( + {bool unreadOnly = false}) async { + return await _supabaseService.getUserNotifications(unreadOnly: unreadOnly); + } + + // Metode untuk menandai notifikasi sebagai telah dibaca + Future markNotificationAsRead(int notificationId) async { + await _supabaseService.markNotificationAsRead(notificationId); + } +} diff --git a/lib/app/modules/auth/bindings/auth_binding.dart b/lib/app/modules/auth/bindings/auth_binding.dart new file mode 100644 index 0000000..0de4e10 --- /dev/null +++ b/lib/app/modules/auth/bindings/auth_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; + +class AuthBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => AuthController(), + ); + } +} diff --git a/lib/app/modules/auth/controllers/auth_controller.dart b/lib/app/modules/auth/controllers/auth_controller.dart new file mode 100644 index 0000000..b803741 --- /dev/null +++ b/lib/app/modules/auth/controllers/auth_controller.dart @@ -0,0 +1,512 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/data/models/user_model.dart'; +import 'package:penyaluran_app/app/data/providers/auth_provider.dart'; +import 'package:penyaluran_app/app/routes/app_pages.dart'; + +class AuthController extends GetxController { + static AuthController get to => Get.find(); + + final AuthProvider _authProvider = AuthProvider(); + + final Rx _user = Rx(null); + UserModel? get user => _user.value; + + final RxBool isLoading = false.obs; + final RxBool isWargaProfileComplete = false.obs; + + // Form controllers + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final TextEditingController confirmPasswordController = + TextEditingController(); + + // Form controllers untuk data warga + final TextEditingController nikController = TextEditingController(); + final TextEditingController namaLengkapController = TextEditingController(); + final TextEditingController jenisKelaminController = TextEditingController(); + final TextEditingController noHpController = TextEditingController(); + final TextEditingController alamatController = TextEditingController(); + final TextEditingController tempatLahirController = TextEditingController(); + final TextEditingController tanggalLahirController = TextEditingController(); + final TextEditingController agamaController = TextEditingController(); + + // Form keys + final GlobalKey loginFormKey = GlobalKey(); + final GlobalKey registerFormKey = GlobalKey(); + final GlobalKey wargaProfileFormKey = GlobalKey(); + + @override + void onInit() { + super.onInit(); + + // Tambahkan penundaan kecil untuk menghindari loop navigasi + Future.delayed(const Duration(milliseconds: 100), () { + checkAuthStatus(); + }); + } + + @override + void onClose() { + // Pastikan semua controller dibersihkan sebelum dilepaskan + clearAndDisposeControllers(); + super.onClose(); + } + + // Metode untuk membersihkan dan melepaskan controller + void clearAndDisposeControllers() { + try { + if (emailController.text.isNotEmpty) emailController.clear(); + if (passwordController.text.isNotEmpty) passwordController.clear(); + if (confirmPasswordController.text.isNotEmpty) + confirmPasswordController.clear(); + if (nikController.text.isNotEmpty) nikController.clear(); + if (namaLengkapController.text.isNotEmpty) namaLengkapController.clear(); + if (jenisKelaminController.text.isNotEmpty) + jenisKelaminController.clear(); + if (noHpController.text.isNotEmpty) noHpController.clear(); + if (alamatController.text.isNotEmpty) alamatController.clear(); + if (tempatLahirController.text.isNotEmpty) tempatLahirController.clear(); + if (tanggalLahirController.text.isNotEmpty) + tanggalLahirController.clear(); + if (agamaController.text.isNotEmpty) agamaController.clear(); + + emailController.dispose(); + passwordController.dispose(); + confirmPasswordController.dispose(); + nikController.dispose(); + namaLengkapController.dispose(); + jenisKelaminController.dispose(); + noHpController.dispose(); + alamatController.dispose(); + tempatLahirController.dispose(); + tanggalLahirController.dispose(); + agamaController.dispose(); + } catch (e) { + print('Error disposing controllers: $e'); + } + } + + // Memeriksa status autentikasi + Future checkAuthStatus() async { + isLoading.value = true; + try { + final currentUser = await _authProvider.getCurrentUser(); + if (currentUser != null) { + _user.value = currentUser; + + // Periksa apakah profil warga sudah lengkap + await checkWargaProfileStatus(); + + // Hindari navigasi jika sudah berada di halaman yang sesuai + final currentRoute = Get.currentRoute; + + // Untuk semua role, arahkan ke dashboard masing-masing + final targetRoute = _getTargetRouteForRole(currentUser.role); + if (currentRoute != targetRoute) { + navigateBasedOnRole(currentUser.role); + } + } else { + // Jika tidak ada user yang login, arahkan ke halaman login + if (Get.currentRoute != Routes.LOGIN) { + // Bersihkan dependensi form sebelum navigasi + clearFormDependencies(); + Get.offAllNamed(Routes.LOGIN); + } + } + } catch (e) { + print('Error checking auth status: $e'); + // Jika terjadi error, arahkan ke halaman login + if (Get.currentRoute != Routes.LOGIN) { + // Bersihkan dependensi form sebelum navigasi + clearFormDependencies(); + Get.offAllNamed(Routes.LOGIN); + } + } finally { + isLoading.value = false; + } + } + + // Memeriksa status profil warga + Future checkWargaProfileStatus() async { + try { + if (_user.value?.role == 'WARGA') { + final wargaData = await _authProvider.getWargaData(); + isWargaProfileComplete.value = wargaData != null; + } else { + isWargaProfileComplete.value = true; + } + } catch (e) { + print('Error checking warga profile: $e'); + isWargaProfileComplete.value = false; + } + } + + // Metode untuk membersihkan dependensi form + void clearFormDependencies() { + try { + // Hapus fokus dari semua field + FocusManager.instance.primaryFocus?.unfocus(); + + // Reset form state jika ada + loginFormKey.currentState?.reset(); + registerFormKey.currentState?.reset(); + wargaProfileFormKey.currentState?.reset(); + } catch (e) { + print('Error clearing form dependencies: $e'); + } + } + + // Metode untuk navigasi berdasarkan peran + void navigateBasedOnRole(String role) { + // Bersihkan dependensi form sebelum navigasi + clearFormDependencies(); + + switch (role) { + case 'WARGA': + Get.offAllNamed(Routes.WARGA_DASHBOARD); + break; + case 'PETUGASVERIFIKASI': + Get.offAllNamed(Routes.PETUGAS_VERIFIKASI_DASHBOARD); + break; + case 'PETUGASDESA': + Get.offAllNamed(Routes.PETUGAS_DESA_DASHBOARD); + break; + case 'DONATUR': + Get.offAllNamed(Routes.DONATUR_DASHBOARD); + break; + default: + Get.offAllNamed(Routes.HOME); + break; + } + } + + // Metode untuk login + Future login() async { + if (!loginFormKey.currentState!.validate()) return; + + // Simpan nilai dari controller sebelum melakukan operasi asinkron + final email = emailController.text.trim(); + final password = passwordController.text; + + try { + isLoading.value = true; + final user = await _authProvider.signIn( + email, + password, + ); + + if (user != null) { + _user.value = user; + clearControllers(); + + // Periksa apakah profil warga sudah lengkap + await checkWargaProfileStatus(); + + // Arahkan ke dashboard sesuai peran + navigateBasedOnRole(user.role); + + // Tampilkan notifikasi jika profil belum lengkap untuk warga + if (user.role == 'WARGA' && !isWargaProfileComplete.value) { + Get.snackbar( + 'Informasi', + 'Profil Anda belum lengkap. Silakan lengkapi profil Anda melalui menu Profil', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.blue, + colorText: Colors.white, + duration: const Duration(seconds: 5), + ); + } else { + Get.snackbar( + 'Berhasil', + 'Login berhasil', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } + } + } catch (e) { + print('Error login: $e'); + Get.snackbar( + 'Error', + 'Login gagal: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } finally { + isLoading.value = false; + } + } + + // Metode untuk register + Future register() async { + if (!registerFormKey.currentState!.validate()) return; + + // Simpan nilai dari controller sebelum melakukan operasi asinkron + final email = emailController.text.trim(); + final password = passwordController.text; + final confirmPassword = confirmPasswordController.text; + + if (password != confirmPassword) { + Get.snackbar( + 'Error', + 'Password dan konfirmasi password tidak sama', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + try { + isLoading.value = true; + final user = await _authProvider.signUp( + email, + password, + ); + + if (user != null) { + _user.value = user; + clearControllers(); + + // Periksa status profil + await checkWargaProfileStatus(); + + // Arahkan ke dashboard sesuai peran + navigateBasedOnRole(user.role); + + // Tampilkan notifikasi untuk melengkapi profil + Get.snackbar( + 'Berhasil', + 'Registrasi berhasil. Silakan lengkapi profil Anda melalui menu Profil', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 5), + ); + } + } catch (e) { + Get.snackbar( + 'Error', + 'Registrasi gagal: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } finally { + isLoading.value = false; + } + } + + // Metode untuk melengkapi profil warga + Future completeWargaProfile() async { + if (!wargaProfileFormKey.currentState!.validate()) return; + + // Simpan nilai dari controller sebelum melakukan operasi asinkron + final nik = nikController.text.trim(); + final namaLengkap = namaLengkapController.text.trim(); + final jenisKelamin = jenisKelaminController.text.trim(); + final noHp = noHpController.text.trim(); + final alamat = alamatController.text.trim(); + final tempatLahir = tempatLahirController.text.trim(); + final tanggalLahirText = tanggalLahirController.text; + final agama = agamaController.text.trim(); + + try { + isLoading.value = true; + + DateTime? tanggalLahir; + if (tanggalLahirText.isNotEmpty) { + try { + final parts = tanggalLahirText.split('-'); + if (parts.length == 3) { + tanggalLahir = DateTime( + int.parse(parts[2]), // tahun + int.parse(parts[1]), // bulan + int.parse(parts[0]), // hari + ); + } + } catch (e) { + print('Error parsing date: $e'); + } + } + + await _authProvider.createWargaProfile( + nik: nik, + namaLengkap: namaLengkap, + jenisKelamin: jenisKelamin, + noHp: noHp, + alamat: alamat, + tempatLahir: tempatLahir, + tanggalLahir: tanggalLahir, + agama: agama, + ); + + isWargaProfileComplete.value = true; + + // Kembali ke halaman sebelumnya jika menggunakan Get.toNamed + if (Get.previousRoute.isNotEmpty) { + Get.back(); + Get.snackbar( + 'Berhasil', + 'Profil berhasil dilengkapi', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } else { + // Jika tidak ada halaman sebelumnya, navigasi ke dashboard warga + Get.offAllNamed(Routes.WARGA_DASHBOARD); + Get.snackbar( + 'Berhasil', + 'Profil berhasil dilengkapi', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } + } catch (e) { + Get.snackbar( + 'Error', + 'Gagal melengkapi profil: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } finally { + isLoading.value = false; + } + } + + // Metode untuk logout + Future logout() async { + try { + await _authProvider.signOut(); + _user.value = null; + isWargaProfileComplete.value = false; + + // Bersihkan dependensi form sebelum navigasi + clearFormDependencies(); + + Get.offAllNamed(Routes.LOGIN); + } catch (e) { + Get.snackbar( + 'Error', + 'Logout gagal: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } + + // Metode untuk membersihkan controller + void clearControllers() { + try { + if (emailController.text.isNotEmpty) emailController.clear(); + if (passwordController.text.isNotEmpty) passwordController.clear(); + if (confirmPasswordController.text.isNotEmpty) + confirmPasswordController.clear(); + } catch (e) { + print('Error clearing controllers: $e'); + } + } + + // Validasi email + String? validateEmail(String? value) { + if (value == null || value.isEmpty) { + return 'Email tidak boleh kosong'; + } + if (!GetUtils.isEmail(value)) { + return 'Email tidak valid'; + } + return null; + } + + // Validasi password + String? validatePassword(String? value) { + if (value == null || value.isEmpty) { + return 'Password tidak boleh kosong'; + } + if (value.length < 6) { + return 'Password minimal 6 karakter'; + } + return null; + } + + // Validasi konfirmasi password + String? validateConfirmPassword(String? value) { + try { + if (value == null || value.isEmpty) { + return 'Konfirmasi password tidak boleh kosong'; + } + + // Ambil nilai password dari controller jika tersedia + final password = passwordController.text; + if (value != password) { + return 'Password dan konfirmasi password tidak sama'; + } + return null; + } catch (e) { + print('Error validating confirm password: $e'); + return 'Terjadi kesalahan saat validasi'; + } + } + + // Validasi NIK + String? validateNIK(String? value) { + if (value == null || value.isEmpty) { + return 'NIK tidak boleh kosong'; + } + if (value.length != 16) { + return 'NIK harus 16 digit'; + } + if (!GetUtils.isNumericOnly(value)) { + return 'NIK harus berupa angka'; + } + return null; + } + + // Validasi nama lengkap + String? validateNamaLengkap(String? value) { + if (value == null || value.isEmpty) { + return 'Nama lengkap tidak boleh kosong'; + } + return null; + } + + // Validasi jenis kelamin + String? validateJenisKelamin(String? value) { + if (value == null || value.isEmpty) { + return 'Jenis kelamin tidak boleh kosong'; + } + return null; + } + + // Mendapatkan rute target berdasarkan peran + String _getTargetRouteForRole(String role) { + switch (role) { + case 'WARGA': + return Routes.WARGA_DASHBOARD; + case 'PETUGASVERIFIKASI': + return Routes.PETUGAS_VERIFIKASI_DASHBOARD; + case 'PETUGASDESA': + return Routes.PETUGAS_DESA_DASHBOARD; + case 'DONATUR': + return Routes.DONATUR_DASHBOARD; + default: + return Routes.HOME; + } + } + + // Metode untuk navigasi ke halaman lengkapi profil + void navigateToCompleteProfile() { + // Bersihkan dependensi form sebelum navigasi + clearFormDependencies(); + + // Gunakan preventDuplicates untuk mencegah navigasi berulang + Get.toNamed(Routes.COMPLETE_PROFILE, preventDuplicates: true); + } +} diff --git a/lib/app/modules/auth/views/complete_profile_view.dart b/lib/app/modules/auth/views/complete_profile_view.dart new file mode 100644 index 0000000..8e1e467 --- /dev/null +++ b/lib/app/modules/auth/views/complete_profile_view.dart @@ -0,0 +1,270 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; + +class CompleteProfileView extends GetView { + const CompleteProfileView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Lengkapi Profil'), + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Get.back(), + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Form( + key: controller.wargaProfileFormKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 10), + const Center( + child: Text( + 'Lengkapi Data Diri Anda', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + ), + const SizedBox(height: 5), + const Center( + child: Text( + 'Data ini diperlukan untuk verifikasi', + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ), + const SizedBox(height: 30), + + // NIK Field + TextFormField( + controller: controller.nikController, + keyboardType: TextInputType.number, + maxLength: 16, + decoration: InputDecoration( + labelText: 'NIK', + prefixIcon: const Icon(Icons.credit_card), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + validator: controller.validateNIK, + ), + const SizedBox(height: 15), + + // Nama Lengkap Field + TextFormField( + controller: controller.namaLengkapController, + decoration: InputDecoration( + labelText: 'Nama Lengkap', + prefixIcon: const Icon(Icons.person), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + validator: controller.validateNamaLengkap, + ), + const SizedBox(height: 15), + + // Jenis Kelamin Field + DropdownButtonFormField( + decoration: InputDecoration( + labelText: 'Jenis Kelamin', + prefixIcon: const Icon(Icons.people), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + items: const [ + DropdownMenuItem( + value: 'LAKI-LAKI', + child: Text('Laki-laki'), + ), + DropdownMenuItem( + value: 'PEREMPUAN', + child: Text('Perempuan'), + ), + ], + onChanged: (value) { + controller.jenisKelaminController.text = value ?? ''; + }, + validator: controller.validateJenisKelamin, + ), + const SizedBox(height: 15), + + // No HP Field + TextFormField( + controller: controller.noHpController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + labelText: 'No. HP', + prefixIcon: const Icon(Icons.phone), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + const SizedBox(height: 15), + + // Alamat Field + TextFormField( + controller: controller.alamatController, + maxLines: 3, + decoration: InputDecoration( + labelText: 'Alamat', + prefixIcon: const Icon(Icons.home), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + const SizedBox(height: 15), + + // Tempat Lahir Field + TextFormField( + controller: controller.tempatLahirController, + decoration: InputDecoration( + labelText: 'Tempat Lahir', + prefixIcon: const Icon(Icons.location_city), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + const SizedBox(height: 15), + + // Tanggal Lahir Field + TextFormField( + controller: controller.tanggalLahirController, + keyboardType: TextInputType.datetime, + decoration: InputDecoration( + labelText: 'Tanggal Lahir (DD-MM-YYYY)', + prefixIcon: const Icon(Icons.calendar_today), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onTap: () async { + FocusScope.of(context).requestFocus(FocusNode()); + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (picked != null) { + controller.tanggalLahirController.text = + '${picked.day.toString().padLeft(2, '0')}-' + '${picked.month.toString().padLeft(2, '0')}-' + '${picked.year}'; + } + }, + ), + const SizedBox(height: 15), + + // Agama Field + DropdownButtonFormField( + decoration: InputDecoration( + labelText: 'Agama', + prefixIcon: const Icon(Icons.church), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + items: const [ + DropdownMenuItem( + value: 'ISLAM', + child: Text('Islam'), + ), + DropdownMenuItem( + value: 'KRISTEN PROTESTAN', + child: Text('Kristen Protestan'), + ), + DropdownMenuItem( + value: 'KRISTEN KATOLIK', + child: Text('Kristen Katolik'), + ), + DropdownMenuItem( + value: 'HINDU', + child: Text('Hindu'), + ), + DropdownMenuItem( + value: 'BUDHA', + child: Text('Budha'), + ), + DropdownMenuItem( + value: 'KONGHUCU', + child: Text('Konghucu'), + ), + ], + onChanged: (value) { + controller.agamaController.text = value ?? ''; + }, + ), + const SizedBox(height: 30), + + // Submit Button + Obx(() => ElevatedButton( + onPressed: controller.isLoading.value + ? null + : controller.completeWargaProfile, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: controller.isLoading.value + ? const SpinKitThreeBounce( + color: Colors.white, + size: 24, + ) + : const Text( + 'SIMPAN', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + )), + const SizedBox(height: 15), + + // Kembali Button + OutlinedButton( + onPressed: () => Get.back(), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text( + 'KEMBALI KE DASHBOARD', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/auth/views/login_view.dart b/lib/app/modules/auth/views/login_view.dart new file mode 100644 index 0000000..42ce72a --- /dev/null +++ b/lib/app/modules/auth/views/login_view.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; +import 'package:penyaluran_app/app/routes/app_pages.dart'; + +class LoginView extends GetView { + const LoginView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Form( + key: controller.loginFormKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 50), + // Logo atau Judul + const Center( + child: Text( + 'Penyaluran App', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + ), + const SizedBox(height: 10), + const Center( + child: Text( + 'Masuk ke akun Anda', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ), + const SizedBox(height: 50), + + // Email Field + TextFormField( + controller: controller.emailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'Email', + prefixIcon: const Icon(Icons.email), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + validator: controller.validateEmail, + ), + const SizedBox(height: 20), + + // Password Field + TextFormField( + controller: controller.passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Password', + prefixIcon: const Icon(Icons.lock), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + validator: controller.validatePassword, + ), + const SizedBox(height: 10), + + // Forgot Password + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + // Implementasi lupa password + }, + child: const Text('Lupa Password?'), + ), + ), + const SizedBox(height: 20), + + // Login Button + Obx(() => ElevatedButton( + onPressed: controller.isLoading.value + ? null + : controller.login, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: controller.isLoading.value + ? const SpinKitThreeBounce( + color: Colors.white, + size: 24, + ) + : const Text( + 'MASUK', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + )), + const SizedBox(height: 20), + + // Register Link + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Belum punya akun?'), + TextButton( + onPressed: () => Get.toNamed(Routes.REGISTER), + child: const Text('Daftar'), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/app/modules/auth/views/register_view.dart b/lib/app/modules/auth/views/register_view.dart new file mode 100644 index 0000000..68c36b8 --- /dev/null +++ b/lib/app/modules/auth/views/register_view.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; +import 'package:penyaluran_app/app/routes/app_pages.dart'; + +class RegisterView extends GetView { + const RegisterView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Daftar Akun'), + elevation: 0, + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Form( + key: controller.registerFormKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 20), + // Logo atau Judul + const Center( + child: Text( + 'Penyaluran App', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + ), + const SizedBox(height: 10), + const Center( + child: Text( + 'Buat akun baru', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ), + const SizedBox(height: 30), + + // Email Field + TextFormField( + controller: controller.emailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'Email', + prefixIcon: const Icon(Icons.email), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + validator: controller.validateEmail, + ), + const SizedBox(height: 20), + + // Password Field + TextFormField( + controller: controller.passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Password', + prefixIcon: const Icon(Icons.lock), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + validator: controller.validatePassword, + ), + const SizedBox(height: 20), + + // Confirm Password Field + TextFormField( + controller: controller.confirmPasswordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Konfirmasi Password', + prefixIcon: const Icon(Icons.lock_outline), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + validator: controller.validateConfirmPassword, + ), + const SizedBox(height: 30), + + // Register Button + Obx(() => ElevatedButton( + onPressed: controller.isLoading.value + ? null + : controller.register, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: controller.isLoading.value + ? const SpinKitThreeBounce( + color: Colors.white, + size: 24, + ) + : const Text( + 'DAFTAR', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + )), + const SizedBox(height: 20), + + // Login Link + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Sudah punya akun?'), + TextButton( + onPressed: () => Get.offAllNamed(Routes.LOGIN), + child: const Text('Masuk'), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/dashboard/bindings/dashboard_binding.dart b/lib/app/modules/dashboard/bindings/dashboard_binding.dart new file mode 100644 index 0000000..5be5eaa --- /dev/null +++ b/lib/app/modules/dashboard/bindings/dashboard_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/dashboard/controllers/dashboard_controller.dart'; + +class DashboardBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => DashboardController(), + ); + } +} diff --git a/lib/app/modules/dashboard/controllers/dashboard_controller.dart b/lib/app/modules/dashboard/controllers/dashboard_controller.dart new file mode 100644 index 0000000..3b4f812 --- /dev/null +++ b/lib/app/modules/dashboard/controllers/dashboard_controller.dart @@ -0,0 +1,39 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/data/models/user_model.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; +import 'package:penyaluran_app/app/services/supabase_service.dart'; + +class DashboardController extends GetxController { + final AuthController _authController = Get.find(); + final SupabaseService _supabaseService = SupabaseService.to; + + final RxBool isLoading = false.obs; + final Rx?> roleData = Rx?>(null); + + UserModel? get user => _authController.user; + String get role => user?.role ?? 'WARGA'; + + @override + void onInit() { + super.onInit(); + loadRoleData(); + } + + Future loadRoleData() async { + isLoading.value = true; + try { + if (user != null) { + final data = await _supabaseService.getRoleSpecificData(role); + roleData.value = data; + } + } catch (e) { + print('Error loading role data: $e'); + } finally { + isLoading.value = false; + } + } + + void logout() { + _authController.logout(); + } +} diff --git a/lib/app/modules/dashboard/views/donatur_dashboard_view.dart b/lib/app/modules/dashboard/views/donatur_dashboard_view.dart new file mode 100644 index 0000000..f241343 --- /dev/null +++ b/lib/app/modules/dashboard/views/donatur_dashboard_view.dart @@ -0,0 +1,229 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/dashboard/controllers/dashboard_controller.dart'; + +class DonaturDashboardView extends GetView { + const DonaturDashboardView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Dashboard Donatur'), + actions: [ + IconButton( + icon: const Icon(Icons.logout), + onPressed: controller.logout, + tooltip: 'Logout', + ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Obx(() { + if (controller.isLoading.value) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Greeting + Text( + 'Halo, ${controller.roleData.value?['namaLengkap'] ?? controller.user?.email ?? 'Donatur'}!', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 5), + const Text( + 'Selamat datang di Dashboard Donatur', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 30), + + // Donatur Data + if (controller.roleData.value != null) ...[ + const Text( + 'Data Donatur', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Card( + elevation: 2, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow( + 'NIK', controller.roleData.value?['NIK'] ?? '-'), + _buildInfoRow('Nama', + controller.roleData.value?['namaLengkap'] ?? '-'), + _buildInfoRow('No. Telp', + controller.roleData.value?['noTelp'] ?? '-'), + _buildInfoRow('Email', + controller.roleData.value?['email'] ?? '-'), + _buildInfoRow( + 'Alamat', + controller.roleData.value?['alamatLengkap'] ?? + '-'), + _buildInfoRow('Desa', + controller.roleData.value?['namaDesa'] ?? '-'), + ], + ), + ), + ), + ] else ...[ + const Center( + child: Text( + 'Data donatur belum tersedia', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ), + ], + + const SizedBox(height: 30), + + // Menu + const Text( + 'Menu', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Penitipan Bantuan', + 'Titipkan bantuan Anda', + Icons.card_giftcard, + Colors.blue, + () { + // Navigasi ke halaman penitipan bantuan + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Riwayat Penitipan', + 'Lihat riwayat penitipan bantuan', + Icons.history, + Colors.green, + () { + // Navigasi ke halaman riwayat penitipan + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Laporan Penyaluran', + 'Lihat laporan penyaluran bantuan', + Icons.assessment, + Colors.orange, + () { + // Navigasi ke halaman laporan penyaluran + }, + ), + ], + ); + }), + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Navigasi ke halaman penitipan bantuan baru + }, + child: const Icon(Icons.add), + tooltip: 'Titipkan Bantuan Baru', + ), + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: Text(value), + ), + ], + ), + ); + } + + Widget _buildMenuCard(String title, String subtitle, IconData icon, + Color color, VoidCallback onTap) { + return Card( + elevation: 2, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8.0), + ), + child: Icon( + icon, + color: color, + size: 30, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart b/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart new file mode 100644 index 0000000..fc04a85 --- /dev/null +++ b/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart @@ -0,0 +1,220 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/dashboard/controllers/dashboard_controller.dart'; + +class PetugasDesaDashboardView extends GetView { + const PetugasDesaDashboardView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Dashboard Petugas Desa'), + actions: [ + IconButton( + icon: const Icon(Icons.logout), + onPressed: controller.logout, + tooltip: 'Logout', + ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Obx(() { + if (controller.isLoading.value) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Greeting + Text( + 'Halo, ${controller.roleData.value?['namaLengkap'] ?? controller.user?.email ?? 'Petugas'}!', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 5), + Text( + 'Selamat datang di Dashboard Petugas Desa ${controller.roleData.value?['Desa'] ?? ''}', + style: const TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 30), + + // Petugas Data + if (controller.roleData.value != null) ...[ + const Text( + 'Data Petugas', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Card( + elevation: 2, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow( + 'NIP', controller.roleData.value?['NIP'] ?? '-'), + _buildInfoRow('Nama', + controller.roleData.value?['namaLengkap'] ?? '-'), + _buildInfoRow('Jabatan', + controller.roleData.value?['jabatan'] ?? '-'), + _buildInfoRow('Desa', + controller.roleData.value?['Desa'] ?? '-'), + _buildInfoRow('No. HP', + controller.roleData.value?['noHP'] ?? '-'), + _buildInfoRow('Email', + controller.roleData.value?['email'] ?? '-'), + ], + ), + ), + ), + ] else ...[ + const Center( + child: Text( + 'Data petugas belum tersedia', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ), + ], + + const SizedBox(height: 30), + + // Menu + const Text( + 'Menu', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Penyaluran Bantuan', + 'Kelola penyaluran bantuan', + Icons.local_shipping, + Colors.blue, + () { + // Navigasi ke halaman penyaluran bantuan + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Pengaduan', + 'Kelola pengaduan warga', + Icons.report_problem, + Colors.orange, + () { + // Navigasi ke halaman pengaduan + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Laporan', + 'Lihat laporan penyaluran', + Icons.assessment, + Colors.green, + () { + // Navigasi ke halaman laporan + }, + ), + ], + ); + }), + ), + ), + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: Text(value), + ), + ], + ), + ); + } + + Widget _buildMenuCard(String title, String subtitle, IconData icon, + Color color, VoidCallback onTap) { + return Card( + elevation: 2, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8.0), + ), + child: Icon( + icon, + color: color, + size: 30, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/dashboard/views/petugas_verifikasi_dashboard_view.dart b/lib/app/modules/dashboard/views/petugas_verifikasi_dashboard_view.dart new file mode 100644 index 0000000..b7cd07b --- /dev/null +++ b/lib/app/modules/dashboard/views/petugas_verifikasi_dashboard_view.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/dashboard/controllers/dashboard_controller.dart'; + +class PetugasVerifikasiDashboardView extends GetView { + const PetugasVerifikasiDashboardView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Dashboard Petugas Verifikasi'), + actions: [ + IconButton( + icon: const Icon(Icons.logout), + onPressed: controller.logout, + tooltip: 'Logout', + ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Obx(() { + if (controller.isLoading.value) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Greeting + Text( + 'Halo, ${controller.roleData.value?['namaLengkap'] ?? controller.user?.email ?? 'Petugas'}!', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 5), + const Text( + 'Selamat datang di Dashboard Petugas Verifikasi', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 30), + + // Petugas Data + if (controller.roleData.value != null) ...[ + const Text( + 'Data Petugas', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Card( + elevation: 2, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow( + 'NIP', controller.roleData.value?['nip'] ?? '-'), + _buildInfoRow('Nama', + controller.roleData.value?['namaLengkap'] ?? '-'), + _buildInfoRow('Jabatan', + controller.roleData.value?['jabatan'] ?? '-'), + _buildInfoRow('Email', + controller.roleData.value?['email'] ?? '-'), + ], + ), + ), + ), + ] else ...[ + const Center( + child: Text( + 'Data petugas belum tersedia', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ), + ], + + const SizedBox(height: 30), + + // Menu + const Text( + 'Menu', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Verifikasi Data Warga', + 'Verifikasi data warga baru', + Icons.verified_user, + Colors.blue, + () { + // Navigasi ke halaman verifikasi data warga + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Verifikasi Pengajuan Bantuan', + 'Verifikasi pengajuan bantuan', + Icons.fact_check, + Colors.green, + () { + // Navigasi ke halaman verifikasi pengajuan bantuan + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Kelola Skema Bantuan', + 'Kelola skema bantuan yang tersedia', + Icons.settings, + Colors.orange, + () { + // Navigasi ke halaman kelola skema bantuan + }, + ), + ], + ); + }), + ), + ), + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: Text(value), + ), + ], + ), + ); + } + + Widget _buildMenuCard(String title, String subtitle, IconData icon, + Color color, VoidCallback onTap) { + return Card( + elevation: 2, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8.0), + ), + child: Icon( + icon, + color: color, + size: 30, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/dashboard/views/warga_dashboard_view.dart b/lib/app/modules/dashboard/views/warga_dashboard_view.dart new file mode 100644 index 0000000..e187a8f --- /dev/null +++ b/lib/app/modules/dashboard/views/warga_dashboard_view.dart @@ -0,0 +1,303 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/dashboard/controllers/dashboard_controller.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; + +class WargaDashboardView extends GetView { + const WargaDashboardView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final authController = Get.find(); + + return Scaffold( + appBar: AppBar( + title: const Text('Dashboard Warga'), + actions: [ + IconButton( + icon: const Icon(Icons.logout), + onPressed: controller.logout, + tooltip: 'Logout', + ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Obx(() { + if (controller.isLoading.value) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Greeting + Text( + 'Halo, ${controller.user?.email ?? 'Pengguna'}!', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 5), + const Text( + 'Selamat datang di Dashboard Warga', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 20), + + // Status Profil + Obx(() => authController.isWargaProfileComplete.value + ? _buildProfileCompleteCard() + : _buildProfileIncompleteCard(authController)), + + const SizedBox(height: 30), + + // Warga Data + if (controller.roleData.value != null) ...[ + const Text( + 'Data Pribadi', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Card( + elevation: 2, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow('NIK', + controller.roleData.value?['NIK'] ?? '-'), + _buildInfoRow( + 'Nama', + controller.roleData.value?['namaLengkap'] ?? + '-'), + _buildInfoRow( + 'Jenis Kelamin', + controller.roleData.value?['jenisKelamin'] ?? + '-'), + _buildInfoRow('No. HP', + controller.roleData.value?['noHp'] ?? '-'), + _buildInfoRow('Alamat', + controller.roleData.value?['alamat'] ?? '-'), + ], + ), + ), + ), + ] else ...[ + const Center( + child: Text( + 'Data warga belum tersedia', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ), + ], + + const SizedBox(height: 30), + + // Menu + const Text( + 'Menu', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Pengajuan Bantuan', + 'Ajukan permohonan bantuan', + Icons.request_page, + Colors.blue, + () { + // Navigasi ke halaman pengajuan bantuan + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Pengaduan', + 'Sampaikan pengaduan Anda', + Icons.report_problem, + Colors.orange, + () { + // Navigasi ke halaman pengaduan + }, + ), + const SizedBox(height: 10), + _buildMenuCard( + 'Status Bantuan', + 'Lihat status bantuan Anda', + Icons.info, + Colors.green, + () { + // Navigasi ke halaman status bantuan + }, + ), + ], + ), + ); + }), + ), + ), + ); + } + + // Widget untuk menampilkan status profil lengkap + Widget _buildProfileCompleteCard() { + return Card( + color: Colors.green.shade50, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Icon(Icons.check_circle, color: Colors.green.shade700), + const SizedBox(width: 10), + const Expanded( + child: Text( + 'Profil Anda sudah lengkap', + style: TextStyle( + color: Colors.green, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); + } + + // Widget untuk menampilkan status profil belum lengkap dengan tombol + Widget _buildProfileIncompleteCard(AuthController authController) { + return Card( + color: Colors.orange.shade50, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.warning, color: Colors.orange.shade700), + const SizedBox(width: 10), + const Expanded( + child: Text( + 'Profil Anda belum lengkap', + style: TextStyle( + color: Colors.orange, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 10), + const Text( + 'Lengkapi profil Anda untuk mengakses semua fitur aplikasi', + style: TextStyle(fontSize: 12), + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: authController.navigateToCompleteProfile, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white, + ), + child: const Text('Lengkapi Profil'), + ), + ], + ), + ), + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: Text(value), + ), + ], + ), + ); + } + + Widget _buildMenuCard(String title, String subtitle, IconData icon, + Color color, VoidCallback onTap) { + return Card( + elevation: 2, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8.0), + ), + child: Icon( + icon, + color: color, + size: 30, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/home/bindings/home_binding.dart b/lib/app/modules/home/bindings/home_binding.dart new file mode 100644 index 0000000..15fb986 --- /dev/null +++ b/lib/app/modules/home/bindings/home_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/home/controllers/home_controller.dart'; + +class HomeBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => HomeController(), + ); + } +} diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart new file mode 100644 index 0000000..90377cc --- /dev/null +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -0,0 +1,14 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; + +class HomeController extends GetxController { + final AuthController _authController = Get.find(); + + // Getter untuk mendapatkan user dari auth controller + get user => _authController.user; + + // Metode untuk logout + void logout() { + _authController.logout(); + } +} diff --git a/lib/app/modules/home/views/home_view.dart b/lib/app/modules/home/views/home_view.dart new file mode 100644 index 0000000..631e693 --- /dev/null +++ b/lib/app/modules/home/views/home_view.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/home/controllers/home_controller.dart'; + +class HomeView extends GetView { + const HomeView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Penyaluran App'), + actions: [ + IconButton( + icon: const Icon(Icons.logout), + onPressed: controller.logout, + tooltip: 'Logout', + ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Greeting + Text( + 'Halo, ${controller.user?.email ?? 'Pengguna'}!', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 5), + const Text( + 'Selamat datang di Penyaluran App', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 30), + + // Dashboard Content + const Expanded( + child: Center( + child: Text( + 'Halaman Dashboard', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart new file mode 100644 index 0000000..fca6d7a --- /dev/null +++ b/lib/app/routes/app_pages.dart @@ -0,0 +1,63 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/auth/views/login_view.dart'; +import 'package:penyaluran_app/app/modules/auth/views/register_view.dart'; +import 'package:penyaluran_app/app/modules/auth/views/complete_profile_view.dart'; +import 'package:penyaluran_app/app/modules/home/views/home_view.dart'; +import 'package:penyaluran_app/app/modules/dashboard/views/warga_dashboard_view.dart'; +import 'package:penyaluran_app/app/modules/dashboard/views/petugas_verifikasi_dashboard_view.dart'; +import 'package:penyaluran_app/app/modules/dashboard/views/petugas_desa_dashboard_view.dart'; +import 'package:penyaluran_app/app/modules/dashboard/views/donatur_dashboard_view.dart'; +import 'package:penyaluran_app/app/modules/auth/bindings/auth_binding.dart'; +import 'package:penyaluran_app/app/modules/home/bindings/home_binding.dart'; +import 'package:penyaluran_app/app/modules/dashboard/bindings/dashboard_binding.dart'; + +part 'app_routes.dart'; + +class AppPages { + AppPages._(); + + static const INITIAL = Routes.LOGIN; + + static final routes = [ + GetPage( + name: _Paths.HOME, + page: () => const HomeView(), + binding: HomeBinding(), + ), + GetPage( + name: _Paths.LOGIN, + page: () => const LoginView(), + binding: AuthBinding(), + ), + GetPage( + name: _Paths.REGISTER, + page: () => const RegisterView(), + binding: AuthBinding(), + ), + GetPage( + name: _Paths.COMPLETE_PROFILE, + page: () => const CompleteProfileView(), + binding: AuthBinding(), + ), + GetPage( + name: _Paths.WARGA_DASHBOARD, + page: () => const WargaDashboardView(), + binding: DashboardBinding(), + ), + GetPage( + name: _Paths.PETUGAS_VERIFIKASI_DASHBOARD, + page: () => const PetugasVerifikasiDashboardView(), + binding: DashboardBinding(), + ), + GetPage( + name: _Paths.PETUGAS_DESA_DASHBOARD, + page: () => const PetugasDesaDashboardView(), + binding: DashboardBinding(), + ), + GetPage( + name: _Paths.DONATUR_DASHBOARD, + page: () => const DonaturDashboardView(), + binding: DashboardBinding(), + ), + ]; +} diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart new file mode 100644 index 0000000..c7077ee --- /dev/null +++ b/lib/app/routes/app_routes.dart @@ -0,0 +1,26 @@ +part of 'app_pages.dart'; + +abstract class Routes { + Routes._(); + static const HOME = _Paths.HOME; + static const LOGIN = _Paths.LOGIN; + static const REGISTER = _Paths.REGISTER; + static const COMPLETE_PROFILE = _Paths.COMPLETE_PROFILE; + static const WARGA_DASHBOARD = _Paths.WARGA_DASHBOARD; + static const PETUGAS_VERIFIKASI_DASHBOARD = + _Paths.PETUGAS_VERIFIKASI_DASHBOARD; + static const PETUGAS_DESA_DASHBOARD = _Paths.PETUGAS_DESA_DASHBOARD; + static const DONATUR_DASHBOARD = _Paths.DONATUR_DASHBOARD; +} + +abstract class _Paths { + _Paths._(); + static const HOME = '/home'; + static const LOGIN = '/login'; + static const REGISTER = '/register'; + static const COMPLETE_PROFILE = '/complete-profile'; + static const WARGA_DASHBOARD = '/warga-dashboard'; + static const PETUGAS_VERIFIKASI_DASHBOARD = '/petugas-verifikasi-dashboard'; + static const PETUGAS_DESA_DASHBOARD = '/petugas-desa-dashboard'; + static const DONATUR_DASHBOARD = '/donatur-dashboard'; +} diff --git a/lib/app/services/supabase_service.dart b/lib/app/services/supabase_service.dart new file mode 100644 index 0000000..80b6e0b --- /dev/null +++ b/lib/app/services/supabase_service.dart @@ -0,0 +1,268 @@ +import 'package:get/get.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class SupabaseService extends GetxService { + static SupabaseService get to => Get.find(); + + late final SupabaseClient client; + + // Ganti dengan URL dan API key Supabase Anda + static const String supabaseUrl = String.fromEnvironment('SUPABASE_URL', + defaultValue: 'http://labulabs.net:8000'); + static const String supabaseKey = String.fromEnvironment('SUPABASE_KEY', + defaultValue: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzMxODYyODAwLAogICJleHAiOiAxODg5NjI5MjAwCn0.4IpwhwCVbfYXxb8JlZOLSBzCt6kQmypkvuso7N8Aicc'); + + Future init() async { + await Supabase.initialize( + url: supabaseUrl, + anonKey: supabaseKey, + ); + + client = Supabase.instance.client; + return this; + } + + // Metode untuk mendaftar pengguna baru + Future signUp(String email, String password) async { + return await client.auth.signUp( + email: email, + password: password, + data: {'autoconfirm': true}, + ); + } + + // Metode untuk login + Future signIn(String email, String password) async { + return await client.auth.signInWithPassword( + email: email, + password: password, + ); + } + + // Metode untuk logout + Future signOut() async { + await client.auth.signOut(); + } + + // Metode untuk mendapatkan user saat ini + User? get currentUser => client.auth.currentUser; + + // Metode untuk memeriksa apakah user sudah login + bool get isAuthenticated => currentUser != null; + + // Metode untuk mendapatkan profil pengguna + Future?> getUserProfile() async { + if (currentUser == null) return null; + + final response = await client + .from('user_profile') + .select() + .eq('id', currentUser!.id) + .maybeSingle(); + + return response; + } + + // Metode untuk mendapatkan role pengguna + Future getUserRole() async { + final profile = await getUserProfile(); + return profile?['role']; + } + + // Metode untuk mendapatkan data berdasarkan peran + Future?> getRoleSpecificData(String role) async { + if (currentUser == null) return null; + + switch (role) { + case 'WARGA': + return await getWargaByUserId(); + case 'PETUGASVERIFIKASI': + return await getPetugasVerifikasiData(); + case 'PETUGASDESA': + return await getPetugasDesaData(); + case 'DONATUR': + return await getDonaturData(); + default: + return null; + } + } + + // Metode untuk mendapatkan data petugas verifikasi + Future?> getPetugasVerifikasiData() async { + if (currentUser == null) return null; + + final response = await client + .from('xx02_PetugasVerifikasi') + .select() + .eq('userId', currentUser!.id) + .maybeSingle(); + + return response; + } + + // Metode untuk mendapatkan data petugas desa + Future?> getPetugasDesaData() async { + if (currentUser == null) return null; + + final response = await client + .from('xx01_PetugasDesa') + .select() + .eq('userId', currentUser!.id) + .maybeSingle(); + + return response; + } + + // Metode untuk mendapatkan data donatur + Future?> getDonaturData() async { + if (currentUser == null) return null; + + final response = await client + .from('xx01_Donatur') + .select() + .eq('userId', currentUser!.id) + .maybeSingle(); + + return response; + } + + // Metode untuk membuat data warga + Future createWargaProfile({ + required String nik, + required String namaLengkap, + required String jenisKelamin, + String? noHp, + String? alamat, + String? tempatLahir, + DateTime? tanggalLahir, + String? agama, + }) async { + if (currentUser == null) return; + + await client.from('xx02_Warga').insert({ + 'NIK': nik, + 'namaLengkap': namaLengkap, + 'jenisKelamin': jenisKelamin, + 'noHp': noHp, + 'alamat': alamat, + 'tempatLahir': tempatLahir, + 'tanggalLahir': tanggalLahir?.toIso8601String(), + 'agama': agama, + 'userId': currentUser!.id, + 'email': currentUser!.email, + }); + } + + // Metode untuk mendapatkan data warga berdasarkan userId + Future?> getWargaByUserId() async { + if (currentUser == null) return null; + + final response = await client + .from('xx02_Warga') + .select() + .eq('userId', currentUser!.id) + .maybeSingle(); + + return response; + } + + // Metode untuk mendapatkan notifikasi pengguna + Future>> getUserNotifications( + {bool unreadOnly = false}) async { + if (currentUser == null) return []; + + final query = client.from('Notification').select(); + + // Tambahkan filter untuk user ID + final filteredQuery = query.eq('userId', currentUser!.id); + + // Tambahkan filter untuk notifikasi yang belum dibaca jika diperlukan + final finalQuery = + unreadOnly ? filteredQuery.eq('isRead', false) : filteredQuery; + + // Tambahkan pengurutan + final response = await finalQuery.order('CREATED_AT', ascending: false); + + return List>.from(response); + } + + // Metode untuk menandai notifikasi sebagai telah dibaca + Future markNotificationAsRead(int notificationId) async { + await client + .from('Notification') + .update({'isRead': true}).eq('notificationId', notificationId); + } + + // Metode untuk mendapatkan data verifikasi warga + Future>> getVerifikasiDataWarga() async { + if (currentUser == null) return []; + + final response = await client + .from('xx02_VerifikasiDataWarga') + .select() + .order('CREATED_AT', ascending: false); + + return List>.from(response); + } + + // Metode untuk mendapatkan data pengajuan bantuan + Future>> getPengajuanBantuan() async { + if (currentUser == null) return []; + + final response = await client + .from('xx02_PengajuanKelayakanBantuan') + .select() + .order('CREATED_AT', ascending: false); + + return List>.from(response); + } + + // Metode untuk mendapatkan data skema bantuan + Future>> getSkemaBantuan() async { + if (currentUser == null) return []; + + final response = await client + .from('xx02_SkemaBantuan') + .select() + .order('CREATED_AT', ascending: false); + + return List>.from(response); + } + + // Metode untuk mendapatkan data penyaluran bantuan + Future>> getPenyaluranBantuan() async { + if (currentUser == null) return []; + + final response = await client + .from('xx01_PenyaluranBantuan') + .select() + .order('CREATED_AT', ascending: false); + + return List>.from(response); + } + + // Metode untuk mendapatkan data penitipan bantuan + Future>> getPenitipanBantuan() async { + if (currentUser == null) return []; + + final response = await client + .from('xx01_PenitipanBantuan') + .select() + .order('CREATED_AT', ascending: false); + + return List>.from(response); + } + + // Metode untuk mendapatkan data pengaduan + Future>> getPengaduan() async { + if (currentUser == null) return []; + + final response = await client + .from('xx01_Pengaduan') + .select() + .order('CREATED_AT', ascending: false); + + return List>.from(response); + } +} diff --git a/lib/main.dart b/lib/main.dart index 8e94089..29b70d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,125 +1,36 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/routes/app_pages.dart'; +import 'package:penyaluran_app/app/services/supabase_service.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Inisialisasi Supabase + await initServices(); -void main() { runApp(const MyApp()); } +// Inisialisasi service +Future initServices() async { + await Get.putAsync(() => SupabaseService().init()); +} + class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', + return GetMaterialApp( + title: 'Penyaluran App', theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + debugShowCheckedModeBanner: false, + initialRoute: AppPages.INITIAL, + getPages: AppPages.routes, ); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..3792af4 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,14 @@ #include "generated_plugin_registrant.h" +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..5d07423 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + gtk + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..92b6497 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,14 @@ import FlutterMacOS import Foundation +import app_links +import path_provider_foundation +import shared_preferences_foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 9999eda..b2c63d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,38 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + app_links: + dependency: transitive + description: + name: app_links + sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" + url: "https://pub.dev" + source: hosted + version: "6.4.0" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" async: dependency: transitive description: @@ -41,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -57,6 +97,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -70,11 +126,88 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_spinkit: + dependency: "direct main" + description: + name: flutter_spinkit + sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + form_validator: + dependency: "direct main" + description: + name: form_validator + sha256: "8cbe91b7d5260870d6fb9e23acd55d5d1d1fdf2397f0279a4931ac3c0c7bf8fb" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + functions_client: + dependency: transitive + description: + name: functions_client + sha256: a49876ebae32a50eb62483c5c5ac80ed0d8da34f98ccc23986b03a8d28cee07c + url: "https://pub.dev" + source: hosted + version: "2.4.1" + get: + dependency: "direct main" + description: + name: get + sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 + url: "https://pub.dev" + source: hosted + version: "4.7.2" + gotrue: + dependency: transitive + description: + name: gotrue + sha256: d6362dff9a54f8c1c372bb137c858b4024c16407324d34e6473e59623c9b9f50 + url: "https://pub.dev" + source: hosted + version: "2.11.1" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb + url: "https://pub.dev" + source: hosted + version: "0.3.1" leak_tracker: dependency: transitive description: @@ -107,6 +240,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -131,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -139,6 +288,158 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + url: "https://pub.dev" + source: hosted + version: "2.2.16" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + postgrest: + dependency: transitive + description: + name: postgrest + sha256: b74dc0f57b5dca5ce9f57a54b08110bf41d6fc8a0483c0fec10c79e9aa0fb2bb + url: "https://pub.dev" + source: hosted + version: "2.4.1" + realtime_client: + dependency: transitive + description: + name: realtime_client + sha256: e3089dac2121917cc0c72d42ab056fea0abbaf3c2229048fc50e64bafc731adf + url: "https://pub.dev" + source: hosted + version: "2.4.2" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -160,6 +461,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + storage_client: + dependency: transitive + description: + name: storage_client + sha256: "9f9ed283943313b23a1b27139bb18986e9b152a6d34530232c702c468d98e91a" + url: "https://pub.dev" + source: hosted + version: "2.3.1" stream_channel: dependency: transitive description: @@ -176,6 +485,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + supabase: + dependency: transitive + description: + name: supabase + sha256: c3ebddba69ddcf16d8b78e8c44c4538b0193d1cf944fde3b72eb5b279892a370 + url: "https://pub.dev" + source: hosted + version: "2.6.3" + supabase_flutter: + dependency: "direct main" + description: + name: supabase_flutter + sha256: "3b5b5b492e342f63f301605d0c66f6528add285b5744f53c9fd9abd5ffdbce5b" + url: "https://pub.dev" + source: hosted + version: "2.8.4" term_glyph: dependency: transitive description: @@ -192,6 +517,78 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" + url: "https://pub.dev" + source: hosted + version: "6.3.15" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" vector_math: dependency: transitive description: @@ -208,6 +605,46 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yet_another_json_isolate: + dependency: transitive + description: + name: yet_another_json_isolate + sha256: "56155e9e0002cc51ea7112857bbcdc714d4c35e176d43e4d3ee233009ff410c9" + url: "https://pub.dev" + source: hosted + version: "2.0.3" sdks: dart: ">=3.6.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2e7d185..4ff1123 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: penyaluran_app description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -35,6 +35,21 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + # GetX untuk manajemen state + get: ^4.6.6 + + # Supabase untuk autentikasi dan database + supabase_flutter: ^2.3.4 + + # Untuk menyimpan data lokal + shared_preferences: ^2.2.2 + + # Untuk validasi form + form_validator: ^2.1.1 + + # Untuk tampilan loading + flutter_spinkit: ^5.2.0 + dev_dependencies: flutter_test: sdk: flutter @@ -51,7 +66,6 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..785a046 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,12 @@ #include "generated_plugin_registrant.h" +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..8f8ee4f 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + app_links + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST