diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a8ecd8d..9ccbca1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ package="com.example.coffee_iot_flutter"> - - - - - + + + diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png new file mode 100644 index 0000000..22a1c6b Binary files /dev/null and b/android/app/src/main/res/drawable/background.png differ diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f..f88598c 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,6 @@ - - - - - + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4..1c71fcc 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b7..9b9c3de 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d4391..54ca34a 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d..06de0f2 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372e..075a42b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 0000000..5fef228 --- /dev/null +++ b/android/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 06952be..dbc9ea9 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -5,6 +5,10 @@ @drawable/launch_background + false + false + false + shortEdges + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef88..86a149a 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,13 @@ - - - - diff --git a/assets/icon/kopibaru.png b/assets/icon/kopibaru.png new file mode 100644 index 0000000..6eaa5b4 Binary files /dev/null and b/assets/icon/kopibaru.png differ diff --git a/assets/icon/kopibaru_removebg.png b/assets/icon/kopibaru_removebg.png new file mode 100644 index 0000000..88642cc Binary files /dev/null and b/assets/icon/kopibaru_removebg.png differ diff --git a/lib/constants/app_colors.dart b/lib/constants/app_colors.dart new file mode 100644 index 0000000..f2491f5 --- /dev/null +++ b/lib/constants/app_colors.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class AppColors { + // Latar Belakang (Mint Ice yang lembut) + static const Color background = Color(0xFFF4F7F6); + static const Color cardBg = Colors.white; + + // Warna Utama (Earth Tone Premium - Olive Sage) + static const Color primary = Color(0xFF627254); + static const Color primaryLight = Color(0xFF76885B); + + // Teks (Menggunakan pilihan barumu: Charcoal & Cool Grey) + static const Color textMain = Color(0xFF3D3D3B); // Charcoal Dark Grey (Tegas) + static const Color textSecondary = Color(0xFF8B949E); // Cool Grey (Elegan) + + // Warna Status & Peringatan + static const Color success = Color(0xFF7DA78C); + static const Color error = Color(0xFFE74C3C); + static const Color warning = Color(0xFFE67E22); + static const Color info = Color(0xFF3498DB); + static const Color light = Color(0xFFF1C40F); + + // Latar belakang transparan untuk elemen status (Update .withValues untuk Flutter terbaru) + static Color get successBg => success.withValues(alpha: 0.15); + static Color get errorBg => error.withValues(alpha: 0.15); + static Color get warningBg => warning.withValues(alpha: 0.15); + static Color get infoBg => info.withValues(alpha: 0.15); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index f644ba0..a7c2860 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,12 @@ import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:provider/provider.dart'; import 'package:app_links/app_links.dart'; +import 'package:google_fonts/google_fonts.dart'; + +// --- IMPORT APP COLORS --- +import 'constants/app_colors.dart'; +// ------------------------- + import 'services/mqtt_service.dart'; import 'screens/login_screen.dart'; import 'screens/register_screen.dart'; @@ -10,6 +16,7 @@ import 'screens/analytics_screen.dart'; import 'screens/image_result_screen.dart'; import 'screens/change_password_screen.dart'; import 'screens/forgot_password_screen.dart'; +import 'screens/splash_screen.dart'; final GlobalKey navigatorKey = GlobalKey(); @@ -61,13 +68,72 @@ class MyApp extends StatelessWidget { navigatorKey: navigatorKey, title: 'Coffee IoT', debugShowCheckedModeBanner: false, + + // --- KONFIGURASI TEMA LIGHT MODE (TERPUSAT) --- theme: ThemeData( - brightness: Brightness.dark, - primarySwatch: Colors.green, - scaffoldBackgroundColor: Colors.black, + useMaterial3: true, + brightness: Brightness.light, + scaffoldBackgroundColor: AppColors.background, + primaryColor: AppColors.primary, + + // ColorScheme untuk warna dasar komponen Material (Tombol, Dialog, dll) + colorScheme: const ColorScheme.light( + primary: AppColors.primary, + secondary: AppColors.primary, + surface: AppColors.cardBg, + error: AppColors.error, + onPrimary: Colors.white, + onSurface: AppColors.textMain, + ), + + // Menerapkan Google Fonts (Poppins) dengan pewarnaan dari AppColors + textTheme: GoogleFonts.poppinsTextTheme( + ThemeData.light().textTheme, + ).copyWith( + displayLarge: GoogleFonts.poppins(color: AppColors.textMain, fontWeight: FontWeight.bold), + displayMedium: GoogleFonts.poppins(color: AppColors.textMain, fontWeight: FontWeight.bold), + headlineLarge: GoogleFonts.poppins(color: AppColors.textMain, fontWeight: FontWeight.bold), + headlineMedium: GoogleFonts.poppins(color: AppColors.textMain, fontWeight: FontWeight.w600), + titleLarge: GoogleFonts.poppins(color: AppColors.textMain, fontWeight: FontWeight.w600), + bodyLarge: GoogleFonts.poppins(color: AppColors.textMain), + bodyMedium: GoogleFonts.poppins(color: AppColors.textMain), + bodySmall: GoogleFonts.poppins(color: AppColors.textSecondary), + labelLarge: GoogleFonts.poppins(color: AppColors.textMain, fontWeight: FontWeight.w500), + ), + + // Tema AppBar Global (Biar transparan dan tulisannya otomatis ikut tema) + appBarTheme: AppBarTheme( + backgroundColor: AppColors.background, + elevation: 0, + centerTitle: true, + iconTheme: const IconThemeData(color: AppColors.textMain), + titleTextStyle: GoogleFonts.poppins( + color: AppColors.textMain, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + + // Tema Card Global (Otomatis membulat dan punya bayangan halus) + cardTheme: CardThemeData( + color: AppColors.cardBg, + elevation: 2, + shadowColor: Colors.black.withValues(alpha: 0.05), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + + // Tema Ikon Global + iconTheme: const IconThemeData( + color: AppColors.textMain, + ), ), - initialRoute: '/login', + // ---------------------------------------------- + + initialRoute: '/', routes: { + '/': (context) => const SplashScreen(), '/login': (context) => const LoginScreen(), '/register': (context) => const RegisterScreen(), '/home': (context) => const HomeScreen(), diff --git a/lib/screens/analytics_screen.dart b/lib/screens/analytics_screen.dart index 7032e2d..577c623 100644 --- a/lib/screens/analytics_screen.dart +++ b/lib/screens/analytics_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import '../services/supabase_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class AnalyticsScreen extends StatefulWidget { const AnalyticsScreen({super.key}); @@ -36,21 +37,27 @@ class _AnalyticsScreenState extends State { Widget build(BuildContext context) { final args = (ModalRoute.of(context)?.settings.arguments as Map?) ?? {}; final String title = args['title'] ?? 'Grafik'; - final Color color = args['color'] ?? Colors.green; + final Color color = args['color'] ?? AppColors.primary; // --- BERUBAH: Fallback color --- final String sensorKey = title.contains('Suhu') ? 'suhu' : title.contains('Kelembaban') ? 'kelembapan' : 'intensitas'; return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background Scaffold Utama --- + backgroundColor: AppColors.background, appBar: AppBar( - backgroundColor: Colors.black, - title: Text(title, style: const TextStyle(color: Colors.white)), + // --- BERUBAH: Dibuat transparan agar menyatu dengan background --- + backgroundColor: Colors.transparent, + elevation: 0, + // --- BERUBAH: Warna Teks Judul --- + title: Text(title, style: const TextStyle(color: AppColors.textMain)), leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.grey), + // --- BERUBAH: Warna Ikon Back --- + icon: const Icon(Icons.arrow_back, color: AppColors.primary), onPressed: () => Navigator.pop(context), ), ), body: _isLoading - ? const Center(child: CircularProgressIndicator(color: Colors.green)) + // --- BERUBAH: Warna Loading Indicator --- + ? const Center(child: CircularProgressIndicator(color: AppColors.primary)) : Padding( padding: const EdgeInsets.all(20.0), child: Column( @@ -60,21 +67,27 @@ class _AnalyticsScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('Riwayat Data', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), - IconButton(icon: const Icon(Icons.refresh, color: Colors.green), onPressed: _fetchLogs), + // --- BERUBAH: Warna Teks Judul Riwayat --- + const Text('Riwayat Data', style: TextStyle(color: AppColors.textMain, fontSize: 18, fontWeight: FontWeight.bold)), + // --- BERUBAH: Warna Ikon Refresh --- + IconButton(icon: const Icon(Icons.refresh, color: AppColors.primary), onPressed: _fetchLogs), ], ), const SizedBox(height: 10), Expanded( child: Container( decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Warna Latar Daftar Riwayat --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + // --- BERUBAH: Menambahkan border halus agar serasi dengan kartu lain --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: ListView.separated( padding: const EdgeInsets.all(15), itemCount: _logs.length, - separatorBuilder: (context, index) => const Divider(color: Colors.grey), + // --- BERUBAH: Warna Garis Pemisah (Divider) antar item --- + separatorBuilder: (context, index) => Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)), itemBuilder: (context, index) { final log = _logs[index]; return Padding( @@ -85,10 +98,13 @@ class _AnalyticsScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(log['timestamp'] ?? '', style: const TextStyle(color: Colors.grey, fontSize: 10)), - Text('Fans: ${log['kipas1']}/${log['kipas2']}', style: const TextStyle(color: Colors.green, fontSize: 10)), + // --- BERUBAH: Warna Teks Tanggal/Waktu --- + Text(log['timestamp'] ?? '', style: const TextStyle(color: AppColors.textSecondary, fontSize: 10)), + // --- BERUBAH: Warna Teks Status Kipas --- + Text('Kipas 1: ${log['kipas1']} | Kipas 2: ${log['kipas2']}', style: const TextStyle(color: AppColors.primary, fontSize: 10)), ], ), + // Warna nilai sensor tetap mengikuti `color` dinamis (Merah/Cyan/Orange) Text('${log[sensorKey]}', style: TextStyle(color: color, fontWeight: FontWeight.bold)), ], ), @@ -116,14 +132,15 @@ class _AnalyticsScreenState extends State { height: 250, padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Warna Latar Kartu Grafik --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + // --- BERUBAH: Menambahkan border halus agar serasi dengan kartu lain --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), - const SizedBox(height: 20), Expanded( child: LineChart( LineChartData( @@ -134,10 +151,12 @@ class _AnalyticsScreenState extends State { LineChartBarData( spots: spots.isEmpty ? [const FlSpot(0, 0)] : spots, isCurved: true, + // Warna garis grafik tetap mengikuti `color` dinamis color: color, barWidth: 4, dotData: FlDotData(show: false), - belowBarData: BarAreaData(show: true, color: color.withOpacity(0.1)), + // --- BERUBAH: Gunakan withValues untuk transparansi area bawah grafik --- + belowBarData: BarAreaData(show: true, color: color.withValues(alpha: 0.1)), ), ], ), @@ -147,6 +166,7 @@ class _AnalyticsScreenState extends State { alignment: Alignment.bottomRight, child: Text( 'Terakhir: ${logs.isNotEmpty ? logs.first[key] : "-"}', + // Warna teks terakhir tetap mengikuti `color` dinamis style: TextStyle(color: color, fontSize: 12) ), ), @@ -154,4 +174,4 @@ class _AnalyticsScreenState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/screens/change_password_screen.dart b/lib/screens/change_password_screen.dart index 092f3b9..9f847b4 100644 --- a/lib/screens/change_password_screen.dart +++ b/lib/screens/change_password_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../services/supabase_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class ChangePasswordScreen extends StatefulWidget { const ChangePasswordScreen({super.key}); @@ -118,17 +119,18 @@ class _ChangePasswordScreenState extends State { content: Row( children: [ Icon(isError ? Icons.error_outline : Icons.check_circle_outline, - color: Colors.white), + color: Colors.white), // Tetap putih agar kontras const SizedBox(width: 10), Expanded( child: Text(pesan, style: const TextStyle( - color: Colors.white, fontWeight: FontWeight.bold)), + color: Colors.white, fontWeight: FontWeight.bold)), // Tetap putih ), ], ), + // --- BERUBAH: Warna Notifikasi --- backgroundColor: - isError ? Colors.red.shade700 : Colors.green.shade700, + isError ? AppColors.error : AppColors.success, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), margin: const EdgeInsets.all(16), @@ -140,11 +142,15 @@ class _ChangePasswordScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background Utama --- + backgroundColor: AppColors.background, appBar: AppBar( - backgroundColor: Colors.black, + // --- BERUBAH: AppBar Transparan --- + backgroundColor: Colors.transparent, + elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), + // --- BERUBAH: Warna Ikon Kembali --- + icon: const Icon(Icons.arrow_back, color: AppColors.primary), onPressed: () => _dariResetLink ? Navigator.pushReplacementNamed(context, '/login') : Navigator.pop(context), @@ -152,7 +158,8 @@ class _ChangePasswordScreenState extends State { title: Text( _dariResetLink ? 'Reset Password' : 'Ganti Password', style: const TextStyle( - color: Colors.white, + // --- BERUBAH: Warna Judul --- + color: AppColors.textMain, fontSize: 20, fontWeight: FontWeight.bold), ), @@ -167,16 +174,19 @@ class _ChangePasswordScreenState extends State { _dariResetLink ? 'Masukkan password baru untuk akun kamu.' : 'Ubah password akun kamu.\nPastikan password baru minimal 6 karakter.', - style: const TextStyle(color: Colors.grey, fontSize: 14), + // --- BERUBAH: Warna Sub-judul --- + style: const TextStyle(color: AppColors.textSecondary, fontSize: 14), ), const SizedBox(height: 30), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Latar Belakang Kotak Form --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.grey.withOpacity(0.3)), + // --- BERUBAH: Garis Pinggir Halus --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( children: [ @@ -189,7 +199,8 @@ class _ChangePasswordScreenState extends State { onToggle: () => setState( () => _obscurePassLama = !_obscurePassLama), ), - const Divider(color: Colors.grey), + // --- BERUBAH: Warna Divider --- + Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)), ], _buildPasswordField( @@ -199,7 +210,8 @@ class _ChangePasswordScreenState extends State { onToggle: () => setState( () => _obscurePassBaru = !_obscurePassBaru), ), - const Divider(color: Colors.grey), + // --- BERUBAH: Warna Divider --- + Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)), _buildPasswordField( controller: _konfirmasiController, @@ -220,20 +232,22 @@ class _ChangePasswordScreenState extends State { child: ElevatedButton( onPressed: _isLoading ? null : _handleGantiPassword, style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - disabledBackgroundColor: Colors.green.withOpacity(0.3), + // --- BERUBAH: Warna Tombol --- + backgroundColor: AppColors.primary, + disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), ), child: _isLoading - ? const CircularProgressIndicator(color: Colors.white) + // Tetap putih agar kontras dengan tombol primer + ? const CircularProgressIndicator(color: Colors.white) : const Text( 'Simpan Password Baru', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: Colors.white), + color: Colors.white), // Tetap putih ), ), ), @@ -253,16 +267,20 @@ class _ChangePasswordScreenState extends State { return TextField( controller: controller, obscureText: obscure, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Teks Inputan --- + style: const TextStyle(color: AppColors.textMain), decoration: InputDecoration( - prefixIcon: const Icon(Icons.lock, color: Colors.green), + // --- BERUBAH: Warna Ikon Kunci --- + prefixIcon: const Icon(Icons.lock, color: AppColors.primary), suffixIcon: IconButton( icon: Icon(obscure ? Icons.visibility : Icons.visibility_off, - color: Colors.grey), + // --- BERUBAH: Warna Ikon Mata --- + color: AppColors.textSecondary), onPressed: onToggle, ), hintText: label, - hintStyle: const TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: const TextStyle(color: AppColors.textSecondary), border: InputBorder.none, ), ); diff --git a/lib/screens/control_settings_screen.dart b/lib/screens/control_settings_screen.dart index 5bdcf86..ac3ae6d 100644 --- a/lib/screens/control_settings_screen.dart +++ b/lib/screens/control_settings_screen.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../services/mqtt_service.dart'; import '../services/supabase_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class ControlSettingsScreen extends StatefulWidget { const ControlSettingsScreen({super.key}); @@ -60,9 +61,10 @@ class _ControlSettingsScreenState extends State { _humMinController.text.isEmpty || _humMaxController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Semua kolom batas harus diisi!'), - backgroundColor: Colors.orange, + SnackBar( + content: const Text('Semua kolom batas harus diisi!'), + // --- BERUBAH: Warna Notifikasi Peringatan --- + backgroundColor: AppColors.warning, ), ); return; @@ -76,18 +78,20 @@ class _ControlSettingsScreenState extends State { // Validasi logika if (suhuMin >= suhuMax) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Suhu min harus lebih kecil dari suhu max!'), - backgroundColor: Colors.orange, + SnackBar( + content: const Text('Suhu min harus lebih kecil dari suhu max!'), + // --- BERUBAH: Warna Notifikasi Peringatan --- + backgroundColor: AppColors.warning, ), ); return; } if (rhMin >= rhMax) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('RH min harus lebih kecil dari RH max!'), - backgroundColor: Colors.orange, + SnackBar( + content: const Text('RH min harus lebih kecil dari RH max!'), + // --- BERUBAH: Warna Notifikasi Peringatan --- + backgroundColor: AppColors.warning, ), ); return; @@ -113,9 +117,10 @@ class _ControlSettingsScreenState extends State { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('✅ Pengaturan Tersimpan & Terkirim ke ESP32'), - backgroundColor: Colors.green, + SnackBar( + content: const Text('✅ Pengaturan Tersimpan & Terkirim ke ESP32'), + // --- BERUBAH: Warna Notifikasi Sukses --- + backgroundColor: AppColors.success, ), ); } @@ -124,7 +129,8 @@ class _ControlSettingsScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('❌ Gagal: $e'), - backgroundColor: Colors.red, + // --- BERUBAH: Warna Notifikasi Error --- + backgroundColor: AppColors.error, ), ); } @@ -136,11 +142,13 @@ class _ControlSettingsScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background Scaffold Utama --- + backgroundColor: AppColors.background, body: SafeArea( child: _isLoading ? const Center( - child: CircularProgressIndicator(color: Colors.green)) + // --- BERUBAH: Warna Loading Indicator --- + child: CircularProgressIndicator(color: AppColors.primary)) : SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20.0), @@ -150,13 +158,15 @@ class _ControlSettingsScreenState extends State { const Text( 'Pengaturan', style: TextStyle( - color: Colors.white, + // --- BERUBAH: Warna Judul Utama --- + color: AppColors.textMain, fontSize: 32, fontWeight: FontWeight.bold), ), const Text( 'Batas Sensor & Mode Operasi', - style: TextStyle(color: Colors.grey, fontSize: 16), + // --- BERUBAH: Warna Sub-judul --- + style: TextStyle(color: AppColors.textSecondary, fontSize: 16), ), const SizedBox(height: 30), @@ -188,15 +198,19 @@ class _ControlSettingsScreenState extends State { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Background Kartu Mode Operasi --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + // --- BERUBAH: Menambahkan border halus agar serasi --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Mode Operasi', style: TextStyle( - color: Colors.white, + // --- BERUBAH: Warna Judul Kartu Mode --- + color: AppColors.textMain, fontWeight: FontWeight.bold, fontSize: 16)), const SizedBox(height: 10), @@ -206,12 +220,15 @@ class _ControlSettingsScreenState extends State { onChanged: (val) => setState(() => _operatingMode = val!), title: const Text('Mode Otomatis', - style: TextStyle(color: Colors.white)), - activeColor: Colors.green, + // --- BERUBAH: Warna Teks Radio Button --- + style: TextStyle(color: AppColors.textMain)), + // --- BERUBAH: Warna Lingkaran Radio Button (Aktif) --- + activeColor: AppColors.primary, subtitle: const Text( 'Kipas bekerja otomatis berdasarkan sensor', + // --- BERUBAH: Warna Sub-teks Radio Button --- style: TextStyle( - color: Colors.grey, fontSize: 10), + color: AppColors.textSecondary, fontSize: 10), ), ), RadioListTile( @@ -220,12 +237,15 @@ class _ControlSettingsScreenState extends State { onChanged: (val) => setState(() => _operatingMode = val!), title: const Text('Mode Manual', - style: TextStyle(color: Colors.white)), - activeColor: Colors.green, + // --- BERUBAH: Warna Teks Radio Button --- + style: TextStyle(color: AppColors.textMain)), + // --- BERUBAH: Warna Lingkaran Radio Button (Aktif) --- + activeColor: AppColors.primary, subtitle: const Text( 'Kontrol penuh kipas melalui dashboard HP', + // --- BERUBAH: Warna Sub-teks Radio Button --- style: TextStyle( - color: Colors.grey, fontSize: 10), + color: AppColors.textSecondary, fontSize: 10), ), ), ], @@ -240,7 +260,8 @@ class _ControlSettingsScreenState extends State { child: ElevatedButton( onPressed: _saveSettings, style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, + // --- BERUBAH: Warna Latar Tombol Simpan --- + backgroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), @@ -250,7 +271,8 @@ class _ControlSettingsScreenState extends State { style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.white), + // Tetap putih agar tulisan dalam tombol kontras + color: Colors.white), ), ), ), @@ -268,31 +290,44 @@ class _ControlSettingsScreenState extends State { return Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Latar Belakang Kartu Input Sensor --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + // --- BERUBAH: Menambahkan border halus agar serasi --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( - color: Colors.white, + // --- BERUBAH: Warna Judul Input Sensor --- + color: AppColors.textMain, fontSize: 12, fontWeight: FontWeight.bold)), const SizedBox(height: 10), TextField( controller: controller, keyboardType: TextInputType.number, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Angka/Nilai Input Sensor --- + style: const TextStyle(color: AppColors.textMain), decoration: InputDecoration( hintText: hint, - hintStyle: const TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Teks Placeholder/Hint --- + hintStyle: const TextStyle(color: AppColors.textSecondary), filled: true, - fillColor: Colors.black, + // --- BERUBAH: Warna Kolom Input Sensor --- + fillColor: AppColors.background, isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(15), - borderSide: const BorderSide(color: Colors.grey), + // --- BERUBAH: Warna Garis Pinggir Input Sensor --- + borderSide: BorderSide(color: AppColors.textSecondary.withValues(alpha: 0.3)), + ), + // Menambahkan fokus border agar saat diklik terlihat jelas warnanya + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(15), + borderSide: const BorderSide(color: AppColors.primary), ), ), ), diff --git a/lib/screens/forgot_password_screen.dart b/lib/screens/forgot_password_screen.dart index 68f9b7c..7f077c4 100644 --- a/lib/screens/forgot_password_screen.dart +++ b/lib/screens/forgot_password_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../services/supabase_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class ForgotPasswordScreen extends StatefulWidget { const ForgotPasswordScreen({super.key}); @@ -104,7 +105,7 @@ class _ForgotPasswordScreenState extends State { content: Row(children: [ Icon( isError ? Icons.error_outline : Icons.check_circle_outline, - color: Colors.white, + color: Colors.white, // Tetap putih untuk kontras ), const SizedBox(width: 10), Expanded( @@ -113,8 +114,9 @@ class _ForgotPasswordScreenState extends State { color: Colors.white, fontWeight: FontWeight.bold)), ), ]), + // --- BERUBAH: Menggunakan warna notifikasi dari AppColors --- backgroundColor: - isError ? Colors.red.shade700 : Colors.green.shade700, + isError ? AppColors.error : AppColors.success, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), margin: const EdgeInsets.all(16), @@ -126,12 +128,14 @@ class _ForgotPasswordScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background Utama --- + backgroundColor: AppColors.background, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.green), + // --- BERUBAH: Warna Ikon Back --- + icon: const Icon(Icons.arrow_back, color: AppColors.primary), onPressed: () => Navigator.pop(context), ), ), @@ -152,12 +156,14 @@ class _ForgotPasswordScreenState extends State { const Text( 'Lupa Password?', style: TextStyle( - color: Colors.green, fontSize: 32, fontWeight: FontWeight.bold), + // --- BERUBAH: Warna Judul --- + color: AppColors.primary, fontSize: 32, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), const Text( 'Masukkan email akun kamu. Kami akan kirim kode verifikasi 6 angka ke inbox kamu.', - style: TextStyle(color: Colors.grey, fontSize: 14), + // --- BERUBAH: Warna Sub-judul --- + style: TextStyle(color: AppColors.textSecondary, fontSize: 14), ), const SizedBox(height: 50), @@ -165,18 +171,23 @@ class _ForgotPasswordScreenState extends State { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Latar Belakang Form --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(25), - border: Border.all(color: Colors.grey.withOpacity(0.3)), + // --- BERUBAH: Garis Pinggir Form --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.3)), ), child: TextField( controller: _emailController, keyboardType: TextInputType.emailAddress, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Teks Inputan --- + style: const TextStyle(color: AppColors.textMain), decoration: const InputDecoration( - prefixIcon: Icon(Icons.email, color: Colors.green), + // --- BERUBAH: Warna Ikon --- + prefixIcon: Icon(Icons.email, color: AppColors.primary), hintText: 'Email', - hintStyle: TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: TextStyle(color: AppColors.textSecondary), border: InputBorder.none, ), ), @@ -190,8 +201,10 @@ class _ForgotPasswordScreenState extends State { child: ElevatedButton( onPressed: _isLoading ? null : _handleKirimOTP, style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - disabledBackgroundColor: Colors.green.withOpacity(0.3), + // --- BERUBAH: Warna Tombol --- + backgroundColor: AppColors.primary, + // --- BERUBAH: Warna Tombol saat Loading --- + disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15)), ), @@ -211,7 +224,8 @@ class _ForgotPasswordScreenState extends State { child: TextButton( onPressed: () => Navigator.pop(context), child: const Text('Kembali ke Login', - style: TextStyle(color: Colors.green)), + // --- BERUBAH: Warna Teks Bawah --- + style: TextStyle(color: AppColors.primary)), ), ), ], @@ -225,19 +239,23 @@ class _ForgotPasswordScreenState extends State { children: [ const CircleAvatar( radius: 40, - backgroundColor: Color(0xFF1A1A1A), - child: Icon(Icons.mark_email_read, size: 40, color: Colors.green), + // --- BERUBAH: Latar Belakang Ikon --- + backgroundColor: AppColors.cardBg, + // --- BERUBAH: Warna Ikon --- + child: Icon(Icons.mark_email_read, size: 40, color: AppColors.primary), ), const SizedBox(height: 24), const Text( 'Cek Email Kamu', style: TextStyle( - color: Colors.green, fontSize: 28, fontWeight: FontWeight.bold), + // --- BERUBAH: Warna Judul --- + color: AppColors.primary, fontSize: 28, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( 'Kode verifikasi 6 angka sudah dikirim ke:\n$_email\n\nMasukkan kode tersebut di bawah ini.', - style: const TextStyle(color: Colors.grey, fontSize: 14), + // --- BERUBAH: Warna Subteks --- + style: const TextStyle(color: AppColors.textSecondary, fontSize: 14), ), const SizedBox(height: 40), @@ -245,9 +263,11 @@ class _ForgotPasswordScreenState extends State { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Latar Belakang Kotak OTP --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(25), - border: Border.all(color: Colors.green.withOpacity(0.5)), + // --- BERUBAH: Garis Pinggir Kotak OTP --- + border: Border.all(color: AppColors.primary.withValues(alpha: 0.5)), ), child: TextField( controller: _otpController, @@ -255,11 +275,14 @@ class _ForgotPasswordScreenState extends State { maxLength: 6, textAlign: TextAlign.center, style: const TextStyle( - color: Colors.white, fontSize: 28, letterSpacing: 12), + // --- BERUBAH: Warna Teks Inputan --- + color: AppColors.textMain, fontSize: 28, letterSpacing: 12), decoration: const InputDecoration( - prefixIcon: Icon(Icons.lock_open, color: Colors.green), + // --- BERUBAH: Warna Ikon --- + prefixIcon: Icon(Icons.lock_open, color: AppColors.primary), hintText: '000000', - hintStyle: TextStyle(color: Colors.grey, letterSpacing: 8), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: TextStyle(color: AppColors.textSecondary, letterSpacing: 8), border: InputBorder.none, counterText: '', // sembunyikan counter maxLength ), @@ -274,8 +297,10 @@ class _ForgotPasswordScreenState extends State { child: ElevatedButton( onPressed: _isLoading ? null : _handleVerifikasiOTP, style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - disabledBackgroundColor: Colors.green.withOpacity(0.3), + // --- BERUBAH: Warna Tombol --- + backgroundColor: AppColors.primary, + // --- BERUBAH: Warna Tombol saat Loading --- + disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15)), ), @@ -303,7 +328,8 @@ class _ForgotPasswordScreenState extends State { }), child: const Text( 'Kirim ulang kode', - style: TextStyle(color: Colors.green), + // --- BERUBAH: Warna Teks Bawah --- + style: TextStyle(color: AppColors.primary), ), ), ), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 5af2375..e70b1e2 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/mqtt_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN import 'monitoring_screen.dart'; import 'control_settings_screen.dart'; import 'recording_screen.dart'; @@ -26,8 +27,15 @@ class _HomeScreenState extends State { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(message), - backgroundColor: isSuccess ? Colors.green.withOpacity(0.8) : Colors.redAccent, + content: Text( + message, + // Opsional: Teks putih agar selalu kontras dengan warna notifikasi + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold) + ), + // --- BERUBAH: Warna Notifikasi Berhasil / Gagal --- + backgroundColor: isSuccess + ? AppColors.success.withValues(alpha: 0.9) + : AppColors.error, duration: const Duration(seconds: 3), ), ); @@ -51,11 +59,15 @@ class _HomeScreenState extends State { currentIndex: _currentIndex, onTap: (index) => setState(() => _currentIndex = index), type: BottomNavigationBarType.fixed, - backgroundColor: Colors.black, - selectedItemColor: Colors.green, - unselectedItemColor: Colors.grey, + // --- BERUBAH: Latar Belakang Menu Navigasi Bawah --- + backgroundColor: AppColors.cardBg, + // --- BERUBAH: Warna Ikon yang Sedang Dipilih --- + selectedItemColor: AppColors.primary, + // --- BERUBAH: Warna Ikon yang Tidak Dipilih --- + unselectedItemColor: AppColors.textSecondary, showSelectedLabels: false, showUnselectedLabels: false, + elevation: 10, // Menambahkan sedikit bayangan agar terpisah dari body items: const [ BottomNavigationBarItem(icon: Icon(Icons.grid_view), label: 'Monitoring'), BottomNavigationBarItem(icon: Icon(Icons.tune), label: 'Control'), @@ -65,4 +77,4 @@ class _HomeScreenState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/screens/image_result_screen.dart b/lib/screens/image_result_screen.dart index b98a84c..aede919 100644 --- a/lib/screens/image_result_screen.dart +++ b/lib/screens/image_result_screen.dart @@ -4,6 +4,7 @@ import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:http/http.dart' as http; +import '../constants/app_colors.dart'; class ImageResultScreen extends StatelessWidget { const ImageResultScreen({super.key}); @@ -14,19 +15,17 @@ class ImageResultScreen extends StatelessWidget { Future _exportPDF( BuildContext context, Map args) async { - // Tampilkan loading showDialog( context: context, barrierDismissible: false, builder: (_) => const Center( - child: CircularProgressIndicator(color: Colors.green), + child: CircularProgressIndicator(color: AppColors.primary), ), ); try { final pdf = pw.Document(); - // Download foto dari URL untuk dimasukkan ke PDF pw.MemoryImage? fotoImage; final urlFoto = args['url_foto']?.toString() ?? ''; if (urlFoto.isNotEmpty) { @@ -40,7 +39,6 @@ class ImageResultScreen extends StatelessWidget { } } - // Buat halaman PDF pdf.addPage( pw.Page( pageFormat: PdfPageFormat.a4, @@ -49,7 +47,6 @@ class ImageResultScreen extends StatelessWidget { return pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ - // ── Header ── pw.Container( width: double.infinity, padding: const pw.EdgeInsets.all(16), @@ -82,7 +79,6 @@ class ImageResultScreen extends StatelessWidget { pw.SizedBox(height: 20), - // ── Info Waktu ── pw.Text( 'Tanggal & Waktu: ${args['date']} - ${args['time']}', style: pw.TextStyle( @@ -95,7 +91,6 @@ class ImageResultScreen extends StatelessWidget { pw.Divider(), pw.SizedBox(height: 16), - // ── Foto ── if (fotoImage != null) ...[ pw.Center( child: pw.Container( @@ -122,7 +117,6 @@ class ImageResultScreen extends StatelessWidget { pw.SizedBox(height: 16), ], - // ── Data Sensor ── pw.Text( 'Data Sensor', style: pw.TextStyle( @@ -151,7 +145,6 @@ class ImageResultScreen extends StatelessWidget { pw.Divider(), pw.SizedBox(height: 8), - // ── Footer ── pw.Text( 'Diekspor dari Aplikasi Monitoring Kopi', style: const pw.TextStyle( @@ -165,10 +158,8 @@ class ImageResultScreen extends StatelessWidget { ), ); - // Tutup loading if (context.mounted) Navigator.pop(context); - // Tampilkan preview PDF + opsi simpan/share await Printing.layoutPdf( onLayout: (PdfPageFormat format) async => pdf.save(), name: @@ -176,11 +167,11 @@ class ImageResultScreen extends StatelessWidget { ); } catch (e) { if (context.mounted) { - Navigator.pop(context); // tutup loading + Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal membuat PDF: $e'), - backgroundColor: Colors.red.shade700, + backgroundColor: AppColors.error, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -231,18 +222,18 @@ class ImageResultScreen extends StatelessWidget { }; return Scaffold( - backgroundColor: Colors.black, + backgroundColor: AppColors.background, appBar: AppBar( - backgroundColor: Colors.black, + backgroundColor: Colors.transparent, + elevation: 0, title: const Text( 'Detail Data Citra', - style: TextStyle(color: Colors.white), + style: TextStyle(color: AppColors.textMain), ), leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.grey), + icon: const Icon(Icons.arrow_back, color: AppColors.primary), onPressed: () => Navigator.pop(context), ), - // Icon download dihapus ), body: Padding( padding: const EdgeInsets.all(20.0), @@ -253,8 +244,9 @@ class ImageResultScreen extends StatelessWidget { height: 250, width: double.infinity, decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), clipBehavior: Clip.hardEdge, child: args['url_foto'] != null && @@ -265,26 +257,26 @@ class ImageResultScreen extends StatelessWidget { loadingBuilder: (context, child, progress) { if (progress == null) return child; return const Center( - child: - CircularProgressIndicator(color: Colors.green), + child: CircularProgressIndicator(color: AppColors.primary), ); }, errorBuilder: (context, error, stack) => Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.broken_image, - size: 80, color: Colors.grey), + size: 80, color: AppColors.textSecondary), Text('Citra #${args['id']}', - style: const TextStyle(color: Colors.grey)), + style: const TextStyle(color: AppColors.textSecondary)), ], ), ) : Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.image, size: 80, color: Colors.grey), + const Icon(Icons.image, + size: 80, color: AppColors.textSecondary), Text('Citra #${args['id']}', - style: const TextStyle(color: Colors.grey)), + style: const TextStyle(color: AppColors.textSecondary)), ], ), ), @@ -294,41 +286,43 @@ class ImageResultScreen extends StatelessWidget { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( children: [ - // Tanggal & waktu Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.calendar_today, - color: Colors.green, size: 16), + color: AppColors.primary, size: 16), const SizedBox(width: 8), Text( '${args['date']} - ${args['time']}', style: const TextStyle( - color: Colors.green, + color: AppColors.primary, fontWeight: FontWeight.bold, fontSize: 16), ), ], ), - // Lokasi dihapus - const Divider(color: Colors.grey, height: 30), + Divider(color: AppColors.textSecondary.withValues(alpha: 0.3), height: 30), _buildDataRow( 'Suhu', args['temp'], 'Kelembaban', args['humidity']), const SizedBox(height: 15), + + // --- BERUBAH: Mengirimkan string kosong ('') untuk menghapus kolom Mode --- _buildDataRow( - 'Cahaya', args['light'], 'Mode', 'Terjadwal'), - const Divider(color: Colors.grey, height: 30), + 'Cahaya', args['light'], '', ''), + + Divider(color: AppColors.textSecondary.withValues(alpha: 0.3), height: 30), _buildDataRow( 'Kipas 1 (Intake)', args['intake'], 'Kipas 2 (Exhaust)', args['exhaust'], - valueColor: Colors.green, + valueColor: AppColors.primary, ), ], ), @@ -342,7 +336,7 @@ class ImageResultScreen extends StatelessWidget { child: ElevatedButton.icon( onPressed: () => _exportPDF(context, args), style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, + backgroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), @@ -361,12 +355,13 @@ class ImageResultScreen extends StatelessWidget { ); } + // --- BERUBAH: Menambahkan logika untuk menyembunyikan kolom kanan jika string kosong --- Widget _buildDataRow( String label1, String value1, String label2, String value2, { - Color valueColor = Colors.white, + Color? valueColor, }) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -376,30 +371,32 @@ class ImageResultScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label1, - style: const TextStyle(color: Colors.grey, fontSize: 12)), + style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)), const SizedBox(height: 4), Text(': $value1', style: TextStyle( - color: valueColor, + color: valueColor ?? AppColors.textMain, fontWeight: FontWeight.bold, fontSize: 15)), ], ), ), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label2, - style: const TextStyle(color: Colors.grey, fontSize: 12)), - const SizedBox(height: 4), - Text(': $value2', - style: TextStyle( - color: valueColor, - fontWeight: FontWeight.bold, - fontSize: 15)), - ], - ), + child: label2.isEmpty + ? const SizedBox.shrink() // Jika kosong, sembunyikan sepenuhnya + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label2, + style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)), + const SizedBox(height: 4), + Text(': $value2', + style: TextStyle( + color: valueColor ?? AppColors.textMain, + fontWeight: FontWeight.bold, + fontSize: 15)), + ], + ), ), ], ); diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index 94ed567..386fbdd 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../services/supabase_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -64,20 +65,21 @@ class _LoginScreenState extends State { SnackBar( content: Row( children: [ - const Icon(Icons.error_outline, color: Colors.white), + const Icon(Icons.error_outline, color: Colors.white), // Tetap putih agar kontras di atas merah const SizedBox(width: 10), Expanded( child: Text( pesan, style: const TextStyle( - color: Colors.white, + color: Colors.white, // Tetap putih fontWeight: FontWeight.bold, ), ), ), ], ), - backgroundColor: Colors.red.shade700, + // --- BERUBAH: Background SnackBar Error --- + backgroundColor: AppColors.error, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -91,70 +93,93 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background Scaffold Utama --- + backgroundColor: AppColors.background, body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 80.0), child: Column( children: [ - const CircleAvatar( + CircleAvatar( radius: 60, - backgroundColor: Color(0xFF1A1A1A), - child: Icon(Icons.coffee, size: 60, color: Colors.orange), + // --- BERUBAH: Latar belakang logo --- + backgroundColor: AppColors.primary, + child: ClipOval( + child: Padding( + padding: const EdgeInsets.all(8), + child: Image.asset( + 'assets/icon/kopibaru_removebg.png', + width: 120, + height: 120, + fit: BoxFit.contain, + ), + ), + ), ), const SizedBox(height: 30), const Text( 'Selamat Datang', style: TextStyle( - color: Colors.green, + // --- BERUBAH: Warna Judul --- + color: AppColors.primary, fontSize: 32, fontWeight: FontWeight.bold), ), const Text( 'Sistem Kendali Pengering Kopi', - style: TextStyle(color: Colors.grey, fontSize: 16), + // --- BERUBAH: Warna Sub-judul --- + style: TextStyle(color: AppColors.textSecondary, fontSize: 16), ), const SizedBox(height: 50), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Warna Kotak Form --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(25), - border: Border.all(color: Colors.grey.withOpacity(0.3)), + // --- BERUBAH: Warna Garis Pinggir Form --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.3)), ), child: Column( children: [ TextField( controller: _emailController, keyboardType: TextInputType.emailAddress, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Teks Inputan --- + style: const TextStyle(color: AppColors.textMain), decoration: const InputDecoration( - prefixIcon: Icon(Icons.email, color: Colors.green), + // --- BERUBAH: Warna Ikon --- + prefixIcon: Icon(Icons.email, color: AppColors.primary), hintText: 'Email', - hintStyle: TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: TextStyle(color: AppColors.textSecondary), border: InputBorder.none, ), ), - const Divider(color: Colors.grey), + // --- BERUBAH: Garis Pemisah (Divider) --- + Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)), TextField( controller: _passwordController, obscureText: _obscurePassword, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Teks Inputan --- + style: const TextStyle(color: AppColors.textMain), decoration: InputDecoration( - prefixIcon: - const Icon(Icons.lock, color: Colors.green), + // --- BERUBAH: Warna Ikon --- + prefixIcon: const Icon(Icons.lock, color: AppColors.primary), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off, - color: Colors.grey, + // --- BERUBAH: Warna Ikon Mata --- + color: AppColors.textSecondary, ), onPressed: () => setState( () => _obscurePassword = !_obscurePassword), ), hintText: 'Password', - hintStyle: const TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: const TextStyle(color: AppColors.textSecondary), border: InputBorder.none, ), ), @@ -165,42 +190,64 @@ class _LoginScreenState extends State { child: ElevatedButton( onPressed: _isLoading ? null : _handleLogin, style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, + // --- BERUBAH: Latar Belakang Tombol --- + backgroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), ), child: _isLoading ? const CircularProgressIndicator( - color: Colors.white) + color: Colors.white) // Tetap putih untuk kontras : const Text( 'Masuk ke Akun', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.white), + color: Colors.white), // Tetap putih ), ), ), ], ), ), - - // Lupa Password dan Daftar — rapat tanpa jarak besar, warna sama hijau - const SizedBox(height: 10), + const SizedBox(height: 16), + + // ── 1. TOMBOL LUPA PASSWORD ── TextButton( - onPressed: () => - Navigator.pushNamed(context, '/forgot_password'), + onPressed: () => Navigator.pushNamed(context, '/forgot_password'), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), child: const Text( 'Lupa Password?', - style: TextStyle(color: Colors.green), + style: TextStyle( + // --- BERUBAH: Warna Teks --- + color: AppColors.primary, + fontSize: 14, + ), ), ), + + const SizedBox(height: 12), + + // ── 2. TOMBOL DAFTAR AKUN ── TextButton( onPressed: () => Navigator.pushNamed(context, '/register'), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), child: const Text( 'Belum punya akun? Daftar Sekarang', - style: TextStyle(color: Colors.green), + style: TextStyle( + // --- BERUBAH: Warna Teks --- + color: AppColors.primary, + fontSize: 14, + ), ), ), ], diff --git a/lib/screens/monitoring_screen.dart b/lib/screens/monitoring_screen.dart index 87c350a..5174b7d 100644 --- a/lib/screens/monitoring_screen.dart +++ b/lib/screens/monitoring_screen.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:mqtt_client/mqtt_client.dart'; import '../services/mqtt_service.dart'; import '../services/supabase_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class MonitoringScreen extends StatefulWidget { final VoidCallback? onProfileTap; @@ -60,19 +61,21 @@ class _MonitoringScreenState extends State { }); return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background Utama --- + backgroundColor: AppColors.background, body: SafeArea( child: _isInitialLoading - ? const Center(child: CircularProgressIndicator(color: Colors.green)) + // --- BERUBAH: Warna Loading Indicator --- + ? const Center(child: CircularProgressIndicator(color: AppColors.primary)) : RefreshIndicator( onRefresh: _initData, - color: Colors.green, + // --- BERUBAH: Warna Refresh Indicator --- + color: AppColors.primary, child: LayoutBuilder( builder: (context, constraints) { - // Tinggi tersedia = tinggi layar dikurangi header - const headerHeight = 175.0; // header + timestamp + mode badge + padding - const gaps = 15.0 * 2; // 2 gap antar 3 baris - const padding = 20.0 * 2; // padding atas bawah + const headerHeight = 175.0; + const gaps = 15.0 * 2; + const padding = 20.0 * 2; final available = constraints.maxHeight - headerHeight - gaps - padding; final cardH = (available / 3).clamp(100.0, 180.0); @@ -94,11 +97,13 @@ class _MonitoringScreenState extends State { children: [ const Text('Monitoring', style: TextStyle( - color: Colors.white, + // --- BERUBAH: Warna Judul --- + color: AppColors.textMain, fontSize: 32, fontWeight: FontWeight.bold)), const Text('Sistem Pengeringan Kopi', - style: TextStyle(color: Colors.grey, fontSize: 16)), + // --- BERUBAH: Warna Sub-judul --- + style: TextStyle(color: AppColors.textSecondary, fontSize: 16)), const SizedBox(height: 5), Row( children: [ @@ -106,10 +111,11 @@ class _MonitoringScreenState extends State { width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, + // --- BERUBAH: Indikator Titik Koneksi --- color: mqtt.client?.connectionStatus?.state == MqttConnectionState.connected - ? Colors.green - : Colors.red, + ? AppColors.success + : AppColors.error, ), ), const SizedBox(width: 8), @@ -119,11 +125,13 @@ class _MonitoringScreenState extends State { ? 'Connected' : 'Disconnected', style: TextStyle( + // --- BERUBAH: Warna Teks Koneksi --- color: mqtt.client?.connectionStatus?.state == MqttConnectionState.connected - ? Colors.green - : Colors.red, + ? AppColors.success + : AppColors.error, fontSize: 12, + fontWeight: FontWeight.bold, ), ), ], @@ -133,9 +141,11 @@ class _MonitoringScreenState extends State { GestureDetector( onTap: widget.onProfileTap, child: const CircleAvatar( - backgroundColor: Color(0xFF1A1A1A), + // --- BERUBAH: Latar Belakang Avatar Profil --- + backgroundColor: AppColors.cardBg, radius: 25, - child: Icon(Icons.person, color: Colors.green), + // --- BERUBAH: Warna Ikon Avatar --- + child: Icon(Icons.person, color: AppColors.primary), ), ), ], @@ -144,25 +154,29 @@ class _MonitoringScreenState extends State { // ── Timestamp ── Text('Update: ${mqtt.timestamp}', - style: const TextStyle(color: Colors.grey, fontSize: 11)), + // --- BERUBAH: Warna Teks Timestamp --- + style: const TextStyle(color: AppColors.textSecondary, fontSize: 11)), const SizedBox(height: 10), // ── Mode Badge ── Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( + // --- BERUBAH: Latar Belakang Transparan Badge --- color: mqtt.mode == 'manual' - ? Colors.orange.withOpacity(0.2) - : Colors.green.withOpacity(0.2), + ? AppColors.warning.withValues(alpha: 0.2) + : AppColors.success.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(20), border: Border.all( - color: mqtt.mode == 'manual' ? Colors.orange : Colors.green, + // --- BERUBAH: Warna Garis Pinggir Badge --- + color: mqtt.mode == 'manual' ? AppColors.warning : AppColors.success, ), ), child: Text( 'Mode: ${mqtt.mode.toUpperCase()}', style: TextStyle( - color: mqtt.mode == 'manual' ? Colors.orange : Colors.green, + // --- BERUBAH: Warna Teks Badge --- + color: mqtt.mode == 'manual' ? AppColors.warning : AppColors.success, fontSize: 12, fontWeight: FontWeight.bold, ), @@ -183,11 +197,12 @@ class _MonitoringScreenState extends State { suffix: '°C', dec: 1, icon: Icons.thermostat, - color: curSuhu > mqtt.maxSuhu ? Colors.red : Colors.redAccent, + // --- BERUBAH: Warna Tema Kartu Suhu --- + color: curSuhu > mqtt.maxSuhu ? AppColors.error : AppColors.warning, isAlert: curSuhu > mqtt.maxSuhu, isLow: curSuhu < mqtt.minSuhu, onTap: () => Navigator.pushNamed(context, '/analytics', - arguments: {'title': 'Grafik Suhu (°C)', 'color': Colors.redAccent, 'value': '${mqtt.suhu}°C'}), + arguments: {'title': 'Grafik Suhu (°C)', 'color': AppColors.error, 'value': '${mqtt.suhu}°C'}), ), ), const SizedBox(width: 15), @@ -199,11 +214,12 @@ class _MonitoringScreenState extends State { suffix: '%', dec: 1, icon: Icons.water_drop, - color: curHum > mqtt.maxRh ? Colors.red : Colors.cyan, + // --- BERUBAH: Warna Tema Kartu Kelembaban --- + color: curHum > mqtt.maxRh ? AppColors.error : AppColors.info, isAlert: curHum > mqtt.maxRh, isLow: curHum < mqtt.minRh, onTap: () => Navigator.pushNamed(context, '/analytics', - arguments: {'title': 'Grafik Kelembaban (%)', 'color': Colors.cyan, 'value': '${mqtt.kelembapan}%'}), + arguments: {'title': 'Grafik Kelembaban (%)', 'color': AppColors.info, 'value': '${mqtt.kelembapan}%'}), ), ), ], @@ -222,9 +238,10 @@ class _MonitoringScreenState extends State { suffix: ' Lux', dec: 1, icon: Icons.wb_sunny, - color: Colors.orange, + // --- BERUBAH: Warna Tema Kartu Cahaya --- + color: AppColors.warning, onTap: () => Navigator.pushNamed(context, '/analytics', - arguments: {'title': 'Grafik Cahaya (Lux)', 'color': Colors.orange, 'value': '${mqtt.intensitas} Lux'}), + arguments: {'title': 'Grafik Cahaya (Lux)', 'color': AppColors.light, 'value': '${mqtt.intensitas} Lux'}), ), ), const SizedBox(height: 15), @@ -261,19 +278,23 @@ class _MonitoringScreenState extends State { margin: const EdgeInsets.only(top: 15), padding: const EdgeInsets.all(15), decoration: BoxDecoration( - color: Colors.red.withOpacity(0.2), + // --- BERUBAH: Background Peringatan --- + color: AppColors.error.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(15), - border: Border.all(color: Colors.red), + // --- BERUBAH: Garis Pinggir Peringatan --- + border: Border.all(color: AppColors.error), ), child: const Row( children: [ - Icon(Icons.warning_amber_rounded, color: Colors.red), + // --- BERUBAH: Ikon Peringatan --- + Icon(Icons.warning_amber_rounded, color: AppColors.error), SizedBox(width: 10), Expanded( child: Text( 'Peringatan: Parameter pengeringan melewati batas aman!', style: TextStyle( - color: Colors.red, + // --- BERUBAH: Teks Peringatan --- + color: AppColors.error, fontWeight: FontWeight.bold, fontSize: 12), ), @@ -286,19 +307,23 @@ class _MonitoringScreenState extends State { margin: const EdgeInsets.only(top: 15), padding: const EdgeInsets.all(15), decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.2), + // --- BERUBAH: Background Info/Low --- + color: AppColors.info.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(15), - border: Border.all(color: Colors.blue), + // --- BERUBAH: Garis Pinggir Info/Low --- + border: Border.all(color: AppColors.info), ), child: const Row( children: [ - Icon(Icons.info_outline, color: Colors.blue), + // --- BERUBAH: Ikon Info/Low --- + Icon(Icons.info_outline, color: AppColors.info), SizedBox(width: 10), Expanded( child: Text( 'Info: Parameter pengeringan di bawah batas minimum!', style: TextStyle( - color: Colors.blue, + // --- BERUBAH: Teks Info/Low --- + color: AppColors.info, fontWeight: FontWeight.bold, fontSize: 12), ), @@ -337,16 +362,23 @@ class _MonitoringScreenState extends State { height: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: isAlert ? Colors.red.withOpacity(0.1) : isLow ? Colors.blue.withOpacity(0.1) : const Color(0xFF1A1A1A), + // --- BERUBAH: Kartu Latar Putih Bersih atau Transparan Merah/Biru --- + color: isAlert ? AppColors.error.withValues(alpha: 0.1) : isLow ? AppColors.info.withValues(alpha: 0.1) : AppColors.cardBg, borderRadius: BorderRadius.circular(20), - border: isAlert ? Border.all(color: Colors.red, width: 2) : isLow ? Border.all(color: Colors.blue, width: 2) : null, + // --- BERUBAH: Memberi garis halus agar kartu terlihat pop up di latar terang --- + border: isAlert + ? Border.all(color: AppColors.error, width: 2) + : isLow + ? Border.all(color: AppColors.info, width: 2) + : Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: color, size: 32), const SizedBox(height: 6), - Text(title, style: const TextStyle(color: Colors.grey, fontSize: 12)), + // --- BERUBAH: Warna Judul Sensor --- + Text(title, style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)), const SizedBox(height: 4), TweenAnimationBuilder( tween: Tween(begin: prev, end: cur), @@ -357,7 +389,8 @@ class _MonitoringScreenState extends State { child: Text( '${value.toStringAsFixed(dec)}$suffix', style: const TextStyle( - color: Colors.white, + // --- BERUBAH: Warna Angka/Nilai Sensor --- + color: AppColors.textMain, fontSize: 22, fontWeight: FontWeight.bold), ), @@ -380,8 +413,10 @@ class _MonitoringScreenState extends State { height: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Kartu Latar Putih Bersih & Garis Halus --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -389,13 +424,15 @@ class _MonitoringScreenState extends State { children: [ Row( children: [ + // --- BERUBAH: Warna Ikon Kontroler --- Icon(Icons.settings_input_component, - color: isOn ? Colors.green : Colors.grey, size: 16), + color: isOn ? AppColors.primary : AppColors.textSecondary, size: 16), const SizedBox(width: 6), Expanded( + // --- BERUBAH: Warna Judul Kartu Kontrol --- child: Text(title, style: const TextStyle( - color: Colors.white, + color: AppColors.textMain, fontSize: 11, fontWeight: FontWeight.bold)), ), @@ -404,16 +441,18 @@ class _MonitoringScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // --- BERUBAH: Warna Status Mode --- Text('Mode: ${mqtt.mode.toUpperCase()}', style: const TextStyle( - color: Colors.green, fontSize: 9, fontWeight: FontWeight.bold)), + color: AppColors.primary, fontSize: 9, fontWeight: FontWeight.bold)), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( isOn ? 'ON' : 'OFF', style: TextStyle( - color: isOn ? Colors.green : Colors.grey, + // --- BERUBAH: Warna Status ON/OFF --- + color: isOn ? AppColors.primary : AppColors.textSecondary, fontSize: 12, fontWeight: FontWeight.bold), ), @@ -422,7 +461,8 @@ class _MonitoringScreenState extends State { child: Switch( value: isOn, onChanged: mqtt.mode == 'manual' ? onChanged : null, - activeColor: Colors.green, + // --- BERUBAH: Warna Switch --- + activeColor: AppColors.primary, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), diff --git a/lib/screens/recording_screen.dart b/lib/screens/recording_screen.dart index 7c64fcd..11258ce 100644 --- a/lib/screens/recording_screen.dart +++ b/lib/screens/recording_screen.dart @@ -3,6 +3,8 @@ import 'package:provider/provider.dart'; import '../services/mqtt_service.dart'; import '../services/supabase_service.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; +import '../constants/app_colors.dart'; +import '../services/recording_service.dart'; // Memori Abadi Timer Tetap Dipakai class RecordingScreen extends StatefulWidget { const RecordingScreen({super.key}); @@ -15,8 +17,8 @@ class _RecordingScreenState extends State { final TextEditingController _intervalController = TextEditingController(); List> _gallery = []; bool _isLoading = true; - bool _isRecording = false; - + + final recordingService = RecordingService(); late final RealtimeChannel _channel; @override @@ -24,7 +26,6 @@ class _RecordingScreenState extends State { super.initState(); _loadData(); - // 🔥 REALTIME LISTENER SUPABASE (AUTO REFRESH GALERI) _channel = Supabase.instance.client .channel('realtime-foto') .onPostgresChanges( @@ -32,7 +33,7 @@ class _RecordingScreenState extends State { schema: 'public', table: 'foto_dataset', callback: (payload) { - _loadData(); // reload gallery otomatis saat foto baru masuk + _loadData(); }, ) .subscribe(); @@ -41,10 +42,7 @@ class _RecordingScreenState extends State { @override void dispose() { _intervalController.dispose(); - - // 🔥 MATIKAN REALTIME LISTENER Supabase.instance.client.removeChannel(_channel); - super.dispose(); } @@ -74,31 +72,31 @@ class _RecordingScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Interval harus lebih dari 0 menit!'), - backgroundColor: Colors.orange, + backgroundColor: AppColors.warning, ), ); return; } if (start) { - // Simpan interval ke Supabase await SupabaseService().updateIntervalSetting(intervalVal); - // Kirim interval ke ESP32 via MQTT mqtt.setIntervalFoto(intervalVal); - setState(() => _isRecording = true); + recordingService.setRecordingStatus(true); } else { - // Kirim stop ke ESP32 Dev via MQTT mqtt.setIntervalFoto(0); - setState(() => _isRecording = false); + recordingService.setRecordingStatus(false); } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(start - ? '📸 Perekaman dimulai — interval ${intervalVal} menit' - : '⏹ Perekaman dihentikan'), - backgroundColor: start ? Colors.green : Colors.red, + content: Text( + start + ? 'Perekaman dimulai — interval $intervalVal menit' + : 'Perekaman dihentikan', + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + backgroundColor: start ? AppColors.success : AppColors.error, ), ); } @@ -107,173 +105,140 @@ class _RecordingScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, + backgroundColor: AppColors.background, body: SafeArea( child: _isLoading - ? const Center( - child: CircularProgressIndicator(color: Colors.green)) + ? const Center(child: CircularProgressIndicator(color: AppColors.primary)) : Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // ── Header ── - const Text( - 'Perekaman', - style: TextStyle( - color: Colors.white, - fontSize: 32, - fontWeight: FontWeight.bold), + const Text('Perekaman', + style: TextStyle(color: AppColors.textMain, fontSize: 32, fontWeight: FontWeight.bold), ), - const Text( - 'Dataset Citra Kopi', - style: TextStyle(color: Colors.grey, fontSize: 16), + const Text('Dataset Citra Kopi', + style: TextStyle(color: AppColors.textSecondary, fontSize: 16), ), const SizedBox(height: 20), - // ── Panel Kontrol ── - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), - borderRadius: BorderRadius.circular(20), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Interval Waktu (menit)', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - TextField( - controller: _intervalController, - keyboardType: TextInputType.number, - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - hintText: '30', - hintStyle: - const TextStyle(color: Colors.grey), - filled: true, - fillColor: Colors.black, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(15), - borderSide: - const BorderSide(color: Colors.grey), - ), - ), - ), - const SizedBox(height: 15), + // LISTENABLE BUILDER: Membuat timer kebal pindah halaman + ListenableBuilder( + listenable: recordingService, + builder: (context, child) { + final isRecording = recordingService.isRecording; - // Status perekaman - if (_isRecording) - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.green.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.green), - ), - child: const Row( - children: [ - Icon(Icons.circle, - color: Colors.green, size: 10), - SizedBox(width: 8), - Text('Perekaman berjalan...', - style: TextStyle( - color: Colors.green, - fontSize: 12)), - ], - ), - ), - const SizedBox(height: 10), - - Row( + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.cardBg, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: _isRecording - ? null - : () => _controlRecording(true), - icon: const Icon(Icons.play_arrow, - color: Colors.white), - label: const Text('Mulai', - style: - TextStyle(color: Colors.white)), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - disabledBackgroundColor: - Colors.green.withOpacity(0.3), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12), - ), + const Text('Interval Waktu (menit)', + style: TextStyle(color: AppColors.textMain, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + TextField( + controller: _intervalController, + keyboardType: TextInputType.number, + style: const TextStyle(color: AppColors.textMain), + decoration: InputDecoration( + hintText: '30', + hintStyle: const TextStyle(color: AppColors.textSecondary), + filled: true, + fillColor: AppColors.background, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(15), + borderSide: BorderSide(color: AppColors.textSecondary.withValues(alpha: 0.3)), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(15), + borderSide: const BorderSide(color: AppColors.primary), ), ), ), - const SizedBox(width: 10), - Expanded( - child: ElevatedButton.icon( - onPressed: !_isRecording - ? null - : () => _controlRecording(false), - icon: const Icon(Icons.stop, - color: Colors.white), - label: const Text('Berhenti', - style: - TextStyle(color: Colors.white)), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.redAccent, - disabledBackgroundColor: - Colors.redAccent.withOpacity(0.3), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12), - ), + const SizedBox(height: 15), + + if (isRecording) + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.success.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppColors.success), + ), + child: const Row( + children: [ + Icon(Icons.circle, color: AppColors.success, size: 10), + SizedBox(width: 8), + Text('Perekaman berjalan...', + style: TextStyle(color: AppColors.success, fontSize: 12, fontWeight: FontWeight.bold)), + ], ), ), + const SizedBox(height: 10), + + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: isRecording ? null : () => _controlRecording(true), + icon: const Icon(Icons.play_arrow, color: Colors.white), + label: const Text('Mulai', style: TextStyle(color: Colors.white)), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton.icon( + onPressed: !isRecording ? null : () => _controlRecording(false), + icon: const Icon(Icons.stop, color: Colors.white), + label: const Text('Berhenti', style: TextStyle(color: Colors.white)), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.error, + disabledBackgroundColor: AppColors.error.withValues(alpha: 0.3), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ), + ), + ], ), ], ), - ], - ), + ); + }, ), const SizedBox(height: 20), - // ── Header Galeri ── Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Galeri Citra Dataset (${_gallery.length} foto)', - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold), + Text('Galeri Citra Dataset (${_gallery.length} foto)', + style: const TextStyle(color: AppColors.textMain, fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( - icon: const Icon(Icons.refresh, - color: Colors.green), + icon: const Icon(Icons.refresh, color: AppColors.primary), onPressed: _loadData, ), ], ), const SizedBox(height: 10), - // ── Grid Galeri ── Expanded( child: _gallery.isEmpty ? const Center( - child: Text( - 'Belum ada data citra', - style: TextStyle(color: Colors.grey), - ), + child: Text('Belum ada data citra', style: TextStyle(color: AppColors.textSecondary)), ) : GridView.builder( - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, @@ -282,9 +247,13 @@ class _RecordingScreenState extends State { itemBuilder: (context, index) { final item = _gallery[index]; final nomorFoto = index + 1; - final timestamp = item['timestamp'] ?? ''; - final tanggal = timestamp.contains(' ') ? timestamp.split(' ')[0] : timestamp; - final jam = timestamp.contains(' ') ? timestamp.split(' ')[1] : ''; + + // PENYARING WAKTU: Ubah 'T' jadi Spasi agar kodingan lamamu aman + String rawTime = item['timestamp'] ?? item['created_at'] ?? ''; + String cleanTime = rawTime.replaceAll('T', ' '); + + final tanggal = cleanTime.contains(' ') ? cleanTime.split(' ')[0] : cleanTime; + final jam = cleanTime.contains(' ') ? cleanTime.split(' ')[1] : ''; final jamSingkat = jam.length >= 5 ? jam.substring(0, 5) : jam; return GestureDetector( @@ -294,10 +263,11 @@ class _RecordingScreenState extends State { arguments: { 'id': item['id'].toString(), 'date': tanggal, - 'time': jam, - 'temp': '${item['suhu']} °C', - 'humidity': '${item['kelembapan']} %', - 'light': '${item['intensitas']} Lux', + 'time': jamSingkat, + // PENYARING SENSOR 0: Jika 0, ubah jadi strip '-' + 'temp': (item['suhu'] == 0 || item['suhu'] == null) ? '-' : '${item['suhu']} °C', + 'humidity': (item['kelembapan'] == 0 || item['kelembapan'] == null) ? '-' : '${item['kelembapan']} %', + 'light': (item['intensitas'] == 0 || item['intensitas'] == null) ? '-' : '${item['intensitas']} Lux', 'location': 'Gudang Pengering', 'intake': item['kipas1'] ?? 'OFF', 'exhaust': item['kipas2'] ?? 'OFF', @@ -306,71 +276,61 @@ class _RecordingScreenState extends State { ), child: Container( decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + color: AppColors.cardBg, borderRadius: BorderRadius.circular(15), + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // Foto 75% tinggi card Expanded( flex: 3, child: ClipRRect( - borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(14)), child: item['url_foto'] != null ? Image.network( item['url_foto'], fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Center( - child: Icon(Icons.broken_image, color: Colors.grey, size: 40), + child: Icon(Icons.broken_image, color: AppColors.textSecondary, size: 40), ), loadingBuilder: (_, child, progress) { if (progress == null) return child; return const Center( - child: CircularProgressIndicator(color: Colors.green, strokeWidth: 2), + child: CircularProgressIndicator(color: AppColors.primary, strokeWidth: 2), ); }, ) : const Center( - child: Icon(Icons.image, color: Colors.grey, size: 40), + child: Icon(Icons.image, color: AppColors.textSecondary, size: 40), ), ), ), - - // Caption bawah foto Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: const BoxDecoration( - color: Color(0xFF1A1A1A), + color: Colors.transparent, borderRadius: BorderRadius.vertical(bottom: Radius.circular(15)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Badge nomor foto Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: Colors.green.withOpacity(0.2), + color: AppColors.primary.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), - border: Border.all(color: Colors.green.withOpacity(0.5)), + border: Border.all(color: AppColors.primary.withValues(alpha: 0.5)), ), - child: Text( - '#$nomorFoto', - style: const TextStyle( - color: Colors.green, fontSize: 10, fontWeight: FontWeight.bold), + child: Text('#$nomorFoto', + style: const TextStyle(color: AppColors.primary, fontSize: 10, fontWeight: FontWeight.bold), ), ), - - // Tanggal dan jam Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text(tanggal, - style: const TextStyle(color: Colors.grey, fontSize: 8)), - Text(jamSingkat, - style: const TextStyle( - color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)), + Text(tanggal, style: const TextStyle(color: AppColors.textSecondary, fontSize: 8)), + Text(jamSingkat, style: const TextStyle(color: AppColors.textMain, fontSize: 10, fontWeight: FontWeight.bold)), ], ), ], diff --git a/lib/screens/register_screen.dart b/lib/screens/register_screen.dart index 5100767..b7baa8a 100644 --- a/lib/screens/register_screen.dart +++ b/lib/screens/register_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../services/supabase_service.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); @@ -89,7 +90,7 @@ class _RegisterScreenState extends State { children: [ Icon( isError ? Icons.error_outline : Icons.check_circle_outline, - color: Colors.white, + color: Colors.white, // Tetap putih agar kontras dengan background ), const SizedBox(width: 10), Expanded( @@ -101,8 +102,9 @@ class _RegisterScreenState extends State { ), ], ), + // --- BERUBAH: Menggunakan warna notifikasi dari AppColors --- backgroundColor: - isError ? Colors.red.shade700 : Colors.green.shade700, + isError ? AppColors.error : AppColors.success, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -116,12 +118,14 @@ class _RegisterScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background Scaffold Utama --- + backgroundColor: AppColors.background, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.green), + // --- BERUBAH: Warna Ikon Back --- + icon: const Icon(Icons.arrow_back, color: AppColors.primary), onPressed: () => Navigator.pop(context), ), ), @@ -134,75 +138,91 @@ class _RegisterScreenState extends State { const Text( 'Buat Akun Baru', style: TextStyle( - color: Colors.green, + // --- BERUBAH: Warna Judul --- + color: AppColors.primary, fontSize: 32, fontWeight: FontWeight.bold), ), const Text( 'Lengkapi data diri Anda untuk memulai', - style: TextStyle(color: Colors.grey, fontSize: 16), + // --- BERUBAH: Warna Sub-judul --- + style: TextStyle(color: AppColors.textSecondary, fontSize: 16), ), const SizedBox(height: 50), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Latar Belakang Form --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(25), + // --- BERUBAH: Garis Pinggir Form --- border: - Border.all(color: Colors.grey.withOpacity(0.3)), + Border.all(color: AppColors.textSecondary.withValues(alpha: 0.3)), ), child: Column( children: [ // Nama Lengkap TextField( controller: _nameController, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Teks Inputan --- + style: const TextStyle(color: AppColors.textMain), textCapitalization: TextCapitalization.words, decoration: const InputDecoration( + // --- BERUBAH: Warna Ikon --- prefixIcon: - Icon(Icons.person, color: Colors.green), + Icon(Icons.person, color: AppColors.primary), hintText: 'Nama Lengkap', - hintStyle: TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: TextStyle(color: AppColors.textSecondary), border: InputBorder.none, ), ), - const Divider(color: Colors.grey), + // --- BERUBAH: Garis Pemisah (Divider) --- + Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)), // Email TextField( controller: _emailController, keyboardType: TextInputType.emailAddress, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Teks Inputan --- + style: const TextStyle(color: AppColors.textMain), decoration: const InputDecoration( + // --- BERUBAH: Warna Ikon --- prefixIcon: - Icon(Icons.email, color: Colors.green), + Icon(Icons.email, color: AppColors.primary), hintText: 'Email', - hintStyle: TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: TextStyle(color: AppColors.textSecondary), border: InputBorder.none, ), ), - const Divider(color: Colors.grey), + // --- BERUBAH: Garis Pemisah (Divider) --- + Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)), // Password TextField( controller: _passwordController, obscureText: _obscurePassword, - style: const TextStyle(color: Colors.white), + // --- BERUBAH: Warna Teks Inputan --- + style: const TextStyle(color: AppColors.textMain), decoration: InputDecoration( + // --- BERUBAH: Warna Ikon Kunci --- prefixIcon: - const Icon(Icons.lock, color: Colors.green), + const Icon(Icons.lock, color: AppColors.primary), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off, - color: Colors.grey, + // --- BERUBAH: Warna Ikon Mata --- + color: AppColors.textSecondary, ), onPressed: () => setState( () => _obscurePassword = !_obscurePassword), ), hintText: 'Password (min. 6 karakter)', - hintStyle: const TextStyle(color: Colors.grey), + // --- BERUBAH: Warna Placeholder/Hint --- + hintStyle: const TextStyle(color: AppColors.textSecondary), border: InputBorder.none, ), ), @@ -215,9 +235,11 @@ class _RegisterScreenState extends State { child: ElevatedButton( onPressed: _isLoading ? null : _handleRegister, style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, + // --- BERUBAH: Warna Tombol --- + backgroundColor: AppColors.primary, + // --- BERUBAH: Warna Tombol saat Nonaktif (Loading) --- disabledBackgroundColor: - Colors.green.withOpacity(0.3), + AppColors.primary.withValues(alpha: 0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), @@ -243,7 +265,8 @@ class _RegisterScreenState extends State { onPressed: () => Navigator.pop(context), child: const Text( 'Sudah punya akun? Masuk', - style: TextStyle(color: Colors.green), + // --- BERUBAH: Warna Teks Bawah --- + style: TextStyle(color: AppColors.primary), ), ), ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 3d1ce5f..7394e1e 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,12 +1,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:excel/excel.dart'; +import 'package:excel/excel.dart' hide Border; import 'package:open_file/open_file.dart'; import 'package:path_provider/path_provider.dart'; import '../services/supabase_service.dart'; import '../services/mqtt_service.dart'; import 'package:mqtt_client/mqtt_client.dart'; +import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -40,7 +41,7 @@ class _SettingsScreenState extends State { } } - // ── Export foto_dataset ke Excel ── + // ── Export foto_dataset ke Excel (Warna Excel Tetap Dipertahankan) ── Future _exportExcel() async { setState(() => _isExporting = true); @@ -51,9 +52,10 @@ class _SettingsScreenState extends State { if (data.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Belum ada data dataset untuk diexport!'), - backgroundColor: Colors.orange, + SnackBar( + content: const Text('Belum ada data dataset untuk diexport!'), + // --- BERUBAH: Warna Notifikasi Peringatan --- + backgroundColor: AppColors.warning, ), ); } @@ -71,12 +73,11 @@ class _SettingsScreenState extends State { 'Suhu (°C)', 'Kelembapan (%)', 'Intensitas Cahaya (Lux)', - 'Kipas 1 (Exhaust)', - 'Kipas 2 (Intake)', - 'URL Foto', + 'Kipas 1 (Intake)', + 'Kipas 2 (Exhaust)', ]; - // Style header + // Style header (Dibiarkan tetap hijau standar Excel) for (int i = 0; i < headers.length; i++) { final cell = sheet.cell( CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0)); @@ -88,56 +89,34 @@ class _SettingsScreenState extends State { ); } - // Isi data + // Isi data — tanpa URL Foto for (int i = 0; i < data.length; i++) { final row = data[i]; final rowIndex = i + 1; - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 0, rowIndex: rowIndex)) + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: rowIndex)) .value = IntCellValue(i + 1); - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 1, rowIndex: rowIndex)) + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: rowIndex)) .value = TextCellValue(row['timestamp']?.toString() ?? '-'); - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 2, rowIndex: rowIndex)) - .value = DoubleCellValue( - double.tryParse(row['suhu'].toString()) ?? 0); + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: rowIndex)) + .value = DoubleCellValue(double.tryParse(row['suhu'].toString()) ?? 0); - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 3, rowIndex: rowIndex)) - .value = DoubleCellValue( - double.tryParse(row['kelembapan'].toString()) ?? 0); + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: rowIndex)) + .value = DoubleCellValue(double.tryParse(row['kelembapan'].toString()) ?? 0); - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 4, rowIndex: rowIndex)) - .value = DoubleCellValue( - double.tryParse(row['intensitas'].toString()) ?? 0); + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: rowIndex)) + .value = DoubleCellValue(double.tryParse(row['intensitas'].toString()) ?? 0); - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 5, rowIndex: rowIndex)) + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: rowIndex)) .value = TextCellValue(row['kipas1']?.toString() ?? '-'); - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 6, rowIndex: rowIndex)) + sheet.cell(CellIndex.indexByColumnRow(columnIndex: 6, rowIndex: rowIndex)) .value = TextCellValue(row['kipas2']?.toString() ?? '-'); - - sheet - .cell(CellIndex.indexByColumnRow( - columnIndex: 7, rowIndex: rowIndex)) - .value = TextCellValue(row['url_foto']?.toString() ?? '-'); } - // Set lebar kolom + // Set lebar kolom — tanpa kolom URL sheet.setColumnWidth(0, 5); sheet.setColumnWidth(1, 22); sheet.setColumnWidth(2, 12); @@ -145,7 +124,6 @@ class _SettingsScreenState extends State { sheet.setColumnWidth(4, 24); sheet.setColumnWidth(5, 18); sheet.setColumnWidth(6, 18); - sheet.setColumnWidth(7, 50); // Simpan file final dir = await getApplicationDocumentsDirectory(); @@ -170,7 +148,8 @@ class _SettingsScreenState extends State { SnackBar( content: Text( '✓ Export berhasil — ${data.length} data tersimpan'), - backgroundColor: Colors.green, + // --- BERUBAH: Warna Notifikasi Sukses --- + backgroundColor: AppColors.success, ), ); } @@ -179,7 +158,8 @@ class _SettingsScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal export: $e'), - backgroundColor: Colors.red, + // --- BERUBAH: Warna Notifikasi Error --- + backgroundColor: AppColors.error, ), ); } @@ -194,7 +174,8 @@ class _SettingsScreenState extends State { final user = SupabaseService().currentUser; return Scaffold( - backgroundColor: Colors.black, + // --- BERUBAH: Background UI Utama --- + backgroundColor: AppColors.background, body: SafeArea( child: SingleChildScrollView( child: Padding( @@ -206,14 +187,18 @@ class _SettingsScreenState extends State { Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Background Kartu Profil --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + // --- BERUBAH: Garis pinggir halus --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Row( children: [ const CircleAvatar( radius: 30, - backgroundColor: Colors.green, + // --- BERUBAH: Warna Avatar --- + backgroundColor: AppColors.primary, child: Icon(Icons.person, color: Colors.white, size: 40), ), @@ -226,8 +211,9 @@ class _SettingsScreenState extends State { width: 100, height: 16, child: LinearProgressIndicator( - color: Colors.green, - backgroundColor: Colors.grey, + // --- BERUBAH: Warna Loading --- + color: AppColors.primary, + backgroundColor: AppColors.background, ), ) : Text( @@ -235,7 +221,8 @@ class _SettingsScreenState extends State { ? _namaLengkap : 'Admin Kopi', style: const TextStyle( - color: Colors.white, + // --- BERUBAH: Warna Nama Profil --- + color: AppColors.textMain, fontSize: 18, fontWeight: FontWeight.bold, ), @@ -244,12 +231,20 @@ class _SettingsScreenState extends State { Text( user?.email ?? 'admin@coffee.io', style: const TextStyle( - color: Colors.grey, fontSize: 12), + // --- BERUBAH: Warna Email Profil --- + color: AppColors.textSecondary, + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), const Text( 'Status: Active User', style: TextStyle( - color: Colors.green, fontSize: 12), + // --- BERUBAH: Warna Status Profil --- + color: AppColors.success, + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), ], ), @@ -264,7 +259,8 @@ class _SettingsScreenState extends State { _buildSettingsCard([ _buildConnectionStatus( 'Database Supabase', true, Icons.storage), - const Divider(color: Colors.grey, height: 1), + // --- BERUBAH: Warna Garis Pemisah Menu --- + Divider(color: AppColors.textSecondary.withValues(alpha: 0.2), height: 1), _buildConnectionStatus( 'MQTT Broker (EMQX)', mqtt.client?.connectionStatus?.state == @@ -284,12 +280,14 @@ class _SettingsScreenState extends State { width: 22, height: 22, child: CircularProgressIndicator( - color: Colors.green, strokeWidth: 2), + // --- BERUBAH: Warna Loading --- + color: AppColors.primary, strokeWidth: 2), ), title: Text( 'Menyiapkan file Excel...', style: TextStyle( - color: Colors.grey, fontSize: 14), + // --- BERUBAH: Warna Teks Loading --- + color: AppColors.textSecondary, fontSize: 14), ), ) : _buildSettingItem( @@ -303,14 +301,16 @@ class _SettingsScreenState extends State { // ── Keamanan ── _buildSectionTitle('Keamanan'), - _buildSettingItem( - Icons.lock_reset, 'Ganti Password Akun', () { - Navigator.pushNamed( - context, - '/change_password', - arguments: {'dari_reset': false}, - ); - }), + _buildSettingsCard([ + _buildSettingItem( + Icons.lock_reset, 'Ganti Password Akun', () { + Navigator.pushNamed( + context, + '/change_password', + arguments: {'dari_reset': false}, + ); + }), + ]), const SizedBox(height: 30), @@ -319,25 +319,70 @@ class _SettingsScreenState extends State { width: double.infinity, height: 55, child: ElevatedButton.icon( - onPressed: () async { + onPressed: () async { + final konfirmasi = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + // --- BERUBAH: Latar Belakang Dialog --- + backgroundColor: AppColors.cardBg, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + title: const Row( + children: [ + Icon(Icons.logout, color: AppColors.error), + SizedBox(width: 10), + Text('Keluar dari Akun', + // --- BERUBAH: Warna Judul Dialog --- + style: TextStyle(color: AppColors.textMain, fontSize: 18)), + ], + ), + content: const Text( + 'Apakah kamu yakin ingin keluar dari akun ini?', + // --- BERUBAH: Warna Teks Konten Dialog --- + style: TextStyle(color: AppColors.textSecondary), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Batal', + // --- BERUBAH: Warna Tombol Batal --- + style: TextStyle(color: AppColors.primary)), + ), + ElevatedButton( + onPressed: () => Navigator.pop(ctx, true), + style: ElevatedButton.styleFrom( + // --- BERUBAH: Warna Tombol Keluar di Dialog --- + backgroundColor: AppColors.error, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + ), + child: const Text('Keluar', + style: TextStyle(color: Colors.white)), + ), + ], + ), + ); + + if (konfirmasi == true) { await SupabaseService().signOut(); if (context.mounted) { Navigator.pushReplacementNamed(context, '/login'); } - }, + } + }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.redAccent.withOpacity(0.8), + // --- BERUBAH: Warna Latar Tombol Keluar --- + backgroundColor: AppColors.error.withValues(alpha: 0.8), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), ), - icon: const Icon(Icons.logout, color: Colors.white), + icon: const Icon(Icons.logout, color: Colors.white), // Tetap putih label: const Text( 'Keluar dari Akun', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: Colors.white), + color: Colors.white), // Tetap putih ), ), ), @@ -356,7 +401,8 @@ class _SettingsScreenState extends State { child: Text( title, style: const TextStyle( - color: Colors.grey, + // --- BERUBAH: Warna Judul Seksi/Kategori --- + color: AppColors.textSecondary, fontSize: 13, fontWeight: FontWeight.bold), ), @@ -366,8 +412,11 @@ class _SettingsScreenState extends State { Widget _buildSettingsCard(List children) { return Container( decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), + // --- BERUBAH: Latar Belakang Kartu Pengaturan --- + color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), + // --- BERUBAH: Garis pinggir halus --- + border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column(children: children), ); @@ -376,13 +425,17 @@ class _SettingsScreenState extends State { Widget _buildSettingItem(IconData icon, String title, VoidCallback? onTap, {String? trailing}) { return ListTile( - leading: Icon(icon, color: Colors.green, size: 22), + // --- BERUBAH: Warna Ikon Leading --- + leading: Icon(icon, color: AppColors.primary, size: 22), title: Text(title, - style: const TextStyle(color: Colors.white, fontSize: 14)), + // --- BERUBAH: Warna Teks Menu --- + style: const TextStyle(color: AppColors.textMain, fontSize: 14)), trailing: trailing != null ? Text(trailing, - style: const TextStyle(color: Colors.grey, fontSize: 12)) - : const Icon(Icons.chevron_right, color: Colors.grey, size: 18), + // --- BERUBAH: Warna Teks Trailing --- + style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)) + // --- BERUBAH: Warna Ikon Panah Kanan --- + : const Icon(Icons.chevron_right, color: AppColors.textSecondary, size: 18), onTap: onTap, ); } @@ -390,10 +443,12 @@ class _SettingsScreenState extends State { Widget _buildConnectionStatus( String title, bool isConnected, IconData icon) { return ListTile( + // --- BERUBAH: Warna Ikon Status Database/MQTT --- leading: Icon(icon, - color: isConnected ? Colors.blue : Colors.red, size: 22), + color: isConnected ? AppColors.info : AppColors.error, size: 22), title: Text(title, - style: const TextStyle(color: Colors.white, fontSize: 14)), + // --- BERUBAH: Warna Teks Nama Layanan --- + style: const TextStyle(color: AppColors.textMain, fontSize: 14)), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -402,14 +457,16 @@ class _SettingsScreenState extends State { height: 8, decoration: BoxDecoration( shape: BoxShape.circle, - color: isConnected ? Colors.green : Colors.red, + // --- BERUBAH: Warna Titik Indikator --- + color: isConnected ? AppColors.success : AppColors.error, ), ), const SizedBox(width: 8), Text( isConnected ? 'Online' : 'Offline', style: TextStyle( - color: isConnected ? Colors.green : Colors.red, + // --- BERUBAH: Warna Teks Online/Offline --- + color: isConnected ? AppColors.success : AppColors.error, fontSize: 12, fontWeight: FontWeight.bold), ), diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart new file mode 100644 index 0000000..d3c36db --- /dev/null +++ b/lib/screens/splash_screen.dart @@ -0,0 +1,308 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; // <-- IMPORT SUPABASE DITAMBAHKAN +import '../constants/app_colors.dart'; +import 'login_screen.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State + with TickerProviderStateMixin { + // Controllers + late AnimationController _mainController; + late AnimationController _slideController; + late AnimationController _textController; + late AnimationController _revealController; + + // Animations + late Animation _logoY; + late Animation _shadowScale; + late Animation _shadowOpacity; + late Animation _logoSlideX; + late Animation _textOpacity; + late Animation _revealProgress; + + // Typewriter State + final String _fullText = 'IoT Kopi'; + String _displayText = ''; + int _charIndex = 0; + + @override + void initState() { + super.initState(); + _setupAnimations(); + _mulaiAnimasi(); + } + + void _setupAnimations() { + // Total durasi sekuens utama = 3.6 detik + _mainController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 3600), + ); + + // ── KOREOGRAFI LOGO DENGAN EFEK GRAVITASI ── + _logoY = TweenSequence([ + TweenSequenceItem(tween: ConstantTween(120.0), weight: 25), + TweenSequenceItem( + tween: Tween(begin: 120.0, end: -180.0).chain(CurveTween(curve: Curves.easeOut)), + weight: 14, + ), + TweenSequenceItem( + tween: TweenSequence([ + TweenSequenceItem(tween: Tween(begin: -180.0, end: -188.0).chain(CurveTween(curve: Curves.easeInOut)), weight: 50), + TweenSequenceItem(tween: Tween(begin: -188.0, end: -180.0).chain(CurveTween(curve: Curves.easeInOut)), weight: 50), + ]), + weight: 25, + ), + TweenSequenceItem( + tween: Tween(begin: -180.0, end: 0.0).chain(CurveTween(curve: Curves.easeIn)), + weight: 11, + ), + TweenSequenceItem(tween: Tween(begin: 0.0, end: -45.0).chain(CurveTween(curve: Curves.easeOut)), weight: 5), + TweenSequenceItem(tween: Tween(begin: -45.0, end: 0.0).chain(CurveTween(curve: Curves.easeIn)), weight: 5), + TweenSequenceItem(tween: Tween(begin: 0.0, end: -15.0).chain(CurveTween(curve: Curves.easeOut)), weight: 4), + TweenSequenceItem(tween: Tween(begin: -15.0, end: 0.0).chain(CurveTween(curve: Curves.easeIn)), weight: 4), + TweenSequenceItem(tween: ConstantTween(0.0), weight: 7), + ]).animate(_mainController); + + // ── KOREOGRAFI UKURAN BAYANGAN ── + _shadowScale = TweenSequence([ + TweenSequenceItem(tween: ConstantTween(2.5), weight: 25), + TweenSequenceItem(tween: Tween(begin: 2.5, end: 0.1).chain(CurveTween(curve: Curves.easeOut)), weight: 14), + TweenSequenceItem(tween: ConstantTween(0.1), weight: 61), + ]).animate(_mainController); + + // ── KOREOGRAFI TRANSPARANSI BAYANGAN ── + _shadowOpacity = TweenSequence([ + TweenSequenceItem(tween: ConstantTween(0.6), weight: 25), + TweenSequenceItem(tween: Tween(begin: 0.6, end: 0.0).chain(CurveTween(curve: Curves.easeOut)), weight: 14), + TweenSequenceItem(tween: ConstantTween(0.0), weight: 61), + ]).animate(_mainController); + + // ── SLIDE LOGO KE KIRI ── + _slideController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + _logoSlideX = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeInOutCubic), + ); + + // ── FADE IN OPACITY TEKS ── + _textController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); + _textOpacity = Tween(begin: 0.0, end: 1.0).animate(_textController); + + // ── KONTROL TRANSISI MENGECIL ── + _revealController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 750), + ); + _revealProgress = Tween(begin: 1.0, end: 0.0).animate( + CurvedAnimation(parent: _revealController, curve: Curves.easeInOutQuad), + ); + } + + // --- FUNGSI ANIMASI YANG SUDAH DIUPDATE DENGAN PENGECEKAN LOGIN --- + Future _mulaiAnimasi() async { + await _mainController.forward(); + await Future.delayed(const Duration(milliseconds: 200)); + + _slideController.forward(); + await Future.delayed(const Duration(milliseconds: 300)); + + _textController.forward(); + await Future.delayed(const Duration(milliseconds: 500)); + + // Typewriter + _charIndex = 0; + _displayText = ''; + for (int i = 0; i < _fullText.length; i++) { + await Future.delayed(const Duration(milliseconds: 120)); + if (mounted) { + setState(() { + _charIndex++; + _displayText = _fullText.substring(0, _charIndex); + }); + } + } + + await Future.delayed(const Duration(milliseconds: 100)); + + if (mounted) { + // 1. Jalankan animasi lingkaran mengecil internal sampai habis + await _revealController.forward(); + + // 2. CEK SESI LOGIN DI SINI + if (mounted) { + final session = Supabase.instance.client.auth.currentSession; + + if (session != null) { + // JIKA SUDAH LOGIN: Lempar ke Monitoring / Home + Navigator.of(context).pushReplacementNamed('/home'); + } else { + // JIKA BELUM LOGIN: Lempar ke LoginScreen + Navigator.of(context).pushReplacement( + PageRouteBuilder( + pageBuilder: (_, __, ___) => const LoginScreen(), + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + ), + ); + } + } + } + } + + @override + void dispose() { + _mainController.dispose(); + _slideController.dispose(); + _textController.dispose(); + _revealController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + const logoSize = 80.0; + + const textEstimatedWidth = 140.0; + const gap = 16.0; + const totalWidth = logoSize + gap + textEstimatedWidth; + final startX = (size.width - totalWidth) / 2; + final logoFinalX = startX; + final logoCenterX = size.width / 2 - logoSize / 2; + final centerY = size.height / 2; + + return AnimatedBuilder( + animation: _revealProgress, + builder: (context, child) { + return ClipPath( + clipper: CircularRevealClipper(progress: _revealProgress.value), + child: Scaffold( + backgroundColor: AppColors.primary, + body: AnimatedBuilder( + animation: Listenable.merge([_mainController, _slideController, _textController]), + builder: (context, child) { + final currentLogoX = logoCenterX - (_logoSlideX.value * (logoCenterX - logoFinalX)); + final currentLogoY = centerY - logoSize / 2 + _logoY.value; + + final shadowW = logoSize * 0.75 * _shadowScale.value; + final shadowH = 14.0 * _shadowScale.value; + final shadowX = currentLogoX + (logoSize - shadowW) / 2; + final shadowY = centerY + logoSize / 2 + 6; + + final textX = currentLogoX + logoSize + gap; + final textY = centerY - 20; + + return Stack( + children: [ + // ── 1. BAYANGAN ELIPS ── + Positioned( + left: shadowX, + top: shadowY, + child: Opacity( + opacity: _shadowOpacity.value.clamp(0.0, 1.0), + child: Container( + width: shadowW.clamp(0.0, logoSize * 2.5), + height: shadowH.clamp(0.0, 20.0 * 2.5), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(50), + ), + ), + ), + ), + + // ── 2. LOGO ── + Positioned( + left: currentLogoX, + top: currentLogoY, + child: Opacity( + opacity: _mainController.value > 0.25 ? 1.0 : 0.0, + child: Container( + width: logoSize, + height: logoSize, + decoration: BoxDecoration( + color: AppColors.cardBg, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.15), + blurRadius: 10, + offset: const Offset(0, 3), + ), + ], + ), + child: ClipOval( + child: Padding( + padding: const EdgeInsets.all(12), + child: Image.asset( + 'assets/icon/kopibaru.png', + fit: BoxFit.contain, + ), + ), + ), + ), + ), + ), + + // ── 3. TEKS POPPINS ── + Positioned( + left: textX, + top: textY, + child: Opacity( + opacity: _textOpacity.value, + child: Text( + _displayText, + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 30, + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + ), + ), + ), + ), + ], + ); + }, + ), + ), + ); + }, + ); + } +} + +// ── CUSTOM CLIPPER UNTUK EFEK LINGKARAN MENGECIL TEPAT DI TENGAH LAYAR ── +class CircularRevealClipper extends CustomClipper { + final double progress; + + CircularRevealClipper({required this.progress}); + + @override + Path getClip(Size size) { + final center = Offset(size.width / 2, size.height / 2); + final maxRadius = sqrt(size.width * size.width + size.height * size.height); + final radius = maxRadius * progress; + + final path = Path(); + path.addOval(Rect.fromCircle(center: center, radius: radius)); + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) => true; +} \ No newline at end of file diff --git a/lib/services/recording_service.dart b/lib/services/recording_service.dart new file mode 100644 index 0000000..479f979 --- /dev/null +++ b/lib/services/recording_service.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class RecordingService extends ChangeNotifier { + // Membuat Singleton + static final RecordingService _instance = RecordingService._internal(); + factory RecordingService() => _instance; + + RecordingService._internal() { + // Saat file ini pertama kali dipanggil (saat app dibuka), + // langsung cek ke dalam memori hardisk HP! + _loadStatusDariMemoriHP(); + } + + bool isRecording = false; // Status bawaan + + // FUNGSI BARU: Membaca status terakhir dari dalam HP + Future _loadStatusDariMemoriHP() async { + final prefs = await SharedPreferences.getInstance(); + // Jika ada catatan sebelumnya, pakai itu. Jika tidak, anggap false. + isRecording = prefs.getBool('status_perekaman') ?? false; + notifyListeners(); // Beritahu layar untuk update UI + } + + // FUNGSI DIUPDATE: Mengubah status DAN menyimpannya ke memori HP + Future setRecordingStatus(bool status) async { + isRecording = status; + notifyListeners(); // Beritahu layar untuk update tombol + + // Simpan ke hardisk HP agar tahan banting walau app di-close paksa! + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('status_perekaman', status); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index a0408d6..f332c76 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" app_links: dependency: transitive description: @@ -89,6 +97,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -137,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -230,6 +262,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -238,6 +278,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_native_splash: + dependency: "direct dev" + description: + name: flutter_native_splash + sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" + url: "https://pub.dev" + source: hosted + version: "2.4.4" flutter_test: dependency: "direct dev" description: flutter @@ -264,6 +312,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 + url: "https://pub.dev" + source: hosted + version: "6.3.3" gotrue: dependency: transitive description: @@ -288,6 +344,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: "direct main" description: @@ -336,6 +400,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" jwt_decode: dependency: transitive description: @@ -853,6 +925,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" url_launcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index aa3c379..0d97b2f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: excel: ^4.0.3 open_file: ^3.3.2 path_provider: ^2.1.2 + google_fonts: ^6.2.1 dependency_overrides: win32: ^6.1.0 @@ -51,27 +52,38 @@ dependency_overrides: dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^6.0.0 + flutter_launcher_icons: ^0.13.1 + flutter_native_splash: ^2.3.9 + +flutter_native_splash: + color: "#627254" # background hitam sesuai tema + #image: assets/icon/kopibaru.png + android: true + ios: false + +flutter_launcher_icons: + android: true + ios: false + image_path: "assets/icon/kopibaru.png" + min_sdk_android: 21 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # 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. uses-material-design: true - # To add assets to your application, add an assets section, like this: - # assets: + assets: + - assets/icon/kopibaru.png + - assets/icon/kopibaru_removebg.png # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg diff --git a/web/index.html b/web/index.html index a896ce3..4749c2c 100644 --- a/web/index.html +++ b/web/index.html @@ -1,6 +1,4 @@ - - - + - + coffee_iot_flutter + + + + + + + + + + + + + + + - - - + + + + \ No newline at end of file