feat: revamp detail screen soil temp
This commit is contained in:
parent
e5b9cf0c4c
commit
426445a865
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.idea/
|
||||||
|
|
@ -2,7 +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/conductivity/view/conductivity_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/conductivity/view/conductivity_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/luminosity/view/light_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/nitrogen/view/nitrogen_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/nitrogen/view/nitrogen_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/ph/view/ph_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/ph/view/ph_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/phosphorus/view/phosphorus_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/phosphorus/view/phosphorus_screen.dart';
|
||||||
|
|
@ -136,11 +136,14 @@ class AppRoute {
|
||||||
|
|
||||||
static GoRoute buildSoilTempRoute() {
|
static GoRoute buildSoilTempRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'soil_temperature/:value',
|
path: 'soil_temperature/:value1/:value2',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value =
|
final double value1 =
|
||||||
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
||||||
return SoilTemperatureScreen(temperature: value);
|
final double value2 =
|
||||||
|
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
||||||
|
return SoilTemperatureScreen(
|
||||||
|
npk1Temperature: value1, npk2Temperature: value2);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||||
import 'package:agrilink_vocpro/core/extension/extention.dart';
|
import 'package:agrilink_vocpro/core/extension/extention.dart';
|
||||||
|
import 'package:agrilink_vocpro/data/model/dht_graphic_response.dart';
|
||||||
|
import 'package:agrilink_vocpro/data/model/npk1_graphic_response.dart';
|
||||||
|
import 'package:agrilink_vocpro/data/model/npk2_graphic_response.dart';
|
||||||
import 'package:agrilink_vocpro/data/model/relay_response.dart';
|
import 'package:agrilink_vocpro/data/model/relay_response.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/model/latest_data_response.dart';
|
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/model/npk1_soil_temp_grafik.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
|
@ -31,32 +33,74 @@ class AppService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Npk1SoilTempGrafik> getNpk1SoilTempGrafik({
|
//get grafik data dht
|
||||||
|
|
||||||
|
Future<DhtGraphicResponse> getGrafikDataDht({
|
||||||
required String metric,
|
required String metric,
|
||||||
required String sensor,
|
|
||||||
}) async {
|
}) async {
|
||||||
String dateNow = DateTime.now().toString();
|
String date = DateTime.now().toString();
|
||||||
String dateYesterday =
|
final formatedDate = dateFormatterShort(date);
|
||||||
DateTime.now().subtract(Duration(days: 1)).toString();
|
|
||||||
final formatedDateNow = dateFormatterShort(dateNow);
|
|
||||||
final formatedDateYesterday = dateFormatterShort(dateYesterday);
|
|
||||||
try {
|
try {
|
||||||
final result = await _dioWithoutInterceptor.get(
|
final result = await _dioWithoutInterceptor.get(
|
||||||
'/sensor/getData?metric=$metric&range[start]=$formatedDateYesterday&range[end]=$formatedDateNow&range[time_range]=HOURLY&sensor=$sensor',
|
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=dht',
|
||||||
);
|
);
|
||||||
if (result.statusCode == 200) {
|
if (result.statusCode == 200) {
|
||||||
print(result.data.toString());
|
final data = DhtGraphicResponse.fromJson(result.data);
|
||||||
final data = Npk1SoilTempGrafik.fromJson(result.data);
|
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to load data');
|
throw Exception('Failed to load data');
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
print(e);
|
final errorMessage = e.response?.data['message'];
|
||||||
rethrow;
|
throw (errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get grafik data npk1
|
||||||
|
Future<Npk1GraphicResponse> getGraphicDataNpk1(
|
||||||
|
{required String metric}) async {
|
||||||
|
String date = DateTime.now().toString();
|
||||||
|
final formatedDate = dateFormatterShort(date);
|
||||||
|
try {
|
||||||
|
final result = await _dioWithoutInterceptor.get(
|
||||||
|
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk1',
|
||||||
|
);
|
||||||
|
if (result.statusCode == 200) {
|
||||||
|
final data = Npk1GraphicResponse.fromJson(result.data);
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load data');
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final errorMessage = e.response?.data['message'];
|
||||||
|
throw (errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get grafik data npk2
|
||||||
|
|
||||||
|
Future<Npk2GraphicResponse> getGraphicDataNpk2(
|
||||||
|
{required String metric}) async {
|
||||||
|
String date = DateTime.now().toString();
|
||||||
|
final formatedDate = dateFormatterShort(date);
|
||||||
|
try {
|
||||||
|
final result = await _dioWithoutInterceptor.get(
|
||||||
|
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk2',
|
||||||
|
);
|
||||||
|
if (result.statusCode == 200) {
|
||||||
|
final data = Npk2GraphicResponse.fromJson(result.data);
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load data');
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final errorMessage = e.response?.data['message'];
|
||||||
|
throw (errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get latest data
|
||||||
|
|
||||||
Future<LatestDataResponse> getLatestData() async {
|
Future<LatestDataResponse> getLatestData() async {
|
||||||
try {
|
try {
|
||||||
final result = await _dioWithoutInterceptor.get(
|
final result = await _dioWithoutInterceptor.get(
|
||||||
|
|
@ -69,8 +113,8 @@ class AppService {
|
||||||
throw Exception('Failed to load data');
|
throw Exception('Failed to load data');
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
print(e);
|
final errorMessage = e.response?.data['message'];
|
||||||
rethrow;
|
throw (errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,170 +1,170 @@
|
||||||
import 'dart:convert';
|
// import 'dart:convert';
|
||||||
|
|
||||||
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
// import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
// import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:mqtt_client/mqtt_client.dart';
|
// import 'package:mqtt_client/mqtt_client.dart';
|
||||||
import 'package:mqtt_client/mqtt_server_client.dart';
|
// import 'package:mqtt_client/mqtt_server_client.dart';
|
||||||
|
|
||||||
class MQTTService {
|
// class MQTTService {
|
||||||
MqttServerClient? client;
|
// MqttServerClient? client;
|
||||||
|
|
||||||
Future<ResultState> setupMqtt() async {
|
// Future<ResultState> setupMqtt() async {
|
||||||
client = MqttServerClient(AppConstant.mqttServer, '');
|
// client = MqttServerClient(AppConstant.mqttServer, '');
|
||||||
client!.port = 1883;
|
// client!.port = 1883;
|
||||||
|
|
||||||
client!.connectionMessage = MqttConnectMessage()
|
// client!.connectionMessage = MqttConnectMessage()
|
||||||
.authenticateAs(AppConstant.mqttUsername, AppConstant.mqttPassword)
|
// .authenticateAs(AppConstant.mqttUsername, AppConstant.mqttPassword)
|
||||||
.withClientIdentifier('mobile_client_controller')
|
// .withClientIdentifier('mobile_client_controller')
|
||||||
.startClean() // reset session
|
// .startClean() // reset session
|
||||||
.withWillQos(MqttQos.atLeastOnce);
|
// .withWillQos(MqttQos.atLeastOnce);
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
print('MQTT: Connecting....');
|
// print('MQTT: Connecting....');
|
||||||
await client!.connect();
|
// await client!.connect();
|
||||||
print('MQTT: Connected');
|
// print('MQTT: Connected');
|
||||||
return ResultState.hasData;
|
// return ResultState.hasData;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print('MQTT: Error: $e');
|
// print('MQTT: Error: $e');
|
||||||
return ResultState.error;
|
// return ResultState.error;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<ResultState> publishMessage(String topic, String message) async {
|
// Future<ResultState> publishMessage(String topic, String message) async {
|
||||||
final builder = MqttClientPayloadBuilder();
|
// final builder = MqttClientPayloadBuilder();
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
// final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
||||||
if (!isConnected) {
|
// if (!isConnected) {
|
||||||
print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
// print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
||||||
return ResultState.error;
|
// return ResultState.error;
|
||||||
}
|
// }
|
||||||
|
|
||||||
print('MQTT: Published message to $topic: $message');
|
// print('MQTT: Published message to $topic: $message');
|
||||||
builder.addString(message);
|
// builder.addString(message);
|
||||||
client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
// client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
||||||
print('MQTT: Message published');
|
// print('MQTT: Message published');
|
||||||
return ResultState.hasData;
|
// return ResultState.hasData;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print('MQTT: Error: $e');
|
// print('MQTT: Error: $e');
|
||||||
return ResultState.error;
|
// return ResultState.error;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<ResultState> disconnectMqtt() async {
|
// Future<ResultState> disconnectMqtt() async {
|
||||||
final bool isConnected = await isMqttConnected();
|
// final bool isConnected = await isMqttConnected();
|
||||||
if (isConnected) {
|
// if (isConnected) {
|
||||||
print('Memutus koneksi dari broker...');
|
// print('Memutus koneksi dari broker...');
|
||||||
|
|
||||||
client!.disconnect();
|
// client!.disconnect();
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
// await Future.delayed(const Duration(seconds: 1));
|
||||||
print('Koneksi telah terputus.');
|
// print('Koneksi telah terputus.');
|
||||||
return ResultState.hasData;
|
// return ResultState.hasData;
|
||||||
} else {
|
// } else {
|
||||||
print('Tidak ada koneksi yang sedang aktif.');
|
// print('Tidak ada koneksi yang sedang aktif.');
|
||||||
return ResultState.error;
|
// return ResultState.error;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<bool> isMqttConnected() async {
|
// Future<bool> isMqttConnected() async {
|
||||||
if (client != null &&
|
// if (client != null &&
|
||||||
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||||
return true; //connected
|
// return true; //connected
|
||||||
} else {
|
// } else {
|
||||||
return false; //not connected
|
// return false; //not connected
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<bool> subscribeToTopic(String topic) async {
|
// Future<bool> subscribeToTopic(String topic) async {
|
||||||
bool isActive = false;
|
// bool isActive = false;
|
||||||
if (client != null &&
|
// if (client != null &&
|
||||||
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||||
try {
|
// try {
|
||||||
print('MQTT: Subscribing to $topic');
|
// print('MQTT: Subscribing to $topic');
|
||||||
client!.subscribe(topic, MqttQos.atMostOnce);
|
// client!.subscribe(topic, MqttQos.atMostOnce);
|
||||||
print('MQTT: Subscribed to $topic');
|
// print('MQTT: Subscribed to $topic');
|
||||||
|
|
||||||
// Tambahkan log ini untuk memastikan bahwa listener dijalankan
|
// // Tambahkan log ini untuk memastikan bahwa listener dijalankan
|
||||||
if (client!.updates != null) {
|
// if (client!.updates != null) {
|
||||||
print('MQTT: Listening for updates...');
|
// print('MQTT: Listening for updates...');
|
||||||
} else {
|
// } else {
|
||||||
print('MQTT: No updates stream available');
|
// print('MQTT: No updates stream available');
|
||||||
}
|
// }
|
||||||
|
|
||||||
client!.updates!.listen(
|
// client!.updates!.listen(
|
||||||
(List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
// (List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||||
print('MQTT: Subscribe Message received!');
|
// print('MQTT: Subscribe Message received!');
|
||||||
if (messages != null && messages.isNotEmpty) {
|
// if (messages != null && messages.isNotEmpty) {
|
||||||
final MqttPublishMessage recMessage =
|
// final MqttPublishMessage recMessage =
|
||||||
messages[0].payload as MqttPublishMessage;
|
// messages[0].payload as MqttPublishMessage;
|
||||||
final String payload = MqttPublishPayload.bytesToStringAsString(
|
// final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||||
recMessage.payload.message);
|
// recMessage.payload.message);
|
||||||
print(
|
// print(
|
||||||
'MQTT: Subscribe Message received on topic ${messages[0].topic}: $payload');
|
// 'MQTT: Subscribe Message received on topic ${messages[0].topic}: $payload');
|
||||||
|
|
||||||
if (payload == 'ON') {
|
// if (payload == 'ON') {
|
||||||
isActive = true;
|
// isActive = true;
|
||||||
// Update UI atau provider untuk menandakan relay ON
|
// // Update UI atau provider untuk menandakan relay ON
|
||||||
} else if (payload == 'OFF') {
|
// } else if (payload == 'OFF') {
|
||||||
isActive = false;
|
// isActive = false;
|
||||||
// Update UI atau provider untuk menandakan relay OFF
|
// // Update UI atau provider untuk menandakan relay OFF
|
||||||
} else {
|
// } else {
|
||||||
print('MQTT: Invalid Subscribe message received');
|
// print('MQTT: Invalid Subscribe message received');
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
print('MQTT: No Subscribe messages received');
|
// print('MQTT: No Subscribe messages received');
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
return isActive;
|
// return isActive;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print('MQTT: Error subscribing to $topic: $e');
|
// print('MQTT: Error subscribing to $topic: $e');
|
||||||
return isActive;
|
// return isActive;
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
print('MQTT: Not connected, cannot subscribe.');
|
// print('MQTT: Not connected, cannot subscribe.');
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<ResultState> subscribeToRelayStatus() async {
|
// Future<ResultState> subscribeToRelayStatus() async {
|
||||||
if (client != null &&
|
// if (client != null &&
|
||||||
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||||
try {
|
// try {
|
||||||
print('MQTT: Subscribing to /smartfarming/getRelayStatus');
|
// print('MQTT: Subscribing to /smartfarming/getRelayStatus');
|
||||||
client!.subscribe('smartfarming/getRelayStatus', MqttQos.atMostOnce);
|
// client!.subscribe('smartfarming/getRelayStatus', MqttQos.atMostOnce);
|
||||||
print('MQTT: Subscribed to /smartfarming/getRelayStatus');
|
// print('MQTT: Subscribed to /smartfarming/getRelayStatus');
|
||||||
|
|
||||||
client!.updates!
|
// client!.updates!
|
||||||
.listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
// .listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||||
if (messages != null && messages.isNotEmpty) {
|
// if (messages != null && messages.isNotEmpty) {
|
||||||
final MqttPublishMessage recMessage =
|
// final MqttPublishMessage recMessage =
|
||||||
messages[0].payload as MqttPublishMessage;
|
// messages[0].payload as MqttPublishMessage;
|
||||||
final String payload = MqttPublishPayload.bytesToStringAsString(
|
// final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||||
recMessage.payload.message);
|
// recMessage.payload.message);
|
||||||
print(
|
// print(
|
||||||
'MQTT: Message received on topic ${messages[0].topic}: $payload');
|
// 'MQTT: Message received on topic ${messages[0].topic}: $payload');
|
||||||
|
|
||||||
// Parse the received JSON payload
|
// // Parse the received JSON payload
|
||||||
final Map<String, dynamic> relayStatus = jsonDecode(payload);
|
// final Map<String, dynamic> relayStatus = jsonDecode(payload);
|
||||||
|
|
||||||
print('MQTT: Relay status: $relayStatus');
|
// print('MQTT: Relay status: $relayStatus');
|
||||||
|
|
||||||
// Assuming you are using provider, notify it with new relay status
|
// // Assuming you are using provider, notify it with new relay status
|
||||||
// _updateRelayStatus(relayStatus);
|
// // _updateRelayStatus(relayStatus);
|
||||||
} else {
|
// } else {
|
||||||
print('MQTT: No messages received');
|
// print('MQTT: No messages received');
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return ResultState.hasData;
|
// return ResultState.hasData;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print('MQTT: Error subscribing: $e');
|
// print('MQTT: Error subscribing: $e');
|
||||||
return ResultState.error;
|
// return ResultState.error;
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
print('MQTT: Not connected, cannot subscribe.');
|
// print('MQTT: Not connected, cannot subscribe.');
|
||||||
return ResultState.error;
|
// return ResultState.error;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
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:agrilink_vocpro/core/route/app_route.dart';
|
||||||
import 'package:agrilink_vocpro/core/widgets/app_button.dart';
|
import 'package:agrilink_vocpro/core/widgets/app_button.dart';
|
||||||
|
|
@ -47,17 +46,6 @@ class LoginScreen extends StatelessWidget {
|
||||||
hintText: 'Masukkan password',
|
hintText: 'Masukkan password',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
print('Forgot password?');
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Forgot password?',
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
style: AppTheme.labelMedium
|
|
||||||
.copyWith(color: AppColor.secondary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
AppButton(
|
AppButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class ControlProvider extends ChangeNotifier {
|
class ControlProvider extends ChangeNotifier {
|
||||||
final AppService _appService = AppService();
|
final AppService _appService = AppService();
|
||||||
|
|
@ -52,7 +52,9 @@ class ControlProvider extends ChangeNotifier {
|
||||||
relayState = ResultState.loading;
|
relayState = ResultState.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
print('try to get relay status...');
|
if (kDebugMode) {
|
||||||
|
print('try to get relay status...');
|
||||||
|
}
|
||||||
final result = await _appService.getRelayStatus();
|
final result = await _appService.getRelayStatus();
|
||||||
if (result.success == true) {
|
if (result.success == true) {
|
||||||
for (var element in result.data!) {
|
for (var element in result.data!) {
|
||||||
|
|
@ -72,27 +74,13 @@ class ControlProvider extends ChangeNotifier {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
relayState = ResultState.error;
|
relayState = ResultState.error;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print(e);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> disconnectMqtt() async {
|
|
||||||
// try {
|
|
||||||
// await _mqttService.disconnectMqtt();
|
|
||||||
// } catch (e) {
|
|
||||||
// print(e);
|
|
||||||
// rethrow;
|
|
||||||
// }
|
|
||||||
// notifyListeners();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// void dispose() {
|
|
||||||
// disconnectMqtt();
|
|
||||||
// super.dispose();
|
|
||||||
// }
|
|
||||||
|
|
||||||
void switchControl1(bool value) {
|
void switchControl1(bool value) {
|
||||||
_control_1 = value;
|
_control_1 = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
class Npk1SoilTempGrafik {
|
|
||||||
Data? data;
|
|
||||||
int? statusCode;
|
|
||||||
String? message;
|
|
||||||
|
|
||||||
Npk1SoilTempGrafik({this.data, this.statusCode, this.message});
|
|
||||||
|
|
||||||
Npk1SoilTempGrafik.fromJson(Map<String, dynamic> json) {
|
|
||||||
data = json['data'] != null ? Data.fromJson(json['data']) : null;
|
|
||||||
statusCode = json['statusCode'];
|
|
||||||
message = json['message'];
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
|
||||||
if (this.data != null) {
|
|
||||||
data['data'] = this.data!.toJson();
|
|
||||||
}
|
|
||||||
data['statusCode'] = statusCode;
|
|
||||||
data['message'] = message;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Data {
|
|
||||||
List<Npk1>? npk1;
|
|
||||||
|
|
||||||
Data({this.npk1});
|
|
||||||
|
|
||||||
Data.fromJson(Map<String, dynamic> json) {
|
|
||||||
if (json['npk1'] != null) {
|
|
||||||
npk1 = <Npk1>[];
|
|
||||||
json['npk1'].forEach((v) {
|
|
||||||
npk1!.add(Npk1.fromJson(v));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
|
||||||
if (npk1 != null) {
|
|
||||||
data['npk1'] = npk1!.map((v) => v.toJson()).toList();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Npk1 {
|
|
||||||
num? hour;
|
|
||||||
num? soiltemperatureAvg;
|
|
||||||
|
|
||||||
Npk1({this.hour, this.soiltemperatureAvg});
|
|
||||||
|
|
||||||
Npk1.fromJson(Map<String, dynamic> json) {
|
|
||||||
hour = json['hour'];
|
|
||||||
soiltemperatureAvg = json['soiltemperature_avg'];
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = <String, dynamic>{};
|
|
||||||
data['hour'] = hour;
|
|
||||||
data['soiltemperature_avg'] = soiltemperatureAvg;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
|
import 'package:agrilink_vocpro/data/model/dht_graphic_response.dart';
|
||||||
|
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class HumidityProvider extends ChangeNotifier {
|
||||||
|
HumidityProvider() {
|
||||||
|
getHumidityData();
|
||||||
|
}
|
||||||
|
ResultState dataState = ResultState.initial;
|
||||||
|
|
||||||
|
List<Dht> dataFetched = [];
|
||||||
|
|
||||||
|
Future<void> getHumidityData() async {
|
||||||
|
dataState = ResultState.loading;
|
||||||
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
final result =
|
||||||
|
await AppService().getGrafikDataDht(metric: 'viciHumidity');
|
||||||
|
if (result.data == null || result.data!.dht!.isEmpty) {
|
||||||
|
dataState = ResultState.noData;
|
||||||
|
} else {
|
||||||
|
dataFetched = result.data!.dht ?? [];
|
||||||
|
dataState = ResultState.hasData;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('Get Grafik Humidity Error: $e');
|
||||||
|
}
|
||||||
|
dataState = ResultState.error;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
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/features/home/pages/humidity/provider/humidity_provider.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';
|
||||||
|
|
@ -16,153 +18,185 @@ class HumidityScreen extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return ChangeNotifierProvider(
|
||||||
appBar: AppBar(
|
create: (context) => HumidityProvider(),
|
||||||
title: Text('Humidity', style: AppTheme.labelMedium),
|
child: Scaffold(
|
||||||
centerTitle: true,
|
appBar: AppBar(
|
||||||
scrolledUnderElevation: 0,
|
title: Text('Humidity', style: AppTheme.labelMedium),
|
||||||
leading: IconButton(
|
centerTitle: true,
|
||||||
icon: const Icon(CupertinoIcons.back),
|
scrolledUnderElevation: 0,
|
||||||
onPressed: () => Navigator.pop(context),
|
leading: IconButton(
|
||||||
),
|
icon: const Icon(CupertinoIcons.back),
|
||||||
backgroundColor: Colors.white,
|
onPressed: () => Navigator.pop(context),
|
||||||
actions: const [
|
),
|
||||||
Padding(
|
backgroundColor: Colors.white,
|
||||||
padding: EdgeInsets.only(right: 16),
|
actions: const [
|
||||||
child: Icon(
|
Padding(
|
||||||
BootstrapIcons.droplet_half,
|
padding: EdgeInsets.only(right: 16),
|
||||||
color: Colors.blue,
|
child: Icon(
|
||||||
),
|
BootstrapIcons.droplet_half,
|
||||||
)
|
color: Colors.blue,
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: Consumer<HomeProvider>(builder: (context, provider, child) {
|
|
||||||
return ListView(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
|
||||||
),
|
),
|
||||||
SizedBox(
|
)
|
||||||
height: 280.h,
|
],
|
||||||
child: Stack(
|
),
|
||||||
fit: StackFit.expand,
|
body: SafeArea(
|
||||||
children: [
|
child: Consumer<HomeProvider>(builder: (context, provider, child) {
|
||||||
Center(
|
return ListView(
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
SizedBox(
|
||||||
children: [
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
const Icon(BootstrapIcons.droplet_half,
|
),
|
||||||
size: 32, color: Colors.blue),
|
SizedBox(
|
||||||
Text('$humidity %', style: AppTheme.headline1),
|
height: 280.h,
|
||||||
],
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(BootstrapIcons.droplet_half,
|
||||||
|
size: 32, color: Colors.blue),
|
||||||
|
Text('$humidity %', style: AppTheme.headline1),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
RotatedBox(
|
||||||
RotatedBox(
|
quarterTurns: 2,
|
||||||
quarterTurns: 2,
|
child: AnimatedRadialGauge(
|
||||||
child: AnimatedRadialGauge(
|
duration: const Duration(seconds: 3),
|
||||||
duration: const Duration(seconds: 3),
|
curve: Curves.easeOut,
|
||||||
curve: Curves.easeOut,
|
value: humidity,
|
||||||
value: humidity,
|
axis: GaugeAxis(
|
||||||
axis: GaugeAxis(
|
degrees: 360,
|
||||||
degrees: 360,
|
min: 0,
|
||||||
min: 0,
|
max: 100,
|
||||||
max: 100,
|
pointer: null,
|
||||||
pointer: null,
|
style: GaugeAxisStyle(
|
||||||
style: GaugeAxisStyle(
|
background: Colors.grey.shade100,
|
||||||
background: Colors.grey.shade100,
|
thickness: 50,
|
||||||
thickness: 50,
|
),
|
||||||
),
|
progressBar: GaugeBasicProgressBar(
|
||||||
progressBar: GaugeBasicProgressBar(
|
gradient: GaugeAxisGradient(colors: [
|
||||||
gradient: GaugeAxisGradient(colors: [
|
Colors.blue.shade200,
|
||||||
Colors.blue.shade200,
|
Colors.blue,
|
||||||
Colors.blue,
|
]),
|
||||||
]),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Humidity',
|
|
||||||
style: AppTheme.labelMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 20.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(BootstrapIcons.info_circle))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(left: 16.w),
|
|
||||||
child: const Text('Grafik dalam 7 hari terakhir'),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: 2.h,
|
|
||||||
child: Container(
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
|
||||||
border:
|
|
||||||
Border.all(color: Colors.grey.shade300, width: 1.w)),
|
|
||||||
child: GarphicWidget(
|
|
||||||
gradientColors: [
|
|
||||||
Colors.blue.shade200,
|
|
||||||
Colors.blue,
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
SizedBox(height: 16.h),
|
Row(
|
||||||
Padding(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
padding: EdgeInsets.only(left: 16.w),
|
children: [
|
||||||
child: const Text('Deskripsi'),
|
Text(
|
||||||
),
|
'Humidity',
|
||||||
// ListView.builder(
|
style: AppTheme.labelMedium,
|
||||||
// shrinkWrap: true,
|
textAlign: TextAlign.center,
|
||||||
// physics: const NeverScrollableScrollPhysics(),
|
),
|
||||||
// itemCount: provider.humidtyRules.length,
|
IconButton(
|
||||||
// itemBuilder: (context, index) {
|
iconSize: 20.r,
|
||||||
// final item = provider.humidtyRules[index];
|
color: Colors.blue,
|
||||||
// return Theme(
|
onPressed: () {},
|
||||||
// data: Theme.of(context)
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
// .copyWith(dividerColor: Colors.transparent),
|
],
|
||||||
// child: ExpansionTile(
|
),
|
||||||
// trailing: Text(
|
const SizedBox(height: 16),
|
||||||
// item.censorText,
|
Padding(
|
||||||
// style: TextStyle(color: item.color),
|
padding: EdgeInsets.only(left: 16.w),
|
||||||
// ),
|
child: const Text('Grafik dalam 7 hari terakhir'),
|
||||||
// expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
// childrenPadding: EdgeInsets.all(16.r),
|
const SizedBox(height: 16),
|
||||||
// title: Text(
|
AspectRatio(
|
||||||
// 'Kelembaban ${item.minPercentage}% - ${item.maxPercentage}%'),
|
aspectRatio: 2.h,
|
||||||
// children: [
|
child: Container(
|
||||||
// Text(
|
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
||||||
// item.description,
|
decoration: BoxDecoration(
|
||||||
// style: AppTheme.labelMedium,
|
color: Colors.white,
|
||||||
// ),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
// SizedBox(height: 8.h),
|
border: Border.all(
|
||||||
// Text('Tindakan', style: AppTheme.labelSmall),
|
color: Colors.grey.shade300, width: 1.w)),
|
||||||
// SizedBox(height: 8.h),
|
child: Consumer<HumidityProvider>(
|
||||||
// Text(item.action),
|
builder: (context, provider, child) {
|
||||||
// ],
|
switch (provider.dataState) {
|
||||||
// ),
|
case ResultState.loading:
|
||||||
// );
|
return const Center(
|
||||||
// })
|
child: CupertinoActivityIndicator(),
|
||||||
],
|
);
|
||||||
);
|
case ResultState.hasData:
|
||||||
}),
|
return GarphicWidget(
|
||||||
|
gradientColors: const [
|
||||||
|
Colors.cyan,
|
||||||
|
Colors.amber,
|
||||||
|
],
|
||||||
|
hour: List.generate(
|
||||||
|
provider.dataFetched.length,
|
||||||
|
(index) =>
|
||||||
|
provider.dataFetched[index].hour ?? 0),
|
||||||
|
data: List.generate(
|
||||||
|
provider.dataFetched.length,
|
||||||
|
(index) =>
|
||||||
|
provider.dataFetched[index].vicihumidityAvg
|
||||||
|
?.toDouble() ??
|
||||||
|
0),
|
||||||
|
);
|
||||||
|
case ResultState.error:
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
BootstrapIcons.exclamation_circle,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'Terjadi Kesalahan',
|
||||||
|
style: AppTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case ResultState.noData:
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
BootstrapIcons.database_fill_x,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'Tidak Ada Data',
|
||||||
|
style: AppTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case ResultState.initial:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
default:
|
||||||
|
return const Center(
|
||||||
|
child: Text('Default Error'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 16.w),
|
||||||
|
child: const Text('Deskripsi'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,224 +0,0 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.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';
|
|
||||||
import 'package:gauge_indicator/gauge_indicator.dart';
|
|
||||||
|
|
||||||
class LightScreen extends StatelessWidget {
|
|
||||||
const LightScreen({super.key, this.lightIntensity = 0});
|
|
||||||
|
|
||||||
final double lightIntensity;
|
|
||||||
|
|
||||||
double get value => lightIntensity;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text('Light', style: AppTheme.labelMedium),
|
|
||||||
centerTitle: true,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
actions: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 16),
|
|
||||||
child: Icon(
|
|
||||||
BootstrapIcons.sun,
|
|
||||||
color: Colors.yellow.shade600,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: ListView(
|
|
||||||
padding: EdgeInsets.all(16.w),
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 240.h,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
const Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(BootstrapIcons.sun,
|
|
||||||
size: 32, color: Colors.orange),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedRadialGauge(
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
curve: Curves.easeOut,
|
|
||||||
value: value,
|
|
||||||
axis: GaugeAxis(
|
|
||||||
degrees: 360,
|
|
||||||
min: 0,
|
|
||||||
max: 1000,
|
|
||||||
style: GaugeAxisStyle(
|
|
||||||
background: Colors.grey.shade100,
|
|
||||||
thickness: 100,
|
|
||||||
),
|
|
||||||
progressBar: GaugeBasicProgressBar(
|
|
||||||
gradient: GaugeAxisGradient(colors: [
|
|
||||||
Colors.yellow.shade100,
|
|
||||||
Colors.orange.shade200,
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'${value.toStringAsFixed(0)} lux',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Intensitas Cahaya',
|
|
||||||
style: AppTheme.labelMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 20.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(BootstrapIcons.info_circle))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.h),
|
|
||||||
const Text('Grafik'),
|
|
||||||
SizedBox(height: 16.h),
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: 1.8.h,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
|
||||||
),
|
|
||||||
child: GarphicWidget(
|
|
||||||
gradientColors: [
|
|
||||||
Colors.yellow.shade100,
|
|
||||||
Colors.orange.shade200,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// 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,
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// )
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
|
import 'package:agrilink_vocpro/data/model/dht_graphic_response.dart';
|
||||||
|
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class LumProvider extends ChangeNotifier {
|
||||||
|
LumProvider() {
|
||||||
|
getLumData();
|
||||||
|
}
|
||||||
|
ResultState dataState = ResultState.initial;
|
||||||
|
|
||||||
|
List<Dht> dataFetched = [];
|
||||||
|
|
||||||
|
Future<void> getLumData() async {
|
||||||
|
dataState = ResultState.loading;
|
||||||
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
final result =
|
||||||
|
await AppService().getGrafikDataDht(metric: 'viciLuminosity');
|
||||||
|
if (result.data == null || result.data!.dht!.isEmpty) {
|
||||||
|
dataState = ResultState.noData;
|
||||||
|
} else {
|
||||||
|
dataFetched = result.data!.dht ?? [];
|
||||||
|
dataState = ResultState.hasData;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('Get Grafik Luminosity Error: $e');
|
||||||
|
}
|
||||||
|
dataState = ResultState.error;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/pages/luminosity/provider/lum_provider.dart';
|
||||||
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class LightScreen extends StatelessWidget {
|
||||||
|
const LightScreen({super.key, this.lightIntensity = 0});
|
||||||
|
|
||||||
|
final double lightIntensity;
|
||||||
|
|
||||||
|
double get value => lightIntensity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider(
|
||||||
|
create: (context) => LumProvider(),
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Light', style: AppTheme.labelMedium),
|
||||||
|
centerTitle: true,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
actions: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 16),
|
||||||
|
child: Icon(
|
||||||
|
BootstrapIcons.sun,
|
||||||
|
color: Colors.yellow.shade600,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 240.h,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(BootstrapIcons.sun,
|
||||||
|
size: 32, color: Colors.orange),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedRadialGauge(
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
value: value,
|
||||||
|
axis: GaugeAxis(
|
||||||
|
degrees: 360,
|
||||||
|
min: 0,
|
||||||
|
max: 1000,
|
||||||
|
style: GaugeAxisStyle(
|
||||||
|
background: Colors.grey.shade100,
|
||||||
|
thickness: 100,
|
||||||
|
),
|
||||||
|
progressBar: GaugeBasicProgressBar(
|
||||||
|
gradient: GaugeAxisGradient(colors: [
|
||||||
|
Colors.yellow.shade100,
|
||||||
|
Colors.orange.shade200,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'${value.toStringAsFixed(0)} lux',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Intensitas Cahaya',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 20.r,
|
||||||
|
color: Colors.blue,
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
const Text('Grafik'),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 1.8.h,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
|
),
|
||||||
|
child: Consumer<LumProvider>(
|
||||||
|
builder: (context, provider, child) {
|
||||||
|
switch (provider.dataState) {
|
||||||
|
case ResultState.loading:
|
||||||
|
return const Center(
|
||||||
|
child: CupertinoActivityIndicator(),
|
||||||
|
);
|
||||||
|
case ResultState.hasData:
|
||||||
|
return GarphicWidget(
|
||||||
|
gradientColors: [
|
||||||
|
Colors.yellow.shade100,
|
||||||
|
Colors.orange.shade200,
|
||||||
|
],
|
||||||
|
hour: List.generate(provider.dataFetched.length,
|
||||||
|
(index) => provider.dataFetched[index].hour ?? 0),
|
||||||
|
data: List.generate(
|
||||||
|
provider.dataFetched.length,
|
||||||
|
(index) =>
|
||||||
|
provider.dataFetched[index].vicihumidityAvg
|
||||||
|
?.toDouble() ??
|
||||||
|
0),
|
||||||
|
);
|
||||||
|
case ResultState.error:
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
BootstrapIcons.exclamation_circle,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'Terjadi Kesalahan',
|
||||||
|
style: AppTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case ResultState.noData:
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
BootstrapIcons.database_fill_x,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'Tidak Ada Data',
|
||||||
|
style: AppTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case ResultState.initial:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
default:
|
||||||
|
return const Center(
|
||||||
|
child: Text('Default Error'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,47 +1,58 @@
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/model/npk1_soil_temp_grafik.dart';
|
import 'package:agrilink_vocpro/data/model/npk1_graphic_response.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/service/home_service.dart';
|
import 'package:agrilink_vocpro/data/model/npk2_graphic_response.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class SoilTempProvider extends ChangeNotifier {
|
class SoilTempProvider extends ChangeNotifier {
|
||||||
SoilTempProvider() {
|
SoilTempProvider() {
|
||||||
getSoilTempData();
|
getSoilTempNpk1Data();
|
||||||
|
getSoilTempNpk2Data();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Npk1> dataFetched = [];
|
|
||||||
|
|
||||||
void setSoilTempData(List<Npk1> data) {
|
|
||||||
dataFetched = data;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultState dataState = ResultState.initial;
|
ResultState dataState = ResultState.initial;
|
||||||
|
|
||||||
Future<void> getSoilTempData() async {
|
List<Npk1> dataFetchedNpk1 = [];
|
||||||
|
List<Npk2> dataFetchedNpk2 = [];
|
||||||
|
|
||||||
|
Future<void> getSoilTempNpk1Data() async {
|
||||||
dataState = ResultState.loading;
|
dataState = ResultState.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
final result = await HomeService().getNpk1SoilTempGrafik();
|
final result =
|
||||||
|
await AppService().getGraphicDataNpk1(metric: 'soilTemperature');
|
||||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||||
dataState = ResultState.noData;
|
dataState = ResultState.noData;
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
setSoilTempData(result.data?.npk1 ?? []);
|
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||||
dataState = ResultState.hasData;
|
dataState = ResultState.hasData;
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error: $e');
|
if (kDebugMode) {
|
||||||
|
print('Get Grafik Soil Temp Error: $e');
|
||||||
|
}
|
||||||
dataState = ResultState.error;
|
dataState = ResultState.error;
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> getSoilTempNpk2Data() async {
|
||||||
void dispose() {
|
dataState = ResultState.loading;
|
||||||
dataState = ResultState.initial;
|
notifyListeners();
|
||||||
dataFetched = [];
|
try {
|
||||||
super.dispose();
|
final result =
|
||||||
|
await AppService().getGraphicDataNpk2(metric: 'soilTemperature');
|
||||||
|
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||||
|
dataState = ResultState.noData;
|
||||||
|
} else {
|
||||||
|
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||||
|
dataState = ResultState.hasData;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('Get Grafik Soil Temp Error: $e');
|
||||||
|
}
|
||||||
|
dataState = ResultState.error;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,288 +1,242 @@
|
||||||
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||||
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/core/widgets/show_info.dart';
|
import 'package:agrilink_vocpro/core/widgets/show_info.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/soil_temperature/provider/soil_temp_provider.dart';
|
import 'package:agrilink_vocpro/features/home/pages/soil_temperature/provider/soil_temp_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: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:gauge_indicator/gauge_indicator.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class SoilTemperatureScreen extends StatelessWidget {
|
class SoilTemperatureScreen extends StatelessWidget {
|
||||||
const SoilTemperatureScreen({super.key, this.temperature = 0});
|
const SoilTemperatureScreen({
|
||||||
|
super.key,
|
||||||
|
this.npk1Temperature = 0,
|
||||||
|
this.npk2Temperature = 0,
|
||||||
|
});
|
||||||
|
|
||||||
final double temperature;
|
final double npk1Temperature;
|
||||||
double get value => temperature;
|
final double npk2Temperature;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return ChangeNotifierProvider(
|
||||||
create: (context) => SoilTempProvider(),
|
create: (context) => SoilTempProvider(),
|
||||||
child: Scaffold(
|
child: DefaultTabController(
|
||||||
appBar: AppBar(
|
length: 2,
|
||||||
title: Text('Soil Temperature', style: AppTheme.labelMedium),
|
child: Scaffold(
|
||||||
centerTitle: true,
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.white,
|
title: Text('Soil Temperature', style: AppTheme.labelMedium),
|
||||||
scrolledUnderElevation: 0,
|
centerTitle: true,
|
||||||
actions: const [
|
backgroundColor: Colors.white,
|
||||||
Padding(
|
scrolledUnderElevation: 0,
|
||||||
padding: EdgeInsets.only(right: 16),
|
actions: const [
|
||||||
child: Icon(
|
Padding(
|
||||||
BootstrapIcons.water,
|
padding: EdgeInsets.only(right: 16),
|
||||||
color: Colors.green,
|
child: Icon(
|
||||||
),
|
BootstrapIcons.water,
|
||||||
)
|
color: Colors.green,
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: ListView(
|
|
||||||
padding: EdgeInsets.all(16.w),
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 240.h,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 80.h,
|
|
||||||
),
|
|
||||||
const Icon(BootstrapIcons.water,
|
|
||||||
size: 32, color: Colors.green),
|
|
||||||
Text(
|
|
||||||
'$value°C', // Animated percentage text
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedRadialGauge(
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
curve: Curves.easeOut,
|
|
||||||
value: value,
|
|
||||||
axis: GaugeAxis(
|
|
||||||
degrees: 240,
|
|
||||||
min: 0,
|
|
||||||
max: 56.7,
|
|
||||||
style: GaugeAxisStyle(
|
|
||||||
background: Colors.grey.shade100,
|
|
||||||
thickness: 50,
|
|
||||||
),
|
|
||||||
progressBar: const GaugeBasicProgressBar(
|
|
||||||
gradient: GaugeAxisGradient(colors: [
|
|
||||||
Colors.blue,
|
|
||||||
Colors.orange,
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.h),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Soil Temperature',
|
|
||||||
style: AppTheme.labelMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 20.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () {
|
|
||||||
showInfo(
|
|
||||||
context,
|
|
||||||
'Soil Temperature',
|
|
||||||
AppConstant.soilTempInfo,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
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),
|
|
||||||
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),
|
|
||||||
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: Consumer<SoilTempProvider>(
|
|
||||||
builder: (context, provider, child) {
|
|
||||||
switch (provider.dataState) {
|
|
||||||
case ResultState.loading:
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
case ResultState.hasData:
|
|
||||||
return GarphicWidget(
|
|
||||||
gradientColors: const [
|
|
||||||
Colors.cyan,
|
|
||||||
Colors.amber,
|
|
||||||
],
|
|
||||||
hour: List.generate(provider.dataFetched.length,
|
|
||||||
(index) => provider.dataFetched[index].hour ?? 0),
|
|
||||||
data: List.generate(
|
|
||||||
provider.dataFetched.length,
|
|
||||||
(index) =>
|
|
||||||
provider.dataFetched[index].soiltemperatureAvg
|
|
||||||
?.toDouble() ??
|
|
||||||
0),
|
|
||||||
);
|
|
||||||
case ResultState.error:
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
BootstrapIcons.exclamation_circle,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
Text(
|
|
||||||
'Terjadi Kesalahan',
|
|
||||||
style: AppTheme.labelSmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ResultState.noData:
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
BootstrapIcons.database_fill_x,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
Text(
|
|
||||||
'Tidak Ada Data',
|
|
||||||
style: AppTheme.labelSmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ResultState.initial:
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
default:
|
|
||||||
return const Center(
|
|
||||||
child: Text('Default Error'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
bottom: const TabBar(
|
||||||
|
tabs: [
|
||||||
|
Tab(text: 'NPK 1'),
|
||||||
|
Tab(text: 'NPK 2'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: TabBarView(
|
||||||
|
children: [
|
||||||
|
buildTabContent(context, npk1Temperature, 'NPK 1', true),
|
||||||
|
buildTabContent(context, npk2Temperature, 'NPK 2', false),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generalized method for tab content to avoid duplication
|
||||||
|
SafeArea buildTabContent(
|
||||||
|
BuildContext context, double value, String label, bool isNpk1) {
|
||||||
|
return SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
|
children: [
|
||||||
|
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
||||||
|
buildTemperatureInfo(context, value),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
buildInfoRow(context),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
buildTemperatureRange(),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
const Text('Grafik'),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
buildGraphicContent(context, isNpk1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTemperatureInfo(BuildContext context, double value) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 240.h,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 80.h),
|
||||||
|
const Icon(BootstrapIcons.water, size: 32, color: Colors.green),
|
||||||
|
Text(
|
||||||
|
'$value°C', // Display temperature
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedRadialGauge(
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
value: value,
|
||||||
|
axis: GaugeAxis(
|
||||||
|
degrees: 240,
|
||||||
|
min: 0,
|
||||||
|
max: 56.7,
|
||||||
|
style: GaugeAxisStyle(
|
||||||
|
background: Colors.grey.shade100,
|
||||||
|
thickness: 50,
|
||||||
|
),
|
||||||
|
progressBar: const GaugeBasicProgressBar(
|
||||||
|
gradient: GaugeAxisGradient(colors: [
|
||||||
|
Colors.blue,
|
||||||
|
Colors.orange,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the row with 'Low', 'Ideal', 'High' temperature containers
|
||||||
|
Widget buildTemperatureRange() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
_buildInfoContainer('Low', '<20°C', Colors.blue),
|
||||||
|
_buildInfoContainer('Ideal', '20-30°C', Colors.green),
|
||||||
|
_buildInfoContainer('High', '>30°C', Colors.orange),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reusable container builder
|
||||||
|
Widget _buildInfoContainer(String label, String tempRange, Color color) {
|
||||||
|
return Container(
|
||||||
|
height: 100.h,
|
||||||
|
width: 100.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: color, width: 2),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(label, style: AppTheme.labelMedium.copyWith(color: color)),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(tempRange,
|
||||||
|
style: AppTheme.labelMedium, textAlign: TextAlign.center),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Information row with IconButton
|
||||||
|
Widget buildInfoRow(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Soil Temperature',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 20.r,
|
||||||
|
color: Colors.blue,
|
||||||
|
onPressed: () {
|
||||||
|
showInfo(context, 'Soil Temperature', AppConstant.soilTempInfo);
|
||||||
|
},
|
||||||
|
icon: const Icon(BootstrapIcons.info_circle),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generalized method to build the graphic content based on NPK type
|
||||||
|
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||||
|
return 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: Consumer<SoilTempProvider>(
|
||||||
|
builder: (context, provider, child) {
|
||||||
|
final dataState = provider.dataState;
|
||||||
|
|
||||||
|
switch (dataState) {
|
||||||
|
case ResultState.loading:
|
||||||
|
return const Center(child: CupertinoActivityIndicator());
|
||||||
|
case ResultState.hasData:
|
||||||
|
return GarphicWidget(
|
||||||
|
gradientColors: const [Colors.cyan, Colors.amber],
|
||||||
|
hour: List.generate(
|
||||||
|
isNpk1
|
||||||
|
? provider.dataFetchedNpk1.length
|
||||||
|
: provider.dataFetchedNpk2.length,
|
||||||
|
(index) => isNpk1
|
||||||
|
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||||
|
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||||
|
),
|
||||||
|
data: List.generate(
|
||||||
|
isNpk1
|
||||||
|
? provider.dataFetchedNpk1.length
|
||||||
|
: provider.dataFetchedNpk2.length,
|
||||||
|
(index) => isNpk1
|
||||||
|
? provider.dataFetchedNpk1[index].soiltemperatureAvg ??
|
||||||
|
0
|
||||||
|
: provider.dataFetchedNpk2[index].soiltemperatureAvg ??
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case ResultState.error:
|
||||||
|
return const GraphicErrorWidget(message: 'Terjadi Kesalahan');
|
||||||
|
case ResultState.noData:
|
||||||
|
return const GraphicErrorWidget(message: 'Tidak Ada Data');
|
||||||
|
case ResultState.initial:
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
|
import 'package:agrilink_vocpro/data/model/dht_graphic_response.dart';
|
||||||
|
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class TempProvider extends ChangeNotifier {
|
||||||
|
TempProvider() {
|
||||||
|
getTempData();
|
||||||
|
}
|
||||||
|
ResultState dataState = ResultState.initial;
|
||||||
|
|
||||||
|
List<Dht> dataFetched = [];
|
||||||
|
|
||||||
|
Future<void> getTempData() async {
|
||||||
|
dataState = ResultState.loading;
|
||||||
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
final result =
|
||||||
|
await AppService().getGrafikDataDht(metric: 'viciTemperature');
|
||||||
|
if (result.data == null || result.data!.dht!.isEmpty) {
|
||||||
|
dataState = ResultState.noData;
|
||||||
|
} else {
|
||||||
|
dataFetched = result.data!.dht ?? [];
|
||||||
|
dataState = ResultState.hasData;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('Get Grafik Temperature Error: $e');
|
||||||
|
}
|
||||||
|
dataState = ResultState.error;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
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/features/home/pages/temperature/provider/temp_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/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:gauge_indicator/gauge_indicator.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class TemperatureScreen extends StatelessWidget {
|
class TemperatureScreen extends StatelessWidget {
|
||||||
const TemperatureScreen({super.key, this.temperature = 0});
|
const TemperatureScreen({super.key, this.temperature = 0});
|
||||||
|
|
@ -13,213 +17,264 @@ class TemperatureScreen extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return ChangeNotifierProvider(
|
||||||
appBar: AppBar(
|
create: (context) => TempProvider(),
|
||||||
title: Text('Temperature', style: AppTheme.labelMedium),
|
child: Scaffold(
|
||||||
centerTitle: true,
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.white,
|
title: Text('Temperature', style: AppTheme.labelMedium),
|
||||||
scrolledUnderElevation: 0,
|
centerTitle: true,
|
||||||
actions: const [
|
backgroundColor: Colors.white,
|
||||||
Padding(
|
scrolledUnderElevation: 0,
|
||||||
padding: EdgeInsets.only(right: 16),
|
actions: const [
|
||||||
child: Icon(
|
Padding(
|
||||||
BootstrapIcons.thermometer_half,
|
padding: EdgeInsets.only(right: 16),
|
||||||
color: Colors.red,
|
child: Icon(
|
||||||
),
|
BootstrapIcons.thermometer_half,
|
||||||
)
|
color: Colors.red,
|
||||||
],
|
),
|
||||||
),
|
)
|
||||||
body: SafeArea(
|
],
|
||||||
child: ListView(
|
),
|
||||||
padding: EdgeInsets.all(16.w),
|
body: SafeArea(
|
||||||
children: [
|
child: ListView(
|
||||||
SizedBox(
|
padding: EdgeInsets.all(16.w),
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
children: [
|
||||||
),
|
SizedBox(
|
||||||
SizedBox(
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
height: 240.h,
|
),
|
||||||
child: Stack(
|
SizedBox(
|
||||||
fit: StackFit.expand,
|
height: 240.h,
|
||||||
children: [
|
child: Stack(
|
||||||
Center(
|
fit: StackFit.expand,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
Center(
|
||||||
children: [
|
child: Column(
|
||||||
SizedBox(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: 80.h,
|
children: [
|
||||||
),
|
SizedBox(
|
||||||
const Icon(BootstrapIcons.thermometer_half,
|
height: 80.h,
|
||||||
size: 32, color: Colors.orange),
|
|
||||||
Text(
|
|
||||||
'${value.toStringAsFixed(0)}°C', // Animated percentage text
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
),
|
||||||
|
const Icon(BootstrapIcons.thermometer_half,
|
||||||
|
size: 32, color: Colors.orange),
|
||||||
|
Text(
|
||||||
|
'${value.toStringAsFixed(0)}°C', // Animated percentage text
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedRadialGauge(
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
value: value,
|
||||||
|
axis: GaugeAxis(
|
||||||
|
degrees: 240,
|
||||||
|
min: 0,
|
||||||
|
max: 56.7,
|
||||||
|
style: GaugeAxisStyle(
|
||||||
|
background: Colors.grey.shade100,
|
||||||
|
thickness: 50,
|
||||||
|
),
|
||||||
|
progressBar: const GaugeBasicProgressBar(
|
||||||
|
gradient: GaugeAxisGradient(colors: [
|
||||||
|
Colors.white12,
|
||||||
|
Colors.orange,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Temperature',
|
||||||
|
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),
|
||||||
|
Text(
|
||||||
|
'<20°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedRadialGauge(
|
Container(
|
||||||
duration: const Duration(seconds: 2),
|
height: 100.h,
|
||||||
curve: Curves.easeOut,
|
width: 100.w,
|
||||||
value: value,
|
decoration: BoxDecoration(
|
||||||
axis: GaugeAxis(
|
color: Colors.green.withOpacity(0.1),
|
||||||
degrees: 240,
|
borderRadius: BorderRadius.circular(16),
|
||||||
min: 0,
|
border: Border.all(
|
||||||
max: 56.7,
|
color: Colors.green,
|
||||||
style: GaugeAxisStyle(
|
width: 2,
|
||||||
background: Colors.grey.shade100,
|
|
||||||
thickness: 50,
|
|
||||||
),
|
),
|
||||||
progressBar: const GaugeBasicProgressBar(
|
),
|
||||||
gradient: GaugeAxisGradient(colors: [
|
child: Column(
|
||||||
Colors.white12,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Colors.orange,
|
children: [
|
||||||
]),
|
Text('Ideal',
|
||||||
|
style: AppTheme.labelMedium
|
||||||
|
.copyWith(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),
|
||||||
|
Text(
|
||||||
|
'>30°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 16.h),
|
||||||
const SizedBox(height: 16),
|
const Text('Grafik'),
|
||||||
const SizedBox(height: 16),
|
SizedBox(height: 16.h),
|
||||||
Row(
|
AspectRatio(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
aspectRatio: 1.6.h,
|
||||||
children: [
|
child: Container(
|
||||||
Text(
|
|
||||||
'Temperature',
|
|
||||||
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(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
color: Colors.white,
|
||||||
color: Colors.blue.withOpacity(0.1),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
border: Border.all(
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
color: Colors.blue,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Consumer<TempProvider>(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
builder: (context, provider, child) {
|
||||||
children: [
|
switch (provider.dataState) {
|
||||||
Text('Low',
|
case ResultState.loading:
|
||||||
style: AppTheme.labelMedium
|
return const Center(
|
||||||
.copyWith(color: Colors.blue)),
|
child: CupertinoActivityIndicator(),
|
||||||
// SizedBox(height: 8.h),
|
);
|
||||||
// const Icon(
|
|
||||||
// BootstrapIcons.thermometer_low,
|
case ResultState.hasData:
|
||||||
// color: Colors.blue,
|
return GarphicWidget(
|
||||||
// ),
|
gradientColors: const [
|
||||||
SizedBox(height: 8.h),
|
Colors.cyan,
|
||||||
Text(
|
Colors.amber,
|
||||||
'<20°C',
|
],
|
||||||
style: AppTheme.labelMedium,
|
hour: List.generate(
|
||||||
textAlign: TextAlign.center,
|
provider.dataFetched.length,
|
||||||
),
|
(index) =>
|
||||||
],
|
provider.dataFetched[index].hour ?? 0),
|
||||||
|
data: List.generate(
|
||||||
|
provider.dataFetched.length,
|
||||||
|
(index) =>
|
||||||
|
provider
|
||||||
|
.dataFetched[index].vicitemperatureAvg
|
||||||
|
?.toDouble() ??
|
||||||
|
0),
|
||||||
|
);
|
||||||
|
case ResultState.error:
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
BootstrapIcons.exclamation_circle,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'Terjadi Kesalahan',
|
||||||
|
style: AppTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case ResultState.noData:
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
BootstrapIcons.database_fill_x,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'Tidak Ada Data',
|
||||||
|
style: AppTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case ResultState.initial:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
default:
|
||||||
|
return const Center(
|
||||||
|
child: Text('Default Error'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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(
|
|
||||||
gradientColors: [
|
|
||||||
Colors.cyan,
|
|
||||||
Colors.amber,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class HomeProvider extends ChangeNotifier {
|
class HomeProvider extends ChangeNotifier {
|
||||||
final DateTime currentDate = DateTime.now();
|
final DateTime currentDate = DateTime.now();
|
||||||
|
|
@ -95,8 +95,10 @@ class HomeProvider extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error: $e');
|
if (kDebugMode) {
|
||||||
dataState = ResultState.error;
|
print('Get Latest Error: $e');
|
||||||
|
}
|
||||||
|
dataState = ResultState.hasData;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/model/npk1_soil_temp_grafik.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
|
|
||||||
class HomeService {
|
|
||||||
final Dio _dioWithoutInterceptor = Dio(
|
|
||||||
BaseOptions(
|
|
||||||
baseUrl: AppConstant.baseUrl,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<Npk1SoilTempGrafik> getNpk1SoilTempGrafik() async {
|
|
||||||
try {
|
|
||||||
final result = await _dioWithoutInterceptor.get(
|
|
||||||
'/sensor/getData?metric=soilTemperature&range[start]=2024-10-03&range[end]=2024-10-03&range[time_range]=HOURLY&sensor=npk1',
|
|
||||||
);
|
|
||||||
if (result.statusCode == 200) {
|
|
||||||
print(result.data.toString());
|
|
||||||
final data = Npk1SoilTempGrafik.fromJson(result.data);
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to load data');
|
|
||||||
}
|
|
||||||
} on DioException catch (e) {
|
|
||||||
print(e);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
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 GraphicErrorWidget extends StatelessWidget {
|
||||||
|
const GraphicErrorWidget({super.key, required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(BootstrapIcons.exclamation_circle, color: Colors.grey.shade400),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(message, style: AppTheme.labelSmall),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push(
|
||||||
'${AppRoute.soilTemperature}/${provider.npk1Temperature}');
|
'${AppRoute.soilTemperature}/${provider.npk1Temperature}/${provider.npk2Temperature}');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
|
|
|
||||||
|
|
@ -31,20 +31,21 @@ class MyApp extends StatelessWidget {
|
||||||
ChangeNotifierProvider(create: (context) => ControlProvider()),
|
ChangeNotifierProvider(create: (context) => ControlProvider()),
|
||||||
],
|
],
|
||||||
child: ScreenUtilInit(
|
child: ScreenUtilInit(
|
||||||
designSize: const Size(360, 800),
|
designSize: const Size(360, 800),
|
||||||
minTextAdapt: true,
|
minTextAdapt: true,
|
||||||
builder: (_, context) {
|
builder: (_, context) {
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Demo',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
scaffoldBackgroundColor: Colors.white,
|
scaffoldBackgroundColor: Colors.white,
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
routerConfig: AppRoute.router,
|
routerConfig: AppRoute.router,
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user