add some features
|
|
@ -5,7 +5,7 @@
|
|||
<application
|
||||
android:label="monitoring_jamur"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -431,7 +431,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
|
|
@ -488,7 +488,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 906 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 419 KiB |
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class UserSession {
|
||||
static const String _keyUsername = 'username';
|
||||
static SharedPreferences? _prefs;
|
||||
static String? username;
|
||||
|
||||
/// Initialize Preferences and load existing session
|
||||
static Future<void> init() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
username = _prefs?.getString(_keyUsername);
|
||||
}
|
||||
|
||||
/// Save username to persistent storage
|
||||
static Future<void> saveSession(String user) async {
|
||||
username = user;
|
||||
await _prefs?.setString(_keyUsername, user);
|
||||
}
|
||||
|
||||
/// Clear session from persistent storage
|
||||
static Future<void> logout() async {
|
||||
username = null;
|
||||
await _prefs?.remove(_keyUsername);
|
||||
}
|
||||
|
||||
static bool get isLoggedIn => username != null;
|
||||
}
|
||||
|
|
@ -1,16 +1,180 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
||||
import 'package:monitoring_jamur/core/session/user_session.dart';
|
||||
import 'package:monitoring_jamur/features/auth/presentation/pages/login_page.dart';
|
||||
|
||||
class AccountPage extends StatelessWidget {
|
||||
const AccountPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
'Account Content Coming Soon',
|
||||
style: TextStyle(color: AppTheme.textLight),
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.backgroundBeige,
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Manajemen Akun',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.textDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// Main Consolidated Frame
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceWhite,
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(10),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Cool User Icon
|
||||
_buildCoolUserIcon(),
|
||||
const SizedBox(height: 24),
|
||||
// Username Info
|
||||
Text(
|
||||
UserSession.username ?? 'Guest',
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.textDark,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Account Owner',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppTheme.textLight,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Divider(height: 32, thickness: 1.0, color: AppTheme.backgroundBeige),
|
||||
const SizedBox(height: 8),
|
||||
// Menu Items
|
||||
_buildMenuTile(
|
||||
icon: Icons.settings_rounded,
|
||||
title: 'Kelola Akun',
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Fitur Kelola Akun segera hadir')),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildMenuTile(
|
||||
icon: Icons.logout_rounded,
|
||||
title: 'Log out',
|
||||
isDestructive: true,
|
||||
onTap: () async {
|
||||
await UserSession.logout();
|
||||
if (context.mounted) {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const LoginPage()),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoolUserIcon() {
|
||||
return Container(
|
||||
width: 110,
|
||||
height: 110,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
colors: [AppTheme.primaryGreen, Color(0xFF81C784)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppTheme.primaryGreen.withAlpha(40),
|
||||
blurRadius: 15,
|
||||
spreadRadius: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(4),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.manage_accounts_rounded,
|
||||
size: 56,
|
||||
color: AppTheme.primaryGreen,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMenuTile({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required VoidCallback onTap,
|
||||
bool isDestructive = false,
|
||||
}) {
|
||||
final color = isDestructive ? Colors.red.shade700 : AppTheme.primaryGreen;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isDestructive ? Colors.red.withAlpha(10) : AppTheme.backgroundBeige.withAlpha(50),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: isDestructive ? Colors.red.withAlpha(20) : AppTheme.primaryGreen.withAlpha(20),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, color: color, size: 22),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isDestructive ? color : AppTheme.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(Icons.chevron_right_rounded, color: AppTheme.textLight.withAlpha(100)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import '../../data/user_repository.dart';
|
|||
import 'register_page.dart';
|
||||
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
||||
import 'package:monitoring_jamur/features/home/presentation/pages/main_screen.dart';
|
||||
import 'package:monitoring_jamur/core/session/user_session.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
|
@ -33,10 +34,11 @@ class _LoginPageState extends State<LoginPage> {
|
|||
setState(() => _isLoading = false);
|
||||
|
||||
if (user != null) {
|
||||
await UserSession.saveSession(_usernameController.text);
|
||||
if (mounted) {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => MainScreen()),
|
||||
MaterialPageRoute(builder: (context) => const MainScreen()),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
|
|
@ -56,10 +58,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.nature_outlined,
|
||||
size: 80,
|
||||
color: AppTheme.primaryGreen,
|
||||
Image.asset(
|
||||
'lib/assets/mushroom.png',
|
||||
height: 120,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
|
|
|
|||
|
|
@ -6,11 +6,62 @@ class HistoryPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
'History Content Coming Soon',
|
||||
style: TextStyle(color: AppTheme.textLight),
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.backgroundBeige,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Histori Monitoring',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.textDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceWhite,
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(5),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.history_rounded,
|
||||
size: 64,
|
||||
color: AppTheme.backgroundBeige,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'tidak ada data',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: AppTheme.textLight,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
||||
import 'package:monitoring_jamur/features/home/presentation/pages/statistics_page.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class DashboardPage extends StatefulWidget {
|
||||
|
|
@ -12,6 +13,12 @@ class DashboardPage extends StatefulWidget {
|
|||
class _DashboardPageState extends State<DashboardPage> {
|
||||
// Demo humidity value
|
||||
double _humidity = 85.0;
|
||||
bool _isAutoMode = true;
|
||||
bool _isPumpManual = false;
|
||||
bool _isLightManual = false;
|
||||
|
||||
bool get _pumpStatus => _isAutoMode ? (_humidity < 80) : _isPumpManual;
|
||||
bool get _lightStatus => _isAutoMode ? (_humidity > 90) : _isLightManual;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -74,6 +81,16 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// Control Mode Selector
|
||||
_buildModeSelector(),
|
||||
const SizedBox(height: 24),
|
||||
// Device Controls
|
||||
_buildDeviceControls(),
|
||||
const SizedBox(height: 32),
|
||||
// Statistics Button
|
||||
_buildStatisticsButton(context),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -81,6 +98,188 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildModeSelector() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(5),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _isAutoMode = true),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: _isAutoMode ? AppTheme.primaryGreen : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Otomatis',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _isAutoMode ? Colors.white : AppTheme.textLight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _isAutoMode = false),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: !_isAutoMode ? AppTheme.primaryGreen : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Manual',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: !_isAutoMode ? Colors.white : AppTheme.textLight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeviceControls() {
|
||||
return Column(
|
||||
children: [
|
||||
_buildDeviceTile(
|
||||
title: 'Pompa Air',
|
||||
isOn: _pumpStatus,
|
||||
onChanged: _isAutoMode ? null : (val) => setState(() => _isPumpManual = val),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildDeviceTile(
|
||||
title: 'Lampu Pemanas',
|
||||
isOn: _lightStatus,
|
||||
onChanged: _isAutoMode ? null : (val) => setState(() => _isLightManual = val),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeviceTile({
|
||||
required String title,
|
||||
required bool isOn,
|
||||
required ValueChanged<bool>? onChanged,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(5),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isOn ? AppTheme.primaryGreen.withAlpha(30) : Colors.grey.withAlpha(20),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
title.contains('Pompa') ? Icons.water_drop_rounded : Icons.lightbulb_rounded,
|
||||
color: isOn ? AppTheme.primaryGreen : Colors.grey,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (onChanged == null)
|
||||
Text(
|
||||
isOn ? 'MENYALA' : 'MATI',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: isOn ? AppTheme.primaryGreen : AppTheme.textLight,
|
||||
),
|
||||
)
|
||||
else
|
||||
Switch(
|
||||
value: isOn,
|
||||
onChanged: onChanged,
|
||||
activeColor: AppTheme.primaryGreen,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatisticsButton(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const StatisticsPage()),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppTheme.primaryGreen.withAlpha(20),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.bar_chart_rounded, color: AppTheme.primaryGreen, size: 28),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
'Lihat Statistik',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.primaryGreen,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusCard() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
|
|
|
|||
|
|
@ -52,26 +52,52 @@ class _MainScreenState extends State<MainScreen> {
|
|||
|
||||
Widget _buildAnimatedBottomBar() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 24),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
margin: const EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceWhite,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
borderRadius: BorderRadius.circular(35),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
blurRadius: 25,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildNavItem(0, Icons.dashboard_outlined, Icons.dashboard, 'Dashboard'),
|
||||
_buildNavItem(1, Icons.history_outlined, Icons.history, 'History'),
|
||||
_buildNavItem(2, Icons.person_outline, Icons.person, 'Account'),
|
||||
],
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
double totalWidth = constraints.maxWidth;
|
||||
double itemWidth = totalWidth / 3;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// Animated Background Bubble
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
curve: Curves.elasticOut,
|
||||
left: _currentIndex * itemWidth + (itemWidth * 0.15),
|
||||
top: 12,
|
||||
child: Container(
|
||||
width: itemWidth * 0.7,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryGreen.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(23),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Navigation Items
|
||||
Row(
|
||||
children: [
|
||||
_buildNavItem(0, Icons.grid_view_outlined, Icons.grid_view_rounded, 'Home'),
|
||||
_buildNavItem(1, Icons.history_rounded, Icons.history_rounded, 'History'),
|
||||
_buildNavItem(2, Icons.person_outline_rounded, Icons.person_rounded, 'Account'),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -79,44 +105,38 @@ class _MainScreenState extends State<MainScreen> {
|
|||
Widget _buildNavItem(int index, IconData outlineIcon, IconData filledIcon, String label) {
|
||||
bool isSelected = _currentIndex == index;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => _onItemTapped(index),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutBack,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppTheme.primaryGreen.withOpacity(0.12) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => _onItemTapped(index),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedScale(
|
||||
scale: isSelected ? 1.15 : 1.0,
|
||||
scale: isSelected ? 1.0 : 0.9,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutBack,
|
||||
child: Icon(
|
||||
isSelected ? filledIcon : outlineIcon,
|
||||
color: isSelected ? AppTheme.primaryGreen : AppTheme.textLight,
|
||||
size: 26,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Icon(
|
||||
isSelected ? filledIcon : outlineIcon,
|
||||
color: isSelected ? AppTheme.primaryGreen : AppTheme.textLight.withOpacity(0.6),
|
||||
size: 26,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSelected) ...[
|
||||
const SizedBox(width: 8),
|
||||
AnimatedOpacity(
|
||||
opacity: isSelected ? 1.0 : 0.0,
|
||||
if (isSelected)
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primaryGreen,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: AppTheme.primaryGreen,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
||||
|
||||
class StatisticsPage extends StatelessWidget {
|
||||
const StatisticsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.backgroundBeige,
|
||||
appBar: AppBar(
|
||||
title: const Text('Statistik Monitoring'),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
foregroundColor: AppTheme.textDark,
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSummaryRow(),
|
||||
const SizedBox(height: 32),
|
||||
_buildChartSection('Tren Kelembapan', [85, 82, 88, 84, 86, 85, 87], AppTheme.primaryGreen),
|
||||
const SizedBox(height: 32),
|
||||
_buildChartSection('Tren Suhu', [24, 25, 24, 26, 25, 24, 25], Colors.orange),
|
||||
const SizedBox(height: 40),
|
||||
_buildAnalysisCard(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryRow() {
|
||||
return Row(
|
||||
children: [
|
||||
_buildStatCard('Rata-rata', '85%', Icons.water_drop_rounded, AppTheme.primaryGreen),
|
||||
const SizedBox(width: 16),
|
||||
_buildStatCard('Tertinggi', '89%', Icons.trending_up_rounded, Colors.blue),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard(String label, String value, IconData icon, Color color) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(5),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 28),
|
||||
const SizedBox(height: 12),
|
||||
Text(value, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
||||
Text(label, style: const TextStyle(fontSize: 12, color: AppTheme.textLight)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChartSection(String title, List<double> values, Color color) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppTheme.textDark),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
height: 180,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: values.map((v) {
|
||||
double height = (v / 100) * 120;
|
||||
if (title.contains('Suhu')) height = (v / 40) * 120; // Scale for temperature
|
||||
return Container(
|
||||
width: 24,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withAlpha(50),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: color, width: 2),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAnalysisCard() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryGreen.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: AppTheme.primaryGreen.withAlpha(50), width: 2),
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.auto_awesome_rounded, color: AppTheme.primaryGreen),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
'Analisis Sistem',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppTheme.primaryGreen),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
'Berdasarkan data 24 jam terakhir, kelembapan stabil di rentang ideal (80-90%). Pertumbuhan jamur terpantau optimal.',
|
||||
style: TextStyle(fontSize: 14, color: AppTheme.textDark, height: 1.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ import 'package:supabase_flutter/supabase_flutter.dart';
|
|||
import 'package:monitoring_jamur/core/constants/supabase_config.dart';
|
||||
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
||||
import 'package:monitoring_jamur/features/auth/presentation/pages/login_page.dart';
|
||||
import 'package:monitoring_jamur/features/home/presentation/pages/main_screen.dart';
|
||||
import 'package:monitoring_jamur/core/session/user_session.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -12,6 +14,8 @@ void main() async {
|
|||
anonKey: SupabaseConfig.anonKey,
|
||||
);
|
||||
|
||||
await UserSession.init();
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +28,7 @@ class MyApp extends StatelessWidget {
|
|||
title: 'Mushroom Monitor',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.lightTheme,
|
||||
home: const LoginPage(),
|
||||
home: UserSession.isLoggedIn ? const MainScreen() : const LoginPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
pubspec.lock
|
|
@ -33,6 +33,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.9"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -57,6 +73,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -142,6 +174,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
|
@ -224,6 +264,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.11.0"
|
||||
jwt_decode:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -376,6 +432,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -400,6 +464,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
postgrest:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -441,7 +513,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.28.0"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||
|
|
@ -693,6 +765,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ dependencies:
|
|||
cupertino_icons: ^1.0.8
|
||||
supabase_flutter: ^2.8.1
|
||||
google_fonts: ^6.2.1
|
||||
shared_preferences: ^2.5.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
@ -47,6 +48,7 @@ dev_dependencies:
|
|||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^6.0.0
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
@ -90,3 +92,9 @@ flutter:
|
|||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/to/font-from-package
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: "launcher_icon"
|
||||
ios: true
|
||||
image_path: "lib/assets/mushroom.png"
|
||||
min_sdk_android: 21
|
||||
|
|
|
|||