feat: revamp detail screen, add loading shimmer
This commit is contained in:
parent
cf8cd34697
commit
10880bddfd
|
|
@ -2,6 +2,7 @@ import 'package:agrilink_vocpro/features/auth/view/login_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/dashboard/view/dashboard_screen.dart';
|
import 'package:agrilink_vocpro/features/dashboard/view/dashboard_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/humidity/view/humidity_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/humidity/view/humidity_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/light/view/light_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/light/view/light_screen.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/pages/ph/view/ph_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/splash/view/splash_screen.dart';
|
import 'package:agrilink_vocpro/features/splash/view/splash_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
@ -17,6 +18,7 @@ class AppRoute {
|
||||||
static const String temperature = '/temperature';
|
static const String temperature = '/temperature';
|
||||||
static const String soil = '/soil';
|
static const String soil = '/soil';
|
||||||
static const String light = '/dashboard/light';
|
static const String light = '/dashboard/light';
|
||||||
|
static const String ph = '/dashboard/ph';
|
||||||
static const String water = '/water';
|
static const String water = '/water';
|
||||||
static const String acidity = '/acidity';
|
static const String acidity = '/acidity';
|
||||||
|
|
||||||
|
|
@ -53,7 +55,15 @@ class AppRoute {
|
||||||
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
return LightScreen(lightIntensity: value);
|
return LightScreen(lightIntensity: value);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'ph/:value',
|
||||||
|
builder: (context, state) {
|
||||||
|
final double value =
|
||||||
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
|
return PhScreen(phValue: value);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -27,19 +27,22 @@ class MQTTService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ResultState> publishMessage(String topic, String message) async {
|
Future<ResultState> publishMessage(String topic, String message) async {
|
||||||
final bool isConnected = await isMqttConnected();
|
|
||||||
if (isConnected) {
|
|
||||||
final builder = MqttClientPayloadBuilder();
|
final builder = MqttClientPayloadBuilder();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
builder.addString(message);
|
final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
||||||
client!.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
|
if (!isConnected) {
|
||||||
return ResultState.hasData;
|
print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
return ResultState.error;
|
return ResultState.error;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
print('MQTT: Published message to $topic: $message');
|
||||||
|
builder.addString(message);
|
||||||
|
client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
||||||
|
print('MQTT: Message published');
|
||||||
|
return ResultState.hasData;
|
||||||
|
} catch (e) {
|
||||||
|
print('MQTT: Error: $e');
|
||||||
return ResultState.error;
|
return ResultState.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,4 +71,55 @@ class MQTTService {
|
||||||
return false; //not connected
|
return false; //not connected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> subscribeToTopic(String topic) async {
|
||||||
|
bool isActive = false;
|
||||||
|
if (client != null &&
|
||||||
|
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||||
|
try {
|
||||||
|
print('MQTT: Subscribing to $topic');
|
||||||
|
client!.subscribe(topic, MqttQos.atMostOnce);
|
||||||
|
print('MQTT: Subscribed to $topic');
|
||||||
|
|
||||||
|
// Tambahkan log ini untuk memastikan bahwa listener dijalankan
|
||||||
|
if (client!.updates != null) {
|
||||||
|
print('MQTT: Listening for updates...');
|
||||||
|
} else {
|
||||||
|
print('MQTT: No updates stream available');
|
||||||
|
}
|
||||||
|
|
||||||
|
client!.updates!.listen(
|
||||||
|
(List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||||
|
print('MQTT: Message received!');
|
||||||
|
if (messages != null && messages.isNotEmpty) {
|
||||||
|
final MqttPublishMessage recMessage =
|
||||||
|
messages[0].payload as MqttPublishMessage;
|
||||||
|
final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||||
|
recMessage.payload.message);
|
||||||
|
print(
|
||||||
|
'MQTT: Message received on topic ${messages[0].topic}: $payload');
|
||||||
|
|
||||||
|
if (payload == 'ON') {
|
||||||
|
isActive = true;
|
||||||
|
// Update UI atau provider untuk menandakan relay ON
|
||||||
|
} else if (payload == 'OFF') {
|
||||||
|
isActive = false;
|
||||||
|
// Update UI atau provider untuk menandakan relay OFF
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('MQTT: No messages received');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return isActive;
|
||||||
|
} catch (e) {
|
||||||
|
print('MQTT: Error subscribing to $topic: $e');
|
||||||
|
return isActive;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('MQTT: Not connected, cannot subscribe.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,59 +3,57 @@ import 'package:agrilink_vocpro/domain/service/mqtt_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ControlProvider extends ChangeNotifier {
|
class ControlProvider extends ChangeNotifier {
|
||||||
bool _control_1 = false;
|
final MQTTService _mqttService = MQTTService();
|
||||||
bool _control_2 = false;
|
|
||||||
bool _control_3 = false;
|
bool _control_1 = false;
|
||||||
bool _control_4 = false;
|
|
||||||
bool _control_5 = false;
|
|
||||||
bool _control_6 = false;
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
bool get control_1 => _control_1;
|
bool get control_1 => _control_1;
|
||||||
bool get control_2 => _control_2;
|
|
||||||
bool get control_3 => _control_3;
|
|
||||||
bool get control_4 => _control_4;
|
|
||||||
bool get control_5 => _control_5;
|
|
||||||
bool get control_6 => _control_6;
|
|
||||||
|
|
||||||
ControlProvider() {
|
ControlProvider() {
|
||||||
connectMqtt();
|
connectMqtt();
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultState mqttState = ResultState.initial;
|
ResultState mqttState = ResultState.initial;
|
||||||
|
ResultState subscribeState = ResultState.initial;
|
||||||
|
|
||||||
// Koneksi MQTT
|
|
||||||
Future<void> connectMqtt() async {
|
Future<void> connectMqtt() async {
|
||||||
mqttState = ResultState.loading;
|
mqttState = ResultState.loading;
|
||||||
|
subscribeState = ResultState.loading;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await MQTTService().setupMqtt();
|
final result = await _mqttService.setupMqtt();
|
||||||
if (result == ResultState.hasData) {
|
if (result == ResultState.hasData) {
|
||||||
mqttState = result;
|
mqttState = result;
|
||||||
print('Connected to MQTT');
|
final result2 = await _mqttService.subscribeToTopic('relay1');
|
||||||
|
if (result2 == true) {
|
||||||
|
subscribeState = ResultState.hasData;
|
||||||
|
_control_1 = true;
|
||||||
|
} else {
|
||||||
|
subscribeState = ResultState.hasData;
|
||||||
|
_control_1 = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mqttState = ResultState.error;
|
mqttState = ResultState.error;
|
||||||
print('Failed to connect to MQTT');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
mqttState = ResultState.error;
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> disconnectMqtt() async {
|
Future<void> disconnectMqtt() async {
|
||||||
try {
|
try {
|
||||||
final result = await MQTTService().disconnectMqtt();
|
await _mqttService.disconnectMqtt();
|
||||||
if (result == ResultState.hasData) print('Disconnected from MQTT');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isMqttConnected() async {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
disconnectMqtt();
|
disconnectMqtt();
|
||||||
|
|
@ -67,28 +65,22 @@ class ControlProvider extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchControl2() {
|
Future<ResultState> publishMessage(String topic, String message) async {
|
||||||
_control_2 = !_control_2;
|
try {
|
||||||
notifyListeners();
|
final result = await _mqttService.publishMessage(topic, message);
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchControl3() {
|
Future<void> subscribeToTopic(String topic) async {
|
||||||
_control_3 = !_control_3;
|
try {
|
||||||
notifyListeners();
|
await _mqttService.subscribeToTopic(topic);
|
||||||
}
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
void switchControl4() {
|
rethrow;
|
||||||
_control_4 = !_control_4;
|
}
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void switchControl5() {
|
|
||||||
_control_5 = !_control_5;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void switchControl6() {
|
|
||||||
_control_6 = !_control_6;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
@ -21,15 +22,12 @@ class ControlScreen extends StatelessWidget {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Center(
|
||||||
height: 16.h,
|
|
||||||
child: Center(
|
|
||||||
child: provider.mqttState == ResultState.loading
|
child: provider.mqttState == ResultState.loading
|
||||||
? const CircularProgressIndicator()
|
? const CupertinoActivityIndicator()
|
||||||
: provider.mqttState == ResultState.error
|
: provider.mqttState == ResultState.hasData
|
||||||
? const Text('Failed to connect to MQTT')
|
? const Text('Terhubung ke Broker')
|
||||||
: const Text('Connected to MQTT'),
|
: const Text('Gagal terhubung ke Broker'),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -38,7 +36,30 @@ class ControlScreen extends StatelessWidget {
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: provider.control_1,
|
value: provider.control_1,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
|
provider.control_1 == false
|
||||||
|
? provider.publishMessage('relay1', 'ON')
|
||||||
|
: provider.publishMessage('relay1', 'OFF');
|
||||||
provider.switchControl1();
|
provider.switchControl1();
|
||||||
|
// showDialog(
|
||||||
|
// context: context,
|
||||||
|
// builder: (context) => AlertDialog(
|
||||||
|
// title: const Text('Konfirmasi'),
|
||||||
|
// content: const Text('Atur Relay 1?'),
|
||||||
|
// actions: [
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// provider.control_1 == false
|
||||||
|
// ? provider.publishMessage('relay1', 'ON')
|
||||||
|
// : provider.publishMessage('relay1', 'OFF');
|
||||||
|
// provider.switchControl1();
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// },
|
||||||
|
// child:
|
||||||
|
// Text(provider.control_1 == false ? 'ON' : 'OFF'),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/humidity/widgets/circle_chart.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class HumidityScreen extends StatelessWidget {
|
class HumidityScreen extends StatelessWidget {
|
||||||
|
|
@ -15,7 +15,7 @@ class HumidityScreen extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Humidity', style: AppTheme.labelLarge),
|
title: Text('Humidity', style: AppTheme.labelMedium),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
|
|
@ -40,12 +40,46 @@ class HumidityScreen extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
height: 320,
|
height: 280.h,
|
||||||
width: double.infinity,
|
child: Stack(
|
||||||
child: CircleChart(
|
fit: StackFit.expand,
|
||||||
percentage: 60.5,
|
children: [
|
||||||
icon: BootstrapIcons.droplet_half,
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(BootstrapIcons.droplet_half,
|
||||||
|
size: 32, color: Colors.blue),
|
||||||
|
Text('60 %', style: AppTheme.headline1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RotatedBox(
|
||||||
|
quarterTurns: 2,
|
||||||
|
child: AnimatedRadialGauge(
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
value: 60,
|
||||||
|
axis: GaugeAxis(
|
||||||
|
degrees: 360,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
pointer: null,
|
||||||
|
style: GaugeAxisStyle(
|
||||||
|
background: Colors.grey.shade100,
|
||||||
|
thickness: 50,
|
||||||
|
),
|
||||||
|
progressBar: GaugeBasicProgressBar(
|
||||||
|
gradient: GaugeAxisGradient(colors: [
|
||||||
|
Colors.blue.shade200,
|
||||||
|
Colors.blue,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
@ -86,37 +120,37 @@ class HumidityScreen extends StatelessWidget {
|
||||||
padding: EdgeInsets.only(left: 16.w),
|
padding: EdgeInsets.only(left: 16.w),
|
||||||
child: const Text('Deskripsi'),
|
child: const Text('Deskripsi'),
|
||||||
),
|
),
|
||||||
ListView.builder(
|
// ListView.builder(
|
||||||
shrinkWrap: true,
|
// shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: provider.humidtyRules.length,
|
// itemCount: provider.humidtyRules.length,
|
||||||
itemBuilder: (context, index) {
|
// itemBuilder: (context, index) {
|
||||||
final item = provider.humidtyRules[index];
|
// final item = provider.humidtyRules[index];
|
||||||
return Theme(
|
// return Theme(
|
||||||
data: Theme.of(context)
|
// data: Theme.of(context)
|
||||||
.copyWith(dividerColor: Colors.transparent),
|
// .copyWith(dividerColor: Colors.transparent),
|
||||||
child: ExpansionTile(
|
// child: ExpansionTile(
|
||||||
trailing: Text(
|
// trailing: Text(
|
||||||
item.censorText,
|
// item.censorText,
|
||||||
style: TextStyle(color: item.color),
|
// style: TextStyle(color: item.color),
|
||||||
),
|
// ),
|
||||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
// expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
childrenPadding: EdgeInsets.all(16.r),
|
// childrenPadding: EdgeInsets.all(16.r),
|
||||||
title: Text(
|
// title: Text(
|
||||||
'Kelembaban ${item.minPercentage}% - ${item.maxPercentage}%'),
|
// 'Kelembaban ${item.minPercentage}% - ${item.maxPercentage}%'),
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
item.description,
|
// item.description,
|
||||||
style: AppTheme.labelMedium,
|
// style: AppTheme.labelMedium,
|
||||||
),
|
// ),
|
||||||
SizedBox(height: 8.h),
|
// SizedBox(height: 8.h),
|
||||||
Text('Tindakan', style: AppTheme.labelSmall),
|
// Text('Tindakan', style: AppTheme.labelSmall),
|
||||||
SizedBox(height: 8.h),
|
// SizedBox(height: 8.h),
|
||||||
Text(item.action),
|
// Text(item.action),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
})
|
// })
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CircleChart extends StatefulWidget {
|
|
||||||
const CircleChart({
|
|
||||||
super.key,
|
|
||||||
required this.percentage,
|
|
||||||
required this.icon,
|
|
||||||
this.colorStart,
|
|
||||||
this.colorEnd,
|
|
||||||
});
|
|
||||||
|
|
||||||
final double percentage;
|
|
||||||
final IconData icon;
|
|
||||||
final Color? colorStart;
|
|
||||||
final Color? colorEnd;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<CircleChart> createState() => _CircleChartState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CircleChartState extends State<CircleChart> {
|
|
||||||
double currentPercentage = 0;
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
Future.delayed(Duration.zero, () async {
|
|
||||||
for (double i = 0; i <= widget.percentage; i++) {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 25), () {
|
|
||||||
setState(() {
|
|
||||||
currentPercentage = i;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
ShaderMask(
|
|
||||||
shaderCallback: (Rect bounds) {
|
|
||||||
return RadialGradient(
|
|
||||||
center: Alignment.center,
|
|
||||||
radius: 0.6,
|
|
||||||
colors: <Color>[
|
|
||||||
Colors.white,
|
|
||||||
_getAnimatedColor(currentPercentage)
|
|
||||||
],
|
|
||||||
tileMode: TileMode.mirror,
|
|
||||||
).createShader(bounds);
|
|
||||||
},
|
|
||||||
child: PieChart(
|
|
||||||
PieChartData(
|
|
||||||
sections: _createSections(currentPercentage),
|
|
||||||
centerSpaceRadius: MediaQuery.of(context).size.width * 0.25,
|
|
||||||
sectionsSpace: 0,
|
|
||||||
startDegreeOffset: 270,
|
|
||||||
borderData: FlBorderData(show: false),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TweenAnimationBuilder<double>(
|
|
||||||
tween: Tween<double>(begin: 0, end: currentPercentage),
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
builder: (context, value, child) {
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(widget.icon, size: 32, color: _getAnimatedColor(value)),
|
|
||||||
Text(
|
|
||||||
'${value.toStringAsFixed(0)}%', // Animated percentage text
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PieChartSectionData> _createSections(double percentage) {
|
|
||||||
return [
|
|
||||||
PieChartSectionData(
|
|
||||||
color: Colors.white,
|
|
||||||
value: percentage,
|
|
||||||
title: '',
|
|
||||||
radius: 50, // Size of the pie slice
|
|
||||||
titleStyle: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PieChartSectionData(
|
|
||||||
color: Colors.white24,
|
|
||||||
value: 100 - percentage,
|
|
||||||
title: '',
|
|
||||||
radius: 50,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getAnimatedColor(double percentage) {
|
|
||||||
return Color.lerp(
|
|
||||||
widget.colorStart ?? Colors.green,
|
|
||||||
widget.colorEnd ?? Colors.blue,
|
|
||||||
percentage / widget.percentage,
|
|
||||||
)!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,7 @@ class LightScreen extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Light', style: AppTheme.titleLarge),
|
title: Text('Light', style: AppTheme.labelMedium),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
|
|
|
||||||
178
agrilink_vocpro/lib/features/home/pages/ph/view/ph_screen.dart
Normal file
178
agrilink_vocpro/lib/features/home/pages/ph/view/ph_screen.dart
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/pages/ph/widget/ph_bar_pointer.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
class PhScreen extends StatelessWidget {
|
||||||
|
const PhScreen({super.key, required this.phValue});
|
||||||
|
|
||||||
|
final double phValue;
|
||||||
|
|
||||||
|
double get value => phValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('pH Tanah', style: AppTheme.labelMedium),
|
||||||
|
centerTitle: true,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
actions: const [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(right: 16),
|
||||||
|
child: Icon(
|
||||||
|
BootstrapIcons.thermometer_half,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: PhIndicator(phValue: value), // Set nilai pH di sini
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'pH',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 20.r,
|
||||||
|
color: Colors.blue,
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 100.h,
|
||||||
|
width: 100.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: Colors.blue.withOpacity(0.1),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.blue,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Low',
|
||||||
|
style: AppTheme.labelMedium
|
||||||
|
.copyWith(color: Colors.blue)),
|
||||||
|
// SizedBox(height: 8.h),
|
||||||
|
// const Icon(
|
||||||
|
// BootstrapIcons.thermometer_low,
|
||||||
|
// color: Colors.blue,
|
||||||
|
// ),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'<20°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 100.h,
|
||||||
|
width: 100.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.green.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.green,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Ideal',
|
||||||
|
style: AppTheme.labelMedium
|
||||||
|
.copyWith(color: Colors.green)),
|
||||||
|
// SizedBox(height: 8.h),
|
||||||
|
// const Icon(
|
||||||
|
// BootstrapIcons.thermometer_half,
|
||||||
|
// color: Colors.green,
|
||||||
|
// ),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'20-30°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 100.h,
|
||||||
|
width: 100.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.orange.shade800,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('high',
|
||||||
|
style: AppTheme.labelMedium
|
||||||
|
.copyWith(color: Colors.orange)),
|
||||||
|
// SizedBox(height: 8.h),
|
||||||
|
// const Icon(
|
||||||
|
// BootstrapIcons.thermometer_high,
|
||||||
|
// color: Colors.orange,
|
||||||
|
// ),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'>30°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
const Text('Grafik'),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 1.6.h,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
|
),
|
||||||
|
child: const GarphicWidget(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
class PhIndicator extends StatelessWidget {
|
||||||
|
final double phValue; // Nilai pH yang ditampilkan (misal 3.5 atau 6.7)
|
||||||
|
|
||||||
|
const PhIndicator({super.key, required this.phValue});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'pH : $phValue',
|
||||||
|
style: AppTheme.headline1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
SizedBox(
|
||||||
|
height: 80.h,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
CustomPaint(
|
||||||
|
size: Size(300.w, 30.h), // Ukuran bar
|
||||||
|
painter: PhBarPainter(),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 48.h,
|
||||||
|
left: calculatePointerPosition(
|
||||||
|
phValue, 300), // Hitung posisi pointer
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_drop_up,
|
||||||
|
size: 40,
|
||||||
|
color: Colors.black, // Warna pointer
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('0'),
|
||||||
|
Text('7'),
|
||||||
|
Text('14'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk menghitung posisi pointer berdasarkan nilai pH
|
||||||
|
double calculatePointerPosition(double phValue, double barWidth) {
|
||||||
|
// Nilai pH umumnya antara 0 dan 14, sesuaikan dengan lebar bar
|
||||||
|
double normalizedPh = phValue / 14; // Normalisasi nilai pH (-7 hingga 7)
|
||||||
|
return normalizedPh * barWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhBarPainter extends CustomPainter {
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
// Buat gradasi dari biru ke merah
|
||||||
|
const gradient = LinearGradient(
|
||||||
|
colors: [Colors.blue, Colors.orange, Colors.red],
|
||||||
|
stops: [0.0, 0.5, 1.0], // Set stop untuk 0, 7, 14 (pH)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat rect untuk menggambar gradasi
|
||||||
|
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
|
||||||
|
final paint = Paint()..shader = gradient.createShader(rect);
|
||||||
|
|
||||||
|
// Gambar rect dengan gradasi
|
||||||
|
canvas.drawRect(rect, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/humidity/widgets/circle_chart.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||||
|
|
||||||
class SoilMoistureScreen extends StatelessWidget {
|
class SoilMoistureScreen extends StatelessWidget {
|
||||||
const SoilMoistureScreen({super.key});
|
const SoilMoistureScreen({super.key});
|
||||||
|
|
@ -33,14 +35,46 @@ class SoilMoistureScreen extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
height: 320,
|
height: 280.h,
|
||||||
width: double.infinity,
|
child: Stack(
|
||||||
child: CircleChart(
|
fit: StackFit.expand,
|
||||||
percentage: 60.5,
|
children: [
|
||||||
icon: Icons.water_outlined,
|
Center(
|
||||||
colorStart: Colors.lime,
|
child: Column(
|
||||||
colorEnd: Colors.brown,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(BootstrapIcons.water,
|
||||||
|
size: 32, color: Colors.blue),
|
||||||
|
Text('60 %', style: AppTheme.headline1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RotatedBox(
|
||||||
|
quarterTurns: 2,
|
||||||
|
child: AnimatedRadialGauge(
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
value: 60,
|
||||||
|
axis: GaugeAxis(
|
||||||
|
degrees: 360,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
pointer: null,
|
||||||
|
style: GaugeAxisStyle(
|
||||||
|
background: Colors.grey.shade100,
|
||||||
|
thickness: 50,
|
||||||
|
),
|
||||||
|
progressBar: GaugeBasicProgressBar(
|
||||||
|
gradient: GaugeAxisGradient(colors: [
|
||||||
|
Colors.blue.shade200,
|
||||||
|
Colors.blue,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class TemperatureScreen extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Temperature', style: AppTheme.titleLarge),
|
title: Text('Temperature', style: AppTheme.labelMedium),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,87 @@
|
||||||
import 'package:agrilink_vocpro/features/dashboard/model/censor_data_rule.dart';
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class HomeProvider extends ChangeNotifier {
|
class HomeProvider extends ChangeNotifier {
|
||||||
final DateTime currentDate = DateTime.now();
|
final DateTime currentDate = DateTime.now();
|
||||||
|
|
||||||
List<CensorDataRule> humidtyRules = [
|
HomeProvider() {
|
||||||
CensorDataRule(
|
getData();
|
||||||
minPercentage: 0,
|
|
||||||
maxPercentage: 30,
|
|
||||||
censorText: 'Very Low',
|
|
||||||
description:
|
|
||||||
'Udara sangat kering. Tanaman bisa mengalami stress akibat kekurangan air.',
|
|
||||||
action:
|
|
||||||
'Aktifkan sistem penyiraman atau humidifier untuk menaikkan kelembaban. Periksa juga apakah ada kebocoran pada sistem irigasi yang mengakibatkan kelembaban terlalu rendah.',
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
CensorDataRule(
|
|
||||||
minPercentage: 31,
|
|
||||||
maxPercentage: 50,
|
|
||||||
censorText: 'Low',
|
|
||||||
description:
|
|
||||||
'Kelembaban masih cukup rendah. Beberapa jenis tanaman mungkin sudah mulai terpengaruh.',
|
|
||||||
action:
|
|
||||||
'Pertimbangkan untuk menambah irigasi atau memperpanjang durasi penyiraman. Pantau tanaman secara berkala.',
|
|
||||||
color: Colors.orange,
|
|
||||||
),
|
|
||||||
CensorDataRule(
|
|
||||||
minPercentage: 51,
|
|
||||||
maxPercentage: 70,
|
|
||||||
censorText: 'Normal',
|
|
||||||
description:
|
|
||||||
'Ini adalah kelembaban yang ideal untuk sebagian besar tanaman dalam greenhouse.',
|
|
||||||
action:
|
|
||||||
'Pertahankan kondisi ini. Tidak ada tindakan yang diperlukan kecuali jika ada perubahan mendadak.',
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
CensorDataRule(
|
|
||||||
minPercentage: 71,
|
|
||||||
maxPercentage: 85,
|
|
||||||
censorText: 'High',
|
|
||||||
description:
|
|
||||||
'Udara mulai terlalu lembap. Kelembaban tinggi dapat meningkatkan risiko penyakit jamur atau bakteri.',
|
|
||||||
action:
|
|
||||||
'Aktifkan ventilasi atau kipas untuk mengurangi kelembaban. Pastikan aliran udara di greenhouse cukup baik.',
|
|
||||||
color: Colors.lime,
|
|
||||||
),
|
|
||||||
CensorDataRule(
|
|
||||||
minPercentage: 86,
|
|
||||||
maxPercentage: 100,
|
|
||||||
censorText: 'Very High',
|
|
||||||
description:
|
|
||||||
'Udara sangat lembap, yang bisa berisiko menyebabkan jamur, lumut, dan penyakit tanaman.',
|
|
||||||
action:
|
|
||||||
'Segera aktifkan sistem ventilasi maksimal, mungkin juga gunakan dehumidifier jika diperlukan. Kurangi frekuensi penyiraman atau periksa sistem irigasi agar tidak berlebihan.',
|
|
||||||
color: Colors.brown,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResultState dataState = ResultState.initial;
|
||||||
|
|
||||||
|
Future<void> getData() async {
|
||||||
|
dataState = ResultState.loading;
|
||||||
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
print('Fetching data...');
|
||||||
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
print('Data fetched');
|
||||||
|
dataState = ResultState.hasData;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
dataState = ResultState.error;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
dataState = ResultState.initial;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// List<CensorDataRule> humidtyRules = [
|
||||||
|
// CensorDataRule(
|
||||||
|
// minPercentage: 0,
|
||||||
|
// maxPercentage: 30,
|
||||||
|
// censorText: 'Very Low',
|
||||||
|
// description:
|
||||||
|
// 'Udara sangat kering. Tanaman bisa mengalami stress akibat kekurangan air.',
|
||||||
|
// action:
|
||||||
|
// 'Aktifkan sistem penyiraman atau humidifier untuk menaikkan kelembaban. Periksa juga apakah ada kebocoran pada sistem irigasi yang mengakibatkan kelembaban terlalu rendah.',
|
||||||
|
// color: Colors.red,
|
||||||
|
// ),
|
||||||
|
// CensorDataRule(
|
||||||
|
// minPercentage: 31,
|
||||||
|
// maxPercentage: 50,
|
||||||
|
// censorText: 'Low',
|
||||||
|
// description:
|
||||||
|
// 'Kelembaban masih cukup rendah. Beberapa jenis tanaman mungkin sudah mulai terpengaruh.',
|
||||||
|
// action:
|
||||||
|
// 'Pertimbangkan untuk menambah irigasi atau memperpanjang durasi penyiraman. Pantau tanaman secara berkala.',
|
||||||
|
// color: Colors.orange,
|
||||||
|
// ),
|
||||||
|
// CensorDataRule(
|
||||||
|
// minPercentage: 51,
|
||||||
|
// maxPercentage: 70,
|
||||||
|
// censorText: 'Normal',
|
||||||
|
// description:
|
||||||
|
// 'Ini adalah kelembaban yang ideal untuk sebagian besar tanaman dalam greenhouse.',
|
||||||
|
// action:
|
||||||
|
// 'Pertahankan kondisi ini. Tidak ada tindakan yang diperlukan kecuali jika ada perubahan mendadak.',
|
||||||
|
// color: Colors.green,
|
||||||
|
// ),
|
||||||
|
// CensorDataRule(
|
||||||
|
// minPercentage: 71,
|
||||||
|
// maxPercentage: 85,
|
||||||
|
// censorText: 'High',
|
||||||
|
// description:
|
||||||
|
// 'Udara mulai terlalu lembap. Kelembaban tinggi dapat meningkatkan risiko penyakit jamur atau bakteri.',
|
||||||
|
// action:
|
||||||
|
// 'Aktifkan ventilasi atau kipas untuk mengurangi kelembaban. Pastikan aliran udara di greenhouse cukup baik.',
|
||||||
|
// color: Colors.lime,
|
||||||
|
// ),
|
||||||
|
// CensorDataRule(
|
||||||
|
// minPercentage: 86,
|
||||||
|
// maxPercentage: 100,
|
||||||
|
// censorText: 'Very High',
|
||||||
|
// description:
|
||||||
|
// 'Udara sangat lembap, yang bisa berisiko menyebabkan jamur, lumut, dan penyakit tanaman.',
|
||||||
|
// action:
|
||||||
|
// 'Segera aktifkan sistem ventilasi maksimal, mungkin juga gunakan dehumidifier jika diperlukan. Kurangi frekuensi penyiraman atau periksa sistem irigasi agar tidak berlebihan.',
|
||||||
|
// color: Colors.brown,
|
||||||
|
// ),
|
||||||
|
// ];
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/extension/extention.dart';
|
import 'package:agrilink_vocpro/core/extension/extention.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_npk1.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_npk1.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_npk2.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_npk2.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_main_censor.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_dht.dart';
|
||||||
import 'package:animated_segmented_tab_control/animated_segmented_tab_control.dart';
|
import 'package:animated_segmented_tab_control/animated_segmented_tab_control.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
@ -23,7 +25,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
toolbarHeight: 200.h,
|
toolbarHeight: 200.h,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: AppColor.backgroundColor,
|
backgroundColor: Colors.white,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
flexibleSpace: Padding(
|
flexibleSpace: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
|
|
@ -55,6 +57,16 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.read<HomeProvider>().getData();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.refresh_rounded,
|
||||||
|
color: AppColor.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|
@ -130,7 +142,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
),
|
),
|
||||||
textStyle: AppTheme.labelSmall,
|
textStyle: AppTheme.labelSmall,
|
||||||
tabs: const [
|
tabs: const [
|
||||||
SegmentTab(label: 'Main Censor'),
|
SegmentTab(label: 'DHT'),
|
||||||
SegmentTab(label: 'NPK 1'),
|
SegmentTab(label: 'NPK 1'),
|
||||||
SegmentTab(label: 'NPK 2'),
|
SegmentTab(label: 'NPK 2'),
|
||||||
]),
|
]),
|
||||||
|
|
@ -138,7 +150,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 64.h),
|
padding: EdgeInsets.only(top: 64.h),
|
||||||
child: const TabBarView(children: [
|
child: const TabBarView(children: [
|
||||||
ListDataFromMainCensor(),
|
ListDataFromCensorDht(),
|
||||||
ListDataFromCensorNpk1(),
|
ListDataFromCensorNpk1(),
|
||||||
ListDataFromCensorNpk2(),
|
ListDataFromCensorNpk2(),
|
||||||
]),
|
]),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
|
class CensorItemLoadingWidgets extends StatelessWidget {
|
||||||
|
const CensorItemLoadingWidgets({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Shimmer.fromColors(
|
||||||
|
baseColor: Colors.grey.shade300,
|
||||||
|
highlightColor: Colors.grey.shade100,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||||
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/pages/temperature/view/temperature_screen.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/widgets/censor_item_loading_widgets.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart';
|
||||||
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class ListDataFromCensorDht extends StatelessWidget {
|
||||||
|
const ListDataFromCensorDht({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<HomeProvider>(builder: (context, provider, child) {
|
||||||
|
switch (provider.dataState) {
|
||||||
|
case ResultState.loading:
|
||||||
|
return GridView(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: 16.r,
|
||||||
|
mainAxisSpacing: 16.r,
|
||||||
|
childAspectRatio: 0.9,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < 4; i++) const CensorItemLoadingWidgets(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
case ResultState.hasData:
|
||||||
|
return GridView(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: 16.r,
|
||||||
|
mainAxisSpacing: 16.r,
|
||||||
|
childAspectRatio: 0.9,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
DataDisplayerWidget(
|
||||||
|
title: 'Humidity',
|
||||||
|
subtitle: 'kelembaban udara',
|
||||||
|
value: '60',
|
||||||
|
unit: '%',
|
||||||
|
icon: BootstrapIcons.droplet_half,
|
||||||
|
textColor: Colors.white,
|
||||||
|
color: AppColor.secondary,
|
||||||
|
iconColor: Colors.white,
|
||||||
|
censorIdentifier: 'NPK 1',
|
||||||
|
onTap: () async {
|
||||||
|
context.push(AppRoute.humidity, extra: '60');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DataDisplayerWidget(
|
||||||
|
title: 'Temperature',
|
||||||
|
subtitle: 'suhu greenhouse',
|
||||||
|
value: '28',
|
||||||
|
unit: '°C',
|
||||||
|
icon: BootstrapIcons.thermometer_half,
|
||||||
|
color: Colors.white,
|
||||||
|
onTap: () async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const TemperatureScreen()));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DataDisplayerWidget(
|
||||||
|
title: 'Light',
|
||||||
|
subtitle: 'intensitas cahaya',
|
||||||
|
value: '1000',
|
||||||
|
unit: 'lux',
|
||||||
|
icon: BootstrapIcons.sun,
|
||||||
|
color: Colors.white,
|
||||||
|
onTap: () async {
|
||||||
|
context.push('${AppRoute.light}/300');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case ResultState.noData:
|
||||||
|
return const Center(child: Text('No Data'));
|
||||||
|
case ResultState.error:
|
||||||
|
return const Center(child: Text('Error'));
|
||||||
|
case ResultState.initial:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/soil_moisture/view/soil_moisture_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/soil_moisture/view/soil_moisture_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/temperature/view/temperature_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/temperature/view/temperature_screen.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/widgets/censor_item_loading_widgets.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ListDataFromCensorNpk1 extends StatelessWidget {
|
class ListDataFromCensorNpk1 extends StatelessWidget {
|
||||||
const ListDataFromCensorNpk1({
|
const ListDataFromCensorNpk1({
|
||||||
|
|
@ -17,6 +21,23 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const String censorIdentifier = 'NPK 1';
|
const String censorIdentifier = 'NPK 1';
|
||||||
|
return Consumer<HomeProvider>(builder: (context, provider, child) {
|
||||||
|
switch (provider.dataState) {
|
||||||
|
case ResultState.loading:
|
||||||
|
return GridView(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
shrinkWrap: true,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: 16.r,
|
||||||
|
mainAxisSpacing: 16.r,
|
||||||
|
childAspectRatio: 0.9,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < 4; i++) const CensorItemLoadingWidgets(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case ResultState.hasData:
|
||||||
return GridView(
|
return GridView(
|
||||||
padding: EdgeInsets.all(16.r),
|
padding: EdgeInsets.all(16.r),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
|
@ -69,7 +90,9 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
||||||
icon: BootstrapIcons.pie_chart,
|
icon: BootstrapIcons.pie_chart,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
|
context.push('${AppRoute.ph}/6.5');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Conductivity',
|
title: 'Conductivity',
|
||||||
|
|
@ -112,5 +135,13 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
case ResultState.noData:
|
||||||
|
return const Center(child: Text('No Data'));
|
||||||
|
case ResultState.error:
|
||||||
|
return const Center(child: Text('Error'));
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/temperature/view/temperature_screen.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart';
|
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
class ListDataFromMainCensor extends StatelessWidget {
|
|
||||||
const ListDataFromMainCensor({
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GridView(
|
|
||||||
padding: EdgeInsets.all(16.r),
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
crossAxisSpacing: 16.r,
|
|
||||||
mainAxisSpacing: 16.r,
|
|
||||||
childAspectRatio: 0.9,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
DataDisplayerWidget(
|
|
||||||
title: 'Humidity',
|
|
||||||
subtitle: 'kelembaban udara',
|
|
||||||
value: '60',
|
|
||||||
unit: '%',
|
|
||||||
icon: BootstrapIcons.droplet_half,
|
|
||||||
textColor: Colors.white,
|
|
||||||
color: AppColor.secondary,
|
|
||||||
iconColor: Colors.white,
|
|
||||||
censorIdentifier: 'NPK 1',
|
|
||||||
onTap: () async {
|
|
||||||
context.push(AppRoute.humidity, extra: '60');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DataDisplayerWidget(
|
|
||||||
title: 'Temperature',
|
|
||||||
subtitle: 'suhu greenhouse',
|
|
||||||
value: '28',
|
|
||||||
unit: '°C',
|
|
||||||
icon: BootstrapIcons.thermometer_half,
|
|
||||||
color: Colors.white,
|
|
||||||
onTap: () async {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const TemperatureScreen()));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DataDisplayerWidget(
|
|
||||||
title: 'Light',
|
|
||||||
subtitle: 'intensitas cahaya',
|
|
||||||
value: '1000',
|
|
||||||
unit: 'lux',
|
|
||||||
icon: BootstrapIcons.sun,
|
|
||||||
color: Colors.white,
|
|
||||||
onTap: () async {
|
|
||||||
context.push('${AppRoute.light}/300');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,10 +2,12 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class SettingScreen extends StatelessWidget {
|
class SettingScreen extends StatelessWidget {
|
||||||
const SettingScreen({super.key});
|
const SettingScreen({super.key});
|
||||||
|
|
@ -96,7 +98,6 @@ class SettingScreen extends StatelessWidget {
|
||||||
size: 16.r,
|
size: 16.r,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (Platform.isAndroid) {
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
|
|
@ -112,35 +113,12 @@ class SettingScreen extends StatelessWidget {
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text('Ya'),
|
child: Text('Ya'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
context.go(AppRoute.root);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => CupertinoAlertDialog(
|
|
||||||
title: Text('Logout'),
|
|
||||||
content: Text('Apakah anda yakin ingin logout?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: Text('Batal'),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: Text('Ya'),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
|
import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.1"
|
||||||
|
shimmer:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shimmer
|
||||||
|
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ dependencies:
|
||||||
flutter_screenutil: ^5.9.3
|
flutter_screenutil: ^5.9.3
|
||||||
gauge_indicator: ^0.4.3
|
gauge_indicator: ^0.4.3
|
||||||
mqtt_client: ^10.5.1
|
mqtt_client: ^10.5.1
|
||||||
|
shimmer: ^3.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user