refactor: change all detail screen on npk sensor

This commit is contained in:
Syaroful 2024-10-16 08:34:13 +07:00
parent 426445a865
commit a2dc5c4b3f
38 changed files with 1724 additions and 768 deletions

View File

@ -2,7 +2,7 @@ class AppConstant {
static const String appName = 'Kebun Pintar'; static const String appName = 'Kebun Pintar';
static const String appVersion = '1.0.0'; static const String appVersion = '1.0.0';
static const String baseUrl = 'https://jx027dj4-3333.asse.devtunnels.ms/api'; static const String baseUrl = 'https://jx027dj4-3333.asse.devtunnels.ms';
static const String mqttServer = 'armadillo.rmq.cloudamqp.com'; static const String mqttServer = 'armadillo.rmq.cloudamqp.com';
static const String mqttUsername = 'obyskxhx:obyskxhx'; static const String mqttUsername = 'obyskxhx:obyskxhx';

View File

@ -81,55 +81,71 @@ class AppRoute {
static GoRoute buildPotassiumRoute() { static GoRoute buildPotassiumRoute() {
return GoRoute( return GoRoute(
path: 'potassium/:value', path: 'potassium/: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 PotassiumScreen(potassium: value); final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return PotassiumScreen(potassiumNpk1: value1, potassiumNpk2: value2);
}, },
); );
} }
static GoRoute buildPhosphorusRoute() { static GoRoute buildPhosphorusRoute() {
return GoRoute( return GoRoute(
path: 'phosphorus/:value', path: 'phosphorus/: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 PhosphorusScreen(phosphorus: value); final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return PhosphorusScreen(
phosphorusNpk1: value1,
phosphorusNpk2: value2,
);
}, },
); );
} }
static GoRoute buildNitrogenRoute() { static GoRoute buildNitrogenRoute() {
return GoRoute( return GoRoute(
path: 'nitrogen/:value', path: 'nitrogen/: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 NitrogenScreen(nitrogen: value); final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return NitrogenScreen(nitrogenNpk1: value1, nitrogenNpk2: value2);
}, },
); );
} }
static GoRoute buildConductivityRoute() { static GoRoute buildConductivityRoute() {
return GoRoute( return GoRoute(
path: 'conductivity/:value', path: 'conductivity/: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 ConductivityScreen(conductivity: value); final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return ConductivityScreen(
conductivityNpk1: value1,
conductivityNpk2: value2,
);
}, },
); );
} }
static GoRoute buildSoilMoistureRoute() { static GoRoute buildSoilMoistureRoute() {
return GoRoute( return GoRoute(
path: 'soil_moisture/:value', path: 'soil_moisture/: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 SoilMoistureScreen(moisture: value); final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return SoilMoistureScreen(moistureNpk1: value1, moistureNpk2: value2);
}, },
); );
} }
@ -150,11 +166,13 @@ class AppRoute {
static GoRoute buildAcidityRoute() { static GoRoute buildAcidityRoute() {
return GoRoute( return GoRoute(
path: 'ph/:value', path: 'ph/: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 PhScreen(phValue: value); final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return PhScreen(phValueNpk1: value1, phValueNpk2: value2);
}, },
); );
} }

View File

@ -8,28 +8,33 @@ class AppTextfield extends StatelessWidget {
required this.controller, required this.controller,
this.hintText = 'Enter Here', this.hintText = 'Enter Here',
this.suffixIcon, this.suffixIcon,
this.obscureText = false,
}); });
final TextEditingController controller; final TextEditingController controller;
final String hintText; final String hintText;
final Widget? suffixIcon; final Widget? suffixIcon;
final bool obscureText;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return TextField(
controller: controller, controller: controller,
decoration: InputDecoration( decoration: InputDecoration(
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColor.textDisable, width: 1), borderSide: const BorderSide(color: AppColor.textDisable, width: 1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColor.primary, width: 1), borderSide: const BorderSide(color: AppColor.primary, width: 1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
hintText: hintText, hintText: hintText,
hintStyle: AppTheme.hintStyle, hintStyle: AppTheme.hintStyle,
suffixIcon: (suffixIcon != null) ? suffixIcon : null), suffixIcon: (suffixIcon != null) ? suffixIcon : null,
),
onTapOutside: (event) => FocusScope.of(context).unfocus(),
obscureText: obscureText,
); );
} }
} }

View File

@ -1,3 +1,5 @@
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
class DhtGraphicResponse { class DhtGraphicResponse {
DataDht? data; DataDht? data;
int? statusCode; int? statusCode;
@ -44,32 +46,3 @@ class DataDht {
return data; return data;
} }
} }
class Dht {
int? hour;
double? vicitemperatureAvg;
double? vicihumidityAvg;
double? viciluminosityAvg;
Dht(
{this.hour,
this.vicitemperatureAvg,
this.vicihumidityAvg,
this.viciluminosityAvg});
Dht.fromJson(Map<String, dynamic> json) {
hour = json['hour'];
vicitemperatureAvg = json['vicitemperature_avg'];
vicihumidityAvg = json['vicihumidity_avg'];
viciluminosityAvg = json['viciluminosity_avg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['hour'] = hour;
data['vicitemperature_avg'] = vicitemperatureAvg;
data['vicihumidity_avg'] = vicihumidityAvg;
data['viciluminosity_avg'] = viciluminosityAvg;
return data;
}
}

View File

@ -0,0 +1,114 @@
class JwtTokenResponse {
User? user;
int? iat;
int? exp;
JwtTokenResponse({this.user, this.iat, this.exp});
JwtTokenResponse.fromJson(Map<String, dynamic> json) {
user = json['user'] != null ? User.fromJson(json['user']) : null;
iat = json['iat'];
exp = json['exp'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (user != null) {
data['user'] = user!.toJson();
}
data['iat'] = iat;
data['exp'] = exp;
return data;
}
}
class User {
String? id;
String? uroleId;
String? username;
String? email;
String? googleId;
String? fullname;
Null avatar;
bool? isBan;
String? createdAt;
String? updatedAt;
Null deletedAt;
Role? role;
User(
{this.id,
this.uroleId,
this.username,
this.email,
this.googleId,
this.fullname,
this.avatar,
this.isBan,
this.createdAt,
this.updatedAt,
this.deletedAt,
this.role});
User.fromJson(Map<String, dynamic> json) {
id = json['id'];
uroleId = json['urole_id'];
username = json['username'];
email = json['email'];
googleId = json['google_id'];
fullname = json['fullname'];
avatar = json['avatar'];
isBan = json['is_ban'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
deletedAt = json['deleted_at'];
role = json['role'] != null ? Role.fromJson(json['role']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['urole_id'] = uroleId;
data['username'] = username;
data['email'] = email;
data['google_id'] = googleId;
data['fullname'] = fullname;
data['avatar'] = avatar;
data['is_ban'] = isBan;
data['created_at'] = createdAt;
data['updated_at'] = updatedAt;
data['deleted_at'] = deletedAt;
if (role != null) {
data['role'] = role!.toJson();
}
return data;
}
}
class Role {
String? id;
String? code;
String? name;
String? createdAt;
String? updatedAt;
Role({this.id, this.code, this.name, this.createdAt, this.updatedAt});
Role.fromJson(Map<String, dynamic> json) {
id = json['id'];
code = json['code'];
name = json['name'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['code'] = code;
data['name'] = name;
data['created_at'] = createdAt;
data['updated_at'] = updatedAt;
return data;
}
}

View File

@ -0,0 +1,42 @@
class LoginResponse {
Data? data;
int? statusCode;
String? message;
LoginResponse({this.data, this.statusCode, this.message});
LoginResponse.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 {
String? token;
String? jwtToken;
Data({this.token, this.jwtToken});
Data.fromJson(Map<String, dynamic> json) {
token = json['token'];
jwtToken = json['jwtToken'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['token'] = token;
data['jwtToken'] = jwtToken;
return data;
}
}

View File

@ -1,3 +1,5 @@
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
class Npk1GraphicResponse { class Npk1GraphicResponse {
DataNpk1? data; DataNpk1? data;
int? statusCode; int? statusCode;
@ -44,48 +46,3 @@ class DataNpk1 {
return data; return data;
} }
} }
class Npk1 {
int? hour;
double? soiltemperatureAvg;
double? soilhumidityAvg;
double? soilconductivityAvg;
double? soilphAvg;
double? soilnitrogenAvg;
double? soilphosphorusAvg;
double? soilpotassiumAvg;
Npk1(
{this.hour,
this.soiltemperatureAvg,
this.soilhumidityAvg,
this.soilconductivityAvg,
this.soilphAvg,
this.soilnitrogenAvg,
this.soilphosphorusAvg,
this.soilpotassiumAvg});
Npk1.fromJson(Map<String, dynamic> json) {
hour = json['hour'];
soiltemperatureAvg = json['soiltemperature_avg'];
soilhumidityAvg = json['soilhumidity_avg'];
soilconductivityAvg = json['soilconductivity_avg'];
soilphAvg = json['soilph_avg'];
soilnitrogenAvg = json['soilnitrogen_avg'];
soilphosphorusAvg = json['soilphosphorus_avg'];
soilpotassiumAvg = json['soilpotassium_avg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['hour'] = hour;
data['soiltemperature_avg'] = soiltemperatureAvg;
data['soilhumidity_avg'] = soilhumidityAvg;
data['soilconductivity_avg'] = soilconductivityAvg;
data['soilph_avg'] = soilphAvg;
data['soilnitrogen_avg'] = soilnitrogenAvg;
data['soilphosphorus_avg'] = soilphosphorusAvg;
data['soilpotassium_avg'] = soilpotassiumAvg;
return data;
}
}

View File

@ -1,3 +1,5 @@
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
class Npk2GraphicResponse { class Npk2GraphicResponse {
DataNpk2? data; DataNpk2? data;
int? statusCode; int? statusCode;
@ -44,48 +46,3 @@ class DataNpk2 {
return data; return data;
} }
} }
class Npk2 {
int? hour;
double? soiltemperatureAvg;
double? soilhumidityAvg;
double? soilconductivityAvg;
double? soilphAvg;
double? soilnitrogenAvg;
double? soilphosphorusAvg;
double? soilpotassiumAvg;
Npk2(
{this.hour,
this.soiltemperatureAvg,
this.soilhumidityAvg,
this.soilconductivityAvg,
this.soilphAvg,
this.soilnitrogenAvg,
this.soilphosphorusAvg,
this.soilpotassiumAvg});
Npk2.fromJson(Map<String, dynamic> json) {
hour = json['hour'];
soiltemperatureAvg = json['soiltemperature_avg'];
soilhumidityAvg = json['soilhumidity_avg'];
soilconductivityAvg = json['soilconductivity_avg'];
soilphAvg = json['soilph_avg'];
soilnitrogenAvg = json['soilnitrogen_avg'];
soilphosphorusAvg = json['soilphosphorus_avg'];
soilpotassiumAvg = json['soilpotassium_avg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['hour'] = hour;
data['soiltemperature_avg'] = soiltemperatureAvg;
data['soilhumidity_avg'] = soilhumidityAvg;
data['soilconductivity_avg'] = soilconductivityAvg;
data['soilph_avg'] = soilphAvg;
data['soilnitrogen_avg'] = soilnitrogenAvg;
data['soilphosphorus_avg'] = soilphosphorusAvg;
data['soilpotassium_avg'] = soilpotassiumAvg;
return data;
}
}

View File

@ -1,12 +1,18 @@
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/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/dht_graphic_response.dart';
import 'package:agrilink_vocpro/data/model/jwt_token_response.dart';
import 'package:agrilink_vocpro/data/model/login_response.dart';
import 'package:agrilink_vocpro/data/model/npk1_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/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/data/model/latest_data_response.dart'; import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AppService { class AppService {
final Dio _dioWithoutInterceptor = Dio( final Dio _dioWithoutInterceptor = Dio(
@ -15,10 +21,60 @@ class AppService {
), ),
); );
Future<LoginResponse> login({
required String username,
required String password,
String rememberMe = 'false',
}) async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String basicAuth =
'Basic ${base64Encode(utf8.encode('$username:$password'))}';
FormData formData = FormData.fromMap({
'remember_me': rememberMe,
});
try {
final response = await _dioWithoutInterceptor.post(
'/auth/login',
data: formData,
options: Options(
headers: {
'Authorization': basicAuth,
},
),
);
if (response.statusCode == 200) {
final data = LoginResponse.fromJson(response.data);
final decodedToken =
JwtTokenResponse.fromJson(JwtDecoder.decode(data.data!.jwtToken!));
pref.setString('token', data.data!.token!);
pref.setString('jwtToken', data.data!.jwtToken!);
pref.setString('username', decodedToken.user?.username ?? 'unknown');
pref.setString('email', decodedToken.user?.email ?? 'unknown');
pref.setString('fullName', decodedToken.user?.fullname ?? 'unknown');
pref.setBool('isLoggedIn', true);
return data;
} else {
throw Exception('Failed to load data');
}
} on DioException catch (e) {
final String errorMessage = e.response?.data['message'];
throw (errorMessage);
}
}
Future<RelayResponse> getRelayStatus() async { Future<RelayResponse> getRelayStatus() async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
try { try {
await Future.delayed(const Duration(seconds: 3)); await Future.delayed(const Duration(seconds: 3));
final result = await _dioWithoutInterceptor.get('get-relay'); final result = await _dioWithoutInterceptor.get(
'get-relay',
options: Options(
headers: {'Authorization': auth},
),
);
if (result.statusCode == 200) { if (result.statusCode == 200) {
final data = RelayResponse.fromJson(result.data); final data = RelayResponse.fromJson(result.data);
return data; return data;
@ -38,11 +94,16 @@ class AppService {
Future<DhtGraphicResponse> getGrafikDataDht({ Future<DhtGraphicResponse> getGrafikDataDht({
required String metric, required String metric,
}) async { }) async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
String date = DateTime.now().toString(); String date = DateTime.now().toString();
final formatedDate = dateFormatterShort(date); final formatedDate = dateFormatterShort(date);
try { try {
final result = await _dioWithoutInterceptor.get( final result = await _dioWithoutInterceptor.get(
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=dht', '/api/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=dht',
options: Options(
headers: {'Authorization': auth},
),
); );
if (result.statusCode == 200) { if (result.statusCode == 200) {
final data = DhtGraphicResponse.fromJson(result.data); final data = DhtGraphicResponse.fromJson(result.data);
@ -59,11 +120,16 @@ class AppService {
// get grafik data npk1 // get grafik data npk1
Future<Npk1GraphicResponse> getGraphicDataNpk1( Future<Npk1GraphicResponse> getGraphicDataNpk1(
{required String metric}) async { {required String metric}) async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
String date = DateTime.now().toString(); String date = DateTime.now().toString();
final formatedDate = dateFormatterShort(date); final formatedDate = dateFormatterShort(date);
try { try {
final result = await _dioWithoutInterceptor.get( final result = await _dioWithoutInterceptor.get(
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk1', '/api/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk1',
options: Options(
headers: {'Authorization': auth},
),
); );
if (result.statusCode == 200) { if (result.statusCode == 200) {
final data = Npk1GraphicResponse.fromJson(result.data); final data = Npk1GraphicResponse.fromJson(result.data);
@ -81,11 +147,16 @@ class AppService {
Future<Npk2GraphicResponse> getGraphicDataNpk2( Future<Npk2GraphicResponse> getGraphicDataNpk2(
{required String metric}) async { {required String metric}) async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
String date = DateTime.now().toString(); String date = DateTime.now().toString();
final formatedDate = dateFormatterShort(date); final formatedDate = dateFormatterShort(date);
try { try {
final result = await _dioWithoutInterceptor.get( final result = await _dioWithoutInterceptor.get(
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk2', '/api/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk2',
options: Options(
headers: {'Authorization': auth},
),
); );
if (result.statusCode == 200) { if (result.statusCode == 200) {
final data = Npk2GraphicResponse.fromJson(result.data); final data = Npk2GraphicResponse.fromJson(result.data);
@ -102,9 +173,14 @@ class AppService {
// get latest data // get latest data
Future<LatestDataResponse> getLatestData() async { Future<LatestDataResponse> getLatestData() async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
try { try {
final result = await _dioWithoutInterceptor.get( final result = await _dioWithoutInterceptor.get(
'/sensor/getLatest', '/api/sensor/getLatest',
options: Options(
headers: {'Authorization': auth},
),
); );
if (result.statusCode == 200) { if (result.statusCode == 200) {
final data = LatestDataResponse.fromJson(result.data); final data = LatestDataResponse.fromJson(result.data);

View File

@ -1,12 +1,77 @@
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AuthProvider extends ChangeNotifier { class AuthProvider extends ChangeNotifier {
TextEditingController emailController = TextEditingController(); TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController(); TextEditingController passwordController = TextEditingController();
bool _isRememberMe = false;
bool get isRememberMe => _isRememberMe;
String errorMessage = '';
ResultState loginState = ResultState.initial;
void controllerClear() { void controllerClear() {
emailController.clear(); emailController.clear();
passwordController.clear(); passwordController.clear();
notifyListeners(); notifyListeners();
} }
void setRememberMe(bool value) {
_isRememberMe = value;
notifyListeners();
}
Future<void> login(context,
{required String email, required String password}) async {
loginState = ResultState.loading;
notifyListeners();
try {
final result = await AppService().login(
username: email,
password: password,
rememberMe: isRememberMe.toString(),
);
if (result.data != null) {
loginState = ResultState.hasData;
notifyListeners();
} else {
errorMessage = 'Login gagal, data tidak ditemukan';
loginState = ResultState.error;
notifyListeners();
}
} catch (e) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Error'),
content: Text('$e'),
actions: [
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
loginState = ResultState.error;
notifyListeners();
}
}
bool validateInputs() {
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
errorMessage = 'Email dan password tidak boleh kosong';
loginState = ResultState.error;
notifyListeners();
return false;
}
return true;
}
} }

View File

@ -1,5 +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/route/app_route.dart'; import 'package:agrilink_vocpro/core/route/app_route.dart';
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/core/widgets/app_button.dart'; import 'package:agrilink_vocpro/core/widgets/app_button.dart';
import 'package:agrilink_vocpro/core/widgets/app_textfield.dart'; import 'package:agrilink_vocpro/core/widgets/app_textfield.dart';
import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart'; import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
@ -13,18 +14,15 @@ class LoginScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: GestureDetector( body: SafeArea(
onTap: () { child: Consumer<AuthProvider>(
FocusScope.of(context).unfocus(); builder: (context, authP, child) {
},
child: SafeArea(
child: Consumer<AuthProvider>(builder: (context, authP, child) {
return ListView( return ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
const SizedBox(height: 40), const SizedBox(height: 40),
Text( Text(
'Hello Wellcome back 👋', 'Hello, Welcome back 👋',
style: AppTheme.titleLarge, style: AppTheme.titleLarge,
), ),
Text( Text(
@ -36,7 +34,7 @@ class LoginScreen extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
AppTextfield( AppTextfield(
controller: authP.emailController, controller: authP.emailController,
hintText: 'Masukkan username', hintText: 'Masukkan email',
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text('Password', style: AppTheme.labelLarge), Text('Password', style: AppTheme.labelLarge),
@ -44,19 +42,63 @@ class LoginScreen extends StatelessWidget {
AppTextfield( AppTextfield(
controller: authP.passwordController, controller: authP.passwordController,
hintText: 'Masukkan password', hintText: 'Masukkan password',
obscureText: true,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Row(
children: [
Checkbox(
value: authP.isRememberMe,
onChanged: (value) {
authP.setRememberMe(value!);
},
),
Text('Remember me', style: AppTheme.labelLarge),
],
),
const SizedBox(height: 24), const SizedBox(height: 24),
AppButton( authP.loginState == ResultState.loading
onPressed: () { ? const Center(child: CircularProgressIndicator())
GoRouter.of(context).go(AppRoute.dashboard); : AppButton(
}, onPressed: () async {
text: 'Login'), await authP.login(
context,
email: authP.emailController.text,
password: authP.passwordController.text,
);
if (context.mounted) {
if (authP.loginState == ResultState.hasData) {
context.go(AppRoute.dashboard);
authP.controllerClear();
authP.loginState = ResultState.initial;
}
}
},
text: 'Login',
),
], ],
); );
}), },
), ),
), ),
); );
} }
void _showErrorDialog(BuildContext context, String errorMessage) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: Text(errorMessage),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
),
);
}
} }

View File

@ -0,0 +1,57 @@
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart';
class ConductivityProvider extends ChangeNotifier {
ConductivityProvider() {
getSoilConductivityNpk1Data();
getSoilConductivityNpk2Data();
}
ResultState dataState = ResultState.initial;
List<Npk1> dataFetchedNpk1 = [];
List<Npk2> dataFetchedNpk2 = [];
Future<void> getSoilConductivityNpk1Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk1(metric: 'soilConductivity');
if (result.data == null || result.data!.npk1!.isEmpty) {
dataState = ResultState.noData;
} else {
dataFetchedNpk1 = result.data!.npk1 ?? [];
dataState = ResultState.hasData;
}
} catch (e) {
if (kDebugMode) {
print('Get Grafik Soil Temp Error: $e');
}
dataState = ResultState.error;
}
notifyListeners();
}
Future<void> getSoilConductivityNpk2Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk2(metric: 'soilConductivity');
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();
}
}

View File

@ -1,84 +1,161 @@
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/conductivity/provider/conductivity_provider.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.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:provider/provider.dart';
class ConductivityScreen extends StatelessWidget { class ConductivityScreen extends StatelessWidget {
const ConductivityScreen({super.key, this.conductivity = 0.0}); const ConductivityScreen(
{super.key, this.conductivityNpk1 = 0.0, this.conductivityNpk2 = 0.0});
final double conductivity; final double conductivityNpk1;
double get value => conductivity; final double conductivityNpk2;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ChangeNotifierProvider(
appBar: AppBar( create: (context) => ConductivityProvider(),
title: Text('Conductivity', style: AppTheme.labelMedium), child: DefaultTabController(
centerTitle: true, length: 2,
backgroundColor: Colors.white, child: Scaffold(
scrolledUnderElevation: 0, appBar: AppBar(
actions: const [ title: Text('Soil Conductivity', style: AppTheme.labelMedium),
Padding( centerTitle: true,
padding: EdgeInsets.only(right: 16), backgroundColor: Colors.white,
child: Icon( scrolledUnderElevation: 0,
Icons.electric_bolt_rounded, actions: const [
color: Colors.teal, Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
Icons.electric_bolt_rounded,
color: Colors.green,
),
)
],
bottom: const TabBar(
tabs: [
Tab(text: 'NPK 1'),
Tab(text: 'NPK 2'),
],
), ),
) ),
body: TabBarView(
children: [
buildTabContent(context, conductivityNpk1, 'NPK 1', true),
buildTabContent(context, conductivityNpk2, 'NPK 2', false),
],
),
),
),
);
}
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),
buildSoilInfo(context, value),
SizedBox(height: 16.h),
buildInfoRow(context),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
buildGraphicContent(context, isNpk1),
], ],
), ),
body: Center( );
child: ListView( }
padding: EdgeInsets.all(16.w),
children: [ Widget buildSoilInfo(BuildContext context, double value) {
SizedBox(height: 32.h), return Center(
Column( child: Column(
children: [ children: [
Icon( Icon(
Icons.electric_bolt_rounded, Icons.electric_bolt_rounded,
size: 64.r, size: 64.r,
color: Colors.teal, color: Colors.teal,
), ),
Text('$value µS/cm', style: AppTheme.headline1), Text('$value µS/cm', style: AppTheme.headline1),
], ],
), ),
SizedBox(height: 32.h), );
Row( }
mainAxisAlignment: MainAxisAlignment.center,
children: [ Widget buildInfoRow(BuildContext context) {
Text( return Row(
'Daya Arus Listrik', mainAxisAlignment: MainAxisAlignment.center,
style: AppTheme.labelMedium, children: [
textAlign: TextAlign.center, Text(
), 'Soil Condutivity',
IconButton( style: AppTheme.labelMedium,
iconSize: 20.r, textAlign: TextAlign.center,
color: Colors.blue, ),
onPressed: () {}, IconButton(
icon: const Icon(BootstrapIcons.info_circle)) iconSize: 20.r,
], color: Colors.blue,
), onPressed: () {},
SizedBox(height: 16.h), icon: const Icon(BootstrapIcons.info_circle),
const Text('Grafik'), ),
SizedBox(height: 16.h), ],
AspectRatio( );
aspectRatio: 1.6.h, }
child: Container(
decoration: BoxDecoration( Widget buildGraphicContent(BuildContext context, bool isNpk1) {
color: Colors.white, return AspectRatio(
borderRadius: BorderRadius.circular(16.w), aspectRatio: 1.6.h,
border: Border.all(color: Colors.grey.shade300, width: 1.w), child: Container(
), decoration: BoxDecoration(
child: const GarphicWidget( color: Colors.white,
gradientColors: [ borderRadius: BorderRadius.circular(16.w),
Colors.cyan, border: Border.all(color: Colors.grey.shade300, width: 1.w),
Colors.teal, ),
], child: Consumer<ConductivityProvider>(
), 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.teal, Colors.greenAccent],
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].soilconductivityAvg ??
0
: provider.dataFetchedNpk2[index].soilconductivityAvg ??
0,
),
maxValue: 1,
);
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();
}
},
), ),
), ),
); );

View File

@ -1,5 +1,5 @@
import 'package:agrilink_vocpro/core/state/result_state.dart'; import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/dht_graphic_response.dart'; import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart'; import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';

View File

@ -1,5 +1,5 @@
import 'package:agrilink_vocpro/core/state/result_state.dart'; import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/dht_graphic_response.dart'; import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart'; import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -16,7 +16,7 @@ class LumProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
final result = final result =
await AppService().getGrafikDataDht(metric: 'viciLuminosity'); await AppService().getGrafikDataDht(metric: 'viciluminosity');
if (result.data == null || result.data!.dht!.isEmpty) { if (result.data == null || result.data!.dht!.isEmpty) {
dataState = ResultState.noData; dataState = ResultState.noData;
} else { } else {

View File

@ -0,0 +1,57 @@
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart';
class NitrogenProvider extends ChangeNotifier {
NitrogenProvider() {
getSoilNitrogenNpk1Data();
getSoilNitrogenNpk2Data();
}
ResultState dataState = ResultState.initial;
List<Npk1> dataFetchedNpk1 = [];
List<Npk2> dataFetchedNpk2 = [];
Future<void> getSoilNitrogenNpk1Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk1(metric: 'soilNitrogen');
if (result.data == null || result.data!.npk1!.isEmpty) {
dataState = ResultState.noData;
} else {
dataFetchedNpk1 = result.data!.npk1 ?? [];
dataState = ResultState.hasData;
}
} catch (e) {
if (kDebugMode) {
print('Get Grafik Soil Temp Error: $e');
}
dataState = ResultState.error;
}
notifyListeners();
}
Future<void> getSoilNitrogenNpk2Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk2(metric: 'soilNitrogen');
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();
}
}

View File

@ -1,85 +1,159 @@
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/nitrogen/provider/nitrogen_provider.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart'; import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
import 'package:bootstrap_icons/bootstrap_icons.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
class NitrogenScreen extends StatelessWidget { class NitrogenScreen extends StatelessWidget {
const NitrogenScreen({super.key, this.nitrogen = 0.0}); const NitrogenScreen(
{super.key, this.nitrogenNpk1 = 0.0, this.nitrogenNpk2 = 0.0});
final double nitrogen; final double nitrogenNpk1;
double get value => nitrogen; final double nitrogenNpk2;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ChangeNotifierProvider(
appBar: AppBar( create: (context) => NitrogenProvider(),
title: Text('Nitrogen', style: AppTheme.labelMedium), child: DefaultTabController(
centerTitle: true, length: 2,
backgroundColor: Colors.white, child: Scaffold(
scrolledUnderElevation: 0, appBar: AppBar(
actions: const [ title: Text('Soil Nitrogen', style: AppTheme.labelMedium),
Padding( centerTitle: true,
padding: EdgeInsets.only(right: 16), backgroundColor: Colors.white,
child: Icon( scrolledUnderElevation: 0,
CupertinoIcons.eyedropper, actions: const [
color: Colors.blue, Padding(
), padding: EdgeInsets.only(right: 16),
) child: Icon(
], BootstrapIcons.eyedropper,
),
body: Center(
child: ListView(
padding: EdgeInsets.all(16.w),
children: [
SizedBox(height: 32.h),
Column(
children: [
Icon(
CupertinoIcons.eyedropper,
size: 64.r,
color: Colors.blue, color: Colors.blue,
), ),
Text('$value ppm', style: AppTheme.headline1), )
],
bottom: const TabBar(
tabs: [
Tab(text: 'NPK 1'),
Tab(text: 'NPK 2'),
], ],
), ),
SizedBox(height: 32.h), ),
Row( body: TabBarView(
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ buildTabContent(context, nitrogenNpk1, 'NPK 1', true),
Text( buildTabContent(context, nitrogenNpk2, 'NPK 2', false),
'Nitrogen', ],
style: AppTheme.labelMedium, ),
textAlign: TextAlign.center, ),
), ),
IconButton( );
iconSize: 20.r, }
color: Colors.blue,
onPressed: () {}, SafeArea buildTabContent(
icon: const Icon(BootstrapIcons.info_circle)) BuildContext context, double value, String label, bool isNpk1) {
], return SafeArea(
), child: ListView(
SizedBox(height: 16.h), padding: EdgeInsets.all(16.w),
const Text('Grafik'), children: [
SizedBox(height: 16.h), SizedBox(height: MediaQuery.of(context).size.height * 0.05),
AspectRatio( buildSoilInfo(context, value),
aspectRatio: 1.6.h, SizedBox(height: 16.h),
child: Container( buildInfoRow(context),
decoration: BoxDecoration( SizedBox(height: 16.h),
color: Colors.white, const Text('Grafik'),
borderRadius: BorderRadius.circular(16.w), SizedBox(height: 16.h),
border: Border.all(color: Colors.grey.shade300, width: 1.w), buildGraphicContent(context, isNpk1),
), ],
child: GarphicWidget( ),
gradientColors: [ );
Colors.blue.shade200, }
Colors.blue,
], Widget buildSoilInfo(BuildContext context, double value) {
), return Center(
), child: Column(
) children: [
], Icon(
CupertinoIcons.eyedropper,
size: 64.r,
color: Colors.blue,
),
Text('$value ppm', style: AppTheme.headline1),
],
),
);
}
Widget buildInfoRow(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Soil Nitrogen',
style: AppTheme.labelMedium,
textAlign: TextAlign.center,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle),
),
],
);
}
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<NitrogenProvider>(
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.blue, Colors.blueAccent],
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].soilnitrogenAvg ?? 0
: provider.dataFetchedNpk2[index].soilnitrogenAvg ?? 0,
),
maxValue: 1,
);
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();
}
},
), ),
), ),
); );

View File

@ -0,0 +1,55 @@
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart';
class PhProvider extends ChangeNotifier {
PhProvider() {
getSoilPhNpk1Data();
getSoilPhNpk2Data();
}
ResultState dataState = ResultState.initial;
List<Npk1> dataFetchedNpk1 = [];
List<Npk2> dataFetchedNpk2 = [];
Future<void> getSoilPhNpk1Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result = await AppService().getGraphicDataNpk1(metric: 'soilPh');
if (result.data == null || result.data!.npk1!.isEmpty) {
dataState = ResultState.noData;
} else {
dataFetchedNpk1 = result.data!.npk1 ?? [];
dataState = ResultState.hasData;
}
} catch (e) {
if (kDebugMode) {
print('Get Grafik Soil Temp Error: $e');
}
dataState = ResultState.error;
}
notifyListeners();
}
Future<void> getSoilPhNpk2Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result = await AppService().getGraphicDataNpk2(metric: 'soilPh');
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();
}
}

View File

@ -1,181 +1,150 @@
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/ph/provider/ph_provider.dart';
import 'package:agrilink_vocpro/features/home/pages/ph/widget/ph_bar_pointer.dart'; import 'package:agrilink_vocpro/features/home/pages/ph/widget/ph_bar_pointer.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.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:provider/provider.dart';
class PhScreen extends StatelessWidget { class PhScreen extends StatelessWidget {
const PhScreen({super.key, required this.phValue}); const PhScreen({super.key, this.phValueNpk1 = 0, this.phValueNpk2 = 0});
final double phValue; final double phValueNpk1;
final double phValueNpk2;
double get value => phValue;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ChangeNotifierProvider(
appBar: AppBar( create: (context) => PhProvider(),
title: Text('pH Tanah', style: AppTheme.labelMedium), child: DefaultTabController(
centerTitle: true, length: 2,
backgroundColor: Colors.white, child: Scaffold(
scrolledUnderElevation: 0, appBar: AppBar(
actions: const [ title: Text('Soil Temperature', style: AppTheme.labelMedium),
Padding( centerTitle: true,
padding: EdgeInsets.only(right: 16), backgroundColor: Colors.white,
child: Icon( scrolledUnderElevation: 0,
BootstrapIcons.pie_chart, actions: const [
color: Colors.orange, Padding(
), padding: EdgeInsets.only(right: 16),
) 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,
),
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, bottom: const TabBar(
onPressed: () {}, tabs: [
icon: const Icon(BootstrapIcons.info_circle)) Tab(text: 'NPK 1'),
Tab(text: 'NPK 2'),
], ],
), ),
SizedBox(height: 16.h), ),
// Row( body: TabBarView(
// mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
// children: [ buildTabContent(context, phValueNpk1, 'NPK 1', true),
// Container( buildTabContent(context, phValueNpk2, 'NPK 2', false),
// 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, SafeArea buildTabContent(
// ), BuildContext context, double value, String label, bool isNpk1) {
// ), return SafeArea(
// child: Column( child: ListView(
// mainAxisAlignment: MainAxisAlignment.center, padding: EdgeInsets.all(16.w),
// children: [ children: [
// Text('Low', SizedBox(height: MediaQuery.of(context).size.height * 0.05),
// style: AppTheme.labelMedium buildSoilInfo(context, value),
// .copyWith(color: Colors.blue)), SizedBox(height: 16.h),
// // SizedBox(height: 8.h), buildInfoRow(context),
// // const Icon( SizedBox(height: 16.h),
// // BootstrapIcons.thermometer_low, const Text('Grafik'),
// // color: Colors.blue, SizedBox(height: 16.h),
// // ), buildGraphicContent(context, isNpk1),
// SizedBox(height: 8.h), ],
// Text( ),
// '<20°C', );
// style: AppTheme.labelMedium, }
// textAlign: TextAlign.center,
// ), Widget buildSoilInfo(BuildContext context, double value) {
// ], return Center(
// ), child: PhIndicator(phValue: value),
// ), );
// Container( }
// height: 100.h,
// width: 100.w, Widget buildInfoRow(BuildContext context) {
// decoration: BoxDecoration( return Row(
// color: Colors.green.withOpacity(0.1), mainAxisAlignment: MainAxisAlignment.center,
// borderRadius: BorderRadius.circular(16), children: [
// border: Border.all( Text(
// color: Colors.green, 'Soil Acidity',
// width: 2, style: AppTheme.labelMedium,
// ), textAlign: TextAlign.center,
// ), ),
// child: Column( IconButton(
// mainAxisAlignment: MainAxisAlignment.center, iconSize: 20.r,
// children: [ color: Colors.blue,
// Text('Ideal', onPressed: () {},
// style: AppTheme.labelMedium icon: const Icon(BootstrapIcons.info_circle),
// .copyWith(color: Colors.green)), ),
// // SizedBox(height: 8.h), ],
// // const Icon( );
// // BootstrapIcons.thermometer_half, }
// // color: Colors.green,
// // ), Widget buildGraphicContent(BuildContext context, bool isNpk1) {
// SizedBox(height: 8.h), return AspectRatio(
// Text( aspectRatio: 1.6.h,
// '20-30°C', child: Container(
// style: AppTheme.labelMedium, decoration: BoxDecoration(
// textAlign: TextAlign.center, color: Colors.white,
// ), borderRadius: BorderRadius.circular(16.w),
// ], border: Border.all(color: Colors.grey.shade300, width: 1.w),
// ), ),
// ), child: Consumer<PhProvider>(
// Container( builder: (context, provider, child) {
// height: 100.h, final dataState = provider.dataState;
// width: 100.w,
// decoration: BoxDecoration( switch (dataState) {
// color: Colors.orange.withOpacity(0.1), case ResultState.loading:
// borderRadius: BorderRadius.circular(16), return const Center(child: CupertinoActivityIndicator());
// border: Border.all( case ResultState.hasData:
// color: Colors.orange.shade800, return GarphicWidget(
// width: 2, gradientColors: const [Colors.cyan, Colors.amber],
// ), hour: List.generate(
// ), isNpk1
// child: Column( ? provider.dataFetchedNpk1.length
// mainAxisAlignment: MainAxisAlignment.center, : provider.dataFetchedNpk2.length,
// children: [ (index) => isNpk1
// Text('high', ? provider.dataFetchedNpk1[index].hour ?? 0
// style: AppTheme.labelMedium : provider.dataFetchedNpk2[index].hour ?? 0,
// .copyWith(color: Colors.orange)), ),
// // SizedBox(height: 8.h), data: List.generate(
// // const Icon( isNpk1
// // BootstrapIcons.thermometer_high, ? provider.dataFetchedNpk1.length
// // color: Colors.orange, : provider.dataFetchedNpk2.length,
// // ), (index) => isNpk1
// SizedBox(height: 8.h), ? provider.dataFetchedNpk1[index].soilphAvg ?? 0
// Text( : provider.dataFetchedNpk2[index].soilphAvg ?? 0,
// '>30°C', ),
// style: AppTheme.labelMedium, maxValue: 14,
// textAlign: TextAlign.center, );
// ), case ResultState.error:
// ], return const GraphicErrorWidget(message: 'Terjadi Kesalahan');
// ), case ResultState.noData:
// ), return const GraphicErrorWidget(message: 'Tidak Ada Data');
// ], case ResultState.initial:
// ), default:
// SizedBox(height: 16.h), return const SizedBox.shrink();
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: GarphicWidget(
gradientColors: [
Colors.amber.shade200,
Colors.orange,
],
),
),
)
],
), ),
), ),
); );

View File

@ -0,0 +1,57 @@
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart';
class PhosporusProvider extends ChangeNotifier {
PhosporusProvider() {
getSoilPhosporNpk1Data();
getSoilPhosporNpk2Data();
}
ResultState dataState = ResultState.initial;
List<Npk1> dataFetchedNpk1 = [];
List<Npk2> dataFetchedNpk2 = [];
Future<void> getSoilPhosporNpk1Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk1(metric: 'soilPhosphorus');
if (result.data == null || result.data!.npk1!.isEmpty) {
dataState = ResultState.noData;
} else {
dataFetchedNpk1 = result.data!.npk1 ?? [];
dataState = ResultState.hasData;
}
} catch (e) {
if (kDebugMode) {
print('Get Grafik Soil Temp Error: $e');
}
dataState = ResultState.error;
}
notifyListeners();
}
Future<void> getSoilPhosporNpk2Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk2(metric: 'soilPhosphorus');
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();
}
}

View File

@ -1,85 +1,160 @@
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/phosphorus/provider/phosporus_provider.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart'; import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
import 'package:bootstrap_icons/bootstrap_icons.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
class PhosphorusScreen extends StatelessWidget { class PhosphorusScreen extends StatelessWidget {
const PhosphorusScreen({super.key, this.phosphorus = 0.0}); const PhosphorusScreen(
{super.key, this.phosphorusNpk1 = 0.0, this.phosphorusNpk2 = 0.0});
final double phosphorus; final double phosphorusNpk1;
double get value => phosphorus; final double phosphorusNpk2;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ChangeNotifierProvider(
appBar: AppBar( create: (context) => PhosporusProvider(),
title: Text('Phosphorus', style: AppTheme.labelMedium), child: DefaultTabController(
centerTitle: true, length: 2,
backgroundColor: Colors.white, child: Scaffold(
scrolledUnderElevation: 0, appBar: AppBar(
actions: const [ title: Text('Soil Phosphorus', style: AppTheme.labelMedium),
Padding( centerTitle: true,
padding: EdgeInsets.only(right: 16), backgroundColor: Colors.white,
child: Icon( scrolledUnderElevation: 0,
CupertinoIcons.eyedropper, actions: const [
color: Colors.blue, Padding(
), padding: EdgeInsets.only(right: 16),
) child: Icon(
], BootstrapIcons.eyedropper,
),
body: Center(
child: ListView(
padding: EdgeInsets.all(16.w),
children: [
SizedBox(height: 32.h),
Column(
children: [
Icon(
CupertinoIcons.eyedropper,
size: 64.r,
color: Colors.blue, color: Colors.blue,
), ),
Text('$value ppm', style: AppTheme.headline1), )
],
bottom: const TabBar(
tabs: [
Tab(text: 'NPK 1'),
Tab(text: 'NPK 2'),
], ],
), ),
SizedBox(height: 32.h), ),
Row( body: TabBarView(
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ buildTabContent(context, phosphorusNpk1, 'NPK 1', true),
Text( buildTabContent(context, phosphorusNpk2, 'NPK 2', false),
'Fosfor', ],
style: AppTheme.labelMedium, ),
textAlign: TextAlign.center, ),
), ),
IconButton( );
iconSize: 20.r, }
color: Colors.blue,
onPressed: () {}, SafeArea buildTabContent(
icon: const Icon(BootstrapIcons.info_circle)) BuildContext context, double value, String label, bool isNpk1) {
], return SafeArea(
), child: ListView(
SizedBox(height: 16.h), padding: EdgeInsets.all(16.w),
const Text('Grafik'), children: [
SizedBox(height: 16.h), SizedBox(height: MediaQuery.of(context).size.height * 0.05),
AspectRatio( buildSoilInfo(context, value),
aspectRatio: 1.6.h, SizedBox(height: 16.h),
child: Container( buildInfoRow(context),
decoration: BoxDecoration( SizedBox(height: 16.h),
color: Colors.white, const Text('Grafik'),
borderRadius: BorderRadius.circular(16.w), SizedBox(height: 16.h),
border: Border.all(color: Colors.grey.shade300, width: 1.w), buildGraphicContent(context, isNpk1),
), ],
child: GarphicWidget( ),
gradientColors: [ );
Colors.blue.shade200, }
Colors.blue,
], Widget buildSoilInfo(BuildContext context, double value) {
), return Center(
), child: Column(
) children: [
], Icon(
CupertinoIcons.eyedropper,
size: 64.r,
color: Colors.blue,
),
Text('$value ppm', style: AppTheme.headline1),
],
),
);
}
Widget buildInfoRow(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Soil Phosphorus',
style: AppTheme.labelMedium,
textAlign: TextAlign.center,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle),
),
],
);
}
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<PhosporusProvider>(
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.blue, Colors.blueAccent],
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].soilphosphorusAvg ?? 0
: provider.dataFetchedNpk2[index].soilphosphorusAvg ??
0,
),
maxValue: 10,
);
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();
}
},
), ),
), ),
); );

View File

@ -0,0 +1,57 @@
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart';
class PotassiumProvider extends ChangeNotifier {
PotassiumProvider() {
getSoilPotassiumNpk1Data();
getSoilPotassiumNpk2Data();
}
ResultState dataState = ResultState.initial;
List<Npk1> dataFetchedNpk1 = [];
List<Npk2> dataFetchedNpk2 = [];
Future<void> getSoilPotassiumNpk1Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk1(metric: 'soilPotassium');
if (result.data == null || result.data!.npk1!.isEmpty) {
dataState = ResultState.noData;
} else {
dataFetchedNpk1 = result.data!.npk1 ?? [];
dataState = ResultState.hasData;
}
} catch (e) {
if (kDebugMode) {
print('Get Grafik Soil Temp Error: $e');
}
dataState = ResultState.error;
}
notifyListeners();
}
Future<void> getSoilPotassiumNpk2Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk2(metric: 'soilPotassium');
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();
}
}

View File

@ -1,85 +1,159 @@
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/potassium/provider/potassium_provider.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart'; import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
import 'package:bootstrap_icons/bootstrap_icons.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
class PotassiumScreen extends StatelessWidget { class PotassiumScreen extends StatelessWidget {
const PotassiumScreen({super.key, this.potassium = 0.0}); const PotassiumScreen(
{super.key, this.potassiumNpk1 = 0.0, this.potassiumNpk2 = 0.0});
final double potassium; final double potassiumNpk1;
double get value => potassium; final double potassiumNpk2;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ChangeNotifierProvider(
appBar: AppBar( create: (context) => PotassiumProvider(),
title: Text('Potassium', style: AppTheme.labelMedium), child: DefaultTabController(
centerTitle: true, length: 2,
backgroundColor: Colors.white, child: Scaffold(
scrolledUnderElevation: 0, appBar: AppBar(
actions: const [ title: Text('Soil Potassium', style: AppTheme.labelMedium),
Padding( centerTitle: true,
padding: EdgeInsets.only(right: 16), backgroundColor: Colors.white,
child: Icon( scrolledUnderElevation: 0,
CupertinoIcons.eyedropper, actions: const [
color: Colors.green, Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
BootstrapIcons.eyedropper,
color: Colors.red,
),
)
],
bottom: const TabBar(
tabs: [
Tab(text: 'NPK 1'),
Tab(text: 'NPK 2'),
],
), ),
) ),
body: TabBarView(
children: [
buildTabContent(context, potassiumNpk1, 'NPK 1', true),
buildTabContent(context, potassiumNpk2, 'NPK 2', false),
],
),
),
),
);
}
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),
buildSoilInfo(context, value),
SizedBox(height: 16.h),
buildInfoRow(context),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
buildGraphicContent(context, isNpk1),
], ],
), ),
body: Center( );
child: ListView( }
padding: EdgeInsets.all(16.w),
children: [ Widget buildSoilInfo(BuildContext context, double value) {
SizedBox(height: 32.h), return Center(
Column( child: Column(
children: [ children: [
Icon( Icon(
CupertinoIcons.eyedropper, CupertinoIcons.eyedropper,
size: 64.r, size: 64.r,
color: Colors.green, color: Colors.red,
), ),
Text('$value ppm', style: AppTheme.headline1), Text('$value ppm', style: AppTheme.headline1),
], ],
), ),
SizedBox(height: 32.h), );
Row( }
mainAxisAlignment: MainAxisAlignment.center,
children: [ Widget buildInfoRow(BuildContext context) {
Text( return Row(
'Kalium', mainAxisAlignment: MainAxisAlignment.center,
style: AppTheme.labelMedium, children: [
textAlign: TextAlign.center, Text(
), 'Soil Potassium',
IconButton( style: AppTheme.labelMedium,
iconSize: 20.r, textAlign: TextAlign.center,
color: Colors.blue, ),
onPressed: () {}, IconButton(
icon: const Icon(BootstrapIcons.info_circle)) iconSize: 20.r,
], color: Colors.blue,
), onPressed: () {},
SizedBox(height: 16.h), icon: const Icon(BootstrapIcons.info_circle),
const Text('Grafik'), ),
SizedBox(height: 16.h), ],
AspectRatio( );
aspectRatio: 1.6.h, }
child: Container(
decoration: BoxDecoration( Widget buildGraphicContent(BuildContext context, bool isNpk1) {
color: Colors.white, return AspectRatio(
borderRadius: BorderRadius.circular(16.w), aspectRatio: 1.6.h,
border: Border.all(color: Colors.grey.shade300, width: 1.w), child: Container(
), decoration: BoxDecoration(
child: const GarphicWidget( color: Colors.white,
gradientColors: [ borderRadius: BorderRadius.circular(16.w),
Colors.teal, border: Border.all(color: Colors.grey.shade300, width: 1.w),
Colors.green, ),
], child: Consumer<PotassiumProvider>(
), 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.red, Colors.orange],
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].soilpotassiumAvg ?? 0
: provider.dataFetchedNpk2[index].soilpotassiumAvg ?? 0,
),
maxValue: 1,
);
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();
}
},
), ),
), ),
); );

View File

@ -0,0 +1,57 @@
import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart';
class SoilMoistureProvider extends ChangeNotifier {
SoilMoistureProvider() {
getSoilMosNpk1Data();
getSoilMosNpk2Data();
}
ResultState dataState = ResultState.initial;
List<Npk1> dataFetchedNpk1 = [];
List<Npk2> dataFetchedNpk2 = [];
Future<void> getSoilMosNpk1Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk1(metric: 'soilhumidity');
if (result.data == null || result.data!.npk1!.isEmpty) {
dataState = ResultState.noData;
} else {
dataFetchedNpk1 = result.data!.npk1 ?? [];
dataState = ResultState.hasData;
}
} catch (e) {
if (kDebugMode) {
print('Get Grafik Soil Temp Error: $e');
}
dataState = ResultState.error;
}
notifyListeners();
}
Future<void> getSoilMosNpk2Data() async {
dataState = ResultState.loading;
notifyListeners();
try {
final result =
await AppService().getGraphicDataNpk2(metric: 'soilhumidity');
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();
}
}

View File

@ -1,128 +1,189 @@
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/widgets/show_info.dart'; import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/features/home/pages/soil_moisture/provider/soil_moisture_provider.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart'; import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
import 'package:bootstrap_icons/bootstrap_icons.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:gauge_indicator/gauge_indicator.dart'; import 'package:gauge_indicator/gauge_indicator.dart';
import 'package:provider/provider.dart';
class SoilMoistureScreen extends StatelessWidget { class SoilMoistureScreen extends StatelessWidget {
const SoilMoistureScreen({super.key, this.moisture = 0}); const SoilMoistureScreen(
{super.key, this.moistureNpk1 = 0, this.moistureNpk2 = 0});
final double moisture; final double moistureNpk1;
double get value => moisture; final double moistureNpk2;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ChangeNotifierProvider(
appBar: AppBar( create: (context) => SoilMoistureProvider(),
title: Text('Soil Moisture', style: AppTheme.labelMedium), child: DefaultTabController(
centerTitle: true, length: 2,
backgroundColor: Colors.white, child: Scaffold(
leading: IconButton( appBar: AppBar(
icon: const Icon(CupertinoIcons.back), title: Text('Soil Moisture', style: AppTheme.labelMedium),
onPressed: () => Navigator.pop(context), centerTitle: true,
), backgroundColor: Colors.white,
actions: const [ scrolledUnderElevation: 0,
Padding( actions: const [
padding: EdgeInsets.only(right: 16), Padding(
child: Icon( padding: EdgeInsets.only(right: 16),
Icons.water_outlined, child: Icon(
color: Colors.blue, BootstrapIcons.water,
), color: Colors.green,
)
],
),
body: SafeArea(
child: ListView(
padding: EdgeInsets.all(16.w),
children: [
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('$moisture %', style: AppTheme.headline1),
],
),
),
RotatedBox(
quarterTurns: 2,
child: AnimatedRadialGauge(
duration: const Duration(seconds: 3),
curve: Curves.easeOut,
value: value,
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,
]),
),
),
),
),
],
),
),
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, bottom: const TabBar(
onPressed: () { tabs: [
showInfo( Tab(text: 'NPK 1'),
context, Tab(text: 'NPK 2'),
'Soil Temperature',
AppConstant.soilTempInfo,
);
},
icon: const Icon(BootstrapIcons.info_circle))
], ],
), ),
SizedBox(height: 16.h), ),
const Text('Grafik'), body: TabBarView(
SizedBox(height: 16.h), children: [
AspectRatio( buildTabContent(context, moistureNpk1, 'NPK 1', true),
aspectRatio: 1.8.h, buildTabContent(context, moistureNpk2, 'NPK 2', false),
child: Container( ],
decoration: BoxDecoration( ),
color: Colors.white, ),
borderRadius: BorderRadius.circular(16.w), ),
border: Border.all(color: Colors.grey.shade300, width: 1.w), );
}
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),
buildSoilMoistureInfo(context, value),
SizedBox(height: 16.h),
buildInfoRow(context),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
buildGraphicContent(context, isNpk1),
],
),
);
}
Widget buildSoilMoistureInfo(BuildContext context, double value) {
return 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('$value %', style: AppTheme.headline1),
],
),
),
RotatedBox(
quarterTurns: 2,
child: AnimatedRadialGauge(
duration: const Duration(seconds: 3),
curve: Curves.easeOut,
value: value,
axis: GaugeAxis(
degrees: 360,
min: 0,
max: 100,
pointer: null,
style: GaugeAxisStyle(
background: Colors.grey.shade100,
thickness: 50,
), ),
child: GarphicWidget( progressBar: GaugeBasicProgressBar(
gradientColors: [ gradient: GaugeAxisGradient(colors: [
Colors.blue.shade200, Colors.blue.shade200,
Colors.blue, Colors.blue,
], ]),
), ),
), ),
) ),
], ),
],
),
);
}
Widget buildInfoRow(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Soil Moisture',
style: AppTheme.labelMedium,
textAlign: TextAlign.center,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle),
),
],
);
}
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<SoilMoistureProvider>(
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.blue],
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].soilhumidityAvg ?? 0
: provider.dataFetchedNpk2[index].soilhumidityAvg ?? 0,
),
maxValue: 1,
);
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();
}
},
), ),
), ),
); );

View File

@ -1,6 +1,5 @@
import 'package:agrilink_vocpro/core/state/result_state.dart'; import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/npk1_graphic_response.dart'; import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/data/model/npk2_graphic_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart'; import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';

View File

@ -225,6 +225,7 @@ class SoilTemperatureScreen extends StatelessWidget {
: provider.dataFetchedNpk2[index].soiltemperatureAvg ?? : provider.dataFetchedNpk2[index].soiltemperatureAvg ??
0, 0,
), ),
maxValue: 70,
); );
case ResultState.error: case ResultState.error:
return const GraphicErrorWidget(message: 'Terjadi Kesalahan'); return const GraphicErrorWidget(message: 'Terjadi Kesalahan');

View File

@ -1,5 +1,5 @@
import 'package:agrilink_vocpro/core/state/result_state.dart'; import 'package:agrilink_vocpro/core/state/result_state.dart';
import 'package:agrilink_vocpro/data/model/dht_graphic_response.dart'; import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:agrilink_vocpro/domain/service/app_service.dart'; import 'package:agrilink_vocpro/domain/service/app_service.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';

View File

@ -98,7 +98,7 @@ class HomeProvider extends ChangeNotifier {
if (kDebugMode) { if (kDebugMode) {
print('Get Latest Error: $e'); print('Get Latest Error: $e');
} }
dataState = ResultState.hasData; dataState = ResultState.error;
notifyListeners(); notifyListeners();
} }
} }

View File

@ -5,11 +5,17 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
class GarphicWidget extends StatelessWidget { class GarphicWidget extends StatelessWidget {
const GarphicWidget( const GarphicWidget(
{super.key, required this.gradientColors, this.data, this.hour}); {super.key,
required this.gradientColors,
this.data,
this.hour,
this.maxValue = 100});
final List<Color> gradientColors; final List<Color> gradientColors;
final List<num>? hour; final List<num>? hour;
final List<num>? data; final List<num>? data;
final double maxValue;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
@ -50,16 +56,6 @@ class GarphicWidget extends StatelessWidget {
); );
} }
int getMaxValue() {
int max = 0;
for (int i = 0; i < data!.length; i++) {
if (data![i] > max) {
max = data![i].toInt();
}
}
return max;
}
LineChartData mainData() { LineChartData mainData() {
return LineChartData( return LineChartData(
gridData: const FlGridData( gridData: const FlGridData(
@ -92,7 +88,7 @@ class GarphicWidget extends StatelessWidget {
minX: 0, minX: 0,
maxX: 24, maxX: 24,
minY: 0, minY: 0,
maxY: data == null ? 0 : getMaxValue().toDouble(), maxY: data == null ? 0 : maxValue,
lineBarsData: [ lineBarsData: [
LineChartBarData( LineChartBarData(
spots: data == null && hour == null spots: data == null && hour == null

View File

@ -71,7 +71,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
censorIdentifier: censorIdentifier, censorIdentifier: censorIdentifier,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}'); '${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -83,7 +83,8 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white, color: Colors.white,
censorIdentifier: censorIdentifier, censorIdentifier: censorIdentifier,
onTap: () async { onTap: () async {
context.push('${AppRoute.ph}/${provider.npk1SoilPh}'); context.push(
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -95,8 +96,8 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white, color: Colors.white,
censorIdentifier: censorIdentifier, censorIdentifier: censorIdentifier,
onTap: () async { onTap: () async {
await context await context.push(
.push('${AppRoute.conductivity}/${provider.npk1SoilEc}'); '${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -108,7 +109,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white, color: Colors.white,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.nitrogen}/${provider.npk1SoilNitrogen}'); '${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -120,7 +121,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white, color: Colors.white,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.potassium}/${provider.npk1SoilPotassium}'); '${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -132,7 +133,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white, color: Colors.white,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.phosphorus}/${provider.npk1SoilPhosphorus}'); '${AppRoute.phosphorus}/${provider.npk1SoilPhosphorus}/${provider.npk2SoilPhosphorus}');
}, },
), ),
], ],

View File

@ -58,7 +58,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
censorIdentifier: censorIdentifier, censorIdentifier: censorIdentifier,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.soilTemperature}/${provider.npk2Temperature}'); '${AppRoute.soilTemperature}/${provider.npk1Temperature}/${provider.npk2Temperature}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -71,7 +71,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
censorIdentifier: censorIdentifier, censorIdentifier: censorIdentifier,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.soilMoisture}/${provider.npk2SoilMoisture}'); '${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -83,7 +83,8 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white, color: Colors.white,
censorIdentifier: censorIdentifier, censorIdentifier: censorIdentifier,
onTap: () async { onTap: () async {
context.push('${AppRoute.ph}/${provider.npk2SoilPh}'); context.push(
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -95,8 +96,8 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white, color: Colors.white,
censorIdentifier: censorIdentifier, censorIdentifier: censorIdentifier,
onTap: () async { onTap: () async {
await context await context.push(
.push('${AppRoute.conductivity}/${provider.npk2SoilEc}'); '${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -108,7 +109,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white, color: Colors.white,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}'); '${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -120,7 +121,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white, color: Colors.white,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.potassium}/${provider.npk2SoilPotassium}'); '${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
}, },
), ),
DataDisplayerWidget( DataDisplayerWidget(
@ -132,7 +133,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white, color: Colors.white,
onTap: () async { onTap: () async {
await context.push( await context.push(
'${AppRoute.phosphorus}/${provider.npk2SoilPhosphorus}'); '${AppRoute.phosphorus}/${provider.npk1SoilPhosphorus}/${provider.npk2SoilPhosphorus}');
}, },
), ),
], ],

View File

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingProvider extends ChangeNotifier {
SettingProvider() {
_init();
}
String userFullName = '';
String userEmail = '';
void _init() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
userFullName = prefs.getString('fullName') ?? 'unknown';
userEmail = prefs.getString('email') ?? 'unknown';
notifyListeners();
}
}

View File

@ -1,10 +1,12 @@
import 'package:agrilink_vocpro/core/constant/app_color.dart'; import 'package:agrilink_vocpro/core/constant/app_color.dart';
import 'package:agrilink_vocpro/core/constant/app_theme.dart'; import 'package:agrilink_vocpro/core/constant/app_theme.dart';
import 'package:agrilink_vocpro/core/route/app_route.dart'; import 'package:agrilink_vocpro/core/route/app_route.dart';
import 'package:agrilink_vocpro/features/setting/provider/setting_provider.dart';
import 'package:bootstrap_icons/bootstrap_icons.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
class SettingScreen extends StatelessWidget { class SettingScreen extends StatelessWidget {
const SettingScreen({super.key}); const SettingScreen({super.key});
@ -30,13 +32,15 @@ class SettingScreen extends StatelessWidget {
child: Icon(BootstrapIcons.person_fill, color: Colors.white), child: Icon(BootstrapIcons.person_fill, color: Colors.white),
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Column( Consumer<SettingProvider>(builder: (context, provider, child) {
crossAxisAlignment: CrossAxisAlignment.start, return Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Text('User Name', style: AppTheme.labelMedium), children: [
Text('useremail@gmail.com', style: AppTheme.labelSmall) Text(provider.userFullName, style: AppTheme.labelMedium),
], Text(provider.userEmail, style: AppTheme.labelSmall)
) ],
);
})
], ],
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),

View File

@ -1,8 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:agrilink_vocpro/core/route/app_route.dart'; import 'package:agrilink_vocpro/core/route/app_route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SplashScreen extends StatefulWidget { class SplashScreen extends StatefulWidget {
const SplashScreen({super.key}); const SplashScreen({super.key});
@ -21,33 +24,36 @@ class _SplashScreenState extends State<SplashScreen> {
} }
Future<void> _initialize() async { Future<void> _initialize() async {
// final authProvider = Provider.of<AuthProvider>(context, listen: false); bool isLoggedIn = await _checkLoginStatus();
// bool isLoggedIn = await _checkLoginStatus(authProvider);
_navigateAfterSplash(isLoggedIn); _navigateAfterSplash(isLoggedIn);
} }
// Future<bool> _checkLoginStatus(AuthProvider authProvider) async { Future<bool> _checkLoginStatus() async {
// SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
// if (prefs.getKeys().isEmpty) return false; if (prefs.getKeys().isEmpty) return false;
// if (prefs.getBool('isLoggedIn') == true) { if (prefs.getBool('isLoggedIn') == true) {
// String? token = prefs.getString('token'); String? token = prefs.getString('jwtToken');
// String? refreshToken = prefs.getString('refreshToken');
// if (token != null && !JwtDecoder.isExpired(token)) { if (token != null && !JwtDecoder.isExpired(token)) {
// return true; if (kDebugMode) {
// } else if (refreshToken != null && !JwtDecoder.isExpired(refreshToken)) { print('Token : ${prefs.getString('token')}');
// final result = await authProvider.refreshToken(); }
// return result == ResultState.hasData; return true;
// } else { } else {
// prefs.remove('token'); prefs.remove('token');
// return false; prefs.remove('jwtToken');
// } prefs.remove('username');
// } prefs.remove('email');
prefs.remove('fullName');
prefs.remove('isLoggedIn');
return false;
}
}
// return false; return false;
// } }
void _navigateAfterSplash(bool isLoggedIn) { void _navigateAfterSplash(bool isLoggedIn) {
Timer(const Duration(seconds: 2), () { Timer(const Duration(seconds: 2), () {

View File

@ -3,6 +3,7 @@ import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart'; import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
import 'package:agrilink_vocpro/features/dashboard/provider/dashboard_provider.dart'; import 'package:agrilink_vocpro/features/dashboard/provider/dashboard_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/setting/provider/setting_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -29,6 +30,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (context) => HomeProvider()), ChangeNotifierProvider(create: (context) => HomeProvider()),
ChangeNotifierProvider(create: (context) => DashboardProvider()), ChangeNotifierProvider(create: (context) => DashboardProvider()),
ChangeNotifierProvider(create: (context) => ControlProvider()), ChangeNotifierProvider(create: (context) => ControlProvider()),
ChangeNotifierProvider(create: (context) => SettingProvider()),
], ],
child: ScreenUtilInit( child: ScreenUtilInit(
designSize: const Size(360, 800), designSize: const Size(360, 800),

View File

@ -240,6 +240,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0" version: "0.19.0"
jwt_decoder:
dependency: "direct main"
description:
name: jwt_decoder
sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:

View File

@ -47,6 +47,7 @@ dependencies:
gauge_indicator: ^0.4.3 gauge_indicator: ^0.4.3
mqtt_client: ^10.5.1 mqtt_client: ^10.5.1
shimmer: ^3.0.0 shimmer: ^3.0.0
jwt_decoder: ^2.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: