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 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 mqttUsername = 'obyskxhx:obyskxhx';

View File

@ -81,55 +81,71 @@ class AppRoute {
static GoRoute buildPotassiumRoute() {
return GoRoute(
path: 'potassium/:value',
path: 'potassium/:value1/:value2',
builder: (context, state) {
final double value =
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
return PotassiumScreen(potassium: value);
final double value1 =
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return PotassiumScreen(potassiumNpk1: value1, potassiumNpk2: value2);
},
);
}
static GoRoute buildPhosphorusRoute() {
return GoRoute(
path: 'phosphorus/:value',
path: 'phosphorus/:value1/:value2',
builder: (context, state) {
final double value =
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
return PhosphorusScreen(phosphorus: value);
final double value1 =
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return PhosphorusScreen(
phosphorusNpk1: value1,
phosphorusNpk2: value2,
);
},
);
}
static GoRoute buildNitrogenRoute() {
return GoRoute(
path: 'nitrogen/:value',
path: 'nitrogen/:value1/:value2',
builder: (context, state) {
final double value =
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
return NitrogenScreen(nitrogen: value);
final double value1 =
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return NitrogenScreen(nitrogenNpk1: value1, nitrogenNpk2: value2);
},
);
}
static GoRoute buildConductivityRoute() {
return GoRoute(
path: 'conductivity/:value',
path: 'conductivity/:value1/:value2',
builder: (context, state) {
final double value =
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
return ConductivityScreen(conductivity: value);
final double value1 =
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
final double value2 =
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
return ConductivityScreen(
conductivityNpk1: value1,
conductivityNpk2: value2,
);
},
);
}
static GoRoute buildSoilMoistureRoute() {
return GoRoute(
path: 'soil_moisture/:value',
path: 'soil_moisture/:value1/:value2',
builder: (context, state) {
final double value =
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
return SoilMoistureScreen(moisture: value);
final double value1 =
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
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() {
return GoRoute(
path: 'ph/:value',
path: 'ph/:value1/:value2',
builder: (context, state) {
final double value =
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
return PhScreen(phValue: value);
final double value1 =
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
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,
this.hintText = 'Enter Here',
this.suffixIcon,
this.obscureText = false,
});
final TextEditingController controller;
final String hintText;
final Widget? suffixIcon;
final bool obscureText;
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColor.textDisable, width: 1),
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColor.primary, width: 1),
borderRadius: BorderRadius.circular(8),
),
hintText: hintText,
hintStyle: AppTheme.hintStyle,
suffixIcon: (suffixIcon != null) ? suffixIcon : null),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColor.textDisable, width: 1),
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: AppColor.primary, width: 1),
borderRadius: BorderRadius.circular(8),
),
hintText: hintText,
hintStyle: AppTheme.hintStyle,
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 {
DataDht? data;
int? statusCode;
@ -44,32 +46,3 @@ class DataDht {
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 {
DataNpk1? data;
int? statusCode;
@ -44,48 +46,3 @@ class DataNpk1 {
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 {
DataNpk2? data;
int? statusCode;
@ -44,48 +46,3 @@ class DataNpk2 {
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/extension/extention.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/npk2_graphic_response.dart';
import 'package:agrilink_vocpro/data/model/relay_response.dart';
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AppService {
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 {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
try {
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) {
final data = RelayResponse.fromJson(result.data);
return data;
@ -38,11 +94,16 @@ class AppService {
Future<DhtGraphicResponse> getGrafikDataDht({
required String metric,
}) async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
String date = DateTime.now().toString();
final formatedDate = dateFormatterShort(date);
try {
final result = await _dioWithoutInterceptor.get(
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=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) {
final data = DhtGraphicResponse.fromJson(result.data);
@ -59,11 +120,16 @@ class AppService {
// get grafik data npk1
Future<Npk1GraphicResponse> getGraphicDataNpk1(
{required String metric}) async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
String date = DateTime.now().toString();
final formatedDate = dateFormatterShort(date);
try {
final result = await _dioWithoutInterceptor.get(
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk1',
'/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) {
final data = Npk1GraphicResponse.fromJson(result.data);
@ -81,11 +147,16 @@ class AppService {
Future<Npk2GraphicResponse> getGraphicDataNpk2(
{required String metric}) async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
String date = DateTime.now().toString();
final formatedDate = dateFormatterShort(date);
try {
final result = await _dioWithoutInterceptor.get(
'/sensor/getData?metric=$metric&range[start]=$formatedDate&range[end]=$formatedDate&range[time_range]=HOURLY&sensor=npk2',
'/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) {
final data = Npk2GraphicResponse.fromJson(result.data);
@ -102,9 +173,14 @@ class AppService {
// get latest data
Future<LatestDataResponse> getLatestData() async {
final SharedPreferences pref = await SharedPreferences.getInstance();
final String auth = 'Bearer ${pref.getString('token')}';
try {
final result = await _dioWithoutInterceptor.get(
'/sensor/getLatest',
'/api/sensor/getLatest',
options: Options(
headers: {'Authorization': auth},
),
);
if (result.statusCode == 200) {
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';
class AuthProvider extends ChangeNotifier {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool _isRememberMe = false;
bool get isRememberMe => _isRememberMe;
String errorMessage = '';
ResultState loginState = ResultState.initial;
void controllerClear() {
emailController.clear();
passwordController.clear();
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/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_textfield.dart';
import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
@ -13,18 +14,15 @@ class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: SafeArea(
child: Consumer<AuthProvider>(builder: (context, authP, child) {
body: SafeArea(
child: Consumer<AuthProvider>(
builder: (context, authP, child) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
const SizedBox(height: 40),
Text(
'Hello Wellcome back 👋',
'Hello, Welcome back 👋',
style: AppTheme.titleLarge,
),
Text(
@ -36,7 +34,7 @@ class LoginScreen extends StatelessWidget {
const SizedBox(height: 4),
AppTextfield(
controller: authP.emailController,
hintText: 'Masukkan username',
hintText: 'Masukkan email',
),
const SizedBox(height: 24),
Text('Password', style: AppTheme.labelLarge),
@ -44,19 +42,63 @@ class LoginScreen extends StatelessWidget {
AppTextfield(
controller: authP.passwordController,
hintText: 'Masukkan password',
obscureText: true,
),
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),
AppButton(
onPressed: () {
GoRouter.of(context).go(AppRoute.dashboard);
},
text: 'Login'),
authP.loginState == ResultState.loading
? const Center(child: CircularProgressIndicator())
: AppButton(
onPressed: () async {
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/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:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
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;
double get value => conductivity;
final double conductivityNpk1;
final double conductivityNpk2;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Conductivity', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
Icons.electric_bolt_rounded,
color: Colors.teal,
return ChangeNotifierProvider(
create: (context) => ConductivityProvider(),
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Soil Conductivity', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
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: [
SizedBox(height: 32.h),
Column(
children: [
Icon(
Icons.electric_bolt_rounded,
size: 64.r,
color: Colors.teal,
),
Text('$value µS/cm', style: AppTheme.headline1),
],
),
SizedBox(height: 32.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Daya Arus Listrik',
style: AppTheme.labelMedium,
textAlign: TextAlign.center,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle))
],
),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
AspectRatio(
aspectRatio: 1.6.h,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.w),
border: Border.all(color: Colors.grey.shade300, width: 1.w),
),
child: const GarphicWidget(
gradientColors: [
Colors.cyan,
Colors.teal,
],
),
),
)
],
);
}
Widget buildSoilInfo(BuildContext context, double value) {
return Center(
child: Column(
children: [
Icon(
Icons.electric_bolt_rounded,
size: 64.r,
color: Colors.teal,
),
Text('$value µS/cm', style: AppTheme.headline1),
],
),
);
}
Widget buildInfoRow(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Soil Condutivity',
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<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/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:flutter/foundation.dart';

View File

@ -1,5 +1,5 @@
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:flutter/foundation.dart';
@ -16,7 +16,7 @@ class LumProvider extends ChangeNotifier {
notifyListeners();
try {
final result =
await AppService().getGrafikDataDht(metric: 'viciLuminosity');
await AppService().getGrafikDataDht(metric: 'viciluminosity');
if (result.data == null || result.data!.dht!.isEmpty) {
dataState = ResultState.noData;
} 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/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:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
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;
double get value => nitrogen;
final double nitrogenNpk1;
final double nitrogenNpk2;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Nitrogen', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
CupertinoIcons.eyedropper,
color: Colors.blue,
),
)
],
),
body: Center(
child: ListView(
padding: EdgeInsets.all(16.w),
children: [
SizedBox(height: 32.h),
Column(
children: [
Icon(
CupertinoIcons.eyedropper,
size: 64.r,
return ChangeNotifierProvider(
create: (context) => NitrogenProvider(),
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Soil Nitrogen', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
BootstrapIcons.eyedropper,
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(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Nitrogen',
style: AppTheme.labelMedium,
textAlign: TextAlign.center,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle))
],
),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
AspectRatio(
aspectRatio: 1.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.blue.shade200,
Colors.blue,
],
),
),
)
],
),
body: TabBarView(
children: [
buildTabContent(context, nitrogenNpk1, 'NPK 1', true),
buildTabContent(context, nitrogenNpk2, '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),
],
),
);
}
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/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/widgets/graphic_error_widget.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:provider/provider.dart';
class PhScreen extends StatelessWidget {
const PhScreen({super.key, required this.phValue});
const PhScreen({super.key, this.phValueNpk1 = 0, this.phValueNpk2 = 0});
final double phValue;
double get value => phValue;
final double phValueNpk1;
final double phValueNpk2;
@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.pie_chart,
color: Colors.orange,
),
)
],
),
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,
return ChangeNotifierProvider(
create: (context) => PhProvider(),
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Soil Temperature', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
BootstrapIcons.water,
color: Colors.green,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle))
)
],
bottom: const TabBar(
tabs: [
Tab(text: 'NPK 1'),
Tab(text: 'NPK 2'),
],
),
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: GarphicWidget(
gradientColors: [
Colors.amber.shade200,
Colors.orange,
],
),
),
)
],
),
body: TabBarView(
children: [
buildTabContent(context, phValueNpk1, 'NPK 1', true),
buildTabContent(context, phValueNpk2, '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),
],
),
);
}
Widget buildSoilInfo(BuildContext context, double value) {
return Center(
child: PhIndicator(phValue: value),
);
}
Widget buildInfoRow(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Soil Acidity',
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<PhProvider>(
builder: (context, provider, child) {
final dataState = provider.dataState;
switch (dataState) {
case ResultState.loading:
return const Center(child: CupertinoActivityIndicator());
case ResultState.hasData:
return GarphicWidget(
gradientColors: const [Colors.cyan, Colors.amber],
hour: List.generate(
isNpk1
? provider.dataFetchedNpk1.length
: provider.dataFetchedNpk2.length,
(index) => isNpk1
? provider.dataFetchedNpk1[index].hour ?? 0
: provider.dataFetchedNpk2[index].hour ?? 0,
),
data: List.generate(
isNpk1
? provider.dataFetchedNpk1.length
: provider.dataFetchedNpk2.length,
(index) => isNpk1
? provider.dataFetchedNpk1[index].soilphAvg ?? 0
: provider.dataFetchedNpk2[index].soilphAvg ?? 0,
),
maxValue: 14,
);
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 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/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:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
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;
double get value => phosphorus;
final double phosphorusNpk1;
final double phosphorusNpk2;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Phosphorus', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
CupertinoIcons.eyedropper,
color: Colors.blue,
),
)
],
),
body: Center(
child: ListView(
padding: EdgeInsets.all(16.w),
children: [
SizedBox(height: 32.h),
Column(
children: [
Icon(
CupertinoIcons.eyedropper,
size: 64.r,
return ChangeNotifierProvider(
create: (context) => PhosporusProvider(),
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Soil Phosphorus', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
BootstrapIcons.eyedropper,
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(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Fosfor',
style: AppTheme.labelMedium,
textAlign: TextAlign.center,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle))
],
),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
AspectRatio(
aspectRatio: 1.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.blue.shade200,
Colors.blue,
],
),
),
)
],
),
body: TabBarView(
children: [
buildTabContent(context, phosphorusNpk1, 'NPK 1', true),
buildTabContent(context, phosphorusNpk2, '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),
],
),
);
}
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/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:bootstrap_icons/bootstrap_icons.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
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;
double get value => potassium;
final double potassiumNpk1;
final double potassiumNpk2;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Potassium', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
CupertinoIcons.eyedropper,
color: Colors.green,
return ChangeNotifierProvider(
create: (context) => PotassiumProvider(),
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Soil Potassium', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
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: [
SizedBox(height: 32.h),
Column(
children: [
Icon(
CupertinoIcons.eyedropper,
size: 64.r,
color: Colors.green,
),
Text('$value ppm', style: AppTheme.headline1),
],
),
SizedBox(height: 32.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Kalium',
style: AppTheme.labelMedium,
textAlign: TextAlign.center,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {},
icon: const Icon(BootstrapIcons.info_circle))
],
),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
AspectRatio(
aspectRatio: 1.6.h,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.w),
border: Border.all(color: Colors.grey.shade300, width: 1.w),
),
child: const GarphicWidget(
gradientColors: [
Colors.teal,
Colors.green,
],
),
),
)
],
);
}
Widget buildSoilInfo(BuildContext context, double value) {
return Center(
child: Column(
children: [
Icon(
CupertinoIcons.eyedropper,
size: 64.r,
color: Colors.red,
),
Text('$value ppm', style: AppTheme.headline1),
],
),
);
}
Widget buildInfoRow(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Soil Potassium',
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<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/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: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 SoilMoistureScreen extends StatelessWidget {
const SoilMoistureScreen({super.key, this.moisture = 0});
const SoilMoistureScreen(
{super.key, this.moistureNpk1 = 0, this.moistureNpk2 = 0});
final double moisture;
double get value => moisture;
final double moistureNpk1;
final double moistureNpk2;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Soil Moisture', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
leading: IconButton(
icon: const Icon(CupertinoIcons.back),
onPressed: () => Navigator.pop(context),
),
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
Icons.water_outlined,
color: Colors.blue,
),
)
],
),
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,
return ChangeNotifierProvider(
create: (context) => SoilMoistureProvider(),
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Soil Moisture', style: AppTheme.labelMedium),
centerTitle: true,
backgroundColor: Colors.white,
scrolledUnderElevation: 0,
actions: const [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(
BootstrapIcons.water,
color: Colors.green,
),
IconButton(
iconSize: 20.r,
color: Colors.blue,
onPressed: () {
showInfo(
context,
'Soil Temperature',
AppConstant.soilTempInfo,
);
},
icon: const Icon(BootstrapIcons.info_circle))
)
],
bottom: const TabBar(
tabs: [
Tab(text: 'NPK 1'),
Tab(text: 'NPK 2'),
],
),
SizedBox(height: 16.h),
const Text('Grafik'),
SizedBox(height: 16.h),
AspectRatio(
aspectRatio: 1.8.h,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.w),
border: Border.all(color: Colors.grey.shade300, width: 1.w),
),
body: TabBarView(
children: [
buildTabContent(context, moistureNpk1, 'NPK 1', true),
buildTabContent(context, moistureNpk2, '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),
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(
gradientColors: [
progressBar: GaugeBasicProgressBar(
gradient: GaugeAxisGradient(colors: [
Colors.blue.shade200,
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/data/model/npk1_graphic_response.dart';
import 'package:agrilink_vocpro/data/model/npk2_graphic_response.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';

View File

@ -225,6 +225,7 @@ class SoilTemperatureScreen extends StatelessWidget {
: provider.dataFetchedNpk2[index].soiltemperatureAvg ??
0,
),
maxValue: 70,
);
case ResultState.error:
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/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:flutter/foundation.dart';

View File

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

View File

@ -5,11 +5,17 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
class GarphicWidget extends StatelessWidget {
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<num>? hour;
final List<num>? data;
final double maxValue;
@override
Widget build(BuildContext context) {
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() {
return LineChartData(
gridData: const FlGridData(
@ -92,7 +88,7 @@ class GarphicWidget extends StatelessWidget {
minX: 0,
maxX: 24,
minY: 0,
maxY: data == null ? 0 : getMaxValue().toDouble(),
maxY: data == null ? 0 : maxValue,
lineBarsData: [
LineChartBarData(
spots: data == null && hour == null

View File

@ -71,7 +71,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
censorIdentifier: censorIdentifier,
onTap: () async {
await context.push(
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}');
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
},
),
DataDisplayerWidget(
@ -83,7 +83,8 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white,
censorIdentifier: censorIdentifier,
onTap: () async {
context.push('${AppRoute.ph}/${provider.npk1SoilPh}');
context.push(
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
},
),
DataDisplayerWidget(
@ -95,8 +96,8 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white,
censorIdentifier: censorIdentifier,
onTap: () async {
await context
.push('${AppRoute.conductivity}/${provider.npk1SoilEc}');
await context.push(
'${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
},
),
DataDisplayerWidget(
@ -108,7 +109,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white,
onTap: () async {
await context.push(
'${AppRoute.nitrogen}/${provider.npk1SoilNitrogen}');
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
},
),
DataDisplayerWidget(
@ -120,7 +121,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white,
onTap: () async {
await context.push(
'${AppRoute.potassium}/${provider.npk1SoilPotassium}');
'${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
},
),
DataDisplayerWidget(
@ -132,7 +133,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
color: Colors.white,
onTap: () async {
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,
onTap: () async {
await context.push(
'${AppRoute.soilTemperature}/${provider.npk2Temperature}');
'${AppRoute.soilTemperature}/${provider.npk1Temperature}/${provider.npk2Temperature}');
},
),
DataDisplayerWidget(
@ -71,7 +71,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
censorIdentifier: censorIdentifier,
onTap: () async {
await context.push(
'${AppRoute.soilMoisture}/${provider.npk2SoilMoisture}');
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
},
),
DataDisplayerWidget(
@ -83,7 +83,8 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white,
censorIdentifier: censorIdentifier,
onTap: () async {
context.push('${AppRoute.ph}/${provider.npk2SoilPh}');
context.push(
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
},
),
DataDisplayerWidget(
@ -95,8 +96,8 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white,
censorIdentifier: censorIdentifier,
onTap: () async {
await context
.push('${AppRoute.conductivity}/${provider.npk2SoilEc}');
await context.push(
'${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
},
),
DataDisplayerWidget(
@ -108,7 +109,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white,
onTap: () async {
await context.push(
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}');
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
},
),
DataDisplayerWidget(
@ -120,7 +121,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white,
onTap: () async {
await context.push(
'${AppRoute.potassium}/${provider.npk2SoilPotassium}');
'${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
},
),
DataDisplayerWidget(
@ -132,7 +133,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
color: Colors.white,
onTap: () async {
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_theme.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:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
class SettingScreen extends StatelessWidget {
const SettingScreen({super.key});
@ -30,13 +32,15 @@ class SettingScreen extends StatelessWidget {
child: Icon(BootstrapIcons.person_fill, color: Colors.white),
),
SizedBox(width: 8.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('User Name', style: AppTheme.labelMedium),
Text('useremail@gmail.com', style: AppTheme.labelSmall)
],
)
Consumer<SettingProvider>(builder: (context, provider, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(provider.userFullName, style: AppTheme.labelMedium),
Text(provider.userEmail, style: AppTheme.labelSmall)
],
);
})
],
),
SizedBox(height: 16.h),

View File

@ -1,8 +1,11 @@
import 'dart:async';
import 'package:agrilink_vocpro/core/route/app_route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.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 {
const SplashScreen({super.key});
@ -21,33 +24,36 @@ class _SplashScreenState extends State<SplashScreen> {
}
Future<void> _initialize() async {
// final authProvider = Provider.of<AuthProvider>(context, listen: false);
// bool isLoggedIn = await _checkLoginStatus(authProvider);
bool isLoggedIn = await _checkLoginStatus();
_navigateAfterSplash(isLoggedIn);
}
// Future<bool> _checkLoginStatus(AuthProvider authProvider) async {
// SharedPreferences prefs = await SharedPreferences.getInstance();
Future<bool> _checkLoginStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
// if (prefs.getKeys().isEmpty) return false;
if (prefs.getKeys().isEmpty) return false;
// if (prefs.getBool('isLoggedIn') == true) {
// String? token = prefs.getString('token');
// String? refreshToken = prefs.getString('refreshToken');
if (prefs.getBool('isLoggedIn') == true) {
String? token = prefs.getString('jwtToken');
// if (token != null && !JwtDecoder.isExpired(token)) {
// return true;
// } else if (refreshToken != null && !JwtDecoder.isExpired(refreshToken)) {
// final result = await authProvider.refreshToken();
// return result == ResultState.hasData;
// } else {
// prefs.remove('token');
// return false;
// }
// }
if (token != null && !JwtDecoder.isExpired(token)) {
if (kDebugMode) {
print('Token : ${prefs.getString('token')}');
}
return true;
} else {
prefs.remove('token');
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) {
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/dashboard/provider/dashboard_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/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -29,6 +30,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (context) => HomeProvider()),
ChangeNotifierProvider(create: (context) => DashboardProvider()),
ChangeNotifierProvider(create: (context) => ControlProvider()),
ChangeNotifierProvider(create: (context) => SettingProvider()),
],
child: ScreenUtilInit(
designSize: const Size(360, 800),

View File

@ -240,6 +240,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View File

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