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/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/ph/view/ph_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/splash/view/splash_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -17,6 +18,7 @@ class AppRoute {
|
|||
static const String temperature = '/temperature';
|
||||
static const String soil = '/soil';
|
||||
static const String light = '/dashboard/light';
|
||||
static const String ph = '/dashboard/ph';
|
||||
static const String water = '/water';
|
||||
static const String acidity = '/acidity';
|
||||
|
||||
|
|
@ -53,7 +55,15 @@ class AppRoute {
|
|||
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||
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 {
|
||||
final bool isConnected = await isMqttConnected();
|
||||
if (isConnected) {
|
||||
final builder = MqttClientPayloadBuilder();
|
||||
final builder = MqttClientPayloadBuilder();
|
||||
|
||||
try {
|
||||
builder.addString(message);
|
||||
client!.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
|
||||
return ResultState.hasData;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
try {
|
||||
final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
||||
if (!isConnected) {
|
||||
print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -68,4 +71,55 @@ class MQTTService {
|
|||
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';
|
||||
|
||||
class ControlProvider extends ChangeNotifier {
|
||||
bool _control_1 = false;
|
||||
bool _control_2 = false;
|
||||
bool _control_3 = false;
|
||||
bool _control_4 = false;
|
||||
bool _control_5 = false;
|
||||
bool _control_6 = false;
|
||||
final MQTTService _mqttService = MQTTService();
|
||||
|
||||
bool _control_1 = false;
|
||||
|
||||
// Getters
|
||||
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() {
|
||||
connectMqtt();
|
||||
}
|
||||
|
||||
ResultState mqttState = ResultState.initial;
|
||||
ResultState subscribeState = ResultState.initial;
|
||||
|
||||
// Koneksi MQTT
|
||||
Future<void> connectMqtt() async {
|
||||
mqttState = ResultState.loading;
|
||||
subscribeState = ResultState.loading;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final result = await MQTTService().setupMqtt();
|
||||
final result = await _mqttService.setupMqtt();
|
||||
if (result == ResultState.hasData) {
|
||||
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 {
|
||||
mqttState = ResultState.error;
|
||||
print('Failed to connect to MQTT');
|
||||
}
|
||||
} catch (e) {
|
||||
mqttState = ResultState.error;
|
||||
print(e);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> disconnectMqtt() async {
|
||||
try {
|
||||
final result = await MQTTService().disconnectMqtt();
|
||||
if (result == ResultState.hasData) print('Disconnected from MQTT');
|
||||
await _mqttService.disconnectMqtt();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
rethrow;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<bool> isMqttConnected() async {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disconnectMqtt();
|
||||
|
|
@ -67,28 +65,22 @@ class ControlProvider extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void switchControl2() {
|
||||
_control_2 = !_control_2;
|
||||
notifyListeners();
|
||||
Future<ResultState> publishMessage(String topic, String message) async {
|
||||
try {
|
||||
final result = await _mqttService.publishMessage(topic, message);
|
||||
return result;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
void switchControl3() {
|
||||
_control_3 = !_control_3;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void switchControl4() {
|
||||
_control_4 = !_control_4;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void switchControl5() {
|
||||
_control_5 = !_control_5;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void switchControl6() {
|
||||
_control_6 = !_control_6;
|
||||
notifyListeners();
|
||||
Future<void> subscribeToTopic(String topic) async {
|
||||
try {
|
||||
await _mqttService.subscribeToTopic(topic);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
@ -21,15 +22,12 @@ class ControlScreen extends StatelessWidget {
|
|||
return SafeArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 16.h,
|
||||
child: Center(
|
||||
child: provider.mqttState == ResultState.loading
|
||||
? const CircularProgressIndicator()
|
||||
: provider.mqttState == ResultState.error
|
||||
? const Text('Failed to connect to MQTT')
|
||||
: const Text('Connected to MQTT'),
|
||||
),
|
||||
Center(
|
||||
child: provider.mqttState == ResultState.loading
|
||||
? const CupertinoActivityIndicator()
|
||||
: provider.mqttState == ResultState.hasData
|
||||
? const Text('Terhubung ke Broker')
|
||||
: const Text('Gagal terhubung ke Broker'),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
ListTile(
|
||||
|
|
@ -38,7 +36,30 @@ class ControlScreen extends StatelessWidget {
|
|||
trailing: Switch(
|
||||
value: provider.control_1,
|
||||
onChanged: (value) {
|
||||
provider.control_1 == false
|
||||
? provider.publishMessage('relay1', 'ON')
|
||||
: provider.publishMessage('relay1', 'OFF');
|
||||
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/features/home/pages/humidity/widgets/circle_chart.dart';
|
||||
import 'package:agrilink_vocpro/features/home/provider/home_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 HumidityScreen extends StatelessWidget {
|
||||
|
|
@ -15,7 +15,7 @@ class HumidityScreen extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Humidity', style: AppTheme.labelLarge),
|
||||
title: Text('Humidity', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
|
|
@ -40,12 +40,46 @@ class HumidityScreen extends StatelessWidget {
|
|||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 320,
|
||||
width: double.infinity,
|
||||
child: CircleChart(
|
||||
percentage: 60.5,
|
||||
icon: BootstrapIcons.droplet_half,
|
||||
SizedBox(
|
||||
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('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),
|
||||
|
|
@ -86,37 +120,37 @@ class HumidityScreen extends StatelessWidget {
|
|||
padding: EdgeInsets.only(left: 16.w),
|
||||
child: const Text('Deskripsi'),
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: provider.humidtyRules.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = provider.humidtyRules[index];
|
||||
return Theme(
|
||||
data: Theme.of(context)
|
||||
.copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
trailing: Text(
|
||||
item.censorText,
|
||||
style: TextStyle(color: item.color),
|
||||
),
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||
childrenPadding: EdgeInsets.all(16.r),
|
||||
title: Text(
|
||||
'Kelembaban ${item.minPercentage}% - ${item.maxPercentage}%'),
|
||||
children: [
|
||||
Text(
|
||||
item.description,
|
||||
style: AppTheme.labelMedium,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Text('Tindakan', style: AppTheme.labelSmall),
|
||||
SizedBox(height: 8.h),
|
||||
Text(item.action),
|
||||
],
|
||||
),
|
||||
);
|
||||
})
|
||||
// ListView.builder(
|
||||
// shrinkWrap: true,
|
||||
// physics: const NeverScrollableScrollPhysics(),
|
||||
// itemCount: provider.humidtyRules.length,
|
||||
// itemBuilder: (context, index) {
|
||||
// final item = provider.humidtyRules[index];
|
||||
// return Theme(
|
||||
// data: Theme.of(context)
|
||||
// .copyWith(dividerColor: Colors.transparent),
|
||||
// child: ExpansionTile(
|
||||
// trailing: Text(
|
||||
// item.censorText,
|
||||
// style: TextStyle(color: item.color),
|
||||
// ),
|
||||
// expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||
// childrenPadding: EdgeInsets.all(16.r),
|
||||
// title: Text(
|
||||
// 'Kelembaban ${item.minPercentage}% - ${item.maxPercentage}%'),
|
||||
// children: [
|
||||
// Text(
|
||||
// item.description,
|
||||
// style: AppTheme.labelMedium,
|
||||
// ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text('Tindakan', style: AppTheme.labelSmall),
|
||||
// SizedBox(height: 8.h),
|
||||
// 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) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Light', style: AppTheme.titleLarge),
|
||||
title: Text('Light', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
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/features/home/pages/humidity/widgets/circle_chart.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';
|
||||
|
||||
class SoilMoistureScreen extends StatelessWidget {
|
||||
const SoilMoistureScreen({super.key});
|
||||
|
|
@ -33,14 +35,46 @@ class SoilMoistureScreen extends StatelessWidget {
|
|||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 320,
|
||||
width: double.infinity,
|
||||
child: CircleChart(
|
||||
percentage: 60.5,
|
||||
icon: Icons.water_outlined,
|
||||
colorStart: Colors.lime,
|
||||
colorEnd: Colors.brown,
|
||||
SizedBox(
|
||||
height: 280.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
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),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class TemperatureScreen extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Temperature', style: AppTheme.titleLarge),
|
||||
title: Text('Temperature', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
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';
|
||||
|
||||
class HomeProvider extends ChangeNotifier {
|
||||
final DateTime currentDate = DateTime.now();
|
||||
|
||||
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,
|
||||
),
|
||||
];
|
||||
HomeProvider() {
|
||||
getData();
|
||||
}
|
||||
|
||||
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_theme.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_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:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
|
@ -23,7 +25,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
appBar: AppBar(
|
||||
toolbarHeight: 200.h,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.backgroundColor,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
flexibleSpace: Padding(
|
||||
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(
|
||||
|
|
@ -130,7 +142,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
),
|
||||
textStyle: AppTheme.labelSmall,
|
||||
tabs: const [
|
||||
SegmentTab(label: 'Main Censor'),
|
||||
SegmentTab(label: 'DHT'),
|
||||
SegmentTab(label: 'NPK 1'),
|
||||
SegmentTab(label: 'NPK 2'),
|
||||
]),
|
||||
|
|
@ -138,7 +150,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
Padding(
|
||||
padding: EdgeInsets.only(top: 64.h),
|
||||
child: const TabBarView(children: [
|
||||
ListDataFromMainCensor(),
|
||||
ListDataFromCensorDht(),
|
||||
ListDataFromCensorNpk1(),
|
||||
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/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/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/cupertino.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 ListDataFromCensorNpk1 extends StatelessWidget {
|
||||
const ListDataFromCensorNpk1({
|
||||
|
|
@ -17,100 +21,127 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const String censorIdentifier = 'NPK 1';
|
||||
return GridView(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16.r,
|
||||
mainAxisSpacing: 16.r,
|
||||
childAspectRatio: 0.9,
|
||||
),
|
||||
children: [
|
||||
DataDisplayerWidget(
|
||||
title: 'Temperature',
|
||||
subtitle: 'Suhu tanah',
|
||||
value: '28',
|
||||
unit: '°C',
|
||||
icon: BootstrapIcons.thermometer_half,
|
||||
textColor: Colors.white,
|
||||
color: AppColor.secondary,
|
||||
iconColor: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const TemperatureScreen()));
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Soil Moisture',
|
||||
subtitle: 'kelembaban tanah',
|
||||
value: '40',
|
||||
unit: '%',
|
||||
icon: Icons.water_outlined,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SoilMoistureScreen(),
|
||||
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(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16.r,
|
||||
mainAxisSpacing: 16.r,
|
||||
childAspectRatio: 0.9,
|
||||
),
|
||||
children: [
|
||||
DataDisplayerWidget(
|
||||
title: 'Temperature',
|
||||
subtitle: 'Suhu tanah',
|
||||
value: '28',
|
||||
unit: '°C',
|
||||
icon: BootstrapIcons.thermometer_half,
|
||||
textColor: Colors.white,
|
||||
color: AppColor.secondary,
|
||||
iconColor: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const TemperatureScreen()));
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Acid Level (PH)',
|
||||
subtitle: 'tingkat keasaman',
|
||||
value: '6.5',
|
||||
unit: 'pH',
|
||||
icon: BootstrapIcons.pie_chart,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () {},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Conductivity',
|
||||
subtitle: 'Daya Arus Listrik',
|
||||
value: '234',
|
||||
unit: 'µS/cm',
|
||||
icon: Icons.electric_bolt,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
context.push(AppRoute.humidity, extra: '60');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Nitrogen',
|
||||
subtitle: 'Kadar Nitrogen',
|
||||
value: '30',
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Potassium',
|
||||
subtitle: 'Kadar kalium',
|
||||
value: '20',
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Phosphorus',
|
||||
subtitle: 'Kadar Fosfor',
|
||||
value: '54',
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
);
|
||||
DataDisplayerWidget(
|
||||
title: 'Soil Moisture',
|
||||
subtitle: 'kelembaban tanah',
|
||||
value: '40',
|
||||
unit: '%',
|
||||
icon: Icons.water_outlined,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SoilMoistureScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Acid Level (PH)',
|
||||
subtitle: 'tingkat keasaman',
|
||||
value: '6.5',
|
||||
unit: 'pH',
|
||||
icon: BootstrapIcons.pie_chart,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () {
|
||||
context.push('${AppRoute.ph}/6.5');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Conductivity',
|
||||
subtitle: 'Daya Arus Listrik',
|
||||
value: '234',
|
||||
unit: 'µS/cm',
|
||||
icon: Icons.electric_bolt,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
context.push(AppRoute.humidity, extra: '60');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Nitrogen',
|
||||
subtitle: 'Kadar Nitrogen',
|
||||
value: '30',
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Potassium',
|
||||
subtitle: 'Kadar kalium',
|
||||
value: '20',
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Phosphorus',
|
||||
subtitle: 'Kadar Fosfor',
|
||||
value: '54',
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
);
|
||||
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_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/route/app_route.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:go_router/go_router.dart';
|
||||
|
||||
class SettingScreen extends StatelessWidget {
|
||||
const SettingScreen({super.key});
|
||||
|
|
@ -96,51 +98,27 @@ class SettingScreen extends StatelessWidget {
|
|||
size: 16.r,
|
||||
),
|
||||
onTap: () {
|
||||
if (Platform.isAndroid) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} 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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Logout'),
|
||||
content: Text('Apakah anda yakin ingin logout?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Batal'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Ya'),
|
||||
onPressed: () {
|
||||
context.go(AppRoute.root);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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/features/auth/provider/auth_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
||||
|
|
|
|||
|
|
@ -456,6 +456,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ dependencies:
|
|||
flutter_screenutil: ^5.9.3
|
||||
gauge_indicator: ^0.4.3
|
||||
mqtt_client: ^10.5.1
|
||||
shimmer: ^3.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user