Compare commits
10 Commits
04769c4f00
...
fa041950c5
| Author | SHA1 | Date | |
|---|---|---|---|
| fa041950c5 | |||
| bbc4d6a632 | |||
| 0e89909893 | |||
| a2dc5c4b3f | |||
| 426445a865 | |||
| e5b9cf0c4c | |||
| 75eedb8c6b | |||
| 08c7012f1d | |||
| 5ddc748bc9 | |||
| c9ad942a6c |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.idea/
|
||||
162
README.md
162
README.md
|
|
@ -1,93 +1,97 @@
|
|||
# mobile-smartfarming
|
||||
# Agrilink Vocpro (Smart Farming Application)
|
||||
|
||||
A mobile application for managing and monitoring greenhouse conditions with IoT devices using Flutter.
|
||||
|
||||
## Overview
|
||||
This application enables user to monitor greenhouse conditions and control devices like water valves via an IoT interface. The app retrieves data from various sensors (humidity, temperature, etc.) and allows manual control of devices through MQTT.
|
||||
|
||||
## Features
|
||||
- Monitor sensor data (humidity, temperature, light intensity, etc.)
|
||||
- Control IoT devices (water valves, lights)
|
||||
- User authentication and profile management
|
||||
- MQTT integration for real-time data communication (via Rest API)
|
||||
|
||||
## Technology Stack
|
||||
- Flutter (Mobile Framework)
|
||||
- MQTT (IoT communication)
|
||||
- REST API (Backend communication)
|
||||
- GitLab/GitHub for version control
|
||||
|
||||
## Flutter Dependency Stack
|
||||
|
||||
This project uses several packages to support various functionalities. Below is a list of the key dependencies:
|
||||
|
||||
- **[Dio](https://pub.dev/packages/dio)**: A powerful HTTP client for Dart, used for integrating with the API backend.
|
||||
- **[shared_preferences](https://pub.dev/packages/shared_preferences)**: A package for storing simple data persistently across sessions (e.g., token management).
|
||||
- **[intl](https://pub.dev/packages/intl)**: Provides internationalization and localization support, including date formatting and number formatting.
|
||||
- **[go_router](https://pub.dev/packages/go_router)**: A declarative routing package used for managing navigation between screens.
|
||||
- **[bootstrap_icons](https://pub.dev/packages/bootstrap_icons)**: A package that provides Bootstrap icons for UI components.
|
||||
- **[fl_chart](https://pub.dev/packages/fl_chart)**: A powerful chart library for visualizing data in different formats, used for sensor data graphing.
|
||||
- **[flutter_screenutil](https://pub.dev/packages/flutter_screenutil)**: A package to manage screen size responsiveness, ensuring the app looks good on different device resolutions.
|
||||
- **[gauge_indicator](https://pub.dev/packages/gauge_indicator)**: A widget for visualizing sensor data (e.g., humidity, temperature) with gauge indicators.
|
||||
- **[mqtt_client](https://pub.dev/packages/mqtt_client)**: A library used for MQTT integration, enabling real-time communication between the app and IoT devices.
|
||||
|
||||
|
||||
## Installation Guide
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/username/smart-farming.git
|
||||
```
|
||||
2. Navigate to the project directory:
|
||||
```bash
|
||||
cd smart-farming
|
||||
```
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
if it does't work try:
|
||||
```bash
|
||||
flutter outdated
|
||||
//follow with
|
||||
flutter pub upgrade --major-versions
|
||||
```
|
||||
or check your latest dart version make sure it compatible with this project
|
||||
4. Run the project:
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
## Getting started
|
||||
### 6. **Project Structure**
|
||||
Berikan deskripsi singkat tentang struktur folder dan file penting di dalam proyek.
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
## Project Structure
|
||||
- `lib/`: Contains the Flutter app source code.
|
||||
- `core/`: Contains all constant vaalue and data needed for the project.
|
||||
- `data/`: Conatains model and data response
|
||||
- `domain/`: logic and service of the application
|
||||
- `feature/`: Contains every feature in the app
|
||||
- `provider/`
|
||||
- `view`
|
||||
- `widget`
|
||||
- `pubspec.yaml`: Project dependencies and configurations.
|
||||
|
||||
## Add your files
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
## Progress Report
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin https://gitlab.com/profile-image/kedaireka/smartfarming/mobile-smartfarming.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
| Date | Type | Description |
|
||||
|------------|-------|-----------------------------------------------------------------------------------------------------|
|
||||
| 2024-10-09 | feat | Completed the integration of MQTT for sensor data and IoT device control. |
|
||||
| 2024-10-02 | fix | Implemented auto-reconnect for MQTT on app open/close. |
|
||||
| 2024-09-30 | feat | Added UI for displaying soil pH level with a linear bar. |
|
||||
| 2024-09-27 | feat | Finalized group display for 'Doses' in ExpansionTile for medicine dosage recommendations. |
|
||||
| 2024-09-24 | feat | Completed sorting dialog functionality with single sorter selection capability. |
|
||||
| 2024-09-20 | feat | Implemented patient data creation form with validation and state management using dialogs. |
|
||||
|
||||
## Integrate with your tools
|
||||
|
||||
- [ ] [Set up project integrations](https://gitlab.com/profile-image/kedaireka/smartfarming/mobile-smartfarming/-/settings/integrations)
|
||||
|
||||
## Collaborate with your team
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
1. Fork the repository.
|
||||
2. Create a new branch (`git checkout -b feature/your-feature`).
|
||||
3. Commit your changes (`git commit -m 'Add your feature'`).
|
||||
4. Push to the branch (`git push origin feature/your-feature`).
|
||||
5. Open a Pull Request.
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
This project is licensed under the MIT License.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
plugins {
|
||||
id "com.android.application"
|
||||
// START: FlutterFire Configuration
|
||||
id 'com.google.gms.google-services'
|
||||
// END: FlutterFire Configuration
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
|
|
@ -24,7 +27,7 @@ android {
|
|||
applicationId = "com.pis.agrilink_vocpro"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
minSdk = 21
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
|
|
|||
29
agrilink_vocpro/android/app/google-services.json
Normal file
29
agrilink_vocpro/android/app/google-services.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"project_info": {
|
||||
"project_number": "445047869982",
|
||||
"project_id": "agrilink-vocpro-b37f9",
|
||||
"storage_bucket": "agrilink-vocpro-b37f9.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:445047869982:android:d40dc2fd624f86a315540f",
|
||||
"android_client_info": {
|
||||
"package_name": "com.pis.agrilink_vocpro"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCB8i2dE3Oc0kTNtVPw_qSz-T8gPYNjxFk"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@ pluginManagement {
|
|||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||
// END: FlutterFire Configuration
|
||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
agrilink_vocpro/assets/images/coming_soon.png
Normal file
BIN
agrilink_vocpro/assets/images/coming_soon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"type": "service_account",
|
||||
"project_id": "agrilink-vocpro-b37f9",
|
||||
"private_key_id": "e6565344e36c0aaf355068ad83cb904b8c0b7f5f",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC8o5OUuUconoKe\nASbkganjcX7aByEIai3O5XSC5PxKkEzXGgefaHHtNawJO/nhOmsM1Wqd6sBUy8iT\nbH1LI4hKeIS8k/U0n84VgFUnDdScmsgAbC+06BGEncvqElXI6F9LDqdV21qzu7ZC\n4YpkZuTZsoxImaocUIUE0kfIqq4BZc7kkJqaXqYSOSs2wHLUrQT10jezJmw2EXsx\n+cPD2iwjxasP6IaYCylQr+WU+3e5B2+GKjaXPm7p3pLtSNb+rntvBD212R1VDwuT\nZu+Z2t82Hdsm/r+z1RSKqu1pa2hWYFvSiIl1eV+vqCs4MHdGFinhkNZA9o4NCLBV\nixJxiPDtAgMBAAECggEALu9rR7Ce1Cw2Vx51wRrC/MCPPavS6/9RNP6R8gSMAnhr\nagTDu0W3PPxi+Ow3lPLeUlPigna3kpGerxGqDhFB9reMXUhaFRUi52YmKcCu8yUu\n2K4SY6pvO2Ywp6WWuxWYKjpz57CbnYc5xuPLtYYFcGYUsfar5P4izquMaVEQWO/A\nsABd8gnh0oxwigkk1J9c+qtc6ZKwxgSNOeLld4GFw2z9YBqN70QbEvkrB7JA+QdM\nUFL3edSjEazonGdrySbTh/A7l5E31Oft7qS/VpnFyerhGd3AGFU/FVNpohWiSurD\n9O25xt/YIwpMIOxo0/b4jBv2KMLdOr75MV+2tR8iwQKBgQDj9zSMwO6fm3ZfDrkB\nNw2Qba7Tpn5hW5MshyyPVcpiXMuOyZrjsv19IbVHNZ4Hfl6CRQrGWWDs2rNR6+pW\n+zK82HMzrcwl8A7Dn6GCYQt+EHXiPNXlqJy6v83nFD0TU0BgJn4U21KreLyHxriu\nXnJr062bE9k2MRbp3V/HCiwnrQKBgQDT1krQHdHJyVMNDPbU+EHJVQbuJa4HveE+\n4uDW3srH3dzmlbRgji35EA8WOYJQWc2tiDiwuAFwHB0RoI/EGunCgkVT7qDXcbCm\n3MfHJqrlRGamfAmqT+ZrmDN+izrszVJjaUqiv8/4zbUVfiMF7UZkmeVImIa870t4\nrkdnOdIWQQKBgA30mDb1qKYEsl5nXFQCtsXcsRMr4hi7rmrPa57qtZUH8T4wgVbn\nIjWC1sIhOidjSQ3YhCxYVGKrkMa3FoV0o0qERakoPpMj+wNmxlBg+H8jKtRKvR1u\nEQotq8E1r/d65Tn9oUJNmuX8Yi49sBicl4yaTMptkKKMpsZyUuawbLuRAoGALYOw\nZVDS31krR/WOHyoL8HcdWxOrdaxddgaHKoOJ7DGGIeDudYR26jmCFFNc5Wi2IV/Q\nVl/ipzTTJhNqcEccDJeyz5rI4iFiRCfRoL91Hnd42rJx3S73ogx8m+bMTU68MfHQ\ngHFX/OwtySVl3wMhCcJrGhLzeCqt+4rueoZY1kECgYAuN5gouN8YiqtzvrJ+UtUb\n+BqIu0m+DWMBzrJV4391jpnberxTaMVoQIwCMbHYYJ3u8Ur7smFwyrk5zWvTiszx\nNnPfUD4D6BcNbP8+zwNkzEWnfmtQmh5C5V+DrRjxHSgDu/tirzC7WxPnCJlny3eO\nethubLDwgibzNGemftmqXQ==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "firebase-adminsdk-a4f6g@agrilink-vocpro-b37f9.iam.gserviceaccount.com",
|
||||
"client_id": "109510416441723731803",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-a4f6g%40agrilink-vocpro-b37f9.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
3
agrilink_vocpro/devtools_options.yaml
Normal file
3
agrilink_vocpro/devtools_options.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
1
agrilink_vocpro/firebase.json
Normal file
1
agrilink_vocpro/firebase.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"flutter":{"platforms":{"android":{"default":{"projectId":"agrilink-vocpro-b37f9","appId":"1:445047869982:android:d40dc2fd624f86a315540f","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"agrilink-vocpro-b37f9","configurations":{"android":"1:445047869982:android:d40dc2fd624f86a315540f","ios":"1:445047869982:ios:9350ac31c363c40415540f"}}}}}}
|
||||
|
|
@ -2,7 +2,7 @@ class AppConstant {
|
|||
static const String appName = 'Kebun Pintar';
|
||||
static const String appVersion = '1.0.0';
|
||||
|
||||
static const String baseUrl = 'http://192.168.11.41:3333/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';
|
||||
|
|
@ -10,4 +10,19 @@ class AppConstant {
|
|||
|
||||
static const String soilTempInfo =
|
||||
'Suhu tanah mengacu pada suhu tanah di permukaan atau pada kedalaman tertentu, yang berperan penting dalam pertumbuhan tanaman dan proses pertanian. Suhu ini memengaruhi perkecambahan benih, aktivitas akar, serta penyerapan air dan nutrisi, yang semuanya esensial bagi perkembangan tanaman. Selain itu, suhu tanah juga memengaruhi aktivitas mikroorganisme yang berkontribusi pada kesuburan tanah. Dalam pertanian pintar, sensor suhu tanah sering digunakan untuk memantau dan mengoptimalkan kondisi tanah, memastikan tanaman tumbuh dalam rentang suhu yang ideal.';
|
||||
|
||||
static const String npk1 = 'npk1';
|
||||
static const String npk2 = 'npk2';
|
||||
static const String dht = 'dht';
|
||||
|
||||
static const String soilTemp = 'soilTemperature';
|
||||
static const String soilMoisture = 'soilMoisture';
|
||||
static const String airTemp = 'viciTemperature';
|
||||
static const String humidity = 'humidity';
|
||||
static const String lightIntensity = 'lightIntensity';
|
||||
static const String conductivity = 'conductivity';
|
||||
static const String ph = 'ph';
|
||||
static const String nitrogen = 'nitrogen';
|
||||
static const String phosphorus = 'phosphorus';
|
||||
static const String potassium = 'potassium';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ String dateFormater(String date) {
|
|||
return formatter.format(dateTime);
|
||||
}
|
||||
|
||||
String dateFormatterShort(String date) {
|
||||
final DateTime dateTime = DateTime.parse(date);
|
||||
final DateFormat formatter = DateFormat('yyyy-MM-dd');
|
||||
return formatter.format(dateTime);
|
||||
}
|
||||
|
||||
String getGreeting(String time) {
|
||||
DateTime parsedTime = DateTime.parse(time);
|
||||
int hour = parsedTime.hour;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:agrilink_vocpro/features/auth/view/login_screen.dart';
|
|||
import 'package:agrilink_vocpro/features/dashboard/view/dashboard_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/conductivity/view/conductivity_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/humidity/view/humidity_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/light/view/light_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/luminosity/view/light_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/nitrogen/view/nitrogen_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/ph/view/ph_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/phosphorus/view/phosphorus_screen.dart';
|
||||
|
|
@ -81,77 +81,98 @@ 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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static GoRoute buildSoilTempRoute() {
|
||||
return GoRoute(
|
||||
path: 'soil_temperature/:value',
|
||||
path: 'soil_temperature/:value1/:value2',
|
||||
builder: (context, state) {
|
||||
final double value =
|
||||
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||
return SoilTemperatureScreen(temperature: value);
|
||||
final double value1 =
|
||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
||||
final double value2 =
|
||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
||||
return SoilTemperatureScreen(
|
||||
npk1Temperature: value1, npk2Temperature: value2);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
agrilink_vocpro/lib/core/widgets/password_textfield.dart
Normal file
51
agrilink_vocpro/lib/core/widgets/password_textfield.dart
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PasswordTextfield extends StatefulWidget {
|
||||
const PasswordTextfield({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.hintText = 'Enter Here',
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
|
||||
@override
|
||||
State<PasswordTextfield> createState() => _PasswordTextfieldState();
|
||||
}
|
||||
|
||||
class _PasswordTextfieldState extends State<PasswordTextfield> {
|
||||
bool obscureText = true;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: widget.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: widget.hintText,
|
||||
hintStyle: AppTheme.hintStyle,
|
||||
suffixIconColor: AppColor.textDisable,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
obscureText = !obscureText;
|
||||
});
|
||||
},
|
||||
icon: obscureText
|
||||
? const Icon(Icons.visibility_off)
|
||||
: const Icon(Icons.visibility)),
|
||||
),
|
||||
obscureText: obscureText,
|
||||
onTapOutside: (event) => FocusScope.of(context).unfocus(),
|
||||
);
|
||||
}
|
||||
}
|
||||
48
agrilink_vocpro/lib/data/model/dht_graphic_response.dart
Normal file
48
agrilink_vocpro/lib/data/model/dht_graphic_response.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
|
||||
|
||||
class DhtGraphicResponse {
|
||||
DataDht? data;
|
||||
int? statusCode;
|
||||
String? message;
|
||||
|
||||
DhtGraphicResponse({this.data, this.statusCode, this.message});
|
||||
|
||||
DhtGraphicResponse.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'] != null ? DataDht.fromJson(json['data']) : null;
|
||||
statusCode = json['statusCode'];
|
||||
message = json['message'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (this.data != null) {
|
||||
data['data'] = this.data!.toJson();
|
||||
}
|
||||
data['statusCode'] = statusCode;
|
||||
data['message'] = message;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class DataDht {
|
||||
List<Dht>? dht;
|
||||
|
||||
DataDht({this.dht});
|
||||
|
||||
DataDht.fromJson(Map<String, dynamic> json) {
|
||||
if (json['dht'] != null) {
|
||||
dht = <Dht>[];
|
||||
json['dht'].forEach((v) {
|
||||
dht!.add(Dht.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (dht != null) {
|
||||
data['dht'] = dht!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
114
agrilink_vocpro/lib/data/model/jwt_token_response.dart
Normal file
114
agrilink_vocpro/lib/data/model/jwt_token_response.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
class JwtTokenResponse {
|
||||
User? user;
|
||||
int? iat;
|
||||
int? exp;
|
||||
|
||||
JwtTokenResponse({this.user, this.iat, this.exp});
|
||||
|
||||
JwtTokenResponse.fromJson(Map<String, dynamic> json) {
|
||||
user = json['user'] != null ? User.fromJson(json['user']) : null;
|
||||
iat = json['iat'];
|
||||
exp = json['exp'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (user != null) {
|
||||
data['user'] = user!.toJson();
|
||||
}
|
||||
data['iat'] = iat;
|
||||
data['exp'] = exp;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class User {
|
||||
String? id;
|
||||
String? uroleId;
|
||||
String? username;
|
||||
String? email;
|
||||
String? googleId;
|
||||
String? fullname;
|
||||
Null avatar;
|
||||
bool? isBan;
|
||||
String? createdAt;
|
||||
String? updatedAt;
|
||||
Null deletedAt;
|
||||
Role? role;
|
||||
|
||||
User(
|
||||
{this.id,
|
||||
this.uroleId,
|
||||
this.username,
|
||||
this.email,
|
||||
this.googleId,
|
||||
this.fullname,
|
||||
this.avatar,
|
||||
this.isBan,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.deletedAt,
|
||||
this.role});
|
||||
|
||||
User.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
uroleId = json['urole_id'];
|
||||
username = json['username'];
|
||||
email = json['email'];
|
||||
googleId = json['google_id'];
|
||||
fullname = json['fullname'];
|
||||
avatar = json['avatar'];
|
||||
isBan = json['is_ban'];
|
||||
createdAt = json['created_at'];
|
||||
updatedAt = json['updated_at'];
|
||||
deletedAt = json['deleted_at'];
|
||||
role = json['role'] != null ? Role.fromJson(json['role']) : null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['urole_id'] = uroleId;
|
||||
data['username'] = username;
|
||||
data['email'] = email;
|
||||
data['google_id'] = googleId;
|
||||
data['fullname'] = fullname;
|
||||
data['avatar'] = avatar;
|
||||
data['is_ban'] = isBan;
|
||||
data['created_at'] = createdAt;
|
||||
data['updated_at'] = updatedAt;
|
||||
data['deleted_at'] = deletedAt;
|
||||
if (role != null) {
|
||||
data['role'] = role!.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Role {
|
||||
String? id;
|
||||
String? code;
|
||||
String? name;
|
||||
String? createdAt;
|
||||
String? updatedAt;
|
||||
|
||||
Role({this.id, this.code, this.name, this.createdAt, this.updatedAt});
|
||||
|
||||
Role.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
code = json['code'];
|
||||
name = json['name'];
|
||||
createdAt = json['created_at'];
|
||||
updatedAt = json['updated_at'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['code'] = code;
|
||||
data['name'] = name;
|
||||
data['created_at'] = createdAt;
|
||||
data['updated_at'] = updatedAt;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
140
agrilink_vocpro/lib/data/model/latest_data_response.dart
Normal file
140
agrilink_vocpro/lib/data/model/latest_data_response.dart
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
class LatestDataResponse {
|
||||
Data? data;
|
||||
int? statusCode;
|
||||
String? message;
|
||||
|
||||
LatestDataResponse({this.data, this.statusCode, this.message});
|
||||
|
||||
LatestDataResponse.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'] != null ? Data.fromJson(json['data']) : null;
|
||||
statusCode = json['statusCode'];
|
||||
message = json['message'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (this.data != null) {
|
||||
data['data'] = this.data!.toJson();
|
||||
}
|
||||
data['statusCode'] = statusCode;
|
||||
data['message'] = message;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Data {
|
||||
List<Dht>? dht;
|
||||
List<Npk>? npk1;
|
||||
List<Npk>? npk2;
|
||||
|
||||
Data({this.dht, this.npk1, this.npk2});
|
||||
|
||||
Data.fromJson(Map<String, dynamic> json) {
|
||||
if (json['dht'] != null) {
|
||||
dht = <Dht>[];
|
||||
json['dht'].forEach((v) {
|
||||
dht!.add(Dht.fromJson(v));
|
||||
});
|
||||
}
|
||||
if (json['npk1'] != null) {
|
||||
npk1 = <Npk>[];
|
||||
json['npk1'].forEach((v) {
|
||||
npk1!.add(Npk.fromJson(v));
|
||||
});
|
||||
}
|
||||
if (json['npk2'] != null) {
|
||||
npk2 = <Npk>[];
|
||||
json['npk2'].forEach((v) {
|
||||
npk2!.add(Npk.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (dht != null) {
|
||||
data['dht'] = dht!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (npk1 != null) {
|
||||
data['npk1'] = npk1!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (npk2 != null) {
|
||||
data['npk2'] = npk2!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Dht {
|
||||
int? hour;
|
||||
num? vicitemperature;
|
||||
num? vicihumidity;
|
||||
num? viciluminosity;
|
||||
|
||||
Dht(
|
||||
{this.hour,
|
||||
this.vicitemperature,
|
||||
this.vicihumidity,
|
||||
this.viciluminosity});
|
||||
|
||||
Dht.fromJson(Map<String, dynamic> json) {
|
||||
hour = json['hour'];
|
||||
vicitemperature = json['vicitemperature'];
|
||||
vicihumidity = json['vicihumidity'];
|
||||
viciluminosity = json['viciluminosity'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['hour'] = hour;
|
||||
data['vicitemperature'] = vicitemperature;
|
||||
data['vicihumidity'] = vicihumidity;
|
||||
data['viciluminosity'] = viciluminosity;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Npk {
|
||||
int? hour;
|
||||
num? soiltemperature;
|
||||
num? soilhumidity;
|
||||
num? soilconductivity;
|
||||
num? soilph;
|
||||
num? soilnitrogen;
|
||||
num? soilphosphorus;
|
||||
num? soilpotassium;
|
||||
|
||||
Npk(
|
||||
{this.hour,
|
||||
this.soiltemperature,
|
||||
this.soilhumidity,
|
||||
this.soilconductivity,
|
||||
this.soilph,
|
||||
this.soilnitrogen,
|
||||
this.soilphosphorus,
|
||||
this.soilpotassium});
|
||||
|
||||
Npk.fromJson(Map<String, dynamic> json) {
|
||||
hour = json['hour'];
|
||||
soiltemperature = json['soiltemperature'];
|
||||
soilhumidity = json['soilhumidity'];
|
||||
soilconductivity = json['soilconductivity'];
|
||||
soilph = json['soilph'];
|
||||
soilnitrogen = json['soilnitrogen'];
|
||||
soilphosphorus = json['soilphosphorus'];
|
||||
soilpotassium = json['soilpotassium'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['hour'] = hour;
|
||||
data['soiltemperature'] = soiltemperature;
|
||||
data['soilhumidity'] = soilhumidity;
|
||||
data['soilconductivity'] = soilconductivity;
|
||||
data['soilph'] = soilph;
|
||||
data['soilnitrogen'] = soilnitrogen;
|
||||
data['soilphosphorus'] = soilphosphorus;
|
||||
data['soilpotassium'] = soilpotassium;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
42
agrilink_vocpro/lib/data/model/login_response.dart
Normal file
42
agrilink_vocpro/lib/data/model/login_response.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
class LoginResponse {
|
||||
Data? data;
|
||||
int? statusCode;
|
||||
String? message;
|
||||
|
||||
LoginResponse({this.data, this.statusCode, this.message});
|
||||
|
||||
LoginResponse.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'] != null ? Data.fromJson(json['data']) : null;
|
||||
statusCode = json['statusCode'];
|
||||
message = json['message'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (this.data != null) {
|
||||
data['data'] = this.data!.toJson();
|
||||
}
|
||||
data['statusCode'] = statusCode;
|
||||
data['message'] = message;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Data {
|
||||
String? token;
|
||||
String? jwtToken;
|
||||
|
||||
Data({this.token, this.jwtToken});
|
||||
|
||||
Data.fromJson(Map<String, dynamic> json) {
|
||||
token = json['token'];
|
||||
jwtToken = json['jwtToken'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['token'] = token;
|
||||
data['jwtToken'] = jwtToken;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
48
agrilink_vocpro/lib/data/model/npk1_graphic_response.dart
Normal file
48
agrilink_vocpro/lib/data/model/npk1_graphic_response.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
|
||||
|
||||
class Npk1GraphicResponse {
|
||||
DataNpk1? data;
|
||||
int? statusCode;
|
||||
String? message;
|
||||
|
||||
Npk1GraphicResponse({this.data, this.statusCode, this.message});
|
||||
|
||||
Npk1GraphicResponse.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'] != null ? DataNpk1.fromJson(json['data']) : null;
|
||||
statusCode = json['statusCode'];
|
||||
message = json['message'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (this.data != null) {
|
||||
data['data'] = this.data!.toJson();
|
||||
}
|
||||
data['statusCode'] = statusCode;
|
||||
data['message'] = message;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class DataNpk1 {
|
||||
List<Npk>? npk1;
|
||||
|
||||
DataNpk1({this.npk1});
|
||||
|
||||
DataNpk1.fromJson(Map<String, dynamic> json) {
|
||||
if (json['npk1'] != null) {
|
||||
npk1 = <Npk>[];
|
||||
json['npk1'].forEach((v) {
|
||||
npk1!.add(Npk.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (npk1 != null) {
|
||||
data['npk1'] = npk1!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
48
agrilink_vocpro/lib/data/model/npk2_graphic_response.dart
Normal file
48
agrilink_vocpro/lib/data/model/npk2_graphic_response.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
|
||||
|
||||
class Npk2GraphicResponse {
|
||||
DataNpk2? data;
|
||||
int? statusCode;
|
||||
String? message;
|
||||
|
||||
Npk2GraphicResponse({this.data, this.statusCode, this.message});
|
||||
|
||||
Npk2GraphicResponse.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'] != null ? DataNpk2.fromJson(json['data']) : null;
|
||||
statusCode = json['statusCode'];
|
||||
message = json['message'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (this.data != null) {
|
||||
data['data'] = this.data!.toJson();
|
||||
}
|
||||
data['statusCode'] = statusCode;
|
||||
data['message'] = message;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class DataNpk2 {
|
||||
List<Npk>? npk2;
|
||||
|
||||
DataNpk2({this.npk2});
|
||||
|
||||
DataNpk2.fromJson(Map<String, dynamic> json) {
|
||||
if (json['npk2'] != null) {
|
||||
npk2 = <Npk>[];
|
||||
json['npk2'].forEach((v) {
|
||||
npk2!.add(Npk.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (npk2 != null) {
|
||||
data['npk2'] = npk2!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
18
agrilink_vocpro/lib/data/model/switch_relay_response.dart
Normal file
18
agrilink_vocpro/lib/data/model/switch_relay_response.dart
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
class SwitchRelayResponse {
|
||||
bool? success;
|
||||
String? message;
|
||||
|
||||
SwitchRelayResponse({this.success, this.message});
|
||||
|
||||
SwitchRelayResponse.fromJson(Map<String, dynamic> json) {
|
||||
success = json['success'];
|
||||
message = json['message'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['success'] = success;
|
||||
data['message'] = message;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,118 @@
|
|||
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:agrilink_vocpro/data/model/switch_relay_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(
|
||||
BaseOptions(
|
||||
baseUrl: AppConstant.baseUrl,
|
||||
connectTimeout: Duration(seconds: 5),
|
||||
receiveTimeout: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
|
||||
Future<LoginResponse> login({
|
||||
required String username,
|
||||
required String password,
|
||||
String rememberMe = 'false',
|
||||
}) async {
|
||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
final String basicAuth =
|
||||
'Basic ${base64Encode(utf8.encode('$username:$password'))}';
|
||||
|
||||
FormData formData = FormData.fromMap({
|
||||
'remember_me': rememberMe,
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await _dioWithoutInterceptor.post(
|
||||
'/auth/login',
|
||||
data: formData,
|
||||
options: Options(
|
||||
headers: {
|
||||
'Authorization': basicAuth,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = LoginResponse.fromJson(response.data);
|
||||
final decodedToken =
|
||||
JwtTokenResponse.fromJson(JwtDecoder.decode(data.data!.jwtToken!));
|
||||
pref.setString('token', data.data!.token!);
|
||||
pref.setString('jwtToken', data.data!.jwtToken!);
|
||||
pref.setString('username', decodedToken.user?.username ?? 'unknown');
|
||||
pref.setString('email', decodedToken.user?.email ?? 'unknown');
|
||||
pref.setString('fullName', decodedToken.user?.fullname ?? 'unknown');
|
||||
pref.setBool('isLoggedIn', true);
|
||||
return data;
|
||||
} else {
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final String errorMessage;
|
||||
if (e.response?.data != null) {
|
||||
errorMessage = e.response?.data['message'] ?? 'Something went wrong';
|
||||
} else {
|
||||
errorMessage = 'Something went wrong';
|
||||
}
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// logout
|
||||
Future<LoginResponse> logout() async {
|
||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
final String auth = 'Bearer ${pref.getString('token')}';
|
||||
|
||||
try {
|
||||
final response = await _dioWithoutInterceptor.post(
|
||||
'/auth/logout',
|
||||
options: Options(
|
||||
headers: {'Authorization': auth},
|
||||
),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final data = LoginResponse.fromJson(response.data);
|
||||
pref.remove('token');
|
||||
pref.remove('jwtToken');
|
||||
pref.remove('username');
|
||||
pref.remove('email');
|
||||
pref.remove('fullName');
|
||||
pref.setBool('isLoggedIn', false);
|
||||
return data;
|
||||
} else {
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final String errorMessage = e.response?.data['message'];
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Future<RelayResponse> getRelayStatus() async {
|
||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
final String auth = 'Bearer ${pref.getString('token')}';
|
||||
try {
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
final result = await _dioWithoutInterceptor.get('get-relay');
|
||||
final result = await _dioWithoutInterceptor.get(
|
||||
'/api/get-relay',
|
||||
options: Options(
|
||||
headers: {'Authorization': auth},
|
||||
),
|
||||
);
|
||||
if (result.statusCode == 200) {
|
||||
final data = RelayResponse.fromJson(result.data);
|
||||
return data;
|
||||
|
|
@ -21,10 +120,144 @@ class AppService {
|
|||
throw Exception('Failed to load data');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
final errorMessage = e.response?.data['message'];
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// switch relay
|
||||
|
||||
Future<SwitchRelayResponse> switchRelay(
|
||||
{required int relayNumber, required int state}) async {
|
||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
final String auth = 'Bearer ${pref.getString('token')}';
|
||||
|
||||
try {
|
||||
final response = await _dioWithoutInterceptor.post(
|
||||
'/api/set-relay',
|
||||
data: {
|
||||
'id': relayNumber,
|
||||
'state': state,
|
||||
},
|
||||
options: Options(
|
||||
headers: {'Authorization': auth},
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final data = SwitchRelayResponse.fromJson(response.data);
|
||||
return data;
|
||||
} else {
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
rethrow;
|
||||
} on DioException catch (e) {
|
||||
final errorMessage = e.response?.data['message'];
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
//get grafik data dht
|
||||
|
||||
Future<DhtGraphicResponse> getGrafikDataDht({
|
||||
required String metric,
|
||||
}) async {
|
||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
final String auth = 'Bearer ${pref.getString('token')}';
|
||||
String date = DateTime.now().toString();
|
||||
final formatedDate = dateFormatterShort(date);
|
||||
try {
|
||||
final result = await _dioWithoutInterceptor.get(
|
||||
'/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);
|
||||
return data;
|
||||
} else {
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final errorMessage = e.response?.data['message'];
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// get grafik data npk1
|
||||
Future<Npk1GraphicResponse> getGraphicDataNpk1(
|
||||
{required String metric}) async {
|
||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
final String auth = 'Bearer ${pref.getString('token')}';
|
||||
String date = DateTime.now().toString();
|
||||
final formatedDate = dateFormatterShort(date);
|
||||
try {
|
||||
final result = await _dioWithoutInterceptor.get(
|
||||
'/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);
|
||||
return data;
|
||||
} else {
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final errorMessage = e.response?.data['message'];
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// get grafik data npk2
|
||||
|
||||
Future<Npk2GraphicResponse> getGraphicDataNpk2(
|
||||
{required String metric}) async {
|
||||
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(
|
||||
'/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);
|
||||
return data;
|
||||
} else {
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final errorMessage = e.response?.data['message'];
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// get latest data
|
||||
|
||||
Future<LatestDataResponse> getLatestData() async {
|
||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
final String auth = 'Bearer ${pref.getString('token')}';
|
||||
try {
|
||||
final result = await _dioWithoutInterceptor.get(
|
||||
'/api/sensor/getLatest',
|
||||
options: Options(
|
||||
headers: {'Authorization': auth},
|
||||
),
|
||||
);
|
||||
if (result.statusCode == 200) {
|
||||
final data = LatestDataResponse.fromJson(result.data);
|
||||
return data;
|
||||
} else {
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final errorMessage = e.response?.data['message'];
|
||||
throw (errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
agrilink_vocpro/lib/domain/service/firebase_api.dart
Normal file
20
agrilink_vocpro/lib/domain/service/firebase_api.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class FirebaseApi {
|
||||
final _firebaseMessaging = FirebaseMessaging.instance;
|
||||
|
||||
Future<void> initNotification() async {
|
||||
await _firebaseMessaging.requestPermission(
|
||||
alert: true,
|
||||
badge: true,
|
||||
provisional: false,
|
||||
sound: true,
|
||||
);
|
||||
|
||||
final fCMToken = await _firebaseMessaging.getToken();
|
||||
if (kDebugMode) {
|
||||
print('FCM Token: $fCMToken');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +1,170 @@
|
|||
import 'dart:convert';
|
||||
// import 'dart:convert';
|
||||
|
||||
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:mqtt_client/mqtt_client.dart';
|
||||
import 'package:mqtt_client/mqtt_server_client.dart';
|
||||
// import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||
// import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
// import 'package:mqtt_client/mqtt_client.dart';
|
||||
// import 'package:mqtt_client/mqtt_server_client.dart';
|
||||
|
||||
class MQTTService {
|
||||
MqttServerClient? client;
|
||||
// class MQTTService {
|
||||
// MqttServerClient? client;
|
||||
|
||||
Future<ResultState> setupMqtt() async {
|
||||
client = MqttServerClient(AppConstant.mqttServer, '');
|
||||
client!.port = 1883;
|
||||
// Future<ResultState> setupMqtt() async {
|
||||
// client = MqttServerClient(AppConstant.mqttServer, '');
|
||||
// client!.port = 1883;
|
||||
|
||||
client!.connectionMessage = MqttConnectMessage()
|
||||
.authenticateAs(AppConstant.mqttUsername, AppConstant.mqttPassword)
|
||||
.withClientIdentifier('mobile_client_controller')
|
||||
.startClean() // reset session
|
||||
.withWillQos(MqttQos.atLeastOnce);
|
||||
// client!.connectionMessage = MqttConnectMessage()
|
||||
// .authenticateAs(AppConstant.mqttUsername, AppConstant.mqttPassword)
|
||||
// .withClientIdentifier('mobile_client_controller')
|
||||
// .startClean() // reset session
|
||||
// .withWillQos(MqttQos.atLeastOnce);
|
||||
|
||||
try {
|
||||
print('MQTT: Connecting....');
|
||||
await client!.connect();
|
||||
print('MQTT: Connected');
|
||||
return ResultState.hasData;
|
||||
} catch (e) {
|
||||
print('MQTT: Error: $e');
|
||||
return ResultState.error;
|
||||
}
|
||||
}
|
||||
// try {
|
||||
// print('MQTT: Connecting....');
|
||||
// await client!.connect();
|
||||
// print('MQTT: Connected');
|
||||
// return ResultState.hasData;
|
||||
// } catch (e) {
|
||||
// print('MQTT: Error: $e');
|
||||
// return ResultState.error;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<ResultState> publishMessage(String topic, String message) async {
|
||||
final builder = MqttClientPayloadBuilder();
|
||||
// Future<ResultState> publishMessage(String topic, String message) async {
|
||||
// final builder = MqttClientPayloadBuilder();
|
||||
|
||||
try {
|
||||
final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
||||
if (!isConnected) {
|
||||
print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
||||
return ResultState.error;
|
||||
}
|
||||
// try {
|
||||
// final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
||||
// if (!isConnected) {
|
||||
// print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
||||
// return ResultState.error;
|
||||
// }
|
||||
|
||||
print('MQTT: Published message to $topic: $message');
|
||||
builder.addString(message);
|
||||
client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
||||
print('MQTT: Message published');
|
||||
return ResultState.hasData;
|
||||
} catch (e) {
|
||||
print('MQTT: Error: $e');
|
||||
return ResultState.error;
|
||||
}
|
||||
}
|
||||
// print('MQTT: Published message to $topic: $message');
|
||||
// builder.addString(message);
|
||||
// client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
||||
// print('MQTT: Message published');
|
||||
// return ResultState.hasData;
|
||||
// } catch (e) {
|
||||
// print('MQTT: Error: $e');
|
||||
// return ResultState.error;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<ResultState> disconnectMqtt() async {
|
||||
final bool isConnected = await isMqttConnected();
|
||||
if (isConnected) {
|
||||
print('Memutus koneksi dari broker...');
|
||||
// Future<ResultState> disconnectMqtt() async {
|
||||
// final bool isConnected = await isMqttConnected();
|
||||
// if (isConnected) {
|
||||
// print('Memutus koneksi dari broker...');
|
||||
|
||||
client!.disconnect();
|
||||
// client!.disconnect();
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
print('Koneksi telah terputus.');
|
||||
return ResultState.hasData;
|
||||
} else {
|
||||
print('Tidak ada koneksi yang sedang aktif.');
|
||||
return ResultState.error;
|
||||
}
|
||||
}
|
||||
// await Future.delayed(const Duration(seconds: 1));
|
||||
// print('Koneksi telah terputus.');
|
||||
// return ResultState.hasData;
|
||||
// } else {
|
||||
// print('Tidak ada koneksi yang sedang aktif.');
|
||||
// return ResultState.error;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<bool> isMqttConnected() async {
|
||||
if (client != null &&
|
||||
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||
return true; //connected
|
||||
} else {
|
||||
return false; //not connected
|
||||
}
|
||||
}
|
||||
// Future<bool> isMqttConnected() async {
|
||||
// if (client != null &&
|
||||
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||
// return true; //connected
|
||||
// } else {
|
||||
// return false; //not connected
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<bool> subscribeToTopic(String topic) async {
|
||||
bool isActive = false;
|
||||
if (client != null &&
|
||||
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||
try {
|
||||
print('MQTT: Subscribing to $topic');
|
||||
client!.subscribe(topic, MqttQos.atMostOnce);
|
||||
print('MQTT: Subscribed to $topic');
|
||||
// Future<bool> subscribeToTopic(String topic) async {
|
||||
// bool isActive = false;
|
||||
// if (client != null &&
|
||||
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||
// try {
|
||||
// print('MQTT: Subscribing to $topic');
|
||||
// client!.subscribe(topic, MqttQos.atMostOnce);
|
||||
// print('MQTT: Subscribed to $topic');
|
||||
|
||||
// Tambahkan log ini untuk memastikan bahwa listener dijalankan
|
||||
if (client!.updates != null) {
|
||||
print('MQTT: Listening for updates...');
|
||||
} else {
|
||||
print('MQTT: No updates stream available');
|
||||
}
|
||||
// // Tambahkan log ini untuk memastikan bahwa listener dijalankan
|
||||
// if (client!.updates != null) {
|
||||
// print('MQTT: Listening for updates...');
|
||||
// } else {
|
||||
// print('MQTT: No updates stream available');
|
||||
// }
|
||||
|
||||
client!.updates!.listen(
|
||||
(List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||
print('MQTT: Subscribe Message received!');
|
||||
if (messages != null && messages.isNotEmpty) {
|
||||
final MqttPublishMessage recMessage =
|
||||
messages[0].payload as MqttPublishMessage;
|
||||
final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||
recMessage.payload.message);
|
||||
print(
|
||||
'MQTT: Subscribe Message received on topic ${messages[0].topic}: $payload');
|
||||
// client!.updates!.listen(
|
||||
// (List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||
// print('MQTT: Subscribe Message received!');
|
||||
// if (messages != null && messages.isNotEmpty) {
|
||||
// final MqttPublishMessage recMessage =
|
||||
// messages[0].payload as MqttPublishMessage;
|
||||
// final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||
// recMessage.payload.message);
|
||||
// print(
|
||||
// 'MQTT: Subscribe Message received on topic ${messages[0].topic}: $payload');
|
||||
|
||||
if (payload == 'ON') {
|
||||
isActive = true;
|
||||
// Update UI atau provider untuk menandakan relay ON
|
||||
} else if (payload == 'OFF') {
|
||||
isActive = false;
|
||||
// Update UI atau provider untuk menandakan relay OFF
|
||||
} else {
|
||||
print('MQTT: Invalid Subscribe message received');
|
||||
}
|
||||
} else {
|
||||
print('MQTT: No Subscribe messages received');
|
||||
}
|
||||
},
|
||||
);
|
||||
// if (payload == 'ON') {
|
||||
// isActive = true;
|
||||
// // Update UI atau provider untuk menandakan relay ON
|
||||
// } else if (payload == 'OFF') {
|
||||
// isActive = false;
|
||||
// // Update UI atau provider untuk menandakan relay OFF
|
||||
// } else {
|
||||
// print('MQTT: Invalid Subscribe message received');
|
||||
// }
|
||||
// } else {
|
||||
// print('MQTT: No Subscribe messages received');
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
|
||||
return isActive;
|
||||
} catch (e) {
|
||||
print('MQTT: Error subscribing to $topic: $e');
|
||||
return isActive;
|
||||
}
|
||||
} else {
|
||||
print('MQTT: Not connected, cannot subscribe.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// return isActive;
|
||||
// } catch (e) {
|
||||
// print('MQTT: Error subscribing to $topic: $e');
|
||||
// return isActive;
|
||||
// }
|
||||
// } else {
|
||||
// print('MQTT: Not connected, cannot subscribe.');
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<ResultState> subscribeToRelayStatus() async {
|
||||
if (client != null &&
|
||||
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||
try {
|
||||
print('MQTT: Subscribing to /smartfarming/getRelayStatus');
|
||||
client!.subscribe('smartfarming/getRelayStatus', MqttQos.atMostOnce);
|
||||
print('MQTT: Subscribed to /smartfarming/getRelayStatus');
|
||||
// Future<ResultState> subscribeToRelayStatus() async {
|
||||
// if (client != null &&
|
||||
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||
// try {
|
||||
// print('MQTT: Subscribing to /smartfarming/getRelayStatus');
|
||||
// client!.subscribe('smartfarming/getRelayStatus', MqttQos.atMostOnce);
|
||||
// print('MQTT: Subscribed to /smartfarming/getRelayStatus');
|
||||
|
||||
client!.updates!
|
||||
.listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||
if (messages != null && messages.isNotEmpty) {
|
||||
final MqttPublishMessage recMessage =
|
||||
messages[0].payload as MqttPublishMessage;
|
||||
final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||
recMessage.payload.message);
|
||||
print(
|
||||
'MQTT: Message received on topic ${messages[0].topic}: $payload');
|
||||
// client!.updates!
|
||||
// .listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||
// if (messages != null && messages.isNotEmpty) {
|
||||
// final MqttPublishMessage recMessage =
|
||||
// messages[0].payload as MqttPublishMessage;
|
||||
// final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||
// recMessage.payload.message);
|
||||
// print(
|
||||
// 'MQTT: Message received on topic ${messages[0].topic}: $payload');
|
||||
|
||||
// Parse the received JSON payload
|
||||
final Map<String, dynamic> relayStatus = jsonDecode(payload);
|
||||
// // Parse the received JSON payload
|
||||
// final Map<String, dynamic> relayStatus = jsonDecode(payload);
|
||||
|
||||
print('MQTT: Relay status: $relayStatus');
|
||||
// print('MQTT: Relay status: $relayStatus');
|
||||
|
||||
// Assuming you are using provider, notify it with new relay status
|
||||
// _updateRelayStatus(relayStatus);
|
||||
} else {
|
||||
print('MQTT: No messages received');
|
||||
}
|
||||
});
|
||||
return ResultState.hasData;
|
||||
} catch (e) {
|
||||
print('MQTT: Error subscribing: $e');
|
||||
return ResultState.error;
|
||||
}
|
||||
} else {
|
||||
print('MQTT: Not connected, cannot subscribe.');
|
||||
return ResultState.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
// // Assuming you are using provider, notify it with new relay status
|
||||
// // _updateRelayStatus(relayStatus);
|
||||
// } else {
|
||||
// print('MQTT: No messages received');
|
||||
// }
|
||||
// });
|
||||
// return ResultState.hasData;
|
||||
// } catch (e) {
|
||||
// print('MQTT: Error subscribing: $e');
|
||||
// return ResultState.error;
|
||||
// }
|
||||
// } else {
|
||||
// print('MQTT: Not connected, cannot subscribe.');
|
||||
// return ResultState.error;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,109 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:googleapis_auth/auth_io.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class AuthProvider extends ChangeNotifier {
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
|
||||
bool _isRememberMe = false;
|
||||
bool get isRememberMe => _isRememberMe;
|
||||
|
||||
String errorMessage = '';
|
||||
|
||||
ResultState loginState = ResultState.initial;
|
||||
|
||||
void controllerClear() {
|
||||
emailController.clear();
|
||||
passwordController.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setRememberMe(bool value) {
|
||||
_isRememberMe = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> login(context,
|
||||
{required String email, required String password}) async {
|
||||
loginState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result = await AppService().login(
|
||||
username: email,
|
||||
password: password,
|
||||
rememberMe: isRememberMe.toString(),
|
||||
);
|
||||
if (result.data != null) {
|
||||
loginState = ResultState.hasData;
|
||||
notifyListeners();
|
||||
} else {
|
||||
errorMessage = 'Login gagal, data tidak ditemukan';
|
||||
loginState = ResultState.error;
|
||||
notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Error'),
|
||||
content: Text('$e'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('OK'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
loginState = ResultState.error;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool validateInputs() {
|
||||
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
|
||||
errorMessage = 'Email dan password tidak boleh kosong';
|
||||
loginState = ResultState.error;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> getAccessToken() async {
|
||||
try {
|
||||
final serviceAccountJson = await rootBundle.loadString(
|
||||
'assets/json/agrilink-vocpro-b37f9-firebase-adminsdk-a4f6g-e6565344e3.json',
|
||||
);
|
||||
|
||||
final accountCredentials = ServiceAccountCredentials.fromJson(
|
||||
json.decode(serviceAccountJson),
|
||||
);
|
||||
|
||||
const scope = ['https://www.googleapis.com/auth/firebase.messaging'];
|
||||
|
||||
final client = http.Client();
|
||||
|
||||
try {
|
||||
final accessCredential = await obtainAccessCredentialsViaServiceAccount(
|
||||
accountCredentials, scope, client);
|
||||
final accessToken = accessCredential.accessToken.data;
|
||||
print('Access Token: $accessToken');
|
||||
} catch (e) {
|
||||
print('Error: $e');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error 2: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
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/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/core/widgets/password_textfield.dart';
|
||||
import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -14,18 +15,15 @@ class LoginScreen extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
child: SafeArea(
|
||||
child: Consumer<AuthProvider>(builder: (context, authP, child) {
|
||||
body: SafeArea(
|
||||
child: Consumer<AuthProvider>(
|
||||
builder: (context, authP, child) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
'Hello Wellcome back 👋',
|
||||
'Hello, Welcome back 👋',
|
||||
style: AppTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
|
|
@ -37,36 +35,56 @@ 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),
|
||||
const SizedBox(height: 4),
|
||||
AppTextfield(
|
||||
PasswordTextfield(
|
||||
controller: authP.passwordController,
|
||||
hintText: 'Masukkan password',
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
print('Forgot password?');
|
||||
},
|
||||
child: Text(
|
||||
'Forgot password?',
|
||||
textAlign: TextAlign.end,
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: AppColor.secondary),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: authP.isRememberMe,
|
||||
onChanged: (value) {
|
||||
authP.setRememberMe(value!);
|
||||
},
|
||||
),
|
||||
Text('Remember me', style: AppTheme.labelLarge),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
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',
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
AppButton(
|
||||
onPressed: () {
|
||||
GoRouter.of(context).go(AppRoute.dashboard);
|
||||
authP.getAccessToken();
|
||||
},
|
||||
text: 'Login'),
|
||||
text: 'Get Access Token'),
|
||||
],
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class ControlProvider extends ChangeNotifier {
|
||||
final AppService _appService = AppService();
|
||||
|
|
@ -54,6 +52,9 @@ class ControlProvider extends ChangeNotifier {
|
|||
relayState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
print('try to get relay status...');
|
||||
}
|
||||
final result = await _appService.getRelayStatus();
|
||||
if (result.success == true) {
|
||||
for (var element in result.data!) {
|
||||
|
|
@ -73,26 +74,44 @@ class ControlProvider extends ChangeNotifier {
|
|||
} catch (e) {
|
||||
relayState = ResultState.error;
|
||||
notifyListeners();
|
||||
print(e);
|
||||
rethrow;
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Future<void> disconnectMqtt() async {
|
||||
// try {
|
||||
// await _mqttService.disconnectMqtt();
|
||||
// } catch (e) {
|
||||
// print(e);
|
||||
// rethrow;
|
||||
// }
|
||||
// notifyListeners();
|
||||
// }
|
||||
Future<bool> switchRelay(int relayNumber, bool state) async {
|
||||
relayState = ResultState.loading;
|
||||
notifyListeners();
|
||||
|
||||
// @override
|
||||
// void dispose() {
|
||||
// disconnectMqtt();
|
||||
// super.dispose();
|
||||
// }
|
||||
final int stateConverted;
|
||||
if (state == true) {
|
||||
stateConverted = 1;
|
||||
} else {
|
||||
stateConverted = 0;
|
||||
}
|
||||
try {
|
||||
final result = await _appService.switchRelay(
|
||||
relayNumber: relayNumber, state: stateConverted);
|
||||
if (result.success == true) {
|
||||
relayState = ResultState.hasData;
|
||||
notifyListeners();
|
||||
if (kDebugMode) {
|
||||
print(result.message);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
relayState = ResultState.error;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void switchControl1(bool value) {
|
||||
_control_1 = value;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/control/widgets/control_button_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -22,36 +22,80 @@ class ControlScreen extends StatelessWidget {
|
|||
scrolledUnderElevation: 0,
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
displacement: 10,
|
||||
onRefresh: () async =>
|
||||
await context.read<ControlProvider>().getRelayStatus(),
|
||||
child: SafeArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
Consumer<ControlProvider>(builder: (context, provider, child) {
|
||||
switch (provider.relayState) {
|
||||
case ResultState.loading:
|
||||
return const StatusBarWidget(
|
||||
text: 'Memuat...',
|
||||
icon: BootstrapIcons.cloud,
|
||||
color: Colors.cyan,
|
||||
isLoading: true,
|
||||
);
|
||||
case ResultState.hasData:
|
||||
return const StatusBarWidget(
|
||||
text: 'Berhasil terhubung',
|
||||
icon: BootstrapIcons.check_circle,
|
||||
color: Colors.teal,
|
||||
);
|
||||
case ResultState.noData:
|
||||
return const StatusBarWidget(
|
||||
text: 'Data tidak ditemukan',
|
||||
icon: BootstrapIcons.exclamation_circle,
|
||||
color: Colors.amber,
|
||||
);
|
||||
case ResultState.initial:
|
||||
return const SizedBox.shrink();
|
||||
case ResultState.error:
|
||||
return const StatusBarWidget(
|
||||
text: 'Tidak dapat terhubung',
|
||||
icon: BootstrapIcons.exclamation_circle,
|
||||
color: Colors.red,
|
||||
);
|
||||
}
|
||||
}),
|
||||
SizedBox(height: 16.h),
|
||||
GridView(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16.r,
|
||||
mainAxisSpacing: 16.r,
|
||||
childAspectRatio: 1.4.h,
|
||||
padding: EdgeInsets.all(16.r),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16.r,
|
||||
mainAxisSpacing: 16.r,
|
||||
childAspectRatio: 1.35.h,
|
||||
),
|
||||
children: [
|
||||
ControlButtonWidget(
|
||||
title: 'Katup Air',
|
||||
subtitle: 'Relay 1',
|
||||
isActive: provider.control_1,
|
||||
onTap: () async {
|
||||
final result = provider.control_1 != true
|
||||
? await provider.switchRelay(1, true)
|
||||
: await provider.switchRelay(1, false);
|
||||
result == true
|
||||
? provider.switchControl1(!provider.control_1)
|
||||
: provider.switchControl1(provider.control_1);
|
||||
},
|
||||
),
|
||||
children: [
|
||||
ControlButtonWidget(
|
||||
title: 'Katup Air',
|
||||
subtitle: 'Relay 1',
|
||||
isActive: provider.control_1,
|
||||
onTap: () {},
|
||||
),
|
||||
ControlButtonWidget(
|
||||
title: 'Lampu Utama',
|
||||
subtitle: 'Relay 2',
|
||||
isActive: provider.control_2,
|
||||
onTap: () {},
|
||||
),
|
||||
]),
|
||||
ControlButtonWidget(
|
||||
title: 'Lampu Utama',
|
||||
subtitle: 'Relay 2',
|
||||
isActive: provider.control_2,
|
||||
onTap: () {
|
||||
provider.control_2 != true
|
||||
? provider.switchControl2(true)
|
||||
: provider.switchControl2(false);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -60,74 +104,46 @@ class ControlScreen extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class ControlButtonWidget extends StatelessWidget {
|
||||
const ControlButtonWidget({
|
||||
class StatusBarWidget extends StatelessWidget {
|
||||
const StatusBarWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.isActive,
|
||||
required this.onTap,
|
||||
required this.text,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
this.isLoading = false,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final bool isActive;
|
||||
final Function() onTap;
|
||||
|
||||
final String text;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final bool isLoading;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
|
||||
margin: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
spreadRadius: 1.r,
|
||||
blurRadius: 16.r,
|
||||
offset: Offset(0, 12.r),
|
||||
),
|
||||
],
|
||||
color: color.withAlpha(50),
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(title, style: AppTheme.labelMedium),
|
||||
Text(subtitle, style: AppTheme.labelSmall),
|
||||
Icon(
|
||||
icon,
|
||||
color: color,
|
||||
size: 16.r,
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
Text(text, style: AppTheme.titleSmall.copyWith(color: color)),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Consumer<ControlProvider>(
|
||||
builder: (context, provider, child) {
|
||||
switch (provider.relayState) {
|
||||
case ResultState.loading:
|
||||
return CircleAvatar(
|
||||
radius: 20.r,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: const CupertinoActivityIndicator(),
|
||||
);
|
||||
default:
|
||||
return InkWell(
|
||||
highlightColor: Colors.black,
|
||||
onTap: onTap,
|
||||
child: CircleAvatar(
|
||||
radius: 20.r,
|
||||
backgroundColor: isActive
|
||||
? AppColor.secondary
|
||||
: Colors.grey.shade400,
|
||||
child: const Icon(
|
||||
BootstrapIcons.power,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
if (isLoading)
|
||||
Center(
|
||||
child: CupertinoActivityIndicator(
|
||||
radius: 8.r,
|
||||
))
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
||||
import 'package: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 ControlButtonWidget extends StatelessWidget {
|
||||
const ControlButtonWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.isActive,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final bool isActive;
|
||||
final Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: isActive
|
||||
? AppColor.secondary.withOpacity(0.2)
|
||||
: Colors.grey.withOpacity(0.2),
|
||||
spreadRadius: 1.r,
|
||||
blurRadius: 16.r,
|
||||
offset: Offset(0, 12.r),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: AppTheme.labelMedium),
|
||||
Text(subtitle, style: AppTheme.labelSmall),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Consumer<ControlProvider>(
|
||||
builder: (context, provider, child) {
|
||||
switch (provider.relayState) {
|
||||
case ResultState.loading:
|
||||
return CircleAvatar(
|
||||
radius: 20.r,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: const CupertinoActivityIndicator(),
|
||||
);
|
||||
default:
|
||||
return InkWell(
|
||||
highlightColor: Colors.black,
|
||||
onTap: onTap,
|
||||
child: CircleAvatar(
|
||||
radius: 20.r,
|
||||
backgroundColor: isActive
|
||||
? AppColor.secondary
|
||||
: Colors.grey.shade400,
|
||||
child: const Icon(
|
||||
BootstrapIcons.power,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Npk> dataFetchedNpk1 = [];
|
||||
List<Npk> dataFetchedNpk2 = [];
|
||||
|
||||
Future<void> getSoilConductivityNpk1Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk1(metric: 'soilConductivity');
|
||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getSoilConductivityNpk2Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk2(metric: 'soilConductivity');
|
||||
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +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/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,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: 32.h),
|
||||
Column(
|
||||
children: [
|
||||
Icon(
|
||||
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,
|
||||
size: 64.r,
|
||||
color: Colors.teal,
|
||||
),
|
||||
Text('$value µS/cm', 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(
|
||||
'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,
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSoilInfo(BuildContext context, double value) {
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.electric_bolt_rounded,
|
||||
size: 64.r,
|
||||
color: Colors.teal,
|
||||
),
|
||||
Text('$value µS/cm', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildInfoRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Condutivity',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: Consumer<ConductivityProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final dataState = provider.dataState;
|
||||
|
||||
switch (dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(child: CupertinoActivityIndicator());
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [Colors.teal, Colors.greenAccent],
|
||||
hour: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||
),
|
||||
data: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].soilconductivity ?? 0
|
||||
: provider.dataFetchedNpk2[index].soilconductivity ?? 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
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 HumidityProvider extends ChangeNotifier {
|
||||
HumidityProvider() {
|
||||
getHumidityData();
|
||||
}
|
||||
ResultState dataState = ResultState.initial;
|
||||
|
||||
List<Dht> dataFetched = [];
|
||||
|
||||
Future<void> getHumidityData() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGrafikDataDht(metric: 'viciHumidity');
|
||||
if (result.data == null || result.data!.dht!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetched = result.data!.dht ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Humidity Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/humidity/provider/humidity_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
|
|
@ -16,153 +18,185 @@ class HumidityScreen extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Humidity', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(CupertinoIcons.back),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.droplet_half,
|
||||
color: Colors.blue,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Consumer<HomeProvider>(builder: (context, provider, child) {
|
||||
return ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => HumidityProvider(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Humidity', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(CupertinoIcons.back),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.droplet_half,
|
||||
color: Colors.blue,
|
||||
),
|
||||
SizedBox(
|
||||
height: 280.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(BootstrapIcons.droplet_half,
|
||||
size: 32, color: Colors.blue),
|
||||
Text('60 %', style: AppTheme.headline1),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Consumer<HomeProvider>(builder: (context, provider, child) {
|
||||
return ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
SizedBox(
|
||||
height: 280.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(BootstrapIcons.droplet_half,
|
||||
size: 32, color: Colors.blue),
|
||||
Text('$humidity %', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 3),
|
||||
curve: Curves.easeOut,
|
||||
value: 60,
|
||||
axis: GaugeAxis(
|
||||
degrees: 360,
|
||||
min: 0,
|
||||
max: 100,
|
||||
pointer: null,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
),
|
||||
progressBar: GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.blue.shade200,
|
||||
Colors.blue,
|
||||
]),
|
||||
RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 3),
|
||||
curve: Curves.easeOut,
|
||||
value: humidity,
|
||||
axis: GaugeAxis(
|
||||
degrees: 360,
|
||||
min: 0,
|
||||
max: 100,
|
||||
pointer: null,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
),
|
||||
progressBar: GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.blue.shade200,
|
||||
Colors.blue,
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Humidity',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16.w),
|
||||
child: const Text('Grafik dalam 7 hari terakhir'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AspectRatio(
|
||||
aspectRatio: 2.h,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border:
|
||||
Border.all(color: Colors.grey.shade300, width: 1.w)),
|
||||
child: GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.blue.shade200,
|
||||
Colors.blue,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16.w),
|
||||
child: const Text('Deskripsi'),
|
||||
),
|
||||
// ListView.builder(
|
||||
// shrinkWrap: true,
|
||||
// physics: const NeverScrollableScrollPhysics(),
|
||||
// itemCount: provider.humidtyRules.length,
|
||||
// itemBuilder: (context, index) {
|
||||
// final item = provider.humidtyRules[index];
|
||||
// return Theme(
|
||||
// data: Theme.of(context)
|
||||
// .copyWith(dividerColor: Colors.transparent),
|
||||
// child: ExpansionTile(
|
||||
// trailing: Text(
|
||||
// item.censorText,
|
||||
// style: TextStyle(color: item.color),
|
||||
// ),
|
||||
// expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||
// childrenPadding: EdgeInsets.all(16.r),
|
||||
// title: Text(
|
||||
// 'Kelembaban ${item.minPercentage}% - ${item.maxPercentage}%'),
|
||||
// children: [
|
||||
// Text(
|
||||
// item.description,
|
||||
// style: AppTheme.labelMedium,
|
||||
// ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text('Tindakan', style: AppTheme.labelSmall),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text(item.action),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// })
|
||||
],
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Humidity',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16.w),
|
||||
child: const Text('Grafik dalam 7 hari terakhir'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AspectRatio(
|
||||
aspectRatio: 2.h,
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade300, width: 1.w)),
|
||||
child: Consumer<HumidityProvider>(
|
||||
builder: (context, provider, child) {
|
||||
switch (provider.dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
);
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [
|
||||
Colors.cyan,
|
||||
Colors.amber,
|
||||
],
|
||||
hour: List.generate(
|
||||
provider.dataFetched.length,
|
||||
(index) =>
|
||||
provider.dataFetched[index].hour ?? 0),
|
||||
data: List.generate(
|
||||
provider.dataFetched.length,
|
||||
(index) =>
|
||||
provider.dataFetched[index].vicihumidity
|
||||
?.toDouble() ??
|
||||
0),
|
||||
);
|
||||
case ResultState.error:
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
BootstrapIcons.exclamation_circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'Terjadi Kesalahan',
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case ResultState.noData:
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
BootstrapIcons.database_fill_x,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'Tidak Ada Data',
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case ResultState.initial:
|
||||
return const SizedBox.shrink();
|
||||
default:
|
||||
return const Center(
|
||||
child: Text('Default Error'),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 16.w),
|
||||
child: const Text('Deskripsi'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,224 +0,0 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||
|
||||
class LightScreen extends StatelessWidget {
|
||||
const LightScreen({super.key, this.lightIntensity = 0});
|
||||
|
||||
final double lightIntensity;
|
||||
|
||||
double get value => lightIntensity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Light', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.sun,
|
||||
color: Colors.yellow.shade600,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
SizedBox(
|
||||
height: 240.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(BootstrapIcons.sun,
|
||||
size: 32, color: Colors.orange),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 3),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 360,
|
||||
min: 0,
|
||||
max: 1000,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 100,
|
||||
),
|
||||
progressBar: GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.yellow.shade100,
|
||||
Colors.orange.shade200,
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'${value.toStringAsFixed(0)} lux',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Intensitas Cahaya',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.8.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
),
|
||||
child: GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.yellow.shade100,
|
||||
Colors.orange.shade200,
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
// children: [
|
||||
// Container(
|
||||
// height: 100.h,
|
||||
// width: 100.w,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// color: Colors.blue.withOpacity(0.1),
|
||||
// border: Border.all(
|
||||
// color: Colors.blue,
|
||||
// width: 2,
|
||||
// ),
|
||||
// ),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text('Low',
|
||||
// style: AppTheme.labelMedium
|
||||
// .copyWith(color: Colors.blue)),
|
||||
// // SizedBox(height: 8.h),
|
||||
// // const Icon(
|
||||
// // BootstrapIcons.thermometer_low,
|
||||
// // color: Colors.blue,
|
||||
// // ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text(
|
||||
// '<20°C',
|
||||
// style: AppTheme.labelMedium,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// Container(
|
||||
// height: 100.h,
|
||||
// width: 100.w,
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.green.withOpacity(0.1),
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// border: Border.all(
|
||||
// color: Colors.green,
|
||||
// width: 2,
|
||||
// ),
|
||||
// ),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text('Ideal',
|
||||
// style: AppTheme.labelMedium
|
||||
// .copyWith(color: Colors.green)),
|
||||
// // SizedBox(height: 8.h),
|
||||
// // const Icon(
|
||||
// // BootstrapIcons.thermometer_half,
|
||||
// // color: Colors.green,
|
||||
// // ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text(
|
||||
// '20-30°C',
|
||||
// style: AppTheme.labelMedium,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// Container(
|
||||
// height: 100.h,
|
||||
// width: 100.w,
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.orange.withOpacity(0.1),
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// border: Border.all(
|
||||
// color: Colors.orange.shade800,
|
||||
// width: 2,
|
||||
// ),
|
||||
// ),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text('high',
|
||||
// style: AppTheme.labelMedium
|
||||
// .copyWith(color: Colors.orange)),
|
||||
// // SizedBox(height: 8.h),
|
||||
// // const Icon(
|
||||
// // BootstrapIcons.thermometer_high,
|
||||
// // color: Colors.orange,
|
||||
// // ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text(
|
||||
// '>30°C',
|
||||
// style: AppTheme.labelMedium,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// )
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
|
||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class LumProvider extends ChangeNotifier {
|
||||
LumProvider() {
|
||||
getLumData();
|
||||
}
|
||||
ResultState dataState = ResultState.initial;
|
||||
|
||||
List<Dht> dataFetched = [];
|
||||
|
||||
Future<void> getLumData() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGrafikDataDht(metric: 'viciluminosity');
|
||||
if (result.data == null || result.data!.dht!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetched = result.data!.dht ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Luminosity Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/luminosity/provider/lum_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class LightScreen extends StatelessWidget {
|
||||
const LightScreen({super.key, this.lightIntensity = 0});
|
||||
|
||||
final double lightIntensity;
|
||||
|
||||
double get value => lightIntensity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => LumProvider(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Light', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.sun,
|
||||
color: Colors.yellow.shade600,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
SizedBox(
|
||||
height: 240.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(BootstrapIcons.sun,
|
||||
size: 32, color: Colors.orange),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 3),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 360,
|
||||
min: 0,
|
||||
max: 1000,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 100,
|
||||
),
|
||||
progressBar: GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.yellow.shade100,
|
||||
Colors.orange.shade200,
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'${value.toStringAsFixed(0)} lux',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Intensitas Cahaya',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.8.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
),
|
||||
child: Consumer<LumProvider>(
|
||||
builder: (context, provider, child) {
|
||||
switch (provider.dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
);
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.yellow.shade100,
|
||||
Colors.orange.shade200,
|
||||
],
|
||||
hour: List.generate(provider.dataFetched.length,
|
||||
(index) => provider.dataFetched[index].hour ?? 0),
|
||||
data: List.generate(
|
||||
provider.dataFetched.length,
|
||||
(index) =>
|
||||
provider.dataFetched[index].vicihumidity
|
||||
?.toDouble() ??
|
||||
0),
|
||||
);
|
||||
case ResultState.error:
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
BootstrapIcons.exclamation_circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'Terjadi Kesalahan',
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case ResultState.noData:
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
BootstrapIcons.database_fill_x,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'Tidak Ada Data',
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case ResultState.initial:
|
||||
return const SizedBox.shrink();
|
||||
default:
|
||||
return const Center(
|
||||
child: Text('Default Error'),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Npk> dataFetchedNpk1 = [];
|
||||
List<Npk> dataFetchedNpk2 = [];
|
||||
|
||||
Future<void> getSoilNitrogenNpk1Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk1(metric: 'soilNitrogen');
|
||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getSoilNitrogenNpk2Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk2(metric: 'soilNitrogen');
|
||||
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +1,159 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/nitrogen/provider/nitrogen_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class NitrogenScreen extends StatelessWidget {
|
||||
const NitrogenScreen({super.key, this.nitrogen = 0.0});
|
||||
const NitrogenScreen(
|
||||
{super.key, this.nitrogenNpk1 = 0.0, this.nitrogenNpk2 = 0.0});
|
||||
|
||||
final double nitrogen;
|
||||
double get value => nitrogen;
|
||||
final double nitrogenNpk1;
|
||||
final double nitrogenNpk2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Nitrogen', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
color: Colors.blue,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: 32.h),
|
||||
Column(
|
||||
children: [
|
||||
Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
size: 64.r,
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => NitrogenProvider(),
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Soil Nitrogen', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.eyedropper,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Text('$value ppm', style: AppTheme.headline1),
|
||||
)
|
||||
],
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'NPK 1'),
|
||||
Tab(text: 'NPK 2'),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Nitrogen',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.blue.shade200,
|
||||
Colors.blue,
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
buildTabContent(context, nitrogenNpk1, 'NPK 1', true),
|
||||
buildTabContent(context, nitrogenNpk2, 'NPK 2', false),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SafeArea buildTabContent(
|
||||
BuildContext context, double value, String label, bool isNpk1) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
||||
buildSoilInfo(context, value),
|
||||
SizedBox(height: 16.h),
|
||||
buildInfoRow(context),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
buildGraphicContent(context, isNpk1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSoilInfo(BuildContext context, double value) {
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
size: 64.r,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Text('$value ppm', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildInfoRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Nitrogen',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: Consumer<NitrogenProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final dataState = provider.dataState;
|
||||
|
||||
switch (dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(child: CupertinoActivityIndicator());
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [Colors.blue, Colors.blueAccent],
|
||||
hour: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||
),
|
||||
data: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].soilnitrogen ?? 0
|
||||
: provider.dataFetchedNpk2[index].soilnitrogen ?? 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<Npk> dataFetchedNpk1 = [];
|
||||
List<Npk> dataFetchedNpk2 = [];
|
||||
|
||||
Future<void> getSoilPhNpk1Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result = await AppService().getGraphicDataNpk1(metric: 'soilPh');
|
||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getSoilPhNpk2Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result = await AppService().getGraphicDataNpk2(metric: 'soilPh');
|
||||
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -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.pie_chart,
|
||||
color: Colors.amber,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
)
|
||||
],
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'NPK 1'),
|
||||
Tab(text: 'NPK 2'),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
// children: [
|
||||
// Container(
|
||||
// height: 100.h,
|
||||
// width: 100.w,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// color: Colors.blue.withOpacity(0.1),
|
||||
// border: Border.all(
|
||||
// color: Colors.blue,
|
||||
// width: 2,
|
||||
// ),
|
||||
// ),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text('Low',
|
||||
// style: AppTheme.labelMedium
|
||||
// .copyWith(color: Colors.blue)),
|
||||
// // SizedBox(height: 8.h),
|
||||
// // const Icon(
|
||||
// // BootstrapIcons.thermometer_low,
|
||||
// // color: Colors.blue,
|
||||
// // ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text(
|
||||
// '<20°C',
|
||||
// style: AppTheme.labelMedium,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// Container(
|
||||
// height: 100.h,
|
||||
// width: 100.w,
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.green.withOpacity(0.1),
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// border: Border.all(
|
||||
// color: Colors.green,
|
||||
// width: 2,
|
||||
// ),
|
||||
// ),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text('Ideal',
|
||||
// style: AppTheme.labelMedium
|
||||
// .copyWith(color: Colors.green)),
|
||||
// // SizedBox(height: 8.h),
|
||||
// // const Icon(
|
||||
// // BootstrapIcons.thermometer_half,
|
||||
// // color: Colors.green,
|
||||
// // ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text(
|
||||
// '20-30°C',
|
||||
// style: AppTheme.labelMedium,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// Container(
|
||||
// height: 100.h,
|
||||
// width: 100.w,
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.orange.withOpacity(0.1),
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// border: Border.all(
|
||||
// color: Colors.orange.shade800,
|
||||
// width: 2,
|
||||
// ),
|
||||
// ),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text('high',
|
||||
// style: AppTheme.labelMedium
|
||||
// .copyWith(color: Colors.orange)),
|
||||
// // SizedBox(height: 8.h),
|
||||
// // const Icon(
|
||||
// // BootstrapIcons.thermometer_high,
|
||||
// // color: Colors.orange,
|
||||
// // ),
|
||||
// SizedBox(height: 8.h),
|
||||
// Text(
|
||||
// '>30°C',
|
||||
// style: AppTheme.labelMedium,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.amber.shade200,
|
||||
Colors.orange,
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
buildTabContent(context, phValueNpk1, 'NPK 1', true),
|
||||
buildTabContent(context, phValueNpk2, 'NPK 2', false),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SafeArea buildTabContent(
|
||||
BuildContext context, double value, String label, bool isNpk1) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
||||
buildSoilInfo(context, value),
|
||||
SizedBox(height: 16.h),
|
||||
buildInfoRow(context),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
buildGraphicContent(context, isNpk1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSoilInfo(BuildContext context, double value) {
|
||||
return Center(
|
||||
child: PhIndicator(phValue: value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildInfoRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Acidity',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: Consumer<PhProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final dataState = provider.dataState;
|
||||
|
||||
switch (dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(child: CupertinoActivityIndicator());
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [Colors.cyan, Colors.amber],
|
||||
hour: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||
),
|
||||
data: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].soilph ?? 0
|
||||
: provider.dataFetchedNpk2[index].soilph ?? 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<Npk> dataFetchedNpk1 = [];
|
||||
List<Npk> dataFetchedNpk2 = [];
|
||||
|
||||
Future<void> getSoilPhosporNpk1Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk1(metric: 'soilPhosphorus');
|
||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getSoilPhosporNpk2Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk2(metric: 'soilPhosphorus');
|
||||
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -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/phosphorus/provider/phosporus_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PhosphorusScreen extends StatelessWidget {
|
||||
const PhosphorusScreen({super.key, this.phosphorus = 0.0});
|
||||
const PhosphorusScreen(
|
||||
{super.key, this.phosphorusNpk1 = 0.0, this.phosphorusNpk2 = 0.0});
|
||||
|
||||
final double phosphorus;
|
||||
double get value => phosphorus;
|
||||
final double phosphorusNpk1;
|
||||
final double phosphorusNpk2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Phosphorus', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
color: Colors.blue,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: 32.h),
|
||||
Column(
|
||||
children: [
|
||||
Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
size: 64.r,
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => PhosporusProvider(),
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Soil Phosphorus', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.eyedropper,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Text('$value ppm', style: AppTheme.headline1),
|
||||
)
|
||||
],
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'NPK 1'),
|
||||
Tab(text: 'NPK 2'),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Fosfor',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.blue.shade200,
|
||||
Colors.blue,
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
buildTabContent(context, phosphorusNpk1, 'NPK 1', true),
|
||||
buildTabContent(context, phosphorusNpk2, 'NPK 2', false),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SafeArea buildTabContent(
|
||||
BuildContext context, double value, String label, bool isNpk1) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
||||
buildSoilInfo(context, value),
|
||||
SizedBox(height: 16.h),
|
||||
buildInfoRow(context),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
buildGraphicContent(context, isNpk1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSoilInfo(BuildContext context, double value) {
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
size: 64.r,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Text('$value ppm', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildInfoRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Phosphorus',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: Consumer<PhosporusProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final dataState = provider.dataState;
|
||||
|
||||
switch (dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(child: CupertinoActivityIndicator());
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [Colors.blue, Colors.blueAccent],
|
||||
hour: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||
),
|
||||
data: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].soilphosphorus ?? 0
|
||||
: provider.dataFetchedNpk2[index].soilphosphorus ?? 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<Npk> dataFetchedNpk1 = [];
|
||||
List<Npk> dataFetchedNpk2 = [];
|
||||
|
||||
Future<void> getSoilPotassiumNpk1Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk1(metric: 'soilPotassium');
|
||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getSoilPotassiumNpk2Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk2(metric: 'soilPotassium');
|
||||
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +1,159 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/potassium/provider/potassium_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PotassiumScreen extends StatelessWidget {
|
||||
const PotassiumScreen({super.key, this.potassium = 0.0});
|
||||
const PotassiumScreen(
|
||||
{super.key, this.potassiumNpk1 = 0.0, this.potassiumNpk2 = 0.0});
|
||||
|
||||
final double potassium;
|
||||
double get value => potassium;
|
||||
final double potassiumNpk1;
|
||||
final double potassiumNpk2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Potassium', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
color: Colors.green,
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => PotassiumProvider(),
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Soil Potassium', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.eyedropper,
|
||||
color: Colors.red,
|
||||
),
|
||||
)
|
||||
],
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'NPK 1'),
|
||||
Tab(text: 'NPK 2'),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
buildTabContent(context, potassiumNpk1, 'NPK 1', true),
|
||||
buildTabContent(context, potassiumNpk2, 'NPK 2', false),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SafeArea buildTabContent(
|
||||
BuildContext context, double value, String label, bool isNpk1) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
||||
buildSoilInfo(context, value),
|
||||
SizedBox(height: 16.h),
|
||||
buildInfoRow(context),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
buildGraphicContent(context, isNpk1),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: 32.h),
|
||||
Column(
|
||||
children: [
|
||||
Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
size: 64.r,
|
||||
color: Colors.green,
|
||||
),
|
||||
Text('$value ppm', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Kalium',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: const GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.teal,
|
||||
Colors.green,
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSoilInfo(BuildContext context, double value) {
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
CupertinoIcons.eyedropper,
|
||||
size: 64.r,
|
||||
color: Colors.red,
|
||||
),
|
||||
Text('$value ppm', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildInfoRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Potassium',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: Consumer<PotassiumProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final dataState = provider.dataState;
|
||||
|
||||
switch (dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(child: CupertinoActivityIndicator());
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [Colors.red, Colors.orange],
|
||||
hour: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||
),
|
||||
data: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].soilpotassium ?? 0
|
||||
: provider.dataFetchedNpk2[index].soilpotassium ?? 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<Npk> dataFetchedNpk1 = [];
|
||||
List<Npk> dataFetchedNpk2 = [];
|
||||
|
||||
Future<void> getSoilMosNpk1Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk1(metric: 'soilhumidity');
|
||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getSoilMosNpk2Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk2(metric: 'soilhumidity');
|
||||
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -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('60 %', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
),
|
||||
RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 3),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 360,
|
||||
min: 0,
|
||||
max: 100,
|
||||
pointer: null,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
),
|
||||
progressBar: GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.blue.shade200,
|
||||
Colors.blue,
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Temperature',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => SoilMoistureProvider(),
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Soil Moisture', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.water,
|
||||
color: Colors.green,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {
|
||||
showInfo(
|
||||
context,
|
||||
'Soil Temperature',
|
||||
AppConstant.soilTempInfo,
|
||||
);
|
||||
},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
)
|
||||
],
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'NPK 1'),
|
||||
Tab(text: 'NPK 2'),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.8.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
buildTabContent(context, moistureNpk1, 'NPK 1', true),
|
||||
buildTabContent(context, moistureNpk2, 'NPK 2', false),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SafeArea buildTabContent(
|
||||
BuildContext context, double value, String label, bool isNpk1) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
||||
buildSoilMoistureInfo(context, value),
|
||||
SizedBox(height: 16.h),
|
||||
buildInfoRow(context),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
buildGraphicContent(context, isNpk1),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSoilMoistureInfo(BuildContext context, double value) {
|
||||
return SizedBox(
|
||||
height: 280.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(BootstrapIcons.water, size: 32, color: Colors.blue),
|
||||
Text('$value %', style: AppTheme.headline1),
|
||||
],
|
||||
),
|
||||
),
|
||||
RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 3),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 360,
|
||||
min: 0,
|
||||
max: 100,
|
||||
pointer: null,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
),
|
||||
child: GarphicWidget(
|
||||
gradientColors: [
|
||||
progressBar: GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.blue.shade200,
|
||||
Colors.blue,
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildInfoRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Moisture',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: Consumer<SoilMoistureProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final dataState = provider.dataState;
|
||||
|
||||
switch (dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(child: CupertinoActivityIndicator());
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [Colors.cyan, Colors.blue],
|
||||
hour: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||
),
|
||||
data: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].soilhumidity ?? 0
|
||||
: provider.dataFetchedNpk2[index].soilhumidity ?? 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 SoilTempProvider extends ChangeNotifier {
|
||||
SoilTempProvider() {
|
||||
getSoilTempNpk1Data();
|
||||
getSoilTempNpk2Data();
|
||||
}
|
||||
ResultState dataState = ResultState.initial;
|
||||
|
||||
List<Npk> dataFetchedNpk1 = [];
|
||||
List<Npk> dataFetchedNpk2 = [];
|
||||
|
||||
Future<void> getSoilTempNpk1Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk1(metric: 'soilTemperature');
|
||||
if (result.data == null || result.data!.npk1!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk1 = result.data!.npk1 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getSoilTempNpk2Data() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGraphicDataNpk2(metric: 'soilTemperature');
|
||||
if (result.data == null || result.data!.npk2!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetchedNpk2 = result.data!.npk2 ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Soil Temp Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,232 +1,239 @@
|
|||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/core/widgets/show_info.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/soil_temperature/provider/soil_temp_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||
|
||||
class SoilTemperatureScreen extends StatelessWidget {
|
||||
const SoilTemperatureScreen({super.key, this.temperature = 0});
|
||||
const SoilTemperatureScreen({
|
||||
super.key,
|
||||
this.npk1Temperature = 0,
|
||||
this.npk2Temperature = 0,
|
||||
});
|
||||
|
||||
final double temperature;
|
||||
double get value => temperature;
|
||||
final double npk1Temperature;
|
||||
final double npk2Temperature;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return 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,
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => SoilTempProvider(),
|
||||
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,
|
||||
),
|
||||
)
|
||||
],
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'NPK 1'),
|
||||
Tab(text: 'NPK 2'),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
buildTabContent(context, npk1Temperature, 'NPK 1', true),
|
||||
buildTabContent(context, npk2Temperature, 'NPK 2', false),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Generalized method for tab content to avoid duplication
|
||||
SafeArea buildTabContent(
|
||||
BuildContext context, double value, String label, bool isNpk1) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
||||
buildTemperatureInfo(context, value),
|
||||
SizedBox(height: 16.h),
|
||||
buildInfoRow(context),
|
||||
SizedBox(height: 16.h),
|
||||
buildTemperatureRange(),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
buildGraphicContent(context, isNpk1),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
SizedBox(
|
||||
height: 240.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 80.h,
|
||||
),
|
||||
const Icon(BootstrapIcons.water,
|
||||
size: 32, color: Colors.green),
|
||||
Text(
|
||||
'${value.toStringAsFixed(0)}°C', // Animated percentage text
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 2),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 240,
|
||||
min: 0,
|
||||
max: 56.7,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
),
|
||||
progressBar: const GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.blue,
|
||||
Colors.orange,
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTemperatureInfo(BuildContext context, double value) {
|
||||
return SizedBox(
|
||||
height: 240.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 80.h),
|
||||
const Icon(BootstrapIcons.water, size: 32, color: Colors.green),
|
||||
Text(
|
||||
'Soil Temperature',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {
|
||||
showInfo(
|
||||
context,
|
||||
'Soil Temperature',
|
||||
AppConstant.soilTempInfo,
|
||||
);
|
||||
},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Container(
|
||||
height: 100.h,
|
||||
width: 100.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color: Colors.blue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Low',
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.blue)),
|
||||
// SizedBox(height: 8.h),
|
||||
// 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,
|
||||
),
|
||||
],
|
||||
'$value°C', // Display temperature
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: const GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.cyan,
|
||||
Colors.amber,
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 2),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 240,
|
||||
min: 0,
|
||||
max: 56.7,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
),
|
||||
)
|
||||
],
|
||||
progressBar: const GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.blue,
|
||||
Colors.orange,
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Build the row with 'Low', 'Ideal', 'High' temperature containers
|
||||
Widget buildTemperatureRange() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildInfoContainer('Low', '<20°C', Colors.blue),
|
||||
_buildInfoContainer('Ideal', '20-30°C', Colors.green),
|
||||
_buildInfoContainer('High', '>30°C', Colors.orange),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Reusable container builder
|
||||
Widget _buildInfoContainer(String label, String tempRange, Color color) {
|
||||
return Container(
|
||||
height: 100.h,
|
||||
width: 100.w,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: color, width: 2),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(label, style: AppTheme.labelMedium.copyWith(color: color)),
|
||||
SizedBox(height: 8.h),
|
||||
Text(tempRange,
|
||||
style: AppTheme.labelMedium, textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Information row with IconButton
|
||||
Widget buildInfoRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Soil Temperature',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {
|
||||
showInfo(context, 'Soil Temperature', AppConstant.soilTempInfo);
|
||||
},
|
||||
icon: const Icon(BootstrapIcons.info_circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Generalized method to build the graphic content based on NPK type
|
||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: Consumer<SoilTempProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final dataState = provider.dataState;
|
||||
|
||||
switch (dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(child: CupertinoActivityIndicator());
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [Colors.cyan, Colors.amber],
|
||||
hour: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
||||
),
|
||||
data: List.generate(
|
||||
isNpk1
|
||||
? provider.dataFetchedNpk1.length
|
||||
: provider.dataFetchedNpk2.length,
|
||||
(index) => isNpk1
|
||||
? provider.dataFetchedNpk1[index].soiltemperature ?? 0
|
||||
: provider.dataFetchedNpk2[index].soiltemperature ?? 0,
|
||||
),
|
||||
maxValue: 70,
|
||||
);
|
||||
case ResultState.error:
|
||||
return const GraphicErrorWidget(message: 'Terjadi Kesalahan');
|
||||
case ResultState.noData:
|
||||
return const GraphicErrorWidget(message: 'Tidak Ada Data');
|
||||
case ResultState.initial:
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/data/model/latest_data_response.dart';
|
||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class TempProvider extends ChangeNotifier {
|
||||
TempProvider() {
|
||||
getTempData();
|
||||
}
|
||||
ResultState dataState = ResultState.initial;
|
||||
|
||||
List<Dht> dataFetched = [];
|
||||
|
||||
Future<void> getTempData() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result =
|
||||
await AppService().getGrafikDataDht(metric: 'viciTemperature');
|
||||
if (result.data == null || result.data!.dht!.isEmpty) {
|
||||
dataState = ResultState.noData;
|
||||
} else {
|
||||
dataFetched = result.data!.dht ?? [];
|
||||
dataState = ResultState.hasData;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Grafik Temperature Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/temperature/provider/temp_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||
import 'package: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 TemperatureScreen extends StatelessWidget {
|
||||
const TemperatureScreen({super.key, this.temperature = 0});
|
||||
|
|
@ -13,213 +17,263 @@ class TemperatureScreen extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Temperature', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.thermometer_half,
|
||||
color: Colors.red,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
SizedBox(
|
||||
height: 240.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 80.h,
|
||||
),
|
||||
const Icon(BootstrapIcons.thermometer_half,
|
||||
size: 32, color: Colors.orange),
|
||||
Text(
|
||||
'${value.toStringAsFixed(0)}°C', // Animated percentage text
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => TempProvider(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Temperature', style: AppTheme.labelMedium),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: Icon(
|
||||
BootstrapIcons.thermometer_half,
|
||||
color: Colors.red,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.05,
|
||||
),
|
||||
SizedBox(
|
||||
height: 240.h,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 80.h,
|
||||
),
|
||||
const Icon(BootstrapIcons.thermometer_half,
|
||||
size: 32, color: Colors.orange),
|
||||
Text(
|
||||
'${value.toStringAsFixed(0)}°C', // Animated percentage text
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 2),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 240,
|
||||
min: 0,
|
||||
max: 56.7,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
),
|
||||
progressBar: const GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.white12,
|
||||
Colors.orange,
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Temperature',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Container(
|
||||
height: 100.h,
|
||||
width: 100.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color: Colors.blue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Low',
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.blue)),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'<20°C',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedRadialGauge(
|
||||
duration: const Duration(seconds: 2),
|
||||
curve: Curves.easeOut,
|
||||
value: value,
|
||||
axis: GaugeAxis(
|
||||
degrees: 240,
|
||||
min: 0,
|
||||
max: 56.7,
|
||||
style: GaugeAxisStyle(
|
||||
background: Colors.grey.shade100,
|
||||
thickness: 50,
|
||||
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,
|
||||
),
|
||||
progressBar: const GaugeBasicProgressBar(
|
||||
gradient: GaugeAxisGradient(colors: [
|
||||
Colors.white12,
|
||||
Colors.orange,
|
||||
]),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Ideal',
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.green)),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'20-30°C',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 100.h,
|
||||
width: 100.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Colors.orange.shade800,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('high',
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.orange)),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'>30°C',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Temperature',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 20.r,
|
||||
color: Colors.blue,
|
||||
onPressed: () {},
|
||||
icon: const Icon(BootstrapIcons.info_circle))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Container(
|
||||
height: 100.h,
|
||||
width: 100.w,
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color: Colors.blue,
|
||||
width: 2,
|
||||
),
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
child: Consumer<TempProvider>(
|
||||
builder: (context, provider, child) {
|
||||
switch (provider.dataState) {
|
||||
case ResultState.loading:
|
||||
return const Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
);
|
||||
|
||||
case ResultState.hasData:
|
||||
return GarphicWidget(
|
||||
gradientColors: const [
|
||||
Colors.cyan,
|
||||
Colors.amber,
|
||||
],
|
||||
hour: List.generate(
|
||||
provider.dataFetched.length,
|
||||
(index) =>
|
||||
provider.dataFetched[index].hour ?? 0),
|
||||
data: List.generate(
|
||||
provider.dataFetched.length,
|
||||
(index) =>
|
||||
provider.dataFetched[index].vicitemperature
|
||||
?.toDouble() ??
|
||||
0),
|
||||
);
|
||||
case ResultState.error:
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
BootstrapIcons.exclamation_circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'Terjadi Kesalahan',
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case ResultState.noData:
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
BootstrapIcons.database_fill_x,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'Tidak Ada Data',
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case ResultState.initial:
|
||||
return const SizedBox.shrink();
|
||||
default:
|
||||
return const Center(
|
||||
child: Text('Default Error'),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 100.h,
|
||||
width: 100.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Colors.green,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Ideal',
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.green)),
|
||||
// SizedBox(height: 8.h),
|
||||
// const Icon(
|
||||
// BootstrapIcons.thermometer_half,
|
||||
// color: Colors.green,
|
||||
// ),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'20-30°C',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 100.h,
|
||||
width: 100.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Colors.orange.shade800,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('high',
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.orange)),
|
||||
// SizedBox(height: 8.h),
|
||||
// const Icon(
|
||||
// BootstrapIcons.thermometer_high,
|
||||
// color: Colors.orange,
|
||||
// ),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'>30°C',
|
||||
style: AppTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
const Text('Grafik'),
|
||||
SizedBox(height: 16.h),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.6.h,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||
),
|
||||
child: const GarphicWidget(
|
||||
gradientColors: [
|
||||
Colors.cyan,
|
||||
Colors.amber,
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,25 +1,103 @@
|
|||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class HomeProvider extends ChangeNotifier {
|
||||
final DateTime currentDate = DateTime.now();
|
||||
|
||||
num _dhtHumidity = 0;
|
||||
num get dhtHumidity => _dhtHumidity;
|
||||
|
||||
num _dhtTemperature = 0;
|
||||
num get dhtTemperature => _dhtTemperature;
|
||||
|
||||
num _dhtLuminosity = 0;
|
||||
num get dhtLuminosity => _dhtLuminosity;
|
||||
|
||||
num _npk1SoilMoisture = 0;
|
||||
num get npk1SoilMoisture => _npk1SoilMoisture;
|
||||
|
||||
num _npk1Temperature = 0;
|
||||
num get npk1Temperature => _npk1Temperature;
|
||||
|
||||
num _npk1SoilPh = 0;
|
||||
num get npk1SoilPh => _npk1SoilPh;
|
||||
|
||||
num _npk1SoilEc = 0;
|
||||
num get npk1SoilEc => _npk1SoilEc;
|
||||
|
||||
num _npk1SoilNitrogen = 0;
|
||||
num get npk1SoilNitrogen => _npk1SoilNitrogen;
|
||||
|
||||
num _npk1SoilPhosphorus = 0;
|
||||
num get npk1SoilPhosphorus => _npk1SoilPhosphorus;
|
||||
|
||||
num _npk1SoilPotassium = 0;
|
||||
num get npk1SoilPotassium => _npk1SoilPotassium;
|
||||
|
||||
num _npk2SoilMoisture = 0;
|
||||
num get npk2SoilMoisture => _npk2SoilMoisture;
|
||||
|
||||
num _npk2Temperature = 0;
|
||||
num get npk2Temperature => _npk2Temperature;
|
||||
|
||||
num _npk2SoilPh = 0;
|
||||
num get npk2SoilPh => _npk2SoilPh;
|
||||
|
||||
num _npk2SoilEc = 0;
|
||||
num get npk2SoilEc => _npk2SoilEc;
|
||||
|
||||
num _npk2SoilNitrogen = 0;
|
||||
num get npk2SoilNitrogen => _npk2SoilNitrogen;
|
||||
|
||||
num _npk2SoilPhosphorus = 0;
|
||||
num get npk2SoilPhosphorus => _npk2SoilPhosphorus;
|
||||
|
||||
num _npk2SoilPotassium = 0;
|
||||
num get npk2SoilPotassium => _npk2SoilPotassium;
|
||||
|
||||
HomeProvider() {
|
||||
getData();
|
||||
getLatestData();
|
||||
}
|
||||
|
||||
ResultState dataState = ResultState.initial;
|
||||
|
||||
Future<void> getData() async {
|
||||
Future<void> getLatestData() async {
|
||||
dataState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
print('Fetching data...');
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
print('Data fetched');
|
||||
dataState = ResultState.hasData;
|
||||
notifyListeners();
|
||||
final result = await AppService().getLatestData();
|
||||
if (result.data == null) {
|
||||
dataState = ResultState.noData;
|
||||
notifyListeners();
|
||||
} else {
|
||||
final data = result.data!;
|
||||
_dhtHumidity = data.dht?[0].vicihumidity ?? 0;
|
||||
_dhtTemperature = data.dht?[0].vicitemperature ?? 0;
|
||||
_dhtLuminosity = data.dht?[0].viciluminosity ?? 0;
|
||||
|
||||
_npk1SoilMoisture = data.npk1?[0].soilhumidity ?? 0;
|
||||
_npk1Temperature = data.npk1?[0].soiltemperature ?? 0;
|
||||
_npk1SoilPh = data.npk1?[0].soilph ?? 0;
|
||||
_npk1SoilEc = data.npk1?[0].soilconductivity ?? 0;
|
||||
_npk1SoilNitrogen = data.npk1?[0].soilnitrogen ?? 0;
|
||||
_npk1SoilPhosphorus = data.npk1?[0].soilphosphorus ?? 0;
|
||||
_npk1SoilPotassium = data.npk1?[0].soilpotassium ?? 0;
|
||||
|
||||
_npk2SoilMoisture = data.npk2?[0].soilhumidity ?? 0;
|
||||
_npk2Temperature = data.npk2?[0].soiltemperature ?? 0;
|
||||
_npk2SoilPh = data.npk2?[0].soilph ?? 0;
|
||||
_npk2SoilEc = data.npk2?[0].soilconductivity ?? 0;
|
||||
_npk2SoilNitrogen = data.npk2?[0].soilnitrogen ?? 0;
|
||||
_npk2SoilPhosphorus = data.npk2?[0].soilphosphorus ?? 0;
|
||||
_npk2SoilPotassium = data.npk2?[0].soilpotassium ?? 0;
|
||||
dataState = ResultState.hasData;
|
||||
notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Get Latest Error: $e');
|
||||
}
|
||||
dataState = ResultState.error;
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
@ -31,57 +109,3 @@ class HomeProvider extends ChangeNotifier {
|
|||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// List<CensorDataRule> humidtyRules = [
|
||||
// CensorDataRule(
|
||||
// minPercentage: 0,
|
||||
// maxPercentage: 30,
|
||||
// censorText: 'Very Low',
|
||||
// description:
|
||||
// 'Udara sangat kering. Tanaman bisa mengalami stress akibat kekurangan air.',
|
||||
// action:
|
||||
// 'Aktifkan sistem penyiraman atau humidifier untuk menaikkan kelembaban. Periksa juga apakah ada kebocoran pada sistem irigasi yang mengakibatkan kelembaban terlalu rendah.',
|
||||
// color: Colors.red,
|
||||
// ),
|
||||
// CensorDataRule(
|
||||
// minPercentage: 31,
|
||||
// maxPercentage: 50,
|
||||
// censorText: 'Low',
|
||||
// description:
|
||||
// 'Kelembaban masih cukup rendah. Beberapa jenis tanaman mungkin sudah mulai terpengaruh.',
|
||||
// action:
|
||||
// 'Pertimbangkan untuk menambah irigasi atau memperpanjang durasi penyiraman. Pantau tanaman secara berkala.',
|
||||
// color: Colors.orange,
|
||||
// ),
|
||||
// CensorDataRule(
|
||||
// minPercentage: 51,
|
||||
// maxPercentage: 70,
|
||||
// censorText: 'Normal',
|
||||
// description:
|
||||
// 'Ini adalah kelembaban yang ideal untuk sebagian besar tanaman dalam greenhouse.',
|
||||
// action:
|
||||
// 'Pertahankan kondisi ini. Tidak ada tindakan yang diperlukan kecuali jika ada perubahan mendadak.',
|
||||
// color: Colors.green,
|
||||
// ),
|
||||
// CensorDataRule(
|
||||
// minPercentage: 71,
|
||||
// maxPercentage: 85,
|
||||
// censorText: 'High',
|
||||
// description:
|
||||
// 'Udara mulai terlalu lembap. Kelembaban tinggi dapat meningkatkan risiko penyakit jamur atau bakteri.',
|
||||
// action:
|
||||
// 'Aktifkan ventilasi atau kipas untuk mengurangi kelembaban. Pastikan aliran udara di greenhouse cukup baik.',
|
||||
// color: Colors.lime,
|
||||
// ),
|
||||
// CensorDataRule(
|
||||
// minPercentage: 86,
|
||||
// maxPercentage: 100,
|
||||
// censorText: 'Very High',
|
||||
// description:
|
||||
// 'Udara sangat lembap, yang bisa berisiko menyebabkan jamur, lumut, dan penyakit tanaman.',
|
||||
// action:
|
||||
// 'Segera aktifkan sistem ventilasi maksimal, mungkin juga gunakan dehumidifier jika diperlukan. Kurangi frekuensi penyiraman atau periksa sistem irigasi agar tidak berlebihan.',
|
||||
// color: Colors.brown,
|
||||
// ),
|
||||
// ];
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -5,6 +5,7 @@ import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
|||
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_npk1.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_npk2.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_dht.dart';
|
||||
import 'package:agrilink_vocpro/features/setting/provider/setting_provider.dart';
|
||||
import 'package:animated_segmented_tab_control/animated_segmented_tab_control.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
|
@ -29,96 +30,99 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
scrolledUnderElevation: 0,
|
||||
flexibleSpace: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24.r,
|
||||
backgroundColor: AppColor.primary,
|
||||
child: Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
size: 24.r,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
getGreeting(DateTime.now().toString()),
|
||||
style: AppTheme.labelSmall,
|
||||
child:
|
||||
Consumer<SettingProvider>(builder: (context, settingP, child) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24.r,
|
||||
backgroundColor: AppColor.primary,
|
||||
child: Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
size: 24.r,
|
||||
),
|
||||
Text(
|
||||
'Fikril Mahesaputra',
|
||||
style: AppTheme.labelMedium,
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
getGreeting(DateTime.now().toString()),
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
Text(
|
||||
settingP.userFullName,
|
||||
style: AppTheme.labelMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<HomeProvider>().getLatestData();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh_rounded,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 8.h,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
height: MediaQuery.of(context).size.height * 0.17,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.secondary,
|
||||
image: const DecorationImage(
|
||||
image:
|
||||
AssetImage('assets/images/green_house_image.jpg'),
|
||||
fit: BoxFit.cover),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.ternary.withAlpha(200),
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("${settingP.userFullName}'s",
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.white)),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
dateFormater(DateTime.now().toString()),
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<HomeProvider>().getData();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh_rounded,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 8.h,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
height: MediaQuery.of(context).size.height * 0.17,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.secondary,
|
||||
image: const DecorationImage(
|
||||
image:
|
||||
AssetImage('assets/images/green_house_image.jpg'),
|
||||
fit: BoxFit.cover),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.ternary.withAlpha(200),
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("Fikril's Greenhouse",
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.white)),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
dateFormater(DateTime.now().toString()),
|
||||
style: AppTheme.labelMedium
|
||||
.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
body: DefaultTabController(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
class GraphicErrorWidget extends StatelessWidget {
|
||||
const GraphicErrorWidget({super.key, required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(BootstrapIcons.exclamation_circle, color: Colors.grey.shade400),
|
||||
SizedBox(height: 8.h),
|
||||
Text(message, style: AppTheme.labelSmall),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,17 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
class GarphicWidget extends StatelessWidget {
|
||||
const GarphicWidget({super.key, required this.gradientColors});
|
||||
const GarphicWidget(
|
||||
{super.key,
|
||||
required this.gradientColors,
|
||||
this.data,
|
||||
this.hour,
|
||||
this.maxValue = 100});
|
||||
|
||||
final List<Color> gradientColors;
|
||||
final List<num>? hour;
|
||||
final List<num>? data;
|
||||
final double maxValue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -80,18 +88,16 @@ class GarphicWidget extends StatelessWidget {
|
|||
minX: 0,
|
||||
maxX: 24,
|
||||
minY: 0,
|
||||
maxY: 100,
|
||||
maxY: data == null ? 0 : maxValue,
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 38),
|
||||
FlSpot(1, 42),
|
||||
FlSpot(2, 50),
|
||||
FlSpot(3, 53),
|
||||
FlSpot(4, 58),
|
||||
FlSpot(5, 64),
|
||||
FlSpot(7, 49),
|
||||
],
|
||||
spots: data == null && hour == null
|
||||
? [FlSpot(0, 0)]
|
||||
: List.generate(
|
||||
hour!.length,
|
||||
(index) =>
|
||||
FlSpot(hour![index].toDouble(), data![index].toDouble()),
|
||||
),
|
||||
isCurved: true,
|
||||
gradient: LinearGradient(
|
||||
colors: gradientColors,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class ListDataFromCensorDht extends StatelessWidget {
|
|||
DataDisplayerWidget(
|
||||
title: 'Humidity',
|
||||
subtitle: 'kelembaban udara',
|
||||
value: '60',
|
||||
value: provider.dhtHumidity.toString(),
|
||||
unit: '%',
|
||||
icon: BootstrapIcons.droplet_half,
|
||||
textColor: Colors.white,
|
||||
|
|
@ -58,29 +58,32 @@ class ListDataFromCensorDht extends StatelessWidget {
|
|||
iconColor: Colors.white,
|
||||
censorIdentifier: 'NPK 1',
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.humidity}/60');
|
||||
await context
|
||||
.push('${AppRoute.humidity}/${provider.dhtHumidity}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Temperature',
|
||||
subtitle: 'suhu greenhouse',
|
||||
value: '43',
|
||||
value: provider.dhtTemperature.toString(),
|
||||
unit: '°C',
|
||||
icon: BootstrapIcons.thermometer_half,
|
||||
color: Colors.white,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.temperature}/43');
|
||||
await context.push(
|
||||
'${AppRoute.temperature}/${provider.dhtTemperature}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Light',
|
||||
subtitle: 'intensitas cahaya',
|
||||
value: '320.5',
|
||||
value: provider.dhtLuminosity.toString(),
|
||||
unit: 'lux',
|
||||
icon: BootstrapIcons.sun,
|
||||
color: Colors.white,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.light}/320.5');
|
||||
await context
|
||||
.push('${AppRoute.light}/${provider.dhtLuminosity}');
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
|||
DataDisplayerWidget(
|
||||
title: 'Temperature',
|
||||
subtitle: 'Suhu tanah',
|
||||
value: '28',
|
||||
value: provider.npk1Temperature.toString(),
|
||||
unit: '°C',
|
||||
icon: BootstrapIcons.thermometer_half,
|
||||
textColor: Colors.white,
|
||||
|
|
@ -57,76 +57,83 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
|||
iconColor: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.soilTemperature}/28');
|
||||
await context.push(
|
||||
'${AppRoute.soilTemperature}/${provider.npk1Temperature}/${provider.npk2Temperature}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Soil Moisture',
|
||||
subtitle: 'kelembaban tanah',
|
||||
value: '40',
|
||||
value: provider.npk1SoilMoisture.toString(),
|
||||
unit: '%',
|
||||
icon: Icons.water_outlined,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.soilMoisture}/40');
|
||||
await context.push(
|
||||
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Acid Level (PH)',
|
||||
subtitle: 'tingkat keasaman',
|
||||
value: '6.5',
|
||||
value: provider.npk1SoilPh.toString(),
|
||||
unit: 'pH',
|
||||
icon: BootstrapIcons.pie_chart,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () {
|
||||
context.push('${AppRoute.ph}/6.5');
|
||||
onTap: () async {
|
||||
context.push(
|
||||
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Conductivity',
|
||||
subtitle: 'Daya Arus Listrik',
|
||||
value: '234',
|
||||
value: provider.npk1SoilEc.toString(),
|
||||
unit: 'µS/cm',
|
||||
icon: Icons.electric_bolt,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.conductivity}/234');
|
||||
await context.push(
|
||||
'${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Nitrogen',
|
||||
subtitle: 'Kadar Nitrogen',
|
||||
value: '30',
|
||||
value: provider.npk1SoilNitrogen.toString(),
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.nitrogen}/30');
|
||||
await context.push(
|
||||
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Potassium',
|
||||
subtitle: 'Kadar kalium',
|
||||
value: '20',
|
||||
value: provider.npk1SoilPotassium.toString(),
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.potassium}/20');
|
||||
await context.push(
|
||||
'${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Phosphorus',
|
||||
subtitle: 'Kadar Fosfor',
|
||||
value: '54',
|
||||
value: provider.npk1SoilPhosphorus.toString(),
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () async {
|
||||
await context.push('${AppRoute.phosphorus}/54');
|
||||
await context.push(
|
||||
'${AppRoute.phosphorus}/${provider.npk1SoilPhosphorus}/${provider.npk2SoilPhosphorus}');
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/soil_moisture/view/soil_moisture_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/pages/temperature/view/temperature_screen.dart';
|
||||
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/censor_item_loading_widgets.dart';
|
||||
import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart';
|
||||
|
|
@ -51,7 +49,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
|
|||
DataDisplayerWidget(
|
||||
title: 'Temperature',
|
||||
subtitle: 'Suhu tanah',
|
||||
value: '28',
|
||||
value: provider.npk2Temperature.toString(),
|
||||
unit: '°C',
|
||||
icon: BootstrapIcons.thermometer_half,
|
||||
textColor: Colors.white,
|
||||
|
|
@ -59,77 +57,84 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
|
|||
iconColor: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const TemperatureScreen()));
|
||||
await context.push(
|
||||
'${AppRoute.soilTemperature}/${provider.npk1Temperature}/${provider.npk2Temperature}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Soil Moisture',
|
||||
subtitle: 'kelembaban tanah',
|
||||
value: '40',
|
||||
value: provider.npk2SoilMoisture.toString(),
|
||||
unit: '%',
|
||||
icon: Icons.water_outlined,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SoilMoistureScreen(),
|
||||
),
|
||||
);
|
||||
await context.push(
|
||||
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Acid Level (PH)',
|
||||
subtitle: 'tingkat keasaman',
|
||||
value: '6.5',
|
||||
value: provider.npk2SoilPh.toString(),
|
||||
unit: 'pH',
|
||||
icon: BootstrapIcons.pie_chart,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
context.push(
|
||||
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Conductivity',
|
||||
subtitle: 'Daya Arus Listrik',
|
||||
value: '234',
|
||||
value: provider.npk2SoilEc.toString(),
|
||||
unit: 'µS/cm',
|
||||
icon: Icons.electric_bolt,
|
||||
color: Colors.white,
|
||||
censorIdentifier: censorIdentifier,
|
||||
onTap: () async {
|
||||
context.push(AppRoute.humidity, extra: '60');
|
||||
await context.push(
|
||||
'${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Nitrogen',
|
||||
subtitle: 'Kadar Nitrogen',
|
||||
value: '30',
|
||||
value: provider.npk2SoilNitrogen.toString(),
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
await context.push(
|
||||
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Potassium',
|
||||
subtitle: 'Kadar kalium',
|
||||
value: '20',
|
||||
value: provider.npk2SoilPotassium.toString(),
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
await context.push(
|
||||
'${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
|
||||
},
|
||||
),
|
||||
DataDisplayerWidget(
|
||||
title: 'Phosphorus',
|
||||
subtitle: 'Kadar Fosfor',
|
||||
value: '54',
|
||||
value: provider.npk2SoilPhosphorus.toString(),
|
||||
unit: 'ppm',
|
||||
icon: CupertinoIcons.eyedropper,
|
||||
color: Colors.white,
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
await context.push(
|
||||
'${AppRoute.phosphorus}/${provider.npk1SoilPhosphorus}/${provider.npk2SoilPhosphorus}');
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
class PlantsScreen extends StatelessWidget {
|
||||
const PlantsScreen({super.key});
|
||||
|
|
@ -9,7 +11,19 @@ class PlantsScreen extends StatelessWidget {
|
|||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: Text('Coming Soon', style: AppTheme.labelMedium),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.eco_rounded, size: 100.r, color: AppColor.secondary),
|
||||
Text('Coming Soon', style: AppTheme.titleLarge),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
'This featureis under development',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||
import 'package:flutter/foundation.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();
|
||||
}
|
||||
|
||||
ResultState logoutState = ResultState.initial;
|
||||
Future<void> logout() async {
|
||||
logoutState = ResultState.loading;
|
||||
notifyListeners();
|
||||
try {
|
||||
final result = await AppService().logout();
|
||||
if (result.data == null) {
|
||||
logoutState = ResultState.hasData;
|
||||
} else {
|
||||
logoutState = ResultState.error;
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Error logout: $e');
|
||||
}
|
||||
logoutState = ResultState.error;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
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/core/state/result_state.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});
|
||||
|
|
@ -18,71 +21,72 @@ class SettingScreen extends StatelessWidget {
|
|||
backgroundColor: Colors.white,
|
||||
scrolledUnderElevation: 0,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const CircleAvatar(
|
||||
radius: 30,
|
||||
backgroundColor: AppColor.secondary,
|
||||
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)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
child: Column(
|
||||
body: Consumer<SettingProvider>(builder: (context, provider, child) {
|
||||
return SafeArea(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ListTile(
|
||||
tileColor: Colors.white,
|
||||
title: Text('Account',
|
||||
style:
|
||||
AppTheme.labelSmall.copyWith(color: Colors.black87)),
|
||||
leading: const Icon(BootstrapIcons.person),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16.r,
|
||||
),
|
||||
onTap: () {},
|
||||
const CircleAvatar(
|
||||
radius: 30,
|
||||
backgroundColor: AppColor.secondary,
|
||||
child: Icon(BootstrapIcons.person_fill, color: Colors.white),
|
||||
),
|
||||
ListTile(
|
||||
tileColor: Colors.white,
|
||||
title: Text('Kebijakan & privasi',
|
||||
style:
|
||||
AppTheme.labelSmall.copyWith(color: Colors.black87)),
|
||||
leading: const Icon(BootstrapIcons.shield_check),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16.r,
|
||||
SizedBox(width: 8.w),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(provider.userFullName, style: AppTheme.labelMedium),
|
||||
Text(provider.userEmail, style: AppTheme.labelSmall)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
tileColor: Colors.white,
|
||||
title: Text('Account',
|
||||
style: AppTheme.labelSmall
|
||||
.copyWith(color: Colors.black87)),
|
||||
leading: const Icon(BootstrapIcons.person),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16.r,
|
||||
),
|
||||
onTap: () {},
|
||||
),
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
tileColor: Colors.white,
|
||||
title: Text('Syarat & ketentuan',
|
||||
style:
|
||||
AppTheme.labelSmall.copyWith(color: Colors.black87)),
|
||||
leading: const Icon(BootstrapIcons.file_text),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16.r,
|
||||
ListTile(
|
||||
tileColor: Colors.white,
|
||||
title: Text('Kebijakan & privasi',
|
||||
style: AppTheme.labelSmall
|
||||
.copyWith(color: Colors.black87)),
|
||||
leading: const Icon(BootstrapIcons.shield_check),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16.r,
|
||||
),
|
||||
onTap: () {},
|
||||
),
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
ListTile(
|
||||
tileColor: Colors.white,
|
||||
title: Text('Syarat & ketentuan',
|
||||
style: AppTheme.labelSmall
|
||||
.copyWith(color: Colors.black87)),
|
||||
leading: const Icon(BootstrapIcons.file_text),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16.r,
|
||||
),
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
tileColor: Colors.white,
|
||||
title: Text('Logout',
|
||||
style: AppTheme.labelSmall.copyWith(color: Colors.red)),
|
||||
|
|
@ -109,19 +113,27 @@ class SettingScreen extends StatelessWidget {
|
|||
),
|
||||
TextButton(
|
||||
child: Text('Ya'),
|
||||
onPressed: () {
|
||||
context.go(AppRoute.root);
|
||||
onPressed: () async {
|
||||
await provider.logout();
|
||||
if (context.mounted) {
|
||||
if (provider.logoutState ==
|
||||
ResultState.hasData) {
|
||||
context.go(AppRoute.root);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
));
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.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});
|
||||
|
|
@ -17,37 +21,41 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
FirebaseMessaging.instance.subscribeToTopic('smartfarm');
|
||||
_initialize();
|
||||
}
|
||||
|
||||
Future<void> _initialize() async {
|
||||
// final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
||||
// bool isLoggedIn = await _checkLoginStatus(authProvider);
|
||||
bool isLoggedIn = await _checkLoginStatus();
|
||||
_navigateAfterSplash(isLoggedIn);
|
||||
}
|
||||
|
||||
// Future<bool> _checkLoginStatus(AuthProvider authProvider) async {
|
||||
// SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
Future<bool> _checkLoginStatus() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// if (prefs.getKeys().isEmpty) return false;
|
||||
if (prefs.getKeys().isEmpty) return false;
|
||||
|
||||
// if (prefs.getBool('isLoggedIn') == true) {
|
||||
// String? token = prefs.getString('token');
|
||||
// String? refreshToken = prefs.getString('refreshToken');
|
||||
if (prefs.getBool('isLoggedIn') == true) {
|
||||
String? token = prefs.getString('jwtToken');
|
||||
|
||||
// if (token != null && !JwtDecoder.isExpired(token)) {
|
||||
// return true;
|
||||
// } else if (refreshToken != null && !JwtDecoder.isExpired(refreshToken)) {
|
||||
// final result = await authProvider.refreshToken();
|
||||
// return result == ResultState.hasData;
|
||||
// } else {
|
||||
// prefs.remove('token');
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
if (token != null && !JwtDecoder.isExpired(token)) {
|
||||
if (kDebugMode) {
|
||||
print('Token : ${prefs.getString('token')}');
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
prefs.remove('token');
|
||||
prefs.remove('jwtToken');
|
||||
prefs.remove('username');
|
||||
prefs.remove('email');
|
||||
prefs.remove('fullName');
|
||||
prefs.remove('isLoggedIn');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// return false;
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
void _navigateAfterSplash(bool isLoggedIn) {
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
|
|
|
|||
68
agrilink_vocpro/lib/firebase_options.dart
Normal file
68
agrilink_vocpro/lib/firebase_options.dart
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyCB8i2dE3Oc0kTNtVPw_qSz-T8gPYNjxFk',
|
||||
appId: '1:445047869982:android:d40dc2fd624f86a315540f',
|
||||
messagingSenderId: '445047869982',
|
||||
projectId: 'agrilink-vocpro-b37f9',
|
||||
storageBucket: 'agrilink-vocpro-b37f9.appspot.com',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDZO8tEZFcQrJMwJG_viJTbITWNVqxJo8E',
|
||||
appId: '1:445047869982:ios:9350ac31c363c40415540f',
|
||||
messagingSenderId: '445047869982',
|
||||
projectId: 'agrilink-vocpro-b37f9',
|
||||
storageBucket: 'agrilink-vocpro-b37f9.appspot.com',
|
||||
iosBundleId: 'com.pis.agrilinkVocpro',
|
||||
);
|
||||
}
|
||||
|
|
@ -1,15 +1,21 @@
|
|||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||
import 'package:agrilink_vocpro/domain/service/firebase_api.dart';
|
||||
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:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() {
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp();
|
||||
final firebaseApi = FirebaseApi();
|
||||
await firebaseApi.initNotification();
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp, // Locks the app in portrait mode
|
||||
]).then((_) {
|
||||
|
|
@ -29,22 +35,24 @@ 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),
|
||||
minTextAdapt: true,
|
||||
builder: (_, context) {
|
||||
return MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
|
||||
useMaterial3: true,
|
||||
),
|
||||
routerConfig: AppRoute.router,
|
||||
);
|
||||
}),
|
||||
designSize: const Size(360, 800),
|
||||
minTextAdapt: true,
|
||||
builder: (_, context) {
|
||||
return MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
|
||||
useMaterial3: true,
|
||||
),
|
||||
routerConfig: AppRoute.router,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@
|
|||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import firebase_core
|
||||
import firebase_messaging
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: "5534e701a2c505fed1f0799e652dd6ae23bd4d2c4cf797220e5ced5764a7c1c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.44"
|
||||
animated_segmented_tab_control:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -9,6 +17,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -141,10 +157,58 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
version: "7.0.1"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "51dfe2fbf3a984787a2e7b8592f2f05c986bfedd6fdacea3f9e0a7beb334de96"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.0"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.18.1"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: eb6e28a3a35deda61fe8634967c84215efc19133ba58d8e0fc6c9a2af2cba05e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.1.3"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: b316c4ee10d93d32c033644207afc282d9b2b4372f3cf9c6022f3558b3873d2d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.46"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: d7f0147a1a9fe4313168e20154a01fd5cf332898de1527d3930ff77b8c7f5387
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.2"
|
||||
fl_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -162,10 +226,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "5.0.0"
|
||||
flutter_screenutil:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -196,10 +260,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
|
||||
sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.7"
|
||||
version: "14.3.0"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -208,6 +272,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.1"
|
||||
google_identity_services_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_identity_services_web
|
||||
sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1+4"
|
||||
googleapis_auth:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: googleapis_auth
|
||||
sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -217,7 +297,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.15.4"
|
||||
http:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
|
|
@ -240,6 +320,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:
|
||||
|
|
@ -268,10 +356,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "5.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -340,10 +428,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
||||
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.10"
|
||||
version: "2.2.12"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -412,18 +500,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
|
||||
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.3"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
||||
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
version: "2.5.3"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -561,18 +649,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.5.1 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ dependencies:
|
|||
gauge_indicator: ^0.4.3
|
||||
mqtt_client: ^10.5.1
|
||||
shimmer: ^3.0.0
|
||||
jwt_decoder: ^2.0.1
|
||||
firebase_core: ^3.6.0
|
||||
firebase_messaging: ^15.1.3
|
||||
googleapis_auth: ^1.6.0
|
||||
http: ^1.2.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
@ -57,7 +62,7 @@ dev_dependencies:
|
|||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^4.0.0
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
@ -73,6 +78,7 @@ flutter:
|
|||
assets:
|
||||
- assets/images/
|
||||
- assets/icons/
|
||||
- assets/json/
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
firebase_core
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user