Update fitur, assets, dan perbaikan tampilan
|
|
@ -2,7 +2,7 @@
|
||||||
package="com.example.coffee_iot_flutter">
|
package="com.example.coffee_iot_flutter">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<application
|
<application
|
||||||
android:label="coffee_iot_flutter"
|
android:label="Iot Kopi"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 69 B |
|
|
@ -1,12 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="?android:colorBackground" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 69 B |
|
|
@ -1,12 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -5,6 +5,10 @@
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
|
||||||
the Flutter engine draws its first frame -->
|
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
This theme determines the color of the Android Window while your
|
|
||||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
|
||||||
running.
|
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,12 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:app_links/app_links.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 'services/mqtt_service.dart';
|
||||||
import 'screens/login_screen.dart';
|
import 'screens/login_screen.dart';
|
||||||
import 'screens/register_screen.dart';
|
import 'screens/register_screen.dart';
|
||||||
|
|
@ -10,6 +16,7 @@ import 'screens/analytics_screen.dart';
|
||||||
import 'screens/image_result_screen.dart';
|
import 'screens/image_result_screen.dart';
|
||||||
import 'screens/change_password_screen.dart';
|
import 'screens/change_password_screen.dart';
|
||||||
import 'screens/forgot_password_screen.dart';
|
import 'screens/forgot_password_screen.dart';
|
||||||
|
import 'screens/splash_screen.dart';
|
||||||
|
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
|
@ -61,13 +68,72 @@ class MyApp extends StatelessWidget {
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
title: 'Coffee IoT',
|
title: 'Coffee IoT',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
|
||||||
|
// --- KONFIGURASI TEMA LIGHT MODE (TERPUSAT) ---
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.dark,
|
useMaterial3: true,
|
||||||
primarySwatch: Colors.green,
|
brightness: Brightness.light,
|
||||||
scaffoldBackgroundColor: Colors.black,
|
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: {
|
routes: {
|
||||||
|
'/': (context) => const SplashScreen(),
|
||||||
'/login': (context) => const LoginScreen(),
|
'/login': (context) => const LoginScreen(),
|
||||||
'/register': (context) => const RegisterScreen(),
|
'/register': (context) => const RegisterScreen(),
|
||||||
'/home': (context) => const HomeScreen(),
|
'/home': (context) => const HomeScreen(),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class AnalyticsScreen extends StatefulWidget {
|
class AnalyticsScreen extends StatefulWidget {
|
||||||
const AnalyticsScreen({super.key});
|
const AnalyticsScreen({super.key});
|
||||||
|
|
@ -36,21 +37,27 @@ class _AnalyticsScreenState extends State<AnalyticsScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final args = (ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?) ?? {};
|
final args = (ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?) ?? {};
|
||||||
final String title = args['title'] ?? 'Grafik';
|
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';
|
final String sensorKey = title.contains('Suhu') ? 'suhu' : title.contains('Kelembaban') ? 'kelembapan' : 'intensitas';
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background Scaffold Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Dibuat transparan agar menyatu dengan background ---
|
||||||
title: Text(title, style: const TextStyle(color: Colors.white)),
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
// --- BERUBAH: Warna Teks Judul ---
|
||||||
|
title: Text(title, style: const TextStyle(color: AppColors.textMain)),
|
||||||
leading: IconButton(
|
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),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator(color: Colors.green))
|
// --- BERUBAH: Warna Loading Indicator ---
|
||||||
|
? const Center(child: CircularProgressIndicator(color: AppColors.primary))
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -60,21 +67,27 @@ class _AnalyticsScreenState extends State<AnalyticsScreen> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text('Riwayat Data', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
// --- BERUBAH: Warna Teks Judul Riwayat ---
|
||||||
IconButton(icon: const Icon(Icons.refresh, color: Colors.green), onPressed: _fetchLogs),
|
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),
|
const SizedBox(height: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Warna Latar Daftar Riwayat ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
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(
|
child: ListView.separated(
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
itemCount: _logs.length,
|
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) {
|
itemBuilder: (context, index) {
|
||||||
final log = _logs[index];
|
final log = _logs[index];
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
@ -85,10 +98,13 @@ class _AnalyticsScreenState extends State<AnalyticsScreen> {
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(log['timestamp'] ?? '', style: const TextStyle(color: Colors.grey, fontSize: 10)),
|
// --- BERUBAH: Warna Teks Tanggal/Waktu ---
|
||||||
Text('Fans: ${log['kipas1']}/${log['kipas2']}', style: const TextStyle(color: Colors.green, fontSize: 10)),
|
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)),
|
Text('${log[sensorKey]}', style: TextStyle(color: color, fontWeight: FontWeight.bold)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -116,14 +132,15 @@ class _AnalyticsScreenState extends State<AnalyticsScreen> {
|
||||||
height: 250,
|
height: 250,
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Warna Latar Kartu Grafik ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
|
|
@ -134,10 +151,12 @@ class _AnalyticsScreenState extends State<AnalyticsScreen> {
|
||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
spots: spots.isEmpty ? [const FlSpot(0, 0)] : spots,
|
spots: spots.isEmpty ? [const FlSpot(0, 0)] : spots,
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
|
// Warna garis grafik tetap mengikuti `color` dinamis
|
||||||
color: color,
|
color: color,
|
||||||
barWidth: 4,
|
barWidth: 4,
|
||||||
dotData: FlDotData(show: false),
|
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<AnalyticsScreen> {
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Terakhir: ${logs.isNotEmpty ? logs.first[key] : "-"}',
|
'Terakhir: ${logs.isNotEmpty ? logs.first[key] : "-"}',
|
||||||
|
// Warna teks terakhir tetap mengikuti `color` dinamis
|
||||||
style: TextStyle(color: color, fontSize: 12)
|
style: TextStyle(color: color, fontSize: 12)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class ChangePasswordScreen extends StatefulWidget {
|
class ChangePasswordScreen extends StatefulWidget {
|
||||||
const ChangePasswordScreen({super.key});
|
const ChangePasswordScreen({super.key});
|
||||||
|
|
@ -118,17 +119,18 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
content: Row(
|
content: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(isError ? Icons.error_outline : Icons.check_circle_outline,
|
Icon(isError ? Icons.error_outline : Icons.check_circle_outline,
|
||||||
color: Colors.white),
|
color: Colors.white), // Tetap putih agar kontras
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(pesan,
|
child: Text(pesan,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white, fontWeight: FontWeight.bold)),
|
color: Colors.white, fontWeight: FontWeight.bold)), // Tetap putih
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// --- BERUBAH: Warna Notifikasi ---
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
isError ? Colors.red.shade700 : Colors.green.shade700,
|
isError ? AppColors.error : AppColors.success,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
|
|
@ -140,11 +142,15 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: AppBar Transparan ---
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
leading: IconButton(
|
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
|
onPressed: () => _dariResetLink
|
||||||
? Navigator.pushReplacementNamed(context, '/login')
|
? Navigator.pushReplacementNamed(context, '/login')
|
||||||
: Navigator.pop(context),
|
: Navigator.pop(context),
|
||||||
|
|
@ -152,7 +158,8 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
title: Text(
|
title: Text(
|
||||||
_dariResetLink ? 'Reset Password' : 'Ganti Password',
|
_dariResetLink ? 'Reset Password' : 'Ganti Password',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
// --- BERUBAH: Warna Judul ---
|
||||||
|
color: AppColors.textMain,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
|
|
@ -167,16 +174,19 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
_dariResetLink
|
_dariResetLink
|
||||||
? 'Masukkan password baru untuk akun kamu.'
|
? 'Masukkan password baru untuk akun kamu.'
|
||||||
: 'Ubah password akun kamu.\nPastikan password baru minimal 6 karakter.',
|
: '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),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Kotak Form ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
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(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -189,7 +199,8 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
onToggle: () => setState(
|
onToggle: () => setState(
|
||||||
() => _obscurePassLama = !_obscurePassLama),
|
() => _obscurePassLama = !_obscurePassLama),
|
||||||
),
|
),
|
||||||
const Divider(color: Colors.grey),
|
// --- BERUBAH: Warna Divider ---
|
||||||
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)),
|
||||||
],
|
],
|
||||||
|
|
||||||
_buildPasswordField(
|
_buildPasswordField(
|
||||||
|
|
@ -199,7 +210,8 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
onToggle: () => setState(
|
onToggle: () => setState(
|
||||||
() => _obscurePassBaru = !_obscurePassBaru),
|
() => _obscurePassBaru = !_obscurePassBaru),
|
||||||
),
|
),
|
||||||
const Divider(color: Colors.grey),
|
// --- BERUBAH: Warna Divider ---
|
||||||
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)),
|
||||||
|
|
||||||
_buildPasswordField(
|
_buildPasswordField(
|
||||||
controller: _konfirmasiController,
|
controller: _konfirmasiController,
|
||||||
|
|
@ -220,20 +232,22 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isLoading ? null : _handleGantiPassword,
|
onPressed: _isLoading ? null : _handleGantiPassword,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Tombol ---
|
||||||
disabledBackgroundColor: Colors.green.withOpacity(0.3),
|
backgroundColor: AppColors.primary,
|
||||||
|
disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: _isLoading
|
child: _isLoading
|
||||||
|
// Tetap putih agar kontras dengan tombol primer
|
||||||
? const CircularProgressIndicator(color: Colors.white)
|
? const CircularProgressIndicator(color: Colors.white)
|
||||||
: const Text(
|
: const Text(
|
||||||
'Simpan Password Baru',
|
'Simpan Password Baru',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white),
|
color: Colors.white), // Tetap putih
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -253,16 +267,20 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
obscureText: obscure,
|
obscureText: obscure,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: const Icon(Icons.lock, color: Colors.green),
|
// --- BERUBAH: Warna Ikon Kunci ---
|
||||||
|
prefixIcon: const Icon(Icons.lock, color: AppColors.primary),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(obscure ? Icons.visibility : Icons.visibility_off,
|
icon: Icon(obscure ? Icons.visibility : Icons.visibility_off,
|
||||||
color: Colors.grey),
|
// --- BERUBAH: Warna Ikon Mata ---
|
||||||
|
color: AppColors.textSecondary),
|
||||||
onPressed: onToggle,
|
onPressed: onToggle,
|
||||||
),
|
),
|
||||||
hintText: label,
|
hintText: label,
|
||||||
hintStyle: const TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: const TextStyle(color: AppColors.textSecondary),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../services/mqtt_service.dart';
|
import '../services/mqtt_service.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class ControlSettingsScreen extends StatefulWidget {
|
class ControlSettingsScreen extends StatefulWidget {
|
||||||
const ControlSettingsScreen({super.key});
|
const ControlSettingsScreen({super.key});
|
||||||
|
|
@ -60,9 +61,10 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
_humMinController.text.isEmpty ||
|
_humMinController.text.isEmpty ||
|
||||||
_humMaxController.text.isEmpty) {
|
_humMaxController.text.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Semua kolom batas harus diisi!'),
|
content: const Text('Semua kolom batas harus diisi!'),
|
||||||
backgroundColor: Colors.orange,
|
// --- BERUBAH: Warna Notifikasi Peringatan ---
|
||||||
|
backgroundColor: AppColors.warning,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
@ -76,18 +78,20 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
// Validasi logika
|
// Validasi logika
|
||||||
if (suhuMin >= suhuMax) {
|
if (suhuMin >= suhuMax) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Suhu min harus lebih kecil dari suhu max!'),
|
content: const Text('Suhu min harus lebih kecil dari suhu max!'),
|
||||||
backgroundColor: Colors.orange,
|
// --- BERUBAH: Warna Notifikasi Peringatan ---
|
||||||
|
backgroundColor: AppColors.warning,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rhMin >= rhMax) {
|
if (rhMin >= rhMax) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('RH min harus lebih kecil dari RH max!'),
|
content: const Text('RH min harus lebih kecil dari RH max!'),
|
||||||
backgroundColor: Colors.orange,
|
// --- BERUBAH: Warna Notifikasi Peringatan ---
|
||||||
|
backgroundColor: AppColors.warning,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
@ -113,9 +117,10 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('✅ Pengaturan Tersimpan & Terkirim ke ESP32'),
|
content: const Text('✅ Pengaturan Tersimpan & Terkirim ke ESP32'),
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Notifikasi Sukses ---
|
||||||
|
backgroundColor: AppColors.success,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +129,8 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('❌ Gagal: $e'),
|
content: Text('❌ Gagal: $e'),
|
||||||
backgroundColor: Colors.red,
|
// --- BERUBAH: Warna Notifikasi Error ---
|
||||||
|
backgroundColor: AppColors.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -136,11 +142,13 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background Scaffold Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: _isLoading
|
child: _isLoading
|
||||||
? const Center(
|
? const Center(
|
||||||
child: CircularProgressIndicator(color: Colors.green))
|
// --- BERUBAH: Warna Loading Indicator ---
|
||||||
|
child: CircularProgressIndicator(color: AppColors.primary))
|
||||||
: SingleChildScrollView(
|
: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
|
@ -150,13 +158,15 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
const Text(
|
const Text(
|
||||||
'Pengaturan',
|
'Pengaturan',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
// --- BERUBAH: Warna Judul Utama ---
|
||||||
|
color: AppColors.textMain,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Text(
|
const Text(
|
||||||
'Batas Sensor & Mode Operasi',
|
'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),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
|
@ -188,15 +198,19 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Background Kartu Mode Operasi ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
// --- BERUBAH: Menambahkan border halus agar serasi ---
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text('Mode Operasi',
|
const Text('Mode Operasi',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
// --- BERUBAH: Warna Judul Kartu Mode ---
|
||||||
|
color: AppColors.textMain,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16)),
|
fontSize: 16)),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
@ -206,12 +220,15 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
onChanged: (val) =>
|
onChanged: (val) =>
|
||||||
setState(() => _operatingMode = val!),
|
setState(() => _operatingMode = val!),
|
||||||
title: const Text('Mode Otomatis',
|
title: const Text('Mode Otomatis',
|
||||||
style: TextStyle(color: Colors.white)),
|
// --- BERUBAH: Warna Teks Radio Button ---
|
||||||
activeColor: Colors.green,
|
style: TextStyle(color: AppColors.textMain)),
|
||||||
|
// --- BERUBAH: Warna Lingkaran Radio Button (Aktif) ---
|
||||||
|
activeColor: AppColors.primary,
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
'Kipas bekerja otomatis berdasarkan sensor',
|
'Kipas bekerja otomatis berdasarkan sensor',
|
||||||
|
// --- BERUBAH: Warna Sub-teks Radio Button ---
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey, fontSize: 10),
|
color: AppColors.textSecondary, fontSize: 10),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
RadioListTile<int>(
|
RadioListTile<int>(
|
||||||
|
|
@ -220,12 +237,15 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
onChanged: (val) =>
|
onChanged: (val) =>
|
||||||
setState(() => _operatingMode = val!),
|
setState(() => _operatingMode = val!),
|
||||||
title: const Text('Mode Manual',
|
title: const Text('Mode Manual',
|
||||||
style: TextStyle(color: Colors.white)),
|
// --- BERUBAH: Warna Teks Radio Button ---
|
||||||
activeColor: Colors.green,
|
style: TextStyle(color: AppColors.textMain)),
|
||||||
|
// --- BERUBAH: Warna Lingkaran Radio Button (Aktif) ---
|
||||||
|
activeColor: AppColors.primary,
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
'Kontrol penuh kipas melalui dashboard HP',
|
'Kontrol penuh kipas melalui dashboard HP',
|
||||||
|
// --- BERUBAH: Warna Sub-teks Radio Button ---
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey, fontSize: 10),
|
color: AppColors.textSecondary, fontSize: 10),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -240,7 +260,8 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _saveSettings,
|
onPressed: _saveSettings,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Latar Tombol Simpan ---
|
||||||
|
backgroundColor: AppColors.primary,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
|
|
@ -250,6 +271,7 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
// Tetap putih agar tulisan dalam tombol kontras
|
||||||
color: Colors.white),
|
color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -268,31 +290,44 @@ class _ControlSettingsScreenState extends State<ControlSettingsScreen> {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Kartu Input Sensor ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
// --- BERUBAH: Menambahkan border halus agar serasi ---
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(title,
|
Text(title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
// --- BERUBAH: Warna Judul Input Sensor ---
|
||||||
|
color: AppColors.textMain,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Angka/Nilai Input Sensor ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: hint,
|
hintText: hint,
|
||||||
hintStyle: const TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Teks Placeholder/Hint ---
|
||||||
|
hintStyle: const TextStyle(color: AppColors.textSecondary),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.black,
|
// --- BERUBAH: Warna Kolom Input Sensor ---
|
||||||
|
fillColor: AppColors.background,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class ForgotPasswordScreen extends StatefulWidget {
|
class ForgotPasswordScreen extends StatefulWidget {
|
||||||
const ForgotPasswordScreen({super.key});
|
const ForgotPasswordScreen({super.key});
|
||||||
|
|
@ -104,7 +105,7 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
content: Row(children: [
|
content: Row(children: [
|
||||||
Icon(
|
Icon(
|
||||||
isError ? Icons.error_outline : Icons.check_circle_outline,
|
isError ? Icons.error_outline : Icons.check_circle_outline,
|
||||||
color: Colors.white,
|
color: Colors.white, // Tetap putih untuk kontras
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -113,8 +114,9 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
color: Colors.white, fontWeight: FontWeight.bold)),
|
color: Colors.white, fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
// --- BERUBAH: Menggunakan warna notifikasi dari AppColors ---
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
isError ? Colors.red.shade700 : Colors.green.shade700,
|
isError ? AppColors.error : AppColors.success,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
|
|
@ -126,12 +128,14 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
leading: IconButton(
|
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),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -152,12 +156,14 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
const Text(
|
const Text(
|
||||||
'Lupa Password?',
|
'Lupa Password?',
|
||||||
style: TextStyle(
|
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 SizedBox(height: 8),
|
||||||
const Text(
|
const Text(
|
||||||
'Masukkan email akun kamu. Kami akan kirim kode verifikasi 6 angka ke inbox kamu.',
|
'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),
|
const SizedBox(height: 50),
|
||||||
|
|
||||||
|
|
@ -165,18 +171,23 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Form ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(25),
|
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(
|
child: TextField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
prefixIcon: Icon(Icons.email, color: Colors.green),
|
// --- BERUBAH: Warna Ikon ---
|
||||||
|
prefixIcon: Icon(Icons.email, color: AppColors.primary),
|
||||||
hintText: 'Email',
|
hintText: 'Email',
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: TextStyle(color: AppColors.textSecondary),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -190,8 +201,10 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isLoading ? null : _handleKirimOTP,
|
onPressed: _isLoading ? null : _handleKirimOTP,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Tombol ---
|
||||||
disabledBackgroundColor: Colors.green.withOpacity(0.3),
|
backgroundColor: AppColors.primary,
|
||||||
|
// --- BERUBAH: Warna Tombol saat Loading ---
|
||||||
|
disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15)),
|
borderRadius: BorderRadius.circular(15)),
|
||||||
),
|
),
|
||||||
|
|
@ -211,7 +224,8 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Kembali ke Login',
|
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<ForgotPasswordScreen> {
|
||||||
children: [
|
children: [
|
||||||
const CircleAvatar(
|
const CircleAvatar(
|
||||||
radius: 40,
|
radius: 40,
|
||||||
backgroundColor: Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Ikon ---
|
||||||
child: Icon(Icons.mark_email_read, size: 40, color: Colors.green),
|
backgroundColor: AppColors.cardBg,
|
||||||
|
// --- BERUBAH: Warna Ikon ---
|
||||||
|
child: Icon(Icons.mark_email_read, size: 40, color: AppColors.primary),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
const Text(
|
const Text(
|
||||||
'Cek Email Kamu',
|
'Cek Email Kamu',
|
||||||
style: TextStyle(
|
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),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Kode verifikasi 6 angka sudah dikirim ke:\n$_email\n\nMasukkan kode tersebut di bawah ini.',
|
'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),
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
|
@ -245,9 +263,11 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Kotak OTP ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(25),
|
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(
|
child: TextField(
|
||||||
controller: _otpController,
|
controller: _otpController,
|
||||||
|
|
@ -255,11 +275,14 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
maxLength: 6,
|
maxLength: 6,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white, fontSize: 28, letterSpacing: 12),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
color: AppColors.textMain, fontSize: 28, letterSpacing: 12),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
prefixIcon: Icon(Icons.lock_open, color: Colors.green),
|
// --- BERUBAH: Warna Ikon ---
|
||||||
|
prefixIcon: Icon(Icons.lock_open, color: AppColors.primary),
|
||||||
hintText: '000000',
|
hintText: '000000',
|
||||||
hintStyle: TextStyle(color: Colors.grey, letterSpacing: 8),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: TextStyle(color: AppColors.textSecondary, letterSpacing: 8),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
counterText: '', // sembunyikan counter maxLength
|
counterText: '', // sembunyikan counter maxLength
|
||||||
),
|
),
|
||||||
|
|
@ -274,8 +297,10 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isLoading ? null : _handleVerifikasiOTP,
|
onPressed: _isLoading ? null : _handleVerifikasiOTP,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Tombol ---
|
||||||
disabledBackgroundColor: Colors.green.withOpacity(0.3),
|
backgroundColor: AppColors.primary,
|
||||||
|
// --- BERUBAH: Warna Tombol saat Loading ---
|
||||||
|
disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15)),
|
borderRadius: BorderRadius.circular(15)),
|
||||||
),
|
),
|
||||||
|
|
@ -303,7 +328,8 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
|
||||||
}),
|
}),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Kirim ulang kode',
|
'Kirim ulang kode',
|
||||||
style: TextStyle(color: Colors.green),
|
// --- BERUBAH: Warna Teks Bawah ---
|
||||||
|
style: TextStyle(color: AppColors.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../services/mqtt_service.dart';
|
import '../services/mqtt_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
import 'monitoring_screen.dart';
|
import 'monitoring_screen.dart';
|
||||||
import 'control_settings_screen.dart';
|
import 'control_settings_screen.dart';
|
||||||
import 'recording_screen.dart';
|
import 'recording_screen.dart';
|
||||||
|
|
@ -26,8 +27,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(message),
|
content: Text(
|
||||||
backgroundColor: isSuccess ? Colors.green.withOpacity(0.8) : Colors.redAccent,
|
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),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -51,11 +59,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
currentIndex: _currentIndex,
|
currentIndex: _currentIndex,
|
||||||
onTap: (index) => setState(() => _currentIndex = index),
|
onTap: (index) => setState(() => _currentIndex = index),
|
||||||
type: BottomNavigationBarType.fixed,
|
type: BottomNavigationBarType.fixed,
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Latar Belakang Menu Navigasi Bawah ---
|
||||||
selectedItemColor: Colors.green,
|
backgroundColor: AppColors.cardBg,
|
||||||
unselectedItemColor: Colors.grey,
|
// --- BERUBAH: Warna Ikon yang Sedang Dipilih ---
|
||||||
|
selectedItemColor: AppColors.primary,
|
||||||
|
// --- BERUBAH: Warna Ikon yang Tidak Dipilih ---
|
||||||
|
unselectedItemColor: AppColors.textSecondary,
|
||||||
showSelectedLabels: false,
|
showSelectedLabels: false,
|
||||||
showUnselectedLabels: false,
|
showUnselectedLabels: false,
|
||||||
|
elevation: 10, // Menambahkan sedikit bayangan agar terpisah dari body
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.grid_view), label: 'Monitoring'),
|
BottomNavigationBarItem(icon: Icon(Icons.grid_view), label: 'Monitoring'),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.tune), label: 'Control'),
|
BottomNavigationBarItem(icon: Icon(Icons.tune), label: 'Control'),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:pdf/pdf.dart';
|
||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
import 'package:printing/printing.dart';
|
import 'package:printing/printing.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import '../constants/app_colors.dart';
|
||||||
|
|
||||||
class ImageResultScreen extends StatelessWidget {
|
class ImageResultScreen extends StatelessWidget {
|
||||||
const ImageResultScreen({super.key});
|
const ImageResultScreen({super.key});
|
||||||
|
|
@ -14,19 +15,17 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
|
|
||||||
Future<void> _exportPDF(
|
Future<void> _exportPDF(
|
||||||
BuildContext context, Map<String, dynamic> args) async {
|
BuildContext context, Map<String, dynamic> args) async {
|
||||||
// Tampilkan loading
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (_) => const Center(
|
builder: (_) => const Center(
|
||||||
child: CircularProgressIndicator(color: Colors.green),
|
child: CircularProgressIndicator(color: AppColors.primary),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final pdf = pw.Document();
|
final pdf = pw.Document();
|
||||||
|
|
||||||
// Download foto dari URL untuk dimasukkan ke PDF
|
|
||||||
pw.MemoryImage? fotoImage;
|
pw.MemoryImage? fotoImage;
|
||||||
final urlFoto = args['url_foto']?.toString() ?? '';
|
final urlFoto = args['url_foto']?.toString() ?? '';
|
||||||
if (urlFoto.isNotEmpty) {
|
if (urlFoto.isNotEmpty) {
|
||||||
|
|
@ -40,7 +39,6 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buat halaman PDF
|
|
||||||
pdf.addPage(
|
pdf.addPage(
|
||||||
pw.Page(
|
pw.Page(
|
||||||
pageFormat: PdfPageFormat.a4,
|
pageFormat: PdfPageFormat.a4,
|
||||||
|
|
@ -49,7 +47,6 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
return pw.Column(
|
return pw.Column(
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// ── Header ──
|
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const pw.EdgeInsets.all(16),
|
padding: const pw.EdgeInsets.all(16),
|
||||||
|
|
@ -82,7 +79,6 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
|
|
||||||
pw.SizedBox(height: 20),
|
pw.SizedBox(height: 20),
|
||||||
|
|
||||||
// ── Info Waktu ──
|
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'Tanggal & Waktu: ${args['date']} - ${args['time']}',
|
'Tanggal & Waktu: ${args['date']} - ${args['time']}',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
|
|
@ -95,7 +91,6 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
pw.Divider(),
|
pw.Divider(),
|
||||||
pw.SizedBox(height: 16),
|
pw.SizedBox(height: 16),
|
||||||
|
|
||||||
// ── Foto ──
|
|
||||||
if (fotoImage != null) ...[
|
if (fotoImage != null) ...[
|
||||||
pw.Center(
|
pw.Center(
|
||||||
child: pw.Container(
|
child: pw.Container(
|
||||||
|
|
@ -122,7 +117,6 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
pw.SizedBox(height: 16),
|
pw.SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
|
||||||
// ── Data Sensor ──
|
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'Data Sensor',
|
'Data Sensor',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
|
|
@ -151,7 +145,6 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
pw.Divider(),
|
pw.Divider(),
|
||||||
pw.SizedBox(height: 8),
|
pw.SizedBox(height: 8),
|
||||||
|
|
||||||
// ── Footer ──
|
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'Diekspor dari Aplikasi Monitoring Kopi',
|
'Diekspor dari Aplikasi Monitoring Kopi',
|
||||||
style: const pw.TextStyle(
|
style: const pw.TextStyle(
|
||||||
|
|
@ -165,10 +158,8 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tutup loading
|
|
||||||
if (context.mounted) Navigator.pop(context);
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
|
||||||
// Tampilkan preview PDF + opsi simpan/share
|
|
||||||
await Printing.layoutPdf(
|
await Printing.layoutPdf(
|
||||||
onLayout: (PdfPageFormat format) async => pdf.save(),
|
onLayout: (PdfPageFormat format) async => pdf.save(),
|
||||||
name:
|
name:
|
||||||
|
|
@ -176,11 +167,11 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context); // tutup loading
|
Navigator.pop(context);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Gagal membuat PDF: $e'),
|
content: Text('Gagal membuat PDF: $e'),
|
||||||
backgroundColor: Colors.red.shade700,
|
backgroundColor: AppColors.error,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|
@ -231,18 +222,18 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
};
|
};
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: AppColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Detail Data Citra',
|
'Detail Data Citra',
|
||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: AppColors.textMain),
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.grey),
|
icon: const Icon(Icons.arrow_back, color: AppColors.primary),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
// Icon download dihapus
|
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
|
@ -253,8 +244,9 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
height: 250,
|
height: 250,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: args['url_foto'] != null &&
|
child: args['url_foto'] != null &&
|
||||||
|
|
@ -265,26 +257,26 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
loadingBuilder: (context, child, progress) {
|
loadingBuilder: (context, child, progress) {
|
||||||
if (progress == null) return child;
|
if (progress == null) return child;
|
||||||
return const Center(
|
return const Center(
|
||||||
child:
|
child: CircularProgressIndicator(color: AppColors.primary),
|
||||||
CircularProgressIndicator(color: Colors.green),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
errorBuilder: (context, error, stack) => Column(
|
errorBuilder: (context, error, stack) => Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.broken_image,
|
const Icon(Icons.broken_image,
|
||||||
size: 80, color: Colors.grey),
|
size: 80, color: AppColors.textSecondary),
|
||||||
Text('Citra #${args['id']}',
|
Text('Citra #${args['id']}',
|
||||||
style: const TextStyle(color: Colors.grey)),
|
style: const TextStyle(color: AppColors.textSecondary)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.image, size: 80, color: Colors.grey),
|
const Icon(Icons.image,
|
||||||
|
size: 80, color: AppColors.textSecondary),
|
||||||
Text('Citra #${args['id']}',
|
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(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Tanggal & waktu
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.calendar_today,
|
const Icon(Icons.calendar_today,
|
||||||
color: Colors.green, size: 16),
|
color: AppColors.primary, size: 16),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'${args['date']} - ${args['time']}',
|
'${args['date']} - ${args['time']}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.green,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16),
|
fontSize: 16),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Lokasi dihapus
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3), height: 30),
|
||||||
const Divider(color: Colors.grey, height: 30),
|
|
||||||
|
|
||||||
_buildDataRow(
|
_buildDataRow(
|
||||||
'Suhu', args['temp'], 'Kelembaban', args['humidity']),
|
'Suhu', args['temp'], 'Kelembaban', args['humidity']),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
|
|
||||||
|
// --- BERUBAH: Mengirimkan string kosong ('') untuk menghapus kolom Mode ---
|
||||||
_buildDataRow(
|
_buildDataRow(
|
||||||
'Cahaya', args['light'], 'Mode', 'Terjadwal'),
|
'Cahaya', args['light'], '', ''),
|
||||||
const Divider(color: Colors.grey, height: 30),
|
|
||||||
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3), height: 30),
|
||||||
|
|
||||||
_buildDataRow(
|
_buildDataRow(
|
||||||
'Kipas 1 (Intake)', args['intake'],
|
'Kipas 1 (Intake)', args['intake'],
|
||||||
'Kipas 2 (Exhaust)', args['exhaust'],
|
'Kipas 2 (Exhaust)', args['exhaust'],
|
||||||
valueColor: Colors.green,
|
valueColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -342,7 +336,7 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () => _exportPDF(context, args),
|
onPressed: () => _exportPDF(context, args),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: AppColors.primary,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
|
|
@ -361,12 +355,13 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- BERUBAH: Menambahkan logika untuk menyembunyikan kolom kanan jika string kosong ---
|
||||||
Widget _buildDataRow(
|
Widget _buildDataRow(
|
||||||
String label1,
|
String label1,
|
||||||
String value1,
|
String value1,
|
||||||
String label2,
|
String label2,
|
||||||
String value2, {
|
String value2, {
|
||||||
Color valueColor = Colors.white,
|
Color? valueColor,
|
||||||
}) {
|
}) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
|
@ -376,30 +371,32 @@ class ImageResultScreen extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(label1,
|
Text(label1,
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(': $value1',
|
Text(': $value1',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: valueColor,
|
color: valueColor ?? AppColors.textMain,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 15)),
|
fontSize: 15)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: label2.isEmpty
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? const SizedBox.shrink() // Jika kosong, sembunyikan sepenuhnya
|
||||||
children: [
|
: Column(
|
||||||
Text(label2,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
children: [
|
||||||
const SizedBox(height: 4),
|
Text(label2,
|
||||||
Text(': $value2',
|
style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)),
|
||||||
style: TextStyle(
|
const SizedBox(height: 4),
|
||||||
color: valueColor,
|
Text(': $value2',
|
||||||
fontWeight: FontWeight.bold,
|
style: TextStyle(
|
||||||
fontSize: 15)),
|
color: valueColor ?? AppColors.textMain,
|
||||||
],
|
fontWeight: FontWeight.bold,
|
||||||
),
|
fontSize: 15)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
@ -64,20 +65,21 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Row(
|
content: Row(
|
||||||
children: [
|
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),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
pesan,
|
pesan,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white, // Tetap putih
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.red.shade700,
|
// --- BERUBAH: Background SnackBar Error ---
|
||||||
|
backgroundColor: AppColors.error,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|
@ -91,70 +93,93 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background Scaffold Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 80.0),
|
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 80.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 60,
|
radius: 60,
|
||||||
backgroundColor: Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar belakang logo ---
|
||||||
child: Icon(Icons.coffee, size: 60, color: Colors.orange),
|
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 SizedBox(height: 30),
|
||||||
const Text(
|
const Text(
|
||||||
'Selamat Datang',
|
'Selamat Datang',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.green,
|
// --- BERUBAH: Warna Judul ---
|
||||||
|
color: AppColors.primary,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Text(
|
const Text(
|
||||||
'Sistem Kendali Pengering Kopi',
|
'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),
|
const SizedBox(height: 50),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Warna Kotak Form ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(25),
|
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(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
prefixIcon: Icon(Icons.email, color: Colors.green),
|
// --- BERUBAH: Warna Ikon ---
|
||||||
|
prefixIcon: Icon(Icons.email, color: AppColors.primary),
|
||||||
hintText: 'Email',
|
hintText: 'Email',
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: TextStyle(color: AppColors.textSecondary),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(color: Colors.grey),
|
// --- BERUBAH: Garis Pemisah (Divider) ---
|
||||||
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
obscureText: _obscurePassword,
|
obscureText: _obscurePassword,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon:
|
// --- BERUBAH: Warna Ikon ---
|
||||||
const Icon(Icons.lock, color: Colors.green),
|
prefixIcon: const Icon(Icons.lock, color: AppColors.primary),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_obscurePassword
|
_obscurePassword
|
||||||
? Icons.visibility
|
? Icons.visibility
|
||||||
: Icons.visibility_off,
|
: Icons.visibility_off,
|
||||||
color: Colors.grey,
|
// --- BERUBAH: Warna Ikon Mata ---
|
||||||
|
color: AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
onPressed: () => setState(
|
onPressed: () => setState(
|
||||||
() => _obscurePassword = !_obscurePassword),
|
() => _obscurePassword = !_obscurePassword),
|
||||||
),
|
),
|
||||||
hintText: 'Password',
|
hintText: 'Password',
|
||||||
hintStyle: const TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: const TextStyle(color: AppColors.textSecondary),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -165,42 +190,64 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isLoading ? null : _handleLogin,
|
onPressed: _isLoading ? null : _handleLogin,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Latar Belakang Tombol ---
|
||||||
|
backgroundColor: AppColors.primary,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: _isLoading
|
child: _isLoading
|
||||||
? const CircularProgressIndicator(
|
? const CircularProgressIndicator(
|
||||||
color: Colors.white)
|
color: Colors.white) // Tetap putih untuk kontras
|
||||||
: const Text(
|
: const Text(
|
||||||
'Masuk ke Akun',
|
'Masuk ke Akun',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white),
|
color: Colors.white), // Tetap putih
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Lupa Password dan Daftar — rapat tanpa jarak besar, warna sama hijau
|
// ── 1. TOMBOL LUPA PASSWORD ──
|
||||||
const SizedBox(height: 10),
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () =>
|
onPressed: () => Navigator.pushNamed(context, '/forgot_password'),
|
||||||
Navigator.pushNamed(context, '/forgot_password'),
|
style: TextButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
minimumSize: Size.zero,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Lupa Password?',
|
'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(
|
TextButton(
|
||||||
onPressed: () => Navigator.pushNamed(context, '/register'),
|
onPressed: () => Navigator.pushNamed(context, '/register'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
minimumSize: Size.zero,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Belum punya akun? Daftar Sekarang',
|
'Belum punya akun? Daftar Sekarang',
|
||||||
style: TextStyle(color: Colors.green),
|
style: TextStyle(
|
||||||
|
// --- BERUBAH: Warna Teks ---
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:mqtt_client/mqtt_client.dart';
|
import 'package:mqtt_client/mqtt_client.dart';
|
||||||
import '../services/mqtt_service.dart';
|
import '../services/mqtt_service.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class MonitoringScreen extends StatefulWidget {
|
class MonitoringScreen extends StatefulWidget {
|
||||||
final VoidCallback? onProfileTap;
|
final VoidCallback? onProfileTap;
|
||||||
|
|
@ -60,19 +61,21 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
});
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: _isInitialLoading
|
child: _isInitialLoading
|
||||||
? const Center(child: CircularProgressIndicator(color: Colors.green))
|
// --- BERUBAH: Warna Loading Indicator ---
|
||||||
|
? const Center(child: CircularProgressIndicator(color: AppColors.primary))
|
||||||
: RefreshIndicator(
|
: RefreshIndicator(
|
||||||
onRefresh: _initData,
|
onRefresh: _initData,
|
||||||
color: Colors.green,
|
// --- BERUBAH: Warna Refresh Indicator ---
|
||||||
|
color: AppColors.primary,
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
// Tinggi tersedia = tinggi layar dikurangi header
|
const headerHeight = 175.0;
|
||||||
const headerHeight = 175.0; // header + timestamp + mode badge + padding
|
const gaps = 15.0 * 2;
|
||||||
const gaps = 15.0 * 2; // 2 gap antar 3 baris
|
const padding = 20.0 * 2;
|
||||||
const padding = 20.0 * 2; // padding atas bawah
|
|
||||||
final available = constraints.maxHeight - headerHeight - gaps - padding;
|
final available = constraints.maxHeight - headerHeight - gaps - padding;
|
||||||
final cardH = (available / 3).clamp(100.0, 180.0);
|
final cardH = (available / 3).clamp(100.0, 180.0);
|
||||||
|
|
||||||
|
|
@ -94,11 +97,13 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
children: [
|
children: [
|
||||||
const Text('Monitoring',
|
const Text('Monitoring',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
// --- BERUBAH: Warna Judul ---
|
||||||
|
color: AppColors.textMain,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
const Text('Sistem Pengeringan Kopi',
|
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),
|
const SizedBox(height: 5),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -106,10 +111,11 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
width: 8, height: 8,
|
width: 8, height: 8,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
|
// --- BERUBAH: Indikator Titik Koneksi ---
|
||||||
color: mqtt.client?.connectionStatus?.state ==
|
color: mqtt.client?.connectionStatus?.state ==
|
||||||
MqttConnectionState.connected
|
MqttConnectionState.connected
|
||||||
? Colors.green
|
? AppColors.success
|
||||||
: Colors.red,
|
: AppColors.error,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
@ -119,11 +125,13 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
? 'Connected'
|
? 'Connected'
|
||||||
: 'Disconnected',
|
: 'Disconnected',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
// --- BERUBAH: Warna Teks Koneksi ---
|
||||||
color: mqtt.client?.connectionStatus?.state ==
|
color: mqtt.client?.connectionStatus?.state ==
|
||||||
MqttConnectionState.connected
|
MqttConnectionState.connected
|
||||||
? Colors.green
|
? AppColors.success
|
||||||
: Colors.red,
|
: AppColors.error,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -133,9 +141,11 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: widget.onProfileTap,
|
onTap: widget.onProfileTap,
|
||||||
child: const CircleAvatar(
|
child: const CircleAvatar(
|
||||||
backgroundColor: Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Avatar Profil ---
|
||||||
|
backgroundColor: AppColors.cardBg,
|
||||||
radius: 25,
|
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<MonitoringScreen> {
|
||||||
|
|
||||||
// ── Timestamp ──
|
// ── Timestamp ──
|
||||||
Text('Update: ${mqtt.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),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// ── Mode Badge ──
|
// ── Mode Badge ──
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
// --- BERUBAH: Latar Belakang Transparan Badge ---
|
||||||
color: mqtt.mode == 'manual'
|
color: mqtt.mode == 'manual'
|
||||||
? Colors.orange.withOpacity(0.2)
|
? AppColors.warning.withValues(alpha: 0.2)
|
||||||
: Colors.green.withOpacity(0.2),
|
: AppColors.success.withValues(alpha: 0.2),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
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(
|
child: Text(
|
||||||
'Mode: ${mqtt.mode.toUpperCase()}',
|
'Mode: ${mqtt.mode.toUpperCase()}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: mqtt.mode == 'manual' ? Colors.orange : Colors.green,
|
// --- BERUBAH: Warna Teks Badge ---
|
||||||
|
color: mqtt.mode == 'manual' ? AppColors.warning : AppColors.success,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
|
@ -183,11 +197,12 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
suffix: '°C',
|
suffix: '°C',
|
||||||
dec: 1,
|
dec: 1,
|
||||||
icon: Icons.thermostat,
|
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,
|
isAlert: curSuhu > mqtt.maxSuhu,
|
||||||
isLow: curSuhu < mqtt.minSuhu,
|
isLow: curSuhu < mqtt.minSuhu,
|
||||||
onTap: () => Navigator.pushNamed(context, '/analytics',
|
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),
|
const SizedBox(width: 15),
|
||||||
|
|
@ -199,11 +214,12 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
dec: 1,
|
dec: 1,
|
||||||
icon: Icons.water_drop,
|
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,
|
isAlert: curHum > mqtt.maxRh,
|
||||||
isLow: curHum < mqtt.minRh,
|
isLow: curHum < mqtt.minRh,
|
||||||
onTap: () => Navigator.pushNamed(context, '/analytics',
|
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<MonitoringScreen> {
|
||||||
suffix: ' Lux',
|
suffix: ' Lux',
|
||||||
dec: 1,
|
dec: 1,
|
||||||
icon: Icons.wb_sunny,
|
icon: Icons.wb_sunny,
|
||||||
color: Colors.orange,
|
// --- BERUBAH: Warna Tema Kartu Cahaya ---
|
||||||
|
color: AppColors.warning,
|
||||||
onTap: () => Navigator.pushNamed(context, '/analytics',
|
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),
|
const SizedBox(height: 15),
|
||||||
|
|
@ -261,19 +278,23 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
margin: const EdgeInsets.only(top: 15),
|
margin: const EdgeInsets.only(top: 15),
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.red.withOpacity(0.2),
|
// --- BERUBAH: Background Peringatan ---
|
||||||
|
color: AppColors.error.withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
border: Border.all(color: Colors.red),
|
// --- BERUBAH: Garis Pinggir Peringatan ---
|
||||||
|
border: Border.all(color: AppColors.error),
|
||||||
),
|
),
|
||||||
child: const Row(
|
child: const Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.warning_amber_rounded, color: Colors.red),
|
// --- BERUBAH: Ikon Peringatan ---
|
||||||
|
Icon(Icons.warning_amber_rounded, color: AppColors.error),
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Peringatan: Parameter pengeringan melewati batas aman!',
|
'Peringatan: Parameter pengeringan melewati batas aman!',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.red,
|
// --- BERUBAH: Teks Peringatan ---
|
||||||
|
color: AppColors.error,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
),
|
),
|
||||||
|
|
@ -286,19 +307,23 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
margin: const EdgeInsets.only(top: 15),
|
margin: const EdgeInsets.only(top: 15),
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue.withOpacity(0.2),
|
// --- BERUBAH: Background Info/Low ---
|
||||||
|
color: AppColors.info.withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(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(
|
child: const Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.info_outline, color: Colors.blue),
|
// --- BERUBAH: Ikon Info/Low ---
|
||||||
|
Icon(Icons.info_outline, color: AppColors.info),
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Info: Parameter pengeringan di bawah batas minimum!',
|
'Info: Parameter pengeringan di bawah batas minimum!',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.blue,
|
// --- BERUBAH: Teks Info/Low ---
|
||||||
|
color: AppColors.info,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
),
|
),
|
||||||
|
|
@ -337,16 +362,23 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
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),
|
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(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: color, size: 32),
|
Icon(icon, color: color, size: 32),
|
||||||
const SizedBox(height: 6),
|
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),
|
const SizedBox(height: 4),
|
||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween<double>(begin: prev, end: cur),
|
tween: Tween<double>(begin: prev, end: cur),
|
||||||
|
|
@ -357,7 +389,8 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
child: Text(
|
child: Text(
|
||||||
'${value.toStringAsFixed(dec)}$suffix',
|
'${value.toStringAsFixed(dec)}$suffix',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
// --- BERUBAH: Warna Angka/Nilai Sensor ---
|
||||||
|
color: AppColors.textMain,
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
|
|
@ -380,8 +413,10 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Kartu Latar Putih Bersih & Garis Halus ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -389,13 +424,15 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
// --- BERUBAH: Warna Ikon Kontroler ---
|
||||||
Icon(Icons.settings_input_component,
|
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),
|
const SizedBox(width: 6),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
// --- BERUBAH: Warna Judul Kartu Kontrol ---
|
||||||
child: Text(title,
|
child: Text(title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: AppColors.textMain,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
|
|
@ -404,16 +441,18 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
// --- BERUBAH: Warna Status Mode ---
|
||||||
Text('Mode: ${mqtt.mode.toUpperCase()}',
|
Text('Mode: ${mqtt.mode.toUpperCase()}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.green, fontSize: 9, fontWeight: FontWeight.bold)),
|
color: AppColors.primary, fontSize: 9, fontWeight: FontWeight.bold)),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
isOn ? 'ON' : 'OFF',
|
isOn ? 'ON' : 'OFF',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isOn ? Colors.green : Colors.grey,
|
// --- BERUBAH: Warna Status ON/OFF ---
|
||||||
|
color: isOn ? AppColors.primary : AppColors.textSecondary,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
|
|
@ -422,7 +461,8 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
|
||||||
child: Switch(
|
child: Switch(
|
||||||
value: isOn,
|
value: isOn,
|
||||||
onChanged: mqtt.mode == 'manual' ? onChanged : null,
|
onChanged: mqtt.mode == 'manual' ? onChanged : null,
|
||||||
activeColor: Colors.green,
|
// --- BERUBAH: Warna Switch ---
|
||||||
|
activeColor: AppColors.primary,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import 'package:provider/provider.dart';
|
||||||
import '../services/mqtt_service.dart';
|
import '../services/mqtt_service.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.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 {
|
class RecordingScreen extends StatefulWidget {
|
||||||
const RecordingScreen({super.key});
|
const RecordingScreen({super.key});
|
||||||
|
|
@ -15,8 +17,8 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
final TextEditingController _intervalController = TextEditingController();
|
final TextEditingController _intervalController = TextEditingController();
|
||||||
List<Map<String, dynamic>> _gallery = [];
|
List<Map<String, dynamic>> _gallery = [];
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
bool _isRecording = false;
|
|
||||||
|
|
||||||
|
final recordingService = RecordingService();
|
||||||
late final RealtimeChannel _channel;
|
late final RealtimeChannel _channel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -24,7 +26,6 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadData();
|
_loadData();
|
||||||
|
|
||||||
// 🔥 REALTIME LISTENER SUPABASE (AUTO REFRESH GALERI)
|
|
||||||
_channel = Supabase.instance.client
|
_channel = Supabase.instance.client
|
||||||
.channel('realtime-foto')
|
.channel('realtime-foto')
|
||||||
.onPostgresChanges(
|
.onPostgresChanges(
|
||||||
|
|
@ -32,7 +33,7 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
schema: 'public',
|
schema: 'public',
|
||||||
table: 'foto_dataset',
|
table: 'foto_dataset',
|
||||||
callback: (payload) {
|
callback: (payload) {
|
||||||
_loadData(); // reload gallery otomatis saat foto baru masuk
|
_loadData();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
@ -41,10 +42,7 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_intervalController.dispose();
|
_intervalController.dispose();
|
||||||
|
|
||||||
// 🔥 MATIKAN REALTIME LISTENER
|
|
||||||
Supabase.instance.client.removeChannel(_channel);
|
Supabase.instance.client.removeChannel(_channel);
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,31 +72,31 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Interval harus lebih dari 0 menit!'),
|
content: Text('Interval harus lebih dari 0 menit!'),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: AppColors.warning,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start) {
|
if (start) {
|
||||||
// Simpan interval ke Supabase
|
|
||||||
await SupabaseService().updateIntervalSetting(intervalVal);
|
await SupabaseService().updateIntervalSetting(intervalVal);
|
||||||
// Kirim interval ke ESP32 via MQTT
|
|
||||||
mqtt.setIntervalFoto(intervalVal);
|
mqtt.setIntervalFoto(intervalVal);
|
||||||
setState(() => _isRecording = true);
|
recordingService.setRecordingStatus(true);
|
||||||
} else {
|
} else {
|
||||||
// Kirim stop ke ESP32 Dev via MQTT
|
|
||||||
mqtt.setIntervalFoto(0);
|
mqtt.setIntervalFoto(0);
|
||||||
setState(() => _isRecording = false);
|
recordingService.setRecordingStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(start
|
content: Text(
|
||||||
? '📸 Perekaman dimulai — interval ${intervalVal} menit'
|
start
|
||||||
: '⏹ Perekaman dihentikan'),
|
? 'Perekaman dimulai — interval $intervalVal menit'
|
||||||
backgroundColor: start ? Colors.green : Colors.red,
|
: '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<RecordingScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: AppColors.background,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: _isLoading
|
child: _isLoading
|
||||||
? const Center(
|
? const Center(child: CircularProgressIndicator(color: AppColors.primary))
|
||||||
child: CircularProgressIndicator(color: Colors.green))
|
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// ── Header ──
|
const Text('Perekaman',
|
||||||
const Text(
|
style: TextStyle(color: AppColors.textMain, fontSize: 32, fontWeight: FontWeight.bold),
|
||||||
'Perekaman',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 32,
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
const Text(
|
const Text('Dataset Citra Kopi',
|
||||||
'Dataset Citra Kopi',
|
style: TextStyle(color: AppColors.textSecondary, fontSize: 16),
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 16),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// ── Panel Kontrol ──
|
// LISTENABLE BUILDER: Membuat timer kebal pindah halaman
|
||||||
Container(
|
ListenableBuilder(
|
||||||
padding: const EdgeInsets.all(20),
|
listenable: recordingService,
|
||||||
decoration: BoxDecoration(
|
builder: (context, child) {
|
||||||
color: const Color(0xFF1A1A1A),
|
final isRecording = recordingService.isRecording;
|
||||||
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),
|
|
||||||
|
|
||||||
// Status perekaman
|
return Container(
|
||||||
if (_isRecording)
|
padding: const EdgeInsets.all(20),
|
||||||
Container(
|
decoration: BoxDecoration(
|
||||||
padding: const EdgeInsets.all(10),
|
color: AppColors.cardBg,
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(20),
|
||||||
color: Colors.green.withOpacity(0.1),
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
borderRadius: BorderRadius.circular(10),
|
),
|
||||||
border: Border.all(color: Colors.green),
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
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(
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
const Text('Interval Waktu (menit)',
|
||||||
child: ElevatedButton.icon(
|
style: TextStyle(color: AppColors.textMain, fontWeight: FontWeight.bold),
|
||||||
onPressed: _isRecording
|
),
|
||||||
? null
|
const SizedBox(height: 10),
|
||||||
: () => _controlRecording(true),
|
TextField(
|
||||||
icon: const Icon(Icons.play_arrow,
|
controller: _intervalController,
|
||||||
color: Colors.white),
|
keyboardType: TextInputType.number,
|
||||||
label: const Text('Mulai',
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
style:
|
decoration: InputDecoration(
|
||||||
TextStyle(color: Colors.white)),
|
hintText: '30',
|
||||||
style: ElevatedButton.styleFrom(
|
hintStyle: const TextStyle(color: AppColors.textSecondary),
|
||||||
backgroundColor: Colors.green,
|
filled: true,
|
||||||
disabledBackgroundColor:
|
fillColor: AppColors.background,
|
||||||
Colors.green.withOpacity(0.3),
|
border: OutlineInputBorder(
|
||||||
shape: RoundedRectangleBorder(
|
borderRadius: BorderRadius.circular(15),
|
||||||
borderRadius:
|
borderSide: BorderSide(color: AppColors.textSecondary.withValues(alpha: 0.3)),
|
||||||
BorderRadius.circular(12),
|
),
|
||||||
),
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
borderSide: const BorderSide(color: AppColors.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(height: 15),
|
||||||
Expanded(
|
|
||||||
child: ElevatedButton.icon(
|
if (isRecording)
|
||||||
onPressed: !_isRecording
|
Container(
|
||||||
? null
|
padding: const EdgeInsets.all(10),
|
||||||
: () => _controlRecording(false),
|
decoration: BoxDecoration(
|
||||||
icon: const Icon(Icons.stop,
|
color: AppColors.success.withValues(alpha: 0.15),
|
||||||
color: Colors.white),
|
borderRadius: BorderRadius.circular(10),
|
||||||
label: const Text('Berhenti',
|
border: Border.all(color: AppColors.success),
|
||||||
style:
|
),
|
||||||
TextStyle(color: Colors.white)),
|
child: const Row(
|
||||||
style: ElevatedButton.styleFrom(
|
children: [
|
||||||
backgroundColor: Colors.redAccent,
|
Icon(Icons.circle, color: AppColors.success, size: 10),
|
||||||
disabledBackgroundColor:
|
SizedBox(width: 8),
|
||||||
Colors.redAccent.withOpacity(0.3),
|
Text('Perekaman berjalan...',
|
||||||
shape: RoundedRectangleBorder(
|
style: TextStyle(color: AppColors.success, fontSize: 12, fontWeight: FontWeight.bold)),
|
||||||
borderRadius:
|
],
|
||||||
BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// ── Header Galeri ──
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text('Galeri Citra Dataset (${_gallery.length} foto)',
|
||||||
'Galeri Citra Dataset (${_gallery.length} foto)',
|
style: const TextStyle(color: AppColors.textMain, fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.refresh,
|
icon: const Icon(Icons.refresh, color: AppColors.primary),
|
||||||
color: Colors.green),
|
|
||||||
onPressed: _loadData,
|
onPressed: _loadData,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// ── Grid Galeri ──
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _gallery.isEmpty
|
child: _gallery.isEmpty
|
||||||
? const Center(
|
? const Center(
|
||||||
child: Text(
|
child: Text('Belum ada data citra', style: TextStyle(color: AppColors.textSecondary)),
|
||||||
'Belum ada data citra',
|
|
||||||
style: TextStyle(color: Colors.grey),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: GridView.builder(
|
: GridView.builder(
|
||||||
gridDelegate:
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: 10,
|
crossAxisSpacing: 10,
|
||||||
mainAxisSpacing: 10,
|
mainAxisSpacing: 10,
|
||||||
|
|
@ -282,9 +247,13 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = _gallery[index];
|
final item = _gallery[index];
|
||||||
final nomorFoto = index + 1;
|
final nomorFoto = index + 1;
|
||||||
final timestamp = item['timestamp'] ?? '';
|
|
||||||
final tanggal = timestamp.contains(' ') ? timestamp.split(' ')[0] : timestamp;
|
// PENYARING WAKTU: Ubah 'T' jadi Spasi agar kodingan lamamu aman
|
||||||
final jam = timestamp.contains(' ') ? timestamp.split(' ')[1] : '';
|
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;
|
final jamSingkat = jam.length >= 5 ? jam.substring(0, 5) : jam;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
|
@ -294,10 +263,11 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
arguments: {
|
arguments: {
|
||||||
'id': item['id'].toString(),
|
'id': item['id'].toString(),
|
||||||
'date': tanggal,
|
'date': tanggal,
|
||||||
'time': jam,
|
'time': jamSingkat,
|
||||||
'temp': '${item['suhu']} °C',
|
// PENYARING SENSOR 0: Jika 0, ubah jadi strip '-'
|
||||||
'humidity': '${item['kelembapan']} %',
|
'temp': (item['suhu'] == 0 || item['suhu'] == null) ? '-' : '${item['suhu']} °C',
|
||||||
'light': '${item['intensitas']} Lux',
|
'humidity': (item['kelembapan'] == 0 || item['kelembapan'] == null) ? '-' : '${item['kelembapan']} %',
|
||||||
|
'light': (item['intensitas'] == 0 || item['intensitas'] == null) ? '-' : '${item['intensitas']} Lux',
|
||||||
'location': 'Gudang Pengering',
|
'location': 'Gudang Pengering',
|
||||||
'intake': item['kipas1'] ?? 'OFF',
|
'intake': item['kipas1'] ?? 'OFF',
|
||||||
'exhaust': item['kipas2'] ?? 'OFF',
|
'exhaust': item['kipas2'] ?? 'OFF',
|
||||||
|
|
@ -306,71 +276,61 @@ class _RecordingScreenState extends State<RecordingScreen> {
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
// Foto 75% tinggi card
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(15)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(14)),
|
||||||
child: item['url_foto'] != null
|
child: item['url_foto'] != null
|
||||||
? Image.network(
|
? Image.network(
|
||||||
item['url_foto'],
|
item['url_foto'],
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (_, __, ___) => const Center(
|
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) {
|
loadingBuilder: (_, child, progress) {
|
||||||
if (progress == null) return child;
|
if (progress == null) return child;
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator(color: Colors.green, strokeWidth: 2),
|
child: CircularProgressIndicator(color: AppColors.primary, strokeWidth: 2),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: const Center(
|
: const Center(
|
||||||
child: Icon(Icons.image, color: Colors.grey, size: 40),
|
child: Icon(Icons.image, color: AppColors.textSecondary, size: 40),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Caption bawah foto
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFF1A1A1A),
|
color: Colors.transparent,
|
||||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(15)),
|
borderRadius: BorderRadius.vertical(bottom: Radius.circular(15)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
// Badge nomor foto
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.green.withOpacity(0.2),
|
color: AppColors.primary.withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(6),
|
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(
|
child: Text('#$nomorFoto',
|
||||||
'#$nomorFoto',
|
style: const TextStyle(color: AppColors.primary, fontSize: 10, fontWeight: FontWeight.bold),
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.green, fontSize: 10, fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Tanggal dan jam
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(tanggal,
|
Text(tanggal, style: const TextStyle(color: AppColors.textSecondary, fontSize: 8)),
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 8)),
|
Text(jamSingkat, style: const TextStyle(color: AppColors.textMain, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||||
Text(jamSingkat,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class RegisterScreen extends StatefulWidget {
|
class RegisterScreen extends StatefulWidget {
|
||||||
const RegisterScreen({super.key});
|
const RegisterScreen({super.key});
|
||||||
|
|
@ -89,7 +90,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isError ? Icons.error_outline : Icons.check_circle_outline,
|
isError ? Icons.error_outline : Icons.check_circle_outline,
|
||||||
color: Colors.white,
|
color: Colors.white, // Tetap putih agar kontras dengan background
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -101,8 +102,9 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// --- BERUBAH: Menggunakan warna notifikasi dari AppColors ---
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
isError ? Colors.red.shade700 : Colors.green.shade700,
|
isError ? AppColors.error : AppColors.success,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|
@ -116,12 +118,14 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background Scaffold Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
leading: IconButton(
|
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),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -134,75 +138,91 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
const Text(
|
const Text(
|
||||||
'Buat Akun Baru',
|
'Buat Akun Baru',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.green,
|
// --- BERUBAH: Warna Judul ---
|
||||||
|
color: AppColors.primary,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Text(
|
const Text(
|
||||||
'Lengkapi data diri Anda untuk memulai',
|
'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),
|
const SizedBox(height: 50),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Form ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(25),
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
// --- BERUBAH: Garis Pinggir Form ---
|
||||||
border:
|
border:
|
||||||
Border.all(color: Colors.grey.withOpacity(0.3)),
|
Border.all(color: AppColors.textSecondary.withValues(alpha: 0.3)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Nama Lengkap
|
// Nama Lengkap
|
||||||
TextField(
|
TextField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
textCapitalization: TextCapitalization.words,
|
textCapitalization: TextCapitalization.words,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
// --- BERUBAH: Warna Ikon ---
|
||||||
prefixIcon:
|
prefixIcon:
|
||||||
Icon(Icons.person, color: Colors.green),
|
Icon(Icons.person, color: AppColors.primary),
|
||||||
hintText: 'Nama Lengkap',
|
hintText: 'Nama Lengkap',
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: TextStyle(color: AppColors.textSecondary),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(color: Colors.grey),
|
// --- BERUBAH: Garis Pemisah (Divider) ---
|
||||||
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)),
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
TextField(
|
TextField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
// --- BERUBAH: Warna Ikon ---
|
||||||
prefixIcon:
|
prefixIcon:
|
||||||
Icon(Icons.email, color: Colors.green),
|
Icon(Icons.email, color: AppColors.primary),
|
||||||
hintText: 'Email',
|
hintText: 'Email',
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: TextStyle(color: AppColors.textSecondary),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(color: Colors.grey),
|
// --- BERUBAH: Garis Pemisah (Divider) ---
|
||||||
|
Divider(color: AppColors.textSecondary.withValues(alpha: 0.3)),
|
||||||
|
|
||||||
// Password
|
// Password
|
||||||
TextField(
|
TextField(
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
obscureText: _obscurePassword,
|
obscureText: _obscurePassword,
|
||||||
style: const TextStyle(color: Colors.white),
|
// --- BERUBAH: Warna Teks Inputan ---
|
||||||
|
style: const TextStyle(color: AppColors.textMain),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
// --- BERUBAH: Warna Ikon Kunci ---
|
||||||
prefixIcon:
|
prefixIcon:
|
||||||
const Icon(Icons.lock, color: Colors.green),
|
const Icon(Icons.lock, color: AppColors.primary),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_obscurePassword
|
_obscurePassword
|
||||||
? Icons.visibility
|
? Icons.visibility
|
||||||
: Icons.visibility_off,
|
: Icons.visibility_off,
|
||||||
color: Colors.grey,
|
// --- BERUBAH: Warna Ikon Mata ---
|
||||||
|
color: AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
onPressed: () => setState(
|
onPressed: () => setState(
|
||||||
() => _obscurePassword = !_obscurePassword),
|
() => _obscurePassword = !_obscurePassword),
|
||||||
),
|
),
|
||||||
hintText: 'Password (min. 6 karakter)',
|
hintText: 'Password (min. 6 karakter)',
|
||||||
hintStyle: const TextStyle(color: Colors.grey),
|
// --- BERUBAH: Warna Placeholder/Hint ---
|
||||||
|
hintStyle: const TextStyle(color: AppColors.textSecondary),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -215,9 +235,11 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _isLoading ? null : _handleRegister,
|
onPressed: _isLoading ? null : _handleRegister,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Tombol ---
|
||||||
|
backgroundColor: AppColors.primary,
|
||||||
|
// --- BERUBAH: Warna Tombol saat Nonaktif (Loading) ---
|
||||||
disabledBackgroundColor:
|
disabledBackgroundColor:
|
||||||
Colors.green.withOpacity(0.3),
|
AppColors.primary.withValues(alpha: 0.3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
|
|
@ -243,7 +265,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Sudah punya akun? Masuk',
|
'Sudah punya akun? Masuk',
|
||||||
style: TextStyle(color: Colors.green),
|
// --- BERUBAH: Warna Teks Bawah ---
|
||||||
|
style: TextStyle(color: AppColors.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.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:open_file/open_file.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import '../services/supabase_service.dart';
|
import '../services/supabase_service.dart';
|
||||||
import '../services/mqtt_service.dart';
|
import '../services/mqtt_service.dart';
|
||||||
import 'package:mqtt_client/mqtt_client.dart';
|
import 'package:mqtt_client/mqtt_client.dart';
|
||||||
|
import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN
|
||||||
|
|
||||||
class SettingsScreen extends StatefulWidget {
|
class SettingsScreen extends StatefulWidget {
|
||||||
const SettingsScreen({super.key});
|
const SettingsScreen({super.key});
|
||||||
|
|
@ -40,7 +41,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Export foto_dataset ke Excel ──
|
// ── Export foto_dataset ke Excel (Warna Excel Tetap Dipertahankan) ──
|
||||||
Future<void> _exportExcel() async {
|
Future<void> _exportExcel() async {
|
||||||
setState(() => _isExporting = true);
|
setState(() => _isExporting = true);
|
||||||
|
|
||||||
|
|
@ -51,9 +52,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
if (data.isEmpty) {
|
if (data.isEmpty) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Belum ada data dataset untuk diexport!'),
|
content: const Text('Belum ada data dataset untuk diexport!'),
|
||||||
backgroundColor: Colors.orange,
|
// --- BERUBAH: Warna Notifikasi Peringatan ---
|
||||||
|
backgroundColor: AppColors.warning,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -71,12 +73,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
'Suhu (°C)',
|
'Suhu (°C)',
|
||||||
'Kelembapan (%)',
|
'Kelembapan (%)',
|
||||||
'Intensitas Cahaya (Lux)',
|
'Intensitas Cahaya (Lux)',
|
||||||
'Kipas 1 (Exhaust)',
|
'Kipas 1 (Intake)',
|
||||||
'Kipas 2 (Intake)',
|
'Kipas 2 (Exhaust)',
|
||||||
'URL Foto',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Style header
|
// Style header (Dibiarkan tetap hijau standar Excel)
|
||||||
for (int i = 0; i < headers.length; i++) {
|
for (int i = 0; i < headers.length; i++) {
|
||||||
final cell = sheet.cell(
|
final cell = sheet.cell(
|
||||||
CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0));
|
CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0));
|
||||||
|
|
@ -88,56 +89,34 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Isi data
|
// Isi data — tanpa URL Foto
|
||||||
for (int i = 0; i < data.length; i++) {
|
for (int i = 0; i < data.length; i++) {
|
||||||
final row = data[i];
|
final row = data[i];
|
||||||
final rowIndex = i + 1;
|
final rowIndex = i + 1;
|
||||||
|
|
||||||
sheet
|
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: rowIndex))
|
||||||
.cell(CellIndex.indexByColumnRow(
|
|
||||||
columnIndex: 0, rowIndex: rowIndex))
|
|
||||||
.value = IntCellValue(i + 1);
|
.value = IntCellValue(i + 1);
|
||||||
|
|
||||||
sheet
|
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: rowIndex))
|
||||||
.cell(CellIndex.indexByColumnRow(
|
|
||||||
columnIndex: 1, rowIndex: rowIndex))
|
|
||||||
.value = TextCellValue(row['timestamp']?.toString() ?? '-');
|
.value = TextCellValue(row['timestamp']?.toString() ?? '-');
|
||||||
|
|
||||||
sheet
|
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: rowIndex))
|
||||||
.cell(CellIndex.indexByColumnRow(
|
.value = DoubleCellValue(double.tryParse(row['suhu'].toString()) ?? 0);
|
||||||
columnIndex: 2, rowIndex: rowIndex))
|
|
||||||
.value = DoubleCellValue(
|
|
||||||
double.tryParse(row['suhu'].toString()) ?? 0);
|
|
||||||
|
|
||||||
sheet
|
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: rowIndex))
|
||||||
.cell(CellIndex.indexByColumnRow(
|
.value = DoubleCellValue(double.tryParse(row['kelembapan'].toString()) ?? 0);
|
||||||
columnIndex: 3, rowIndex: rowIndex))
|
|
||||||
.value = DoubleCellValue(
|
|
||||||
double.tryParse(row['kelembapan'].toString()) ?? 0);
|
|
||||||
|
|
||||||
sheet
|
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 4, rowIndex: rowIndex))
|
||||||
.cell(CellIndex.indexByColumnRow(
|
.value = DoubleCellValue(double.tryParse(row['intensitas'].toString()) ?? 0);
|
||||||
columnIndex: 4, rowIndex: rowIndex))
|
|
||||||
.value = DoubleCellValue(
|
|
||||||
double.tryParse(row['intensitas'].toString()) ?? 0);
|
|
||||||
|
|
||||||
sheet
|
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 5, rowIndex: rowIndex))
|
||||||
.cell(CellIndex.indexByColumnRow(
|
|
||||||
columnIndex: 5, rowIndex: rowIndex))
|
|
||||||
.value = TextCellValue(row['kipas1']?.toString() ?? '-');
|
.value = TextCellValue(row['kipas1']?.toString() ?? '-');
|
||||||
|
|
||||||
sheet
|
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 6, rowIndex: rowIndex))
|
||||||
.cell(CellIndex.indexByColumnRow(
|
|
||||||
columnIndex: 6, rowIndex: rowIndex))
|
|
||||||
.value = TextCellValue(row['kipas2']?.toString() ?? '-');
|
.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(0, 5);
|
||||||
sheet.setColumnWidth(1, 22);
|
sheet.setColumnWidth(1, 22);
|
||||||
sheet.setColumnWidth(2, 12);
|
sheet.setColumnWidth(2, 12);
|
||||||
|
|
@ -145,7 +124,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
sheet.setColumnWidth(4, 24);
|
sheet.setColumnWidth(4, 24);
|
||||||
sheet.setColumnWidth(5, 18);
|
sheet.setColumnWidth(5, 18);
|
||||||
sheet.setColumnWidth(6, 18);
|
sheet.setColumnWidth(6, 18);
|
||||||
sheet.setColumnWidth(7, 50);
|
|
||||||
|
|
||||||
// Simpan file
|
// Simpan file
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
|
@ -170,7 +148,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
'✓ Export berhasil — ${data.length} data tersimpan'),
|
'✓ Export berhasil — ${data.length} data tersimpan'),
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Notifikasi Sukses ---
|
||||||
|
backgroundColor: AppColors.success,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +158,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Gagal export: $e'),
|
content: Text('Gagal export: $e'),
|
||||||
backgroundColor: Colors.red,
|
// --- BERUBAH: Warna Notifikasi Error ---
|
||||||
|
backgroundColor: AppColors.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +174,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
final user = SupabaseService().currentUser;
|
final user = SupabaseService().currentUser;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
// --- BERUBAH: Background UI Utama ---
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
@ -206,14 +187,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Background Kartu Profil ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
// --- BERUBAH: Garis pinggir halus ---
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const CircleAvatar(
|
const CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
backgroundColor: Colors.green,
|
// --- BERUBAH: Warna Avatar ---
|
||||||
|
backgroundColor: AppColors.primary,
|
||||||
child: Icon(Icons.person,
|
child: Icon(Icons.person,
|
||||||
color: Colors.white, size: 40),
|
color: Colors.white, size: 40),
|
||||||
),
|
),
|
||||||
|
|
@ -226,8 +211,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 16,
|
height: 16,
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
color: Colors.green,
|
// --- BERUBAH: Warna Loading ---
|
||||||
backgroundColor: Colors.grey,
|
color: AppColors.primary,
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
|
|
@ -235,7 +221,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
? _namaLengkap
|
? _namaLengkap
|
||||||
: 'Admin Kopi',
|
: 'Admin Kopi',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
// --- BERUBAH: Warna Nama Profil ---
|
||||||
|
color: AppColors.textMain,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
|
@ -244,12 +231,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
Text(
|
Text(
|
||||||
user?.email ?? 'admin@coffee.io',
|
user?.email ?? 'admin@coffee.io',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.grey, fontSize: 12),
|
// --- BERUBAH: Warna Email Profil ---
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Text(
|
const Text(
|
||||||
'Status: Active User',
|
'Status: Active User',
|
||||||
style: TextStyle(
|
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<SettingsScreen> {
|
||||||
_buildSettingsCard([
|
_buildSettingsCard([
|
||||||
_buildConnectionStatus(
|
_buildConnectionStatus(
|
||||||
'Database Supabase', true, Icons.storage),
|
'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(
|
_buildConnectionStatus(
|
||||||
'MQTT Broker (EMQX)',
|
'MQTT Broker (EMQX)',
|
||||||
mqtt.client?.connectionStatus?.state ==
|
mqtt.client?.connectionStatus?.state ==
|
||||||
|
|
@ -284,12 +280,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
width: 22,
|
width: 22,
|
||||||
height: 22,
|
height: 22,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
color: Colors.green, strokeWidth: 2),
|
// --- BERUBAH: Warna Loading ---
|
||||||
|
color: AppColors.primary, strokeWidth: 2),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Menyiapkan file Excel...',
|
'Menyiapkan file Excel...',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey, fontSize: 14),
|
// --- BERUBAH: Warna Teks Loading ---
|
||||||
|
color: AppColors.textSecondary, fontSize: 14),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: _buildSettingItem(
|
: _buildSettingItem(
|
||||||
|
|
@ -303,14 +301,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
|
|
||||||
// ── Keamanan ──
|
// ── Keamanan ──
|
||||||
_buildSectionTitle('Keamanan'),
|
_buildSectionTitle('Keamanan'),
|
||||||
_buildSettingItem(
|
_buildSettingsCard([
|
||||||
Icons.lock_reset, 'Ganti Password Akun', () {
|
_buildSettingItem(
|
||||||
Navigator.pushNamed(
|
Icons.lock_reset, 'Ganti Password Akun', () {
|
||||||
context,
|
Navigator.pushNamed(
|
||||||
'/change_password',
|
context,
|
||||||
arguments: {'dari_reset': false},
|
'/change_password',
|
||||||
);
|
arguments: {'dari_reset': false},
|
||||||
}),
|
);
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
|
@ -319,25 +319,70 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 55,
|
height: 55,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
final konfirmasi = await showDialog<bool>(
|
||||||
|
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();
|
await SupabaseService().signOut();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pushReplacementNamed(context, '/login');
|
Navigator.pushReplacementNamed(context, '/login');
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.redAccent.withOpacity(0.8),
|
// --- BERUBAH: Warna Latar Tombol Keluar ---
|
||||||
|
backgroundColor: AppColors.error.withValues(alpha: 0.8),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
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(
|
label: const Text(
|
||||||
'Keluar dari Akun',
|
'Keluar dari Akun',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white),
|
color: Colors.white), // Tetap putih
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -356,7 +401,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.grey,
|
// --- BERUBAH: Warna Judul Seksi/Kategori ---
|
||||||
|
color: AppColors.textSecondary,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
|
|
@ -366,8 +412,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
Widget _buildSettingsCard(List<Widget> children) {
|
Widget _buildSettingsCard(List<Widget> children) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF1A1A1A),
|
// --- BERUBAH: Latar Belakang Kartu Pengaturan ---
|
||||||
|
color: AppColors.cardBg,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
// --- BERUBAH: Garis pinggir halus ---
|
||||||
|
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)),
|
||||||
),
|
),
|
||||||
child: Column(children: children),
|
child: Column(children: children),
|
||||||
);
|
);
|
||||||
|
|
@ -376,13 +425,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
Widget _buildSettingItem(IconData icon, String title, VoidCallback? onTap,
|
Widget _buildSettingItem(IconData icon, String title, VoidCallback? onTap,
|
||||||
{String? trailing}) {
|
{String? trailing}) {
|
||||||
return ListTile(
|
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,
|
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
|
trailing: trailing != null
|
||||||
? Text(trailing,
|
? Text(trailing,
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 12))
|
// --- BERUBAH: Warna Teks Trailing ---
|
||||||
: const Icon(Icons.chevron_right, color: Colors.grey, size: 18),
|
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,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -390,10 +443,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
Widget _buildConnectionStatus(
|
Widget _buildConnectionStatus(
|
||||||
String title, bool isConnected, IconData icon) {
|
String title, bool isConnected, IconData icon) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
// --- BERUBAH: Warna Ikon Status Database/MQTT ---
|
||||||
leading: Icon(icon,
|
leading: Icon(icon,
|
||||||
color: isConnected ? Colors.blue : Colors.red, size: 22),
|
color: isConnected ? AppColors.info : AppColors.error, size: 22),
|
||||||
title: Text(title,
|
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(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -402,14 +457,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
height: 8,
|
height: 8,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: isConnected ? Colors.green : Colors.red,
|
// --- BERUBAH: Warna Titik Indikator ---
|
||||||
|
color: isConnected ? AppColors.success : AppColors.error,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
isConnected ? 'Online' : 'Offline',
|
isConnected ? 'Online' : 'Offline',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isConnected ? Colors.green : Colors.red,
|
// --- BERUBAH: Warna Teks Online/Offline ---
|
||||||
|
color: isConnected ? AppColors.success : AppColors.error,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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<SplashScreen> createState() => _SplashScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SplashScreenState extends State<SplashScreen>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
// Controllers
|
||||||
|
late AnimationController _mainController;
|
||||||
|
late AnimationController _slideController;
|
||||||
|
late AnimationController _textController;
|
||||||
|
late AnimationController _revealController;
|
||||||
|
|
||||||
|
// Animations
|
||||||
|
late Animation<double> _logoY;
|
||||||
|
late Animation<double> _shadowScale;
|
||||||
|
late Animation<double> _shadowOpacity;
|
||||||
|
late Animation<double> _logoSlideX;
|
||||||
|
late Animation<double> _textOpacity;
|
||||||
|
late Animation<double> _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<double>([
|
||||||
|
TweenSequenceItem(tween: ConstantTween<double>(120.0), weight: 25),
|
||||||
|
TweenSequenceItem(
|
||||||
|
tween: Tween(begin: 120.0, end: -180.0).chain(CurveTween(curve: Curves.easeOut)),
|
||||||
|
weight: 14,
|
||||||
|
),
|
||||||
|
TweenSequenceItem(
|
||||||
|
tween: TweenSequence<double>([
|
||||||
|
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<double>(0.0), weight: 7),
|
||||||
|
]).animate(_mainController);
|
||||||
|
|
||||||
|
// ── KOREOGRAFI UKURAN BAYANGAN ──
|
||||||
|
_shadowScale = TweenSequence<double>([
|
||||||
|
TweenSequenceItem(tween: ConstantTween<double>(2.5), weight: 25),
|
||||||
|
TweenSequenceItem(tween: Tween(begin: 2.5, end: 0.1).chain(CurveTween(curve: Curves.easeOut)), weight: 14),
|
||||||
|
TweenSequenceItem(tween: ConstantTween<double>(0.1), weight: 61),
|
||||||
|
]).animate(_mainController);
|
||||||
|
|
||||||
|
// ── KOREOGRAFI TRANSPARANSI BAYANGAN ──
|
||||||
|
_shadowOpacity = TweenSequence<double>([
|
||||||
|
TweenSequenceItem(tween: ConstantTween<double>(0.6), weight: 25),
|
||||||
|
TweenSequenceItem(tween: Tween(begin: 0.6, end: 0.0).chain(CurveTween(curve: Curves.easeOut)), weight: 14),
|
||||||
|
TweenSequenceItem(tween: ConstantTween<double>(0.0), weight: 61),
|
||||||
|
]).animate(_mainController);
|
||||||
|
|
||||||
|
// ── SLIDE LOGO KE KIRI ──
|
||||||
|
_slideController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
);
|
||||||
|
_logoSlideX = Tween<double>(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<double>(begin: 0.0, end: 1.0).animate(_textController);
|
||||||
|
|
||||||
|
// ── KONTROL TRANSISI MENGECIL ──
|
||||||
|
_revealController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 750),
|
||||||
|
);
|
||||||
|
_revealProgress = Tween<double>(begin: 1.0, end: 0.0).animate(
|
||||||
|
CurvedAnimation(parent: _revealController, curve: Curves.easeInOutQuad),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FUNGSI ANIMASI YANG SUDAH DIUPDATE DENGAN PENGECEKAN LOGIN ---
|
||||||
|
Future<void> _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<Path> {
|
||||||
|
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<Path> oldClipper) => true;
|
||||||
|
}
|
||||||
|
|
@ -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<void> _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<void> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
pubspec.lock
|
|
@ -1,6 +1,14 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
app_links:
|
app_links:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -89,6 +97,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
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:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -137,6 +161,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -230,6 +262,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|
@ -238,6 +278,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
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:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -264,6 +312,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
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:
|
gotrue:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -288,6 +344,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
|
html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.6"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -336,6 +400,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
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:
|
jwt_decode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -853,6 +925,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
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:
|
url_launcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
26
pubspec.yaml
|
|
@ -43,6 +43,7 @@ dependencies:
|
||||||
excel: ^4.0.3
|
excel: ^4.0.3
|
||||||
open_file: ^3.3.2
|
open_file: ^3.3.2
|
||||||
path_provider: ^2.1.2
|
path_provider: ^2.1.2
|
||||||
|
google_fonts: ^6.2.1
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
win32: ^6.1.0
|
win32: ^6.1.0
|
||||||
|
|
@ -51,27 +52,38 @@ dependency_overrides:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
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
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# 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
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^6.0.0
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
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
|
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_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html><html><head>
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<!--
|
<!--
|
||||||
If you are serving your web app in a path other than the root, change the
|
If you are serving your web app in a path other than the root, change the
|
||||||
href value below to reflect the base path you are serving from.
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
@ -27,12 +25,90 @@
|
||||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
|
|
||||||
<title>coffee_iot_flutter</title>
|
<title>coffee_iot_flutter</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
|
|
||||||
|
|
||||||
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style id="splash-screen-style">
|
||||||
|
html {
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
background-color: #627254;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contain {
|
||||||
|
display:block;
|
||||||
|
width:100%; height:100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stretch {
|
||||||
|
display:block;
|
||||||
|
width:100%; height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
display:block;
|
||||||
|
width:100%; height:100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
-ms-transform: translate(-50%, 0);
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomLeft {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomRight {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script id="splash-screen-script">
|
||||||
|
function removeSplashFromWeb() {
|
||||||
|
document.getElementById("splash")?.remove();
|
||||||
|
document.getElementById("splash-branding")?.remove();
|
||||||
|
document.body.style.background = "transparent";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
You can customize the "flutter_bootstrap.js" script.
|
You can customize the "flutter_bootstrap.js" script.
|
||||||
This is useful to provide a custom configuration to the Flutter loader
|
This is useful to provide a custom configuration to the Flutter loader
|
||||||
|
|
@ -41,6 +117,7 @@
|
||||||
For more details:
|
For more details:
|
||||||
* https://docs.flutter.dev/platform-integration/web/initialization
|
* https://docs.flutter.dev/platform-integration/web/initialization
|
||||||
-->
|
-->
|
||||||
<script src="flutter_bootstrap.js" async></script>
|
<script src="flutter_bootstrap.js" async=""></script>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
</body></html>
|
||||||