diff --git a/assets/font/DMSans-Italic-VariableFont_opsz,wght.ttf b/assets/font/DMSans-Italic-VariableFont_opsz,wght.ttf new file mode 100644 index 0000000..a7ee5c9 Binary files /dev/null and b/assets/font/DMSans-Italic-VariableFont_opsz,wght.ttf differ diff --git a/assets/font/DMSans-VariableFont_opsz,wght.ttf b/assets/font/DMSans-VariableFont_opsz,wght.ttf new file mode 100644 index 0000000..3d81b31 Binary files /dev/null and b/assets/font/DMSans-VariableFont_opsz,wght.ttf differ diff --git a/lib/app/modules/dashboard/controllers/dashboard_controller.dart b/lib/app/modules/dashboard/controllers/dashboard_controller.dart index 3b4f812..65d014f 100644 --- a/lib/app/modules/dashboard/controllers/dashboard_controller.dart +++ b/lib/app/modules/dashboard/controllers/dashboard_controller.dart @@ -10,6 +10,9 @@ class DashboardController extends GetxController { final RxBool isLoading = false.obs; final Rx?> roleData = Rx?>(null); + // Indeks kategori yang dipilih untuk filter + final RxInt selectedCategoryIndex = 0.obs; + UserModel? get user => _authController.user; String get role => user?.role ?? 'WARGA'; diff --git a/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart b/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart index 25652c5..dc800a3 100644 --- a/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart +++ b/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart @@ -2,212 +2,207 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:penyaluran_app/app/modules/dashboard/controllers/dashboard_controller.dart'; -import 'package:google_fonts/google_fonts.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; +import 'package:penyaluran_app/app/widgets/navigation_button.dart'; +import 'package:penyaluran_app/app/widgets/statistic_card.dart'; +import 'package:penyaluran_app/app/widgets/status_pill.dart'; class PetugasDesaDashboardView extends GetView { const PetugasDesaDashboardView({super.key}); @override Widget build(BuildContext context) { - final TextTheme textTheme = - GoogleFonts.dmSansTextTheme(Theme.of(context).textTheme); final GlobalKey scaffoldKey = GlobalKey(); + final textTheme = Theme.of(context).textTheme; - return Theme( - data: Theme.of(context).copyWith( - textTheme: textTheme, - ), - child: Scaffold( - key: scaffoldKey, - drawer: Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - DrawerHeader( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF2E5077), Color(0xFF5882B1)], - transform: GradientRotation(96.93 * 3.14159 / 180), + return Scaffold( + key: scaffoldKey, + drawer: Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + decoration: const BoxDecoration( + gradient: AppTheme.primaryGradient, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CircleAvatar( + radius: 30, + backgroundColor: Colors.white, + child: Icon( + Icons.person, + size: 40, + color: AppTheme.primaryColor, + ), ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const CircleAvatar( - radius: 30, - backgroundColor: Colors.white, - child: Icon( - Icons.person, - size: 40, - color: Color(0xFF2E5077), - ), + const SizedBox(height: 10), + Text( + 'Petugas Desa', + style: textTheme.titleLarge?.copyWith( + color: Colors.white, + fontSize: 18, ), - const SizedBox(height: 10), - Text( - 'Petugas Desa', - style: textTheme.titleLarge?.copyWith( - color: Colors.white, - fontSize: 18, - ), + ), + Text( + controller.user?.email ?? 'email@example.com', + style: textTheme.bodyMedium?.copyWith( + color: Colors.white70, + fontSize: 14, ), - Text( - controller.user?.email ?? 'email@example.com', - style: textTheme.bodyMedium?.copyWith( - color: Colors.white70, - fontSize: 14, - ), - ), - ], - ), + ), + ], ), - ListTile( - leading: SvgPicture.asset( - 'assets/icons/home-icon.svg', - width: 24, - height: 24, - ), - title: const Text('Beranda'), - onTap: () { - Get.back(); - }, - ), - ListTile( - leading: const Icon(Icons.calendar_today), - title: const Text('Jadwal'), - onTap: () { - Get.back(); - }, - ), - ListTile( - leading: const Icon(Icons.notifications), - title: const Text('Notifikasi'), - onTap: () { - Get.back(); - }, - ), - ListTile( - leading: const Icon(Icons.inventory), - title: const Text('Inventaris'), - onTap: () { - Get.back(); - }, - ), - const Divider(), - ListTile( - leading: const Icon(Icons.logout), - title: const Text('Logout'), - onTap: controller.logout, - ), - ], - ), - ), - body: SafeArea( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Obx(() { - if (controller.isLoading.value) { - return const Center( - child: CircularProgressIndicator(), - ); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // add drawer button - IconButton( - onPressed: () => scaffoldKey.currentState?.openDrawer(), - icon: const Icon(Icons.menu), - ), - // Header dengan greeting - _buildGreetingHeader(textTheme), - const SizedBox(height: 20), - - // Jadwal penyaluran hari ini - _buildScheduleCard( - textTheme, - title: 'Jadwal Penyaluran Hari ini', - location: 'Kantor Kepala Desa (Beras)', - dateTime: '15 April 2023, 13:00 - 14:00', - isToday: true, - ), - const SizedBox(height: 20), - - // Jadwal penyaluran mendatang - _buildScheduleCard( - textTheme, - title: 'Jadwal Penyaluran Mendatang', - location: 'Balai Desa A (Sembako)', - dateTime: '17 April 2023, 13:00 - 14:00', - isToday: false, - ), - const SizedBox(height: 20), - - // Statistik penyaluran - _buildStatisticsRow(textTheme), - const SizedBox(height: 20), - - // Progress penyaluran - _buildProgressSection(textTheme), - const SizedBox(height: 20), - - // Daftar penerima - _buildRecipientsList(textTheme), - - // Daftar Donasi - _buildDonationList(textTheme), - // Daftar Donatur - _buildDonorsList(textTheme), - ], - ); - }), ), - ), - ), - bottomNavigationBar: BottomNavigationBar( - currentIndex: 0, - type: BottomNavigationBarType.fixed, - selectedLabelStyle: textTheme.bodySmall, - unselectedLabelStyle: textTheme.bodySmall, - items: [ - BottomNavigationBarItem( - icon: SvgPicture.asset( + ListTile( + leading: SvgPicture.asset( 'assets/icons/home-icon.svg', width: 24, height: 24, ), - label: 'Beranda', + title: const Text('Beranda'), + onTap: () { + Get.back(); + }, ), - BottomNavigationBarItem( - icon: SvgPicture.asset( - 'assets/icons/jadwal-icon.svg', - width: 24, - height: 24, - ), - label: 'Jadwal', + ListTile( + leading: const Icon(Icons.calendar_today), + title: const Text('Jadwal'), + onTap: () { + Get.back(); + }, ), - BottomNavigationBarItem( - icon: SvgPicture.asset( - 'assets/icons/notif-icon.svg', - width: 24, - height: 24, - ), - label: 'Notifikasi', + ListTile( + leading: const Icon(Icons.notifications), + title: const Text('Notifikasi'), + onTap: () { + Get.back(); + }, ), - BottomNavigationBarItem( - icon: SvgPicture.asset( - 'assets/icons/inventory-icon.svg', - width: 24, - height: 24, - ), - label: 'Inventaris', + ListTile( + leading: const Icon(Icons.inventory), + title: const Text('Inventaris'), + onTap: () { + Get.back(); + }, + ), + const Divider(), + ListTile( + leading: const Icon(Icons.logout), + title: const Text('Logout'), + onTap: controller.logout, ), ], ), ), + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Obx(() { + if (controller.isLoading.value) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + onPressed: () => scaffoldKey.currentState?.openDrawer(), + icon: const Icon(Icons.menu), + ), + + const SizedBox(height: 10), + + // Header dengan greeting + _buildGreetingHeader(textTheme), + + const SizedBox(height: 20), + + // Jadwal penyaluran hari ini + _buildScheduleCard( + textTheme, + title: 'Jadwal Penyaluran Hari ini', + location: 'Kantor Kepala Desa (Beras)', + dateTime: '15 April 2023, 13:00 - 14:00', + isToday: true, + ), + const SizedBox(height: 20), + + // Jadwal penyaluran mendatang + _buildScheduleCard( + textTheme, + title: 'Jadwal Penyaluran Mendatang', + location: 'Balai Desa A (Sembako)', + dateTime: '17 April 2023, 13:00 - 14:00', + isToday: false, + ), + const SizedBox(height: 20), + + // Statistik penyaluran + _buildStatisticsRow(textTheme), + const SizedBox(height: 20), + + // Progress penyaluran + _buildProgressSection(textTheme), + const SizedBox(height: 20), + + // Daftar penerima + _buildRecipientsList(textTheme), + + // Daftar Donasi + _buildDonationList(textTheme), + // Daftar Donatur + _buildDonorsList(textTheme), + ], + ); + }), + ), + ), + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: 0, + type: BottomNavigationBarType.fixed, + selectedLabelStyle: textTheme.bodySmall, + unselectedLabelStyle: textTheme.bodySmall, + items: [ + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/home-icon.svg', + width: 24, + height: 24, + ), + label: 'Beranda', + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/jadwal-icon.svg', + width: 24, + height: 24, + ), + label: 'Jadwal', + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/notif-icon.svg', + width: 24, + height: 24, + ), + label: 'Notifikasi', + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/inventory-icon.svg', + width: 24, + height: 24, + ), + label: 'Inventaris', + ), + ], + ), ); } @@ -261,12 +256,7 @@ class PetugasDesaDashboardView extends GetView { width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF2E5077), Color(0xFF5882B1)], - transform: GradientRotation(96.93 * 3.14159 / 180), - ), + gradient: AppTheme.primaryGradient, borderRadius: BorderRadius.circular(12), ), child: Column( @@ -288,7 +278,7 @@ class PetugasDesaDashboardView extends GetView { color: Colors.white, ), ), - const SizedBox(height: 4), + const SizedBox(height: 8), Text( dateTime, style: textTheme.bodyMedium?.copyWith( @@ -305,58 +295,83 @@ class PetugasDesaDashboardView extends GetView { return Row( children: [ Expanded( - child: _buildStatCard('Penitipan', '3', 'Konfirmasi', textTheme), + child: StatisticCard( + title: 'Penitipan', + count: '3', + subtitle: 'Perlu Konfirmasi', + height: 120, + ), ), const SizedBox(width: 10), Expanded( - child: _buildStatCard('Penjadwalan', '1', 'Pengajuan', textTheme), + child: StatisticCard( + title: 'Penjadwalan', + count: '1', + subtitle: 'Perlu Konfirmasi', + height: 120, + ), ), const SizedBox(width: 10), Expanded( - child: _buildStatCard('Pengaduan', '1', 'Pengaduan', textTheme), + child: StatisticCard( + title: 'Pengaduan', + count: '1', + subtitle: 'Perlu Tindakan', + height: 120, + ), ), ], ); } - Widget _buildStatCard( - String title, String count, String subtitle, TextTheme textTheme) { + Widget _buildProgressSection(TextTheme textTheme) { return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF2E5077), Color(0xFF5882B1)], - transform: GradientRotation(96.93 * 3.14159 / 180), - ), + gradient: AppTheme.primaryGradient, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - title, - style: textTheme.bodyMedium?.copyWith( - fontSize: 14, - color: Colors.white.withAlpha(204), // 0.8 * 255 ≈ 204 - ), - ), - Text( - count, - style: textTheme.headlineSmall?.copyWith( - fontSize: 24, + 'Progress Penyaluran', + style: textTheme.titleMedium?.copyWith( + fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), - const SizedBox(height: 4), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: LinearProgressIndicator( + value: 0.7, + minHeight: 10, + backgroundColor: Colors.white.withOpacity(0.3), + valueColor: const AlwaysStoppedAnimation(Colors.white), + ), + ), + const SizedBox(height: 10), Text( - subtitle, + '70% Selesai', + style: textTheme.bodyMedium?.copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 10), + _buildProgressDetailRow('Telah Menerima', 70, textTheme), + _buildProgressDetailRow('Dijedwalkan', 20, textTheme), + _buildProgressDetailRow('Belum Dijadwalkan', 10, textTheme), + const SizedBox(height: 5), + Text( + 'Total : 100 Penerima, Telah Disalurkan : 70 Penerima, Belum Disalurkan : 30', style: textTheme.bodySmall?.copyWith( fontSize: 12, - color: Colors.white, + color: Colors.white.withOpacity(0.8), ), ), ], @@ -364,52 +379,6 @@ class PetugasDesaDashboardView extends GetView { ); } - Widget _buildProgressSection(TextTheme textTheme) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Progress Penyaluran', - style: textTheme.titleMedium?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: LinearProgressIndicator( - value: 0.7, - minHeight: 10, - backgroundColor: Colors.grey[300], - valueColor: const AlwaysStoppedAnimation(Color(0xFF2E5077)), - ), - ), - const SizedBox(height: 10), - Text( - '70% Selesai', - style: textTheme.bodyMedium?.copyWith( - fontSize: 14, - fontWeight: FontWeight.bold, - color: const Color(0xFF2E5077), - ), - ), - const SizedBox(height: 10), - _buildProgressDetailRow('Telah Menerima', 70, textTheme), - _buildProgressDetailRow('Dijedwalkan', 20, textTheme), - _buildProgressDetailRow('Belum Dijadwalkan', 10, textTheme), - const SizedBox(height: 5), - Text( - 'Total : 100 Penerima, Telah Disalurkan : 70 Penerima, Belum Disalurkan : 30', - style: textTheme.bodySmall?.copyWith( - fontSize: 12, - color: Colors.grey[600], - ), - ), - ], - ); - } - Widget _buildProgressDetailRow(String label, int value, TextTheme textTheme) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), @@ -442,7 +411,7 @@ class PetugasDesaDashboardView extends GetView { 0.7 * (value / 100), decoration: BoxDecoration( - color: const Color(0xFF2E5077), + color: AppTheme.primaryColor, borderRadius: BorderRadius.circular(4), ), ), @@ -504,20 +473,11 @@ class PetugasDesaDashboardView extends GetView { ), TextButton( onPressed: () {}, - child: Row( - children: [ - Text( - 'Lihat Semua', - style: textTheme.bodyMedium?.copyWith( - color: const Color(0xFF2E5077), - ), - ), - Icon( - Icons.chevron_right, - size: 16, - color: const Color(0xFF2E5077), - ), - ], + child: Text( + 'Lihat Semua', + style: textTheme.bodyMedium?.copyWith( + color: AppTheme.primaryColor, + ), ), ), ], @@ -535,33 +495,26 @@ class PetugasDesaDashboardView extends GetView { width: double.infinity, margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( - color: Colors.white, + gradient: AppTheme.primaryGradient, borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withAlpha(26), // 0.1 * 255 ≈ 26 - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], ), child: ListTile( title: Text( name, style: textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, + color: Colors.white, ), ), subtitle: Text( id, - style: textTheme.bodyMedium, - ), - trailing: IconButton( - icon: const Icon( - Icons.chevron_right, - color: Colors.grey, + style: textTheme.bodyMedium?.copyWith( + color: Colors.white, ), + ), + trailing: NavigationButton( + label: 'Detail', + icon: Icons.arrow_forward_ios, onPressed: () {}, ), ), @@ -615,13 +568,13 @@ class PetugasDesaDashboardView extends GetView { Text( 'Lihat Semua', style: textTheme.bodyMedium?.copyWith( - color: const Color(0xFF2E5077), + color: AppTheme.primaryColor, ), ), Icon( Icons.chevron_right, size: 16, - color: const Color(0xFF2E5077), + color: AppTheme.primaryColor, ), ], ), @@ -645,53 +598,33 @@ class PetugasDesaDashboardView extends GetView { width: double.infinity, margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( - color: Colors.white, + gradient: AppTheme.primaryGradient, borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withAlpha(26), // 0.1 * 255 ≈ 26 - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], ), child: ListTile( - title: Text( - title, - style: textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), + title: Row( + children: [ + Text( + title, + style: textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(width: 8), + StatusPill(status: status, backgroundColor: AppTheme.verifiedColor), + ], ), subtitle: Text( subtitle, - style: textTheme.bodyMedium, + style: textTheme.bodyMedium?.copyWith( + color: Colors.white, + ), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: Colors.green.withAlpha(26), // 0.1 * 255 ≈ 26 - borderRadius: BorderRadius.circular(12), - ), - child: Text( - status, - style: textTheme.bodySmall?.copyWith( - color: Colors.green, - fontSize: 12, - ), - ), - ), - IconButton( - icon: const Icon( - Icons.chevron_right, - color: Colors.grey, - ), - onPressed: () {}, - ), - ], + trailing: NavigationButton( + label: 'Detail', + icon: Icons.arrow_forward_ios, + onPressed: () {}, ), ), ); diff --git a/lib/app/theme/app_theme.dart b/lib/app/theme/app_theme.dart new file mode 100644 index 0000000..3c31ca9 --- /dev/null +++ b/lib/app/theme/app_theme.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + // Warna utama aplikasi + static const Color primaryColor = Color(0xFF2E5077); + static const Color secondaryColor = Color(0xFF5882B1); + static const Color accentColor = Color(0xFF4A6D8C); + + // Warna untuk status + static const Color verifiedColor = Color(0xFFACF1C8); + static const Color processedColor = Color(0xFFF1C8AC); + static const Color rejectedColor = Color(0xFFF1ACAC); + static const Color scheduledColor = Color(0xFF2E5077); + static const Color completedColor = Color(0xFF6750A4); + + static const Color successColor = Colors.green; + static const Color warningColor = Colors.orange; + static const Color errorColor = Colors.red; + static const Color infoColor = Colors.blue; + + // Gradient utama aplikasi + static const LinearGradient primaryGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF2E5077), Color(0xFF5882B1)], + transform: GradientRotation(96.93 * 3.14159 / 180), + ); + + // Tema terang + static ThemeData lightTheme = ThemeData( + useMaterial3: true, + fontFamily: 'DMSans', + + // Skema warna + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + primary: primaryColor, + secondary: secondaryColor, + ), + + // AppBar + appBarTheme: const AppBarTheme( + backgroundColor: Colors.white, + foregroundColor: primaryColor, + elevation: 0, + centerTitle: true, + iconTheme: IconThemeData(color: primaryColor), + ), + + // Tombol + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: primaryColor, + side: const BorderSide(color: primaryColor), + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + + // Input + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: primaryColor, width: 2), + ), + prefixIconColor: primaryColor, + floatingLabelStyle: const TextStyle(color: primaryColor), + ), + + // Card + cardTheme: CardTheme( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + + // Drawer + drawerTheme: const DrawerThemeData( + backgroundColor: Colors.white, + ), + + // Bottom Navigation Bar + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: Colors.white, + selectedItemColor: primaryColor, + unselectedItemColor: Colors.grey, + type: BottomNavigationBarType.fixed, + elevation: 8, + ), + ); + + // Tema gelap (jika diperlukan) + static ThemeData darkTheme = ThemeData( + useMaterial3: true, + fontFamily: 'DMSans', + brightness: Brightness.dark, + + // Skema warna + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + primary: primaryColor, + secondary: secondaryColor, + brightness: Brightness.dark, + ), + + // AppBar + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFF1E1E1E), + foregroundColor: Colors.white, + elevation: 0, + centerTitle: true, + iconTheme: IconThemeData(color: Colors.white), + ), + + // Tombol + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.white, + side: const BorderSide(color: Colors.white), + padding: const EdgeInsets.symmetric(vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + + // Input + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide(color: secondaryColor, width: 2), + ), + prefixIconColor: secondaryColor, + floatingLabelStyle: const TextStyle(color: secondaryColor), + ), + + // Card + cardTheme: CardTheme( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + + // Drawer + drawerTheme: const DrawerThemeData( + backgroundColor: Color(0xFF1E1E1E), + ), + + // Bottom Navigation Bar + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: Color(0xFF1E1E1E), + selectedItemColor: secondaryColor, + unselectedItemColor: Colors.grey, + type: BottomNavigationBarType.fixed, + elevation: 8, + ), + ); +} diff --git a/lib/app/widgets/navigation_button.dart b/lib/app/widgets/navigation_button.dart new file mode 100644 index 0000000..24c0169 --- /dev/null +++ b/lib/app/widgets/navigation_button.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class NavigationButton extends StatelessWidget { + final String label; + final IconData? icon; + final Widget? iconWidget; + final VoidCallback onPressed; + + const NavigationButton({ + Key? key, + required this.label, + this.icon, + this.iconWidget, + required this.onPressed, + }) : assert(icon != null || iconWidget != null, + 'Either icon or iconWidget must be provided'), + super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return ConstrainedBox( + constraints: BoxConstraints(minWidth: 70), // Set a minimum width + child: GestureDetector( + onTap: onPressed, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.primaryColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: + MainAxisSize.min, // Important for preventing layout issues + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + label, + style: textTheme.bodySmall?.copyWith( + fontSize: 10, + fontWeight: FontWeight.w500, + color: Color(0xFFAFF8FF), + ), + ), + const SizedBox(width: 4), + if (icon != null) + Icon( + icon, + size: 12, + color: Color(0xFFAFF8FF), + ) + else if (iconWidget != null) + SizedBox( + width: 12, + height: 12, + child: iconWidget, + ), + ], + ), + ), + ), + ); + } +} + +// Data class for navigation button +class NavigationButtonData { + final String label; + final IconData? icon; + final Widget? iconWidget; + + NavigationButtonData({ + required this.label, + this.icon, + this.iconWidget, + }) : assert(icon != null || iconWidget != null, + 'Either icon or iconWidget must be provided'); +} diff --git a/lib/app/widgets/statistic_card.dart b/lib/app/widgets/statistic_card.dart new file mode 100644 index 0000000..055de2b --- /dev/null +++ b/lib/app/widgets/statistic_card.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; + +class StatisticCard extends StatelessWidget { + final String title; + final String count; + final String subtitle; + final double height; + + const StatisticCard({ + Key? key, + required this.title, + required this.count, + required this.subtitle, + required this.height, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return Container( + width: double.infinity, + height: height, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: AppTheme.primaryGradient, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: textTheme.bodyMedium?.copyWith( + fontSize: 14, + color: Colors.white.withAlpha(204), // 0.8 * 255 ≈ 204 + ), + ), + const SizedBox(height: 4), + Text( + count, + style: textTheme.headlineSmall?.copyWith( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: textTheme.bodySmall?.copyWith( + fontSize: 12, + color: Colors.white, + ), + ), + ], + ), + ); + } +} diff --git a/lib/app/widgets/status_pill.dart b/lib/app/widgets/status_pill.dart new file mode 100644 index 0000000..26c50e9 --- /dev/null +++ b/lib/app/widgets/status_pill.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class StatusPill extends StatelessWidget { + final String status; + final Color? backgroundColor; + final TextStyle? textStyle; + + const StatusPill({ + super.key, + required this.status, + this.backgroundColor, + this.textStyle, + }); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration( + color: backgroundColor ?? AppTheme.verifiedColor, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + status, + style: textStyle ?? + textTheme.bodySmall?.copyWith( + fontSize: 10, + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 57df9f6..50ea042 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ 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'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -24,10 +25,9 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return GetMaterialApp( title: 'Penyaluran App', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), - useMaterial3: true, - ), + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: ThemeMode.light, // Default ke tema terang debugShowCheckedModeBanner: false, initialRoute: AppPages.initial, getPages: AppPages.routes, diff --git a/pubspec.yaml b/pubspec.yaml index 45b14cd..7661e83 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -89,12 +89,12 @@ flutter: # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic + fonts: + - family: DMSans + fonts: + - asset: assets/font/DMSans-VariableFont_opsz,wght.ttf + - asset: assets/font/DMSans-Italic-VariableFont_opsz,wght.ttf + style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf