add some features
This commit is contained in:
parent
9a68fd1492
commit
990f76e2b2
|
|
@ -0,0 +1,54 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
import 'package:monitoring_jamur/features/history/data/models/humidity_record.dart';
|
||||||
|
|
||||||
|
class HistoryRepository {
|
||||||
|
final SupabaseClient _supabase = Supabase.instance.client;
|
||||||
|
|
||||||
|
Future<List<HumidityRecord>> fetchHistory() async {
|
||||||
|
try {
|
||||||
|
final response = await _supabase
|
||||||
|
.from('humidity')
|
||||||
|
.select()
|
||||||
|
.order('id', ascending: false);
|
||||||
|
|
||||||
|
return (response as List)
|
||||||
|
.map((json) => HumidityRecord.fromJson(json))
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching history: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteHistoryRecord(int id) async {
|
||||||
|
try {
|
||||||
|
await _supabase.from('humidity').delete().eq('id', id);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deleting record: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> saveHumidityData({
|
||||||
|
required double humidity,
|
||||||
|
required String status,
|
||||||
|
required String lightStatus,
|
||||||
|
required String pumpStatus,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _supabase.from('humidity').insert({
|
||||||
|
'kelembapan': humidity,
|
||||||
|
'status': status,
|
||||||
|
'status_lampu': lightStatus,
|
||||||
|
'status_pompa': pumpStatus,
|
||||||
|
'tanggal_upload': DateFormat('HH:mm:ss').format(DateTime.now()),
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error saving humidity data: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class HumidityRecord {
|
||||||
|
final int id;
|
||||||
|
final double humidity;
|
||||||
|
final String status;
|
||||||
|
final DateTime uploadDate;
|
||||||
|
final String lightStatus;
|
||||||
|
final String pumpStatus;
|
||||||
|
|
||||||
|
HumidityRecord({
|
||||||
|
required this.id,
|
||||||
|
required this.humidity,
|
||||||
|
required this.status,
|
||||||
|
required this.uploadDate,
|
||||||
|
required this.lightStatus,
|
||||||
|
required this.pumpStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory HumidityRecord.fromJson(Map<String, dynamic> json) {
|
||||||
|
DateTime parsedDate;
|
||||||
|
final String rawDate = json['tanggal_upload'] as String;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try parsing as full ISO8601
|
||||||
|
parsedDate = DateTime.parse(rawDate).toLocal();
|
||||||
|
} catch (_) {
|
||||||
|
// Handle "HH:mm:ss" format from 'time' column
|
||||||
|
final now = DateTime.now();
|
||||||
|
final parts = rawDate.split(':');
|
||||||
|
parsedDate = DateTime(
|
||||||
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
int.parse(parts[0]),
|
||||||
|
int.parse(parts[1]),
|
||||||
|
parts.length > 2 ? int.parse(parts[2].split('.')[0]) : 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HumidityRecord(
|
||||||
|
id: json['id'] as int,
|
||||||
|
humidity: (json['kelembapan'] as num).toDouble(),
|
||||||
|
status: json['status'] as String,
|
||||||
|
uploadDate: parsedDate,
|
||||||
|
lightStatus: json['status_lampu'] as String,
|
||||||
|
pumpStatus: json['status_pompa'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get formattedDate => DateFormat('dd MMM yyyy, HH:mm').format(uploadDate);
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,37 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
||||||
|
import 'package:monitoring_jamur/features/history/data/history_repository.dart';
|
||||||
|
import 'package:monitoring_jamur/features/history/data/models/humidity_record.dart';
|
||||||
|
|
||||||
class HistoryPage extends StatelessWidget {
|
class HistoryPage extends StatefulWidget {
|
||||||
const HistoryPage({super.key});
|
const HistoryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HistoryPage> createState() => _HistoryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryPageState extends State<HistoryPage> {
|
||||||
|
final HistoryRepository _repository = HistoryRepository();
|
||||||
|
List<HumidityRecord> _history = [];
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadHistory() async {
|
||||||
|
setState(() => _isLoading = true);
|
||||||
|
final data = await _repository.fetchHistory();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_history = data;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -24,40 +52,23 @@ class HistoryPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: RefreshIndicator(
|
||||||
width: double.infinity,
|
color: AppTheme.primaryGreen,
|
||||||
decoration: BoxDecoration(
|
onRefresh: _loadHistory,
|
||||||
color: AppTheme.surfaceWhite,
|
child: _isLoading
|
||||||
borderRadius: BorderRadius.circular(32),
|
? const Center(
|
||||||
boxShadow: [
|
child: CircularProgressIndicator(
|
||||||
BoxShadow(
|
color: AppTheme.primaryGreen))
|
||||||
color: Colors.black.withAlpha(5),
|
: _history.isEmpty
|
||||||
blurRadius: 20,
|
? _buildEmptyState()
|
||||||
offset: const Offset(0, 10),
|
: ListView.separated(
|
||||||
),
|
padding: const EdgeInsets.only(bottom: 24),
|
||||||
],
|
itemCount: _history.length,
|
||||||
),
|
separatorBuilder: (context, index) =>
|
||||||
child: const Center(
|
const SizedBox(height: 16),
|
||||||
child: Column(
|
itemBuilder: (context, index) =>
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
_buildHistoryCard(_history[index]),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -66,4 +77,167 @@ class HistoryPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyState() {
|
||||||
|
return 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _confirmDelete(int id) async {
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
|
title: const Text('Hapus Data?'),
|
||||||
|
content:
|
||||||
|
const Text('Apakah Anda yakin ingin menghapus data monitoring ini?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child:
|
||||||
|
const Text('Batal', style: TextStyle(color: AppTheme.textLight)),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
child: const Text('Hapus',
|
||||||
|
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed == true) {
|
||||||
|
final success = await _repository.deleteHistoryRecord(id);
|
||||||
|
if (success && mounted) {
|
||||||
|
_loadHistory();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: const Text('Data berhasil dihapus'),
|
||||||
|
backgroundColor: AppTheme.primaryGreen,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
margin: const EdgeInsets.all(24),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHistoryCard(HumidityRecord record) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 12, 12, 24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.surfaceWhite,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withAlpha(5),
|
||||||
|
blurRadius: 15,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'RECORD DATA',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
color: AppTheme.backgroundBeige,
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
icon: const Icon(Icons.delete_outline_rounded,
|
||||||
|
color: Colors.red, size: 22),
|
||||||
|
onPressed: () => _confirmDelete(record.id),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildInfoRow('Kelembapan', '${record.humidity.toInt()}%'),
|
||||||
|
_buildInfoRow('Status', record.status),
|
||||||
|
_buildInfoRow('Status Pompa', record.pumpStatus,
|
||||||
|
valueColor: record.pumpStatus.contains('MENYALA')
|
||||||
|
? AppTheme.primaryGreen
|
||||||
|
: Colors.red),
|
||||||
|
_buildInfoRow('Status Lampu', record.lightStatus,
|
||||||
|
valueColor: record.lightStatus.contains('MENYALA')
|
||||||
|
? AppTheme.primaryGreen
|
||||||
|
: Colors.red),
|
||||||
|
const Divider(height: 24, color: AppTheme.backgroundBeige),
|
||||||
|
_buildInfoRow('Waktu', record.formattedDate, isSmall: true),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoRow(String label, String value,
|
||||||
|
{Color? valueColor, bool isSmall = false}) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'$label :',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: isSmall ? 13 : 15,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppTheme.textLight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: isSmall ? 13 : 15,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: valueColor ?? AppTheme.textDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
import 'package:monitoring_jamur/core/theme/app_theme.dart';
|
||||||
import 'package:monitoring_jamur/features/home/presentation/pages/statistics_page.dart';
|
import 'package:monitoring_jamur/features/home/presentation/pages/statistics_page.dart';
|
||||||
import 'package:monitoring_jamur/core/services/mqtt_service.dart';
|
import 'package:monitoring_jamur/core/services/mqtt_service.dart';
|
||||||
|
import 'package:monitoring_jamur/features/history/data/history_repository.dart';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
class DashboardPage extends StatefulWidget {
|
class DashboardPage extends StatefulWidget {
|
||||||
|
|
@ -22,6 +23,7 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
bool get _lightStatus => _isAutoMode ? (_humidity > 90) : _isLightManual;
|
bool get _lightStatus => _isAutoMode ? (_humidity > 90) : _isLightManual;
|
||||||
|
|
||||||
final MqttService _mqttService = MqttService();
|
final MqttService _mqttService = MqttService();
|
||||||
|
final HistoryRepository _historyRepository = HistoryRepository();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -132,7 +134,10 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
// Device Controls
|
// Device Controls
|
||||||
_buildDeviceControls(),
|
_buildDeviceControls(),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 24),
|
||||||
|
// Save to History Button
|
||||||
|
_buildSaveToHistoryButton(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
// Statistics Button
|
// Statistics Button
|
||||||
_buildStatisticsButton(context),
|
_buildStatisticsButton(context),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
|
|
@ -222,9 +227,15 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
return _buildDeviceTile(
|
return _buildDeviceTile(
|
||||||
title: 'Pompa Air',
|
title: 'Pompa Air',
|
||||||
isOn: isOn,
|
isOn: isOn,
|
||||||
onChanged: isAuto ? null : (val) {
|
onChanged: isAuto
|
||||||
_mqttService.publishControl('pump', val ? 'on' : 'off');
|
? null
|
||||||
},
|
: (val) {
|
||||||
|
_mqttService.publishControl(
|
||||||
|
'pump', val ? 'on' : 'off');
|
||||||
|
if (val) {
|
||||||
|
_saveManualAction('Pompa Air', true);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -235,9 +246,15 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
return _buildDeviceTile(
|
return _buildDeviceTile(
|
||||||
title: 'Lampu Pemanas',
|
title: 'Lampu Pemanas',
|
||||||
isOn: isOn,
|
isOn: isOn,
|
||||||
onChanged: isAuto ? null : (val) {
|
onChanged: isAuto
|
||||||
_mqttService.publishControl('light', val ? 'on' : 'off');
|
? null
|
||||||
},
|
: (val) {
|
||||||
|
_mqttService.publishControl(
|
||||||
|
'light', val ? 'on' : 'off');
|
||||||
|
if (val) {
|
||||||
|
_saveManualAction('Lampu Pemanas', true);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -349,6 +366,123 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getHumidityStatus(double humidity) {
|
||||||
|
if (humidity < 70) return 'Sangat Kering';
|
||||||
|
if (humidity < 80) return 'Kering - Lembab';
|
||||||
|
if (humidity <= 90) return 'Lembab (Ideal)';
|
||||||
|
return 'Sangat Lembab';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveToHistory() async {
|
||||||
|
final status = _getHumidityStatus(_humidity);
|
||||||
|
|
||||||
|
// Show loading
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => const Center(
|
||||||
|
child: CircularProgressIndicator(color: AppTheme.primaryGreen),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final mode = _isAutoMode ? 'OTOMATIS' : 'MANUAL';
|
||||||
|
final pumpText = _mqttService.isPumpOn.value ? 'MENYALA - $mode' : 'MATI';
|
||||||
|
final lightText = _mqttService.isLightOn.value ? 'MENYALA - $mode' : 'MATI';
|
||||||
|
|
||||||
|
final success = await _historyRepository.saveHumidityData(
|
||||||
|
humidity: _humidity,
|
||||||
|
status: status,
|
||||||
|
lightStatus: lightText,
|
||||||
|
pumpStatus: pumpText,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.pop(context); // Pop loading
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
success ? 'Data berhasil disimpan ke histori!' : 'Gagal menyimpan data'),
|
||||||
|
backgroundColor: success ? AppTheme.primaryGreen : Colors.red,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
margin: const EdgeInsets.all(24),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveManualAction(String device, bool isOn) async {
|
||||||
|
final status = _getHumidityStatus(_humidity);
|
||||||
|
|
||||||
|
// Prepare current statuses
|
||||||
|
String pumpStatusText = _mqttService.isPumpOn.value ? 'MENYALA - MANUAL' : 'MATI';
|
||||||
|
String lightStatusText = _mqttService.isLightOn.value ? 'MENYALA - MANUAL' : 'MATI';
|
||||||
|
|
||||||
|
// Override with the new action state
|
||||||
|
if (device.contains('Pompa')) {
|
||||||
|
pumpStatusText = 'MENYALA - MANUAL';
|
||||||
|
} else {
|
||||||
|
lightStatusText = 'MENYALA - MANUAL';
|
||||||
|
}
|
||||||
|
|
||||||
|
await _historyRepository.saveHumidityData(
|
||||||
|
humidity: _humidity,
|
||||||
|
status: status,
|
||||||
|
lightStatus: lightStatusText,
|
||||||
|
pumpStatus: pumpStatusText,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('$device dinyalakan (Data tersimpan!)'),
|
||||||
|
backgroundColor: AppTheme.primaryGreen,
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
margin: const EdgeInsets.all(24),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSaveToHistoryButton() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _saveToHistory,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 18),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryGreen,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppTheme.primaryGreen.withAlpha(40),
|
||||||
|
blurRadius: 15,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.save_rounded, color: Colors.white, size: 24),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Simpan ke Histori',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildStatusCard() {
|
Widget _buildStatusCard() {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
|
|
||||||
|
|
@ -280,6 +280,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.0"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.20.2"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ dependencies:
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
shared_preferences: ^2.5.5
|
shared_preferences: ^2.5.5
|
||||||
mqtt_client: ^10.11.11
|
mqtt_client: ^10.11.11
|
||||||
|
intl: ^0.20.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue