From a2dc5c4b3fea245cb7f154802c22a50b086976b4 Mon Sep 17 00:00:00 2001 From: Syaroful Date: Wed, 16 Oct 2024 08:34:13 +0700 Subject: [PATCH] refactor: change all detail screen on npk sensor --- .../lib/core/constant/app_constant.dart | 2 +- agrilink_vocpro/lib/core/route/app_route.dart | 66 ++-- .../lib/core/widgets/app_textfield.dart | 27 +- .../lib/data/model/dht_graphic_response.dart | 31 +- .../lib/data/model/jwt_token_response.dart | 114 +++++++ .../lib/data/model/login_response.dart | 42 +++ .../lib/data/model/npk1_graphic_response.dart | 47 +-- .../lib/data/model/npk2_graphic_response.dart | 47 +-- .../lib/domain/service/app_service.dart | 86 ++++- .../features/auth/provider/auth_provider.dart | 65 ++++ .../lib/features/auth/view/login_screen.dart | 70 ++++- .../provider/conductivity_provider.dart | 57 ++++ .../view/conductivity_screen.dart | 211 +++++++++---- .../humidity/provider/humidity_provider.dart | 2 +- .../luminosity/provider/lum_provider.dart | 4 +- .../nitrogen/provider/nitrogen_provider.dart | 57 ++++ .../pages/nitrogen/view/nitrogen_screen.dart | 206 ++++++++---- .../home/pages/ph/provider/ph_provider.dart | 55 ++++ .../home/pages/ph/view/ph_screen.dart | 295 ++++++++---------- .../provider/phosporus_provider.dart | 57 ++++ .../phosphorus/view/phosphorus_screen.dart | 207 ++++++++---- .../provider/potassium_provider.dart | 57 ++++ .../potassium/view/potassium_screen.dart | 208 ++++++++---- .../provider/soil_moisture_provider.dart | 57 ++++ .../view/soil_moisture_screen.dart | 269 ++++++++++------ .../provider/soil_temp_provider.dart | 3 +- .../view/soil_temperature_screen.dart | 1 + .../temperature/provider/temp_provider.dart | 2 +- .../features/home/provider/home_provider.dart | 2 +- .../features/home/widgets/graphic_widget.dart | 20 +- .../widgets/list_data_from_censor_npk1.dart | 15 +- .../widgets/list_data_from_censor_npk2.dart | 17 +- .../setting/provider/setting_provider.dart | 18 ++ .../features/setting/view/setting_screen.dart | 18 +- .../features/splash/view/splash_screen.dart | 46 +-- agrilink_vocpro/lib/main.dart | 2 + agrilink_vocpro/pubspec.lock | 8 + agrilink_vocpro/pubspec.yaml | 1 + 38 files changed, 1724 insertions(+), 768 deletions(-) create mode 100644 agrilink_vocpro/lib/data/model/jwt_token_response.dart create mode 100644 agrilink_vocpro/lib/data/model/login_response.dart create mode 100644 agrilink_vocpro/lib/features/home/pages/conductivity/provider/conductivity_provider.dart create mode 100644 agrilink_vocpro/lib/features/home/pages/nitrogen/provider/nitrogen_provider.dart create mode 100644 agrilink_vocpro/lib/features/home/pages/ph/provider/ph_provider.dart create mode 100644 agrilink_vocpro/lib/features/home/pages/phosphorus/provider/phosporus_provider.dart create mode 100644 agrilink_vocpro/lib/features/home/pages/potassium/provider/potassium_provider.dart create mode 100644 agrilink_vocpro/lib/features/home/pages/soil_moisture/provider/soil_moisture_provider.dart create mode 100644 agrilink_vocpro/lib/features/setting/provider/setting_provider.dart diff --git a/agrilink_vocpro/lib/core/constant/app_constant.dart b/agrilink_vocpro/lib/core/constant/app_constant.dart index 0aa7d73..e39365c 100644 --- a/agrilink_vocpro/lib/core/constant/app_constant.dart +++ b/agrilink_vocpro/lib/core/constant/app_constant.dart @@ -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'; diff --git a/agrilink_vocpro/lib/core/route/app_route.dart b/agrilink_vocpro/lib/core/route/app_route.dart index d6ad20f..8c2d740 100644 --- a/agrilink_vocpro/lib/core/route/app_route.dart +++ b/agrilink_vocpro/lib/core/route/app_route.dart @@ -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); }, ); } diff --git a/agrilink_vocpro/lib/core/widgets/app_textfield.dart b/agrilink_vocpro/lib/core/widgets/app_textfield.dart index bac5ea9..f3930f8 100644 --- a/agrilink_vocpro/lib/core/widgets/app_textfield.dart +++ b/agrilink_vocpro/lib/core/widgets/app_textfield.dart @@ -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, ); } } diff --git a/agrilink_vocpro/lib/data/model/dht_graphic_response.dart b/agrilink_vocpro/lib/data/model/dht_graphic_response.dart index 39cda05..d798dd5 100644 --- a/agrilink_vocpro/lib/data/model/dht_graphic_response.dart +++ b/agrilink_vocpro/lib/data/model/dht_graphic_response.dart @@ -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 json) { - hour = json['hour']; - vicitemperatureAvg = json['vicitemperature_avg']; - vicihumidityAvg = json['vicihumidity_avg']; - viciluminosityAvg = json['viciluminosity_avg']; - } - - Map toJson() { - final Map data = {}; - data['hour'] = hour; - data['vicitemperature_avg'] = vicitemperatureAvg; - data['vicihumidity_avg'] = vicihumidityAvg; - data['viciluminosity_avg'] = viciluminosityAvg; - return data; - } -} diff --git a/agrilink_vocpro/lib/data/model/jwt_token_response.dart b/agrilink_vocpro/lib/data/model/jwt_token_response.dart new file mode 100644 index 0000000..f29f714 --- /dev/null +++ b/agrilink_vocpro/lib/data/model/jwt_token_response.dart @@ -0,0 +1,114 @@ +class JwtTokenResponse { + User? user; + int? iat; + int? exp; + + JwtTokenResponse({this.user, this.iat, this.exp}); + + JwtTokenResponse.fromJson(Map json) { + user = json['user'] != null ? User.fromJson(json['user']) : null; + iat = json['iat']; + exp = json['exp']; + } + + Map toJson() { + final Map data = {}; + 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 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 toJson() { + final Map data = {}; + 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 json) { + id = json['id']; + code = json['code']; + name = json['name']; + createdAt = json['created_at']; + updatedAt = json['updated_at']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['code'] = code; + data['name'] = name; + data['created_at'] = createdAt; + data['updated_at'] = updatedAt; + return data; + } +} diff --git a/agrilink_vocpro/lib/data/model/login_response.dart b/agrilink_vocpro/lib/data/model/login_response.dart new file mode 100644 index 0000000..d7efc69 --- /dev/null +++ b/agrilink_vocpro/lib/data/model/login_response.dart @@ -0,0 +1,42 @@ +class LoginResponse { + Data? data; + int? statusCode; + String? message; + + LoginResponse({this.data, this.statusCode, this.message}); + + LoginResponse.fromJson(Map json) { + data = json['data'] != null ? Data.fromJson(json['data']) : null; + statusCode = json['statusCode']; + message = json['message']; + } + + Map toJson() { + final Map data = {}; + 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 json) { + token = json['token']; + jwtToken = json['jwtToken']; + } + + Map toJson() { + final Map data = {}; + data['token'] = token; + data['jwtToken'] = jwtToken; + return data; + } +} diff --git a/agrilink_vocpro/lib/data/model/npk1_graphic_response.dart b/agrilink_vocpro/lib/data/model/npk1_graphic_response.dart index c159658..ed931c3 100644 --- a/agrilink_vocpro/lib/data/model/npk1_graphic_response.dart +++ b/agrilink_vocpro/lib/data/model/npk1_graphic_response.dart @@ -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 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 toJson() { - final Map data = {}; - 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; - } -} diff --git a/agrilink_vocpro/lib/data/model/npk2_graphic_response.dart b/agrilink_vocpro/lib/data/model/npk2_graphic_response.dart index 58302a3..f23f481 100644 --- a/agrilink_vocpro/lib/data/model/npk2_graphic_response.dart +++ b/agrilink_vocpro/lib/data/model/npk2_graphic_response.dart @@ -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 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 toJson() { - final Map data = {}; - 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; - } -} diff --git a/agrilink_vocpro/lib/domain/service/app_service.dart b/agrilink_vocpro/lib/domain/service/app_service.dart index a889706..faac3ad 100644 --- a/agrilink_vocpro/lib/domain/service/app_service.dart +++ b/agrilink_vocpro/lib/domain/service/app_service.dart @@ -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 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 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 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 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 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 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); diff --git a/agrilink_vocpro/lib/features/auth/provider/auth_provider.dart b/agrilink_vocpro/lib/features/auth/provider/auth_provider.dart index 2896c42..fa97d50 100644 --- a/agrilink_vocpro/lib/features/auth/provider/auth_provider.dart +++ b/agrilink_vocpro/lib/features/auth/provider/auth_provider.dart @@ -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 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; + } } diff --git a/agrilink_vocpro/lib/features/auth/view/login_screen.dart b/agrilink_vocpro/lib/features/auth/view/login_screen.dart index 73f5797..00f3bd2 100644 --- a/agrilink_vocpro/lib/features/auth/view/login_screen.dart +++ b/agrilink_vocpro/lib/features/auth/view/login_screen.dart @@ -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(builder: (context, authP, child) { + body: SafeArea( + child: Consumer( + 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'), + ), + ], + ), + ); + } } diff --git a/agrilink_vocpro/lib/features/home/pages/conductivity/provider/conductivity_provider.dart b/agrilink_vocpro/lib/features/home/pages/conductivity/provider/conductivity_provider.dart new file mode 100644 index 0000000..97665d9 --- /dev/null +++ b/agrilink_vocpro/lib/features/home/pages/conductivity/provider/conductivity_provider.dart @@ -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 dataFetchedNpk1 = []; + List dataFetchedNpk2 = []; + + Future 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 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(); + } +} diff --git a/agrilink_vocpro/lib/features/home/pages/conductivity/view/conductivity_screen.dart b/agrilink_vocpro/lib/features/home/pages/conductivity/view/conductivity_screen.dart index 3eb7ae7..3653ca5 100644 --- a/agrilink_vocpro/lib/features/home/pages/conductivity/view/conductivity_screen.dart +++ b/agrilink_vocpro/lib/features/home/pages/conductivity/view/conductivity_screen.dart @@ -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( + 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(); + } + }, ), ), ); diff --git a/agrilink_vocpro/lib/features/home/pages/humidity/provider/humidity_provider.dart b/agrilink_vocpro/lib/features/home/pages/humidity/provider/humidity_provider.dart index c1981eb..36a2050 100644 --- a/agrilink_vocpro/lib/features/home/pages/humidity/provider/humidity_provider.dart +++ b/agrilink_vocpro/lib/features/home/pages/humidity/provider/humidity_provider.dart @@ -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'; diff --git a/agrilink_vocpro/lib/features/home/pages/luminosity/provider/lum_provider.dart b/agrilink_vocpro/lib/features/home/pages/luminosity/provider/lum_provider.dart index dc0ea2d..ccad851 100644 --- a/agrilink_vocpro/lib/features/home/pages/luminosity/provider/lum_provider.dart +++ b/agrilink_vocpro/lib/features/home/pages/luminosity/provider/lum_provider.dart @@ -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 { diff --git a/agrilink_vocpro/lib/features/home/pages/nitrogen/provider/nitrogen_provider.dart b/agrilink_vocpro/lib/features/home/pages/nitrogen/provider/nitrogen_provider.dart new file mode 100644 index 0000000..3f638f8 --- /dev/null +++ b/agrilink_vocpro/lib/features/home/pages/nitrogen/provider/nitrogen_provider.dart @@ -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 dataFetchedNpk1 = []; + List dataFetchedNpk2 = []; + + Future 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 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(); + } +} diff --git a/agrilink_vocpro/lib/features/home/pages/nitrogen/view/nitrogen_screen.dart b/agrilink_vocpro/lib/features/home/pages/nitrogen/view/nitrogen_screen.dart index 9b99e91..fb7493a 100644 --- a/agrilink_vocpro/lib/features/home/pages/nitrogen/view/nitrogen_screen.dart +++ b/agrilink_vocpro/lib/features/home/pages/nitrogen/view/nitrogen_screen.dart @@ -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( + 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(); + } + }, ), ), ); diff --git a/agrilink_vocpro/lib/features/home/pages/ph/provider/ph_provider.dart b/agrilink_vocpro/lib/features/home/pages/ph/provider/ph_provider.dart new file mode 100644 index 0000000..0bcc6b0 --- /dev/null +++ b/agrilink_vocpro/lib/features/home/pages/ph/provider/ph_provider.dart @@ -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 dataFetchedNpk1 = []; + List dataFetchedNpk2 = []; + + Future 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 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(); + } +} diff --git a/agrilink_vocpro/lib/features/home/pages/ph/view/ph_screen.dart b/agrilink_vocpro/lib/features/home/pages/ph/view/ph_screen.dart index a8c7517..f6264de 100644 --- a/agrilink_vocpro/lib/features/home/pages/ph/view/ph_screen.dart +++ b/agrilink_vocpro/lib/features/home/pages/ph/view/ph_screen.dart @@ -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( + 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(); + } + }, ), ), ); diff --git a/agrilink_vocpro/lib/features/home/pages/phosphorus/provider/phosporus_provider.dart b/agrilink_vocpro/lib/features/home/pages/phosphorus/provider/phosporus_provider.dart new file mode 100644 index 0000000..0e6b343 --- /dev/null +++ b/agrilink_vocpro/lib/features/home/pages/phosphorus/provider/phosporus_provider.dart @@ -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 dataFetchedNpk1 = []; + List dataFetchedNpk2 = []; + + Future 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 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(); + } +} diff --git a/agrilink_vocpro/lib/features/home/pages/phosphorus/view/phosphorus_screen.dart b/agrilink_vocpro/lib/features/home/pages/phosphorus/view/phosphorus_screen.dart index e77cc61..e76f14e 100644 --- a/agrilink_vocpro/lib/features/home/pages/phosphorus/view/phosphorus_screen.dart +++ b/agrilink_vocpro/lib/features/home/pages/phosphorus/view/phosphorus_screen.dart @@ -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( + 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(); + } + }, ), ), ); diff --git a/agrilink_vocpro/lib/features/home/pages/potassium/provider/potassium_provider.dart b/agrilink_vocpro/lib/features/home/pages/potassium/provider/potassium_provider.dart new file mode 100644 index 0000000..7c3abbf --- /dev/null +++ b/agrilink_vocpro/lib/features/home/pages/potassium/provider/potassium_provider.dart @@ -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 dataFetchedNpk1 = []; + List dataFetchedNpk2 = []; + + Future 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 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(); + } +} diff --git a/agrilink_vocpro/lib/features/home/pages/potassium/view/potassium_screen.dart b/agrilink_vocpro/lib/features/home/pages/potassium/view/potassium_screen.dart index 6be5a5a..31deb97 100644 --- a/agrilink_vocpro/lib/features/home/pages/potassium/view/potassium_screen.dart +++ b/agrilink_vocpro/lib/features/home/pages/potassium/view/potassium_screen.dart @@ -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( + 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(); + } + }, ), ), ); diff --git a/agrilink_vocpro/lib/features/home/pages/soil_moisture/provider/soil_moisture_provider.dart b/agrilink_vocpro/lib/features/home/pages/soil_moisture/provider/soil_moisture_provider.dart new file mode 100644 index 0000000..780dde8 --- /dev/null +++ b/agrilink_vocpro/lib/features/home/pages/soil_moisture/provider/soil_moisture_provider.dart @@ -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 dataFetchedNpk1 = []; + List dataFetchedNpk2 = []; + + Future 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 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(); + } +} diff --git a/agrilink_vocpro/lib/features/home/pages/soil_moisture/view/soil_moisture_screen.dart b/agrilink_vocpro/lib/features/home/pages/soil_moisture/view/soil_moisture_screen.dart index 7b2fdc0..f5efe78 100644 --- a/agrilink_vocpro/lib/features/home/pages/soil_moisture/view/soil_moisture_screen.dart +++ b/agrilink_vocpro/lib/features/home/pages/soil_moisture/view/soil_moisture_screen.dart @@ -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( + 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(); + } + }, ), ), ); diff --git a/agrilink_vocpro/lib/features/home/pages/soil_temperature/provider/soil_temp_provider.dart b/agrilink_vocpro/lib/features/home/pages/soil_temperature/provider/soil_temp_provider.dart index eaefd42..f681e7f 100644 --- a/agrilink_vocpro/lib/features/home/pages/soil_temperature/provider/soil_temp_provider.dart +++ b/agrilink_vocpro/lib/features/home/pages/soil_temperature/provider/soil_temp_provider.dart @@ -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'; diff --git a/agrilink_vocpro/lib/features/home/pages/soil_temperature/view/soil_temperature_screen.dart b/agrilink_vocpro/lib/features/home/pages/soil_temperature/view/soil_temperature_screen.dart index 9635963..a0ebe80 100644 --- a/agrilink_vocpro/lib/features/home/pages/soil_temperature/view/soil_temperature_screen.dart +++ b/agrilink_vocpro/lib/features/home/pages/soil_temperature/view/soil_temperature_screen.dart @@ -225,6 +225,7 @@ class SoilTemperatureScreen extends StatelessWidget { : provider.dataFetchedNpk2[index].soiltemperatureAvg ?? 0, ), + maxValue: 70, ); case ResultState.error: return const GraphicErrorWidget(message: 'Terjadi Kesalahan'); diff --git a/agrilink_vocpro/lib/features/home/pages/temperature/provider/temp_provider.dart b/agrilink_vocpro/lib/features/home/pages/temperature/provider/temp_provider.dart index 6a99340..fcea985 100644 --- a/agrilink_vocpro/lib/features/home/pages/temperature/provider/temp_provider.dart +++ b/agrilink_vocpro/lib/features/home/pages/temperature/provider/temp_provider.dart @@ -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'; diff --git a/agrilink_vocpro/lib/features/home/provider/home_provider.dart b/agrilink_vocpro/lib/features/home/provider/home_provider.dart index 3235ea8..546ae60 100644 --- a/agrilink_vocpro/lib/features/home/provider/home_provider.dart +++ b/agrilink_vocpro/lib/features/home/provider/home_provider.dart @@ -98,7 +98,7 @@ class HomeProvider extends ChangeNotifier { if (kDebugMode) { print('Get Latest Error: $e'); } - dataState = ResultState.hasData; + dataState = ResultState.error; notifyListeners(); } } diff --git a/agrilink_vocpro/lib/features/home/widgets/graphic_widget.dart b/agrilink_vocpro/lib/features/home/widgets/graphic_widget.dart index da1b498..3bbf334 100644 --- a/agrilink_vocpro/lib/features/home/widgets/graphic_widget.dart +++ b/agrilink_vocpro/lib/features/home/widgets/graphic_widget.dart @@ -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 gradientColors; final List? hour; final List? 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 diff --git a/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk1.dart b/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk1.dart index 593b0cb..dce8afc 100644 --- a/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk1.dart +++ b/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk1.dart @@ -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}'); }, ), ], diff --git a/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk2.dart b/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk2.dart index 9fb46a4..eae8708 100644 --- a/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk2.dart +++ b/agrilink_vocpro/lib/features/home/widgets/list_data_from_censor_npk2.dart @@ -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}'); }, ), ], diff --git a/agrilink_vocpro/lib/features/setting/provider/setting_provider.dart b/agrilink_vocpro/lib/features/setting/provider/setting_provider.dart new file mode 100644 index 0000000..6096f1f --- /dev/null +++ b/agrilink_vocpro/lib/features/setting/provider/setting_provider.dart @@ -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(); + } +} diff --git a/agrilink_vocpro/lib/features/setting/view/setting_screen.dart b/agrilink_vocpro/lib/features/setting/view/setting_screen.dart index ffcef03..f8fa92d 100644 --- a/agrilink_vocpro/lib/features/setting/view/setting_screen.dart +++ b/agrilink_vocpro/lib/features/setting/view/setting_screen.dart @@ -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(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), diff --git a/agrilink_vocpro/lib/features/splash/view/splash_screen.dart b/agrilink_vocpro/lib/features/splash/view/splash_screen.dart index b5e41bb..828d917 100644 --- a/agrilink_vocpro/lib/features/splash/view/splash_screen.dart +++ b/agrilink_vocpro/lib/features/splash/view/splash_screen.dart @@ -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 { } Future _initialize() async { - // final authProvider = Provider.of(context, listen: false); - // bool isLoggedIn = await _checkLoginStatus(authProvider); + bool isLoggedIn = await _checkLoginStatus(); _navigateAfterSplash(isLoggedIn); } - // Future _checkLoginStatus(AuthProvider authProvider) async { - // SharedPreferences prefs = await SharedPreferences.getInstance(); + Future _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), () { diff --git a/agrilink_vocpro/lib/main.dart b/agrilink_vocpro/lib/main.dart index a4413dd..6cfef83 100644 --- a/agrilink_vocpro/lib/main.dart +++ b/agrilink_vocpro/lib/main.dart @@ -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), diff --git a/agrilink_vocpro/pubspec.lock b/agrilink_vocpro/pubspec.lock index bba9259..a03d989 100644 --- a/agrilink_vocpro/pubspec.lock +++ b/agrilink_vocpro/pubspec.lock @@ -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: diff --git a/agrilink_vocpro/pubspec.yaml b/agrilink_vocpro/pubspec.yaml index 0d701b3..d5cd44b 100644 --- a/agrilink_vocpro/pubspec.yaml +++ b/agrilink_vocpro/pubspec.yaml @@ -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: