Compare commits
No commits in common. "fa041950c520064763719c50072044b047cbd61a" and "04769c4f006ae378515eb98e413be0ffbae1f6c6" have entirely different histories.
fa041950c5
...
04769c4f00
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
.idea/
|
|
||||||
162
README.md
162
README.md
|
|
@ -1,97 +1,93 @@
|
||||||
# Agrilink Vocpro (Smart Farming Application)
|
# mobile-smartfarming
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. **Project Structure**
|
## Getting started
|
||||||
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.
|
||||||
|
|
||||||
## Project Structure
|
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)!
|
||||||
- `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
|
||||||
|
|
||||||
## Progress Report
|
- [ ] [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:
|
||||||
|
|
||||||
| Date | Type | Description |
|
```
|
||||||
|------------|-------|-----------------------------------------------------------------------------------------------------|
|
cd existing_repo
|
||||||
| 2024-10-09 | feat | Completed the integration of MQTT for sensor data and IoT device control. |
|
git remote add origin https://gitlab.com/profile-image/kedaireka/smartfarming/mobile-smartfarming.git
|
||||||
| 2024-10-02 | fix | Implemented auto-reconnect for MQTT on app open/close. |
|
git branch -M main
|
||||||
| 2024-09-30 | feat | Added UI for displaying soil pH level with a linear bar. |
|
git push -uf origin main
|
||||||
| 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
|
## Contributing
|
||||||
1. Fork the repository.
|
State if you are open to contributions and what your requirements are for accepting them.
|
||||||
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
|
## License
|
||||||
This project is licensed under the MIT 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.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
plugins {
|
plugins {
|
||||||
id "com.android.application"
|
id "com.android.application"
|
||||||
// START: FlutterFire Configuration
|
|
||||||
id 'com.google.gms.google-services'
|
|
||||||
// END: FlutterFire Configuration
|
|
||||||
id "kotlin-android"
|
id "kotlin-android"
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
id "dev.flutter.flutter-gradle-plugin"
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
|
@ -27,7 +24,7 @@ android {
|
||||||
applicationId = "com.pis.agrilink_vocpro"
|
applicationId = "com.pis.agrilink_vocpro"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = 21
|
minSdk = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"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,9 +19,6 @@ pluginManagement {
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "7.3.0" apply false
|
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
|
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
description: This file stores settings for Dart & Flutter DevTools.
|
|
||||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
|
||||||
extensions:
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"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 appName = 'Kebun Pintar';
|
||||||
static const String appVersion = '1.0.0';
|
static const String appVersion = '1.0.0';
|
||||||
|
|
||||||
static const String baseUrl = 'https://jx027dj4-3333.asse.devtunnels.ms';
|
static const String baseUrl = 'http://192.168.11.41:3333/api/';
|
||||||
|
|
||||||
static const String mqttServer = 'armadillo.rmq.cloudamqp.com';
|
static const String mqttServer = 'armadillo.rmq.cloudamqp.com';
|
||||||
static const String mqttUsername = 'obyskxhx:obyskxhx';
|
static const String mqttUsername = 'obyskxhx:obyskxhx';
|
||||||
|
|
@ -10,19 +10,4 @@ class AppConstant {
|
||||||
|
|
||||||
static const String soilTempInfo =
|
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.';
|
'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,12 +10,6 @@ String dateFormater(String date) {
|
||||||
return formatter.format(dateTime);
|
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) {
|
String getGreeting(String time) {
|
||||||
DateTime parsedTime = DateTime.parse(time);
|
DateTime parsedTime = DateTime.parse(time);
|
||||||
int hour = parsedTime.hour;
|
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/dashboard/view/dashboard_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/conductivity/view/conductivity_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/humidity/view/humidity_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/luminosity/view/light_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/light/view/light_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/nitrogen/view/nitrogen_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/ph/view/ph_screen.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/phosphorus/view/phosphorus_screen.dart';
|
import 'package:agrilink_vocpro/features/home/pages/phosphorus/view/phosphorus_screen.dart';
|
||||||
|
|
@ -81,98 +81,77 @@ class AppRoute {
|
||||||
|
|
||||||
static GoRoute buildPotassiumRoute() {
|
static GoRoute buildPotassiumRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'potassium/:value1/:value2',
|
path: 'potassium/:value',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value1 =
|
final double value =
|
||||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
final double value2 =
|
return PotassiumScreen(potassium: value);
|
||||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
|
||||||
return PotassiumScreen(potassiumNpk1: value1, potassiumNpk2: value2);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GoRoute buildPhosphorusRoute() {
|
static GoRoute buildPhosphorusRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'phosphorus/:value1/:value2',
|
path: 'phosphorus/:value',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value1 =
|
final double value =
|
||||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
final double value2 =
|
return PhosphorusScreen(phosphorus: value);
|
||||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
|
||||||
return PhosphorusScreen(
|
|
||||||
phosphorusNpk1: value1,
|
|
||||||
phosphorusNpk2: value2,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GoRoute buildNitrogenRoute() {
|
static GoRoute buildNitrogenRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'nitrogen/:value1/:value2',
|
path: 'nitrogen/:value',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value1 =
|
final double value =
|
||||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
final double value2 =
|
return NitrogenScreen(nitrogen: value);
|
||||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
|
||||||
return NitrogenScreen(nitrogenNpk1: value1, nitrogenNpk2: value2);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GoRoute buildConductivityRoute() {
|
static GoRoute buildConductivityRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'conductivity/:value1/:value2',
|
path: 'conductivity/:value',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value1 =
|
final double value =
|
||||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
final double value2 =
|
return ConductivityScreen(conductivity: value);
|
||||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
|
||||||
return ConductivityScreen(
|
|
||||||
conductivityNpk1: value1,
|
|
||||||
conductivityNpk2: value2,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GoRoute buildSoilMoistureRoute() {
|
static GoRoute buildSoilMoistureRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'soil_moisture/:value1/:value2',
|
path: 'soil_moisture/:value',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value1 =
|
final double value =
|
||||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
final double value2 =
|
return SoilMoistureScreen(moisture: value);
|
||||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
|
||||||
return SoilMoistureScreen(moistureNpk1: value1, moistureNpk2: value2);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GoRoute buildSoilTempRoute() {
|
static GoRoute buildSoilTempRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'soil_temperature/:value1/:value2',
|
path: 'soil_temperature/:value',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value1 =
|
final double value =
|
||||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
final double value2 =
|
return SoilTemperatureScreen(temperature: value);
|
||||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
|
||||||
return SoilTemperatureScreen(
|
|
||||||
npk1Temperature: value1, npk2Temperature: value2);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GoRoute buildAcidityRoute() {
|
static GoRoute buildAcidityRoute() {
|
||||||
return GoRoute(
|
return GoRoute(
|
||||||
path: 'ph/:value1/:value2',
|
path: 'ph/:value',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double value1 =
|
final double value =
|
||||||
double.tryParse(state.pathParameters['value1'] ?? '') ?? 0.0;
|
double.tryParse(state.pathParameters['value'] ?? '') ?? 0.0;
|
||||||
final double value2 =
|
return PhScreen(phValue: value);
|
||||||
double.tryParse(state.pathParameters['value2'] ?? '') ?? 0.0;
|
|
||||||
return PhScreen(phValueNpk1: value1, phValueNpk2: value2);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,33 +8,28 @@ class AppTextfield extends StatelessWidget {
|
||||||
required this.controller,
|
required this.controller,
|
||||||
this.hintText = 'Enter Here',
|
this.hintText = 'Enter Here',
|
||||||
this.suffixIcon,
|
this.suffixIcon,
|
||||||
this.obscureText = false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final Widget? suffixIcon;
|
final Widget? suffixIcon;
|
||||||
final bool obscureText;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderSide: const BorderSide(color: AppColor.textDisable, width: 1),
|
borderSide: const BorderSide(color: AppColor.textDisable, width: 1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderSide: const BorderSide(color: AppColor.primary, width: 1),
|
borderSide: const BorderSide(color: AppColor.primary, width: 1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
hintStyle: AppTheme.hintStyle,
|
hintStyle: AppTheme.hintStyle,
|
||||||
suffixIcon: (suffixIcon != null) ? suffixIcon : null,
|
suffixIcon: (suffixIcon != null) ? suffixIcon : null),
|
||||||
),
|
|
||||||
onTapOutside: (event) => FocusScope.of(context).unfocus(),
|
|
||||||
obscureText: obscureText,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
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(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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,118 +1,19 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||||
import 'package:agrilink_vocpro/core/extension/extention.dart';
|
|
||||||
import 'package:agrilink_vocpro/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/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:dio/dio.dart';
|
||||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
class AppService {
|
class AppService {
|
||||||
final Dio _dioWithoutInterceptor = Dio(
|
final Dio _dioWithoutInterceptor = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl: AppConstant.baseUrl,
|
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 {
|
Future<RelayResponse> getRelayStatus() async {
|
||||||
final SharedPreferences pref = await SharedPreferences.getInstance();
|
|
||||||
final String auth = 'Bearer ${pref.getString('token')}';
|
|
||||||
try {
|
try {
|
||||||
await Future.delayed(const Duration(seconds: 3));
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
final result = await _dioWithoutInterceptor.get(
|
final result = await _dioWithoutInterceptor.get('get-relay');
|
||||||
'/api/get-relay',
|
|
||||||
options: Options(
|
|
||||||
headers: {'Authorization': auth},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (result.statusCode == 200) {
|
if (result.statusCode == 200) {
|
||||||
final data = RelayResponse.fromJson(result.data);
|
final data = RelayResponse.fromJson(result.data);
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -120,144 +21,10 @@ class AppService {
|
||||||
throw Exception('Failed to load data');
|
throw Exception('Failed to load data');
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
final errorMessage = e.response?.data['message'];
|
if (kDebugMode) {
|
||||||
throw (errorMessage);
|
print(e);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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');
|
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
rethrow;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
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/constant/app_constant.dart';
|
||||||
// import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
// import 'package:mqtt_client/mqtt_client.dart';
|
import 'package:mqtt_client/mqtt_client.dart';
|
||||||
// import 'package:mqtt_client/mqtt_server_client.dart';
|
import 'package:mqtt_client/mqtt_server_client.dart';
|
||||||
|
|
||||||
// class MQTTService {
|
class MQTTService {
|
||||||
// MqttServerClient? client;
|
MqttServerClient? client;
|
||||||
|
|
||||||
// Future<ResultState> setupMqtt() async {
|
Future<ResultState> setupMqtt() async {
|
||||||
// client = MqttServerClient(AppConstant.mqttServer, '');
|
client = MqttServerClient(AppConstant.mqttServer, '');
|
||||||
// client!.port = 1883;
|
client!.port = 1883;
|
||||||
|
|
||||||
// client!.connectionMessage = MqttConnectMessage()
|
client!.connectionMessage = MqttConnectMessage()
|
||||||
// .authenticateAs(AppConstant.mqttUsername, AppConstant.mqttPassword)
|
.authenticateAs(AppConstant.mqttUsername, AppConstant.mqttPassword)
|
||||||
// .withClientIdentifier('mobile_client_controller')
|
.withClientIdentifier('mobile_client_controller')
|
||||||
// .startClean() // reset session
|
.startClean() // reset session
|
||||||
// .withWillQos(MqttQos.atLeastOnce);
|
.withWillQos(MqttQos.atLeastOnce);
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// print('MQTT: Connecting....');
|
print('MQTT: Connecting....');
|
||||||
// await client!.connect();
|
await client!.connect();
|
||||||
// print('MQTT: Connected');
|
print('MQTT: Connected');
|
||||||
// return ResultState.hasData;
|
return ResultState.hasData;
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// print('MQTT: Error: $e');
|
print('MQTT: Error: $e');
|
||||||
// return ResultState.error;
|
return ResultState.error;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Future<ResultState> publishMessage(String topic, String message) async {
|
Future<ResultState> publishMessage(String topic, String message) async {
|
||||||
// final builder = MqttClientPayloadBuilder();
|
final builder = MqttClientPayloadBuilder();
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
final bool isConnected = await isMqttConnected(); // Cek apakah terhubung
|
||||||
// if (!isConnected) {
|
if (!isConnected) {
|
||||||
// print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
print('MQTT: Tidak terhubung ke broker. Tidak bisa publish message.');
|
||||||
// return ResultState.error;
|
return ResultState.error;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// print('MQTT: Published message to $topic: $message');
|
print('MQTT: Published message to $topic: $message');
|
||||||
// builder.addString(message);
|
builder.addString(message);
|
||||||
// client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
||||||
// print('MQTT: Message published');
|
print('MQTT: Message published');
|
||||||
// return ResultState.hasData;
|
return ResultState.hasData;
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// print('MQTT: Error: $e');
|
print('MQTT: Error: $e');
|
||||||
// return ResultState.error;
|
return ResultState.error;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Future<ResultState> disconnectMqtt() async {
|
Future<ResultState> disconnectMqtt() async {
|
||||||
// final bool isConnected = await isMqttConnected();
|
final bool isConnected = await isMqttConnected();
|
||||||
// if (isConnected) {
|
if (isConnected) {
|
||||||
// print('Memutus koneksi dari broker...');
|
print('Memutus koneksi dari broker...');
|
||||||
|
|
||||||
// client!.disconnect();
|
client!.disconnect();
|
||||||
|
|
||||||
// await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
// print('Koneksi telah terputus.');
|
print('Koneksi telah terputus.');
|
||||||
// return ResultState.hasData;
|
return ResultState.hasData;
|
||||||
// } else {
|
} else {
|
||||||
// print('Tidak ada koneksi yang sedang aktif.');
|
print('Tidak ada koneksi yang sedang aktif.');
|
||||||
// return ResultState.error;
|
return ResultState.error;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Future<bool> isMqttConnected() async {
|
Future<bool> isMqttConnected() async {
|
||||||
// if (client != null &&
|
if (client != null &&
|
||||||
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||||
// return true; //connected
|
return true; //connected
|
||||||
// } else {
|
} else {
|
||||||
// return false; //not connected
|
return false; //not connected
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Future<bool> subscribeToTopic(String topic) async {
|
Future<bool> subscribeToTopic(String topic) async {
|
||||||
// bool isActive = false;
|
bool isActive = false;
|
||||||
// if (client != null &&
|
if (client != null &&
|
||||||
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||||
// try {
|
try {
|
||||||
// print('MQTT: Subscribing to $topic');
|
print('MQTT: Subscribing to $topic');
|
||||||
// client!.subscribe(topic, MqttQos.atMostOnce);
|
client!.subscribe(topic, MqttQos.atMostOnce);
|
||||||
// print('MQTT: Subscribed to $topic');
|
print('MQTT: Subscribed to $topic');
|
||||||
|
|
||||||
// // Tambahkan log ini untuk memastikan bahwa listener dijalankan
|
// Tambahkan log ini untuk memastikan bahwa listener dijalankan
|
||||||
// if (client!.updates != null) {
|
if (client!.updates != null) {
|
||||||
// print('MQTT: Listening for updates...');
|
print('MQTT: Listening for updates...');
|
||||||
// } else {
|
} else {
|
||||||
// print('MQTT: No updates stream available');
|
print('MQTT: No updates stream available');
|
||||||
// }
|
}
|
||||||
|
|
||||||
// client!.updates!.listen(
|
client!.updates!.listen(
|
||||||
// (List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
(List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||||
// print('MQTT: Subscribe Message received!');
|
print('MQTT: Subscribe Message received!');
|
||||||
// if (messages != null && messages.isNotEmpty) {
|
if (messages != null && messages.isNotEmpty) {
|
||||||
// final MqttPublishMessage recMessage =
|
final MqttPublishMessage recMessage =
|
||||||
// messages[0].payload as MqttPublishMessage;
|
messages[0].payload as MqttPublishMessage;
|
||||||
// final String payload = MqttPublishPayload.bytesToStringAsString(
|
final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||||
// recMessage.payload.message);
|
recMessage.payload.message);
|
||||||
// print(
|
print(
|
||||||
// 'MQTT: Subscribe Message received on topic ${messages[0].topic}: $payload');
|
'MQTT: Subscribe Message received on topic ${messages[0].topic}: $payload');
|
||||||
|
|
||||||
// if (payload == 'ON') {
|
if (payload == 'ON') {
|
||||||
// isActive = true;
|
isActive = true;
|
||||||
// // Update UI atau provider untuk menandakan relay ON
|
// Update UI atau provider untuk menandakan relay ON
|
||||||
// } else if (payload == 'OFF') {
|
} else if (payload == 'OFF') {
|
||||||
// isActive = false;
|
isActive = false;
|
||||||
// // Update UI atau provider untuk menandakan relay OFF
|
// Update UI atau provider untuk menandakan relay OFF
|
||||||
// } else {
|
} else {
|
||||||
// print('MQTT: Invalid Subscribe message received');
|
print('MQTT: Invalid Subscribe message received');
|
||||||
// }
|
}
|
||||||
// } else {
|
} else {
|
||||||
// print('MQTT: No Subscribe messages received');
|
print('MQTT: No Subscribe messages received');
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// );
|
);
|
||||||
|
|
||||||
// return isActive;
|
return isActive;
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// print('MQTT: Error subscribing to $topic: $e');
|
print('MQTT: Error subscribing to $topic: $e');
|
||||||
// return isActive;
|
return isActive;
|
||||||
// }
|
}
|
||||||
// } else {
|
} else {
|
||||||
// print('MQTT: Not connected, cannot subscribe.');
|
print('MQTT: Not connected, cannot subscribe.');
|
||||||
// return false;
|
return false;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Future<ResultState> subscribeToRelayStatus() async {
|
Future<ResultState> subscribeToRelayStatus() async {
|
||||||
// if (client != null &&
|
if (client != null &&
|
||||||
// client!.connectionStatus!.state == MqttConnectionState.connected) {
|
client!.connectionStatus!.state == MqttConnectionState.connected) {
|
||||||
// try {
|
try {
|
||||||
// print('MQTT: Subscribing to /smartfarming/getRelayStatus');
|
print('MQTT: Subscribing to /smartfarming/getRelayStatus');
|
||||||
// client!.subscribe('smartfarming/getRelayStatus', MqttQos.atMostOnce);
|
client!.subscribe('smartfarming/getRelayStatus', MqttQos.atMostOnce);
|
||||||
// print('MQTT: Subscribed to /smartfarming/getRelayStatus');
|
print('MQTT: Subscribed to /smartfarming/getRelayStatus');
|
||||||
|
|
||||||
// client!.updates!
|
client!.updates!
|
||||||
// .listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
.listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
|
||||||
// if (messages != null && messages.isNotEmpty) {
|
if (messages != null && messages.isNotEmpty) {
|
||||||
// final MqttPublishMessage recMessage =
|
final MqttPublishMessage recMessage =
|
||||||
// messages[0].payload as MqttPublishMessage;
|
messages[0].payload as MqttPublishMessage;
|
||||||
// final String payload = MqttPublishPayload.bytesToStringAsString(
|
final String payload = MqttPublishPayload.bytesToStringAsString(
|
||||||
// recMessage.payload.message);
|
recMessage.payload.message);
|
||||||
// print(
|
print(
|
||||||
// 'MQTT: Message received on topic ${messages[0].topic}: $payload');
|
'MQTT: Message received on topic ${messages[0].topic}: $payload');
|
||||||
|
|
||||||
// // Parse the received JSON payload
|
// Parse the received JSON payload
|
||||||
// final Map<String, dynamic> relayStatus = jsonDecode(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
|
// Assuming you are using provider, notify it with new relay status
|
||||||
// // _updateRelayStatus(relayStatus);
|
// _updateRelayStatus(relayStatus);
|
||||||
// } else {
|
} else {
|
||||||
// print('MQTT: No messages received');
|
print('MQTT: No messages received');
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// return ResultState.hasData;
|
return ResultState.hasData;
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// print('MQTT: Error subscribing: $e');
|
print('MQTT: Error subscribing: $e');
|
||||||
// return ResultState.error;
|
return ResultState.error;
|
||||||
// }
|
}
|
||||||
// } else {
|
} else {
|
||||||
// print('MQTT: Not connected, cannot subscribe.');
|
print('MQTT: Not connected, cannot subscribe.');
|
||||||
// return ResultState.error;
|
return ResultState.error;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,109 +1,12 @@
|
||||||
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/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 {
|
class AuthProvider extends ChangeNotifier {
|
||||||
TextEditingController emailController = TextEditingController();
|
TextEditingController emailController = TextEditingController();
|
||||||
TextEditingController passwordController = TextEditingController();
|
TextEditingController passwordController = TextEditingController();
|
||||||
|
|
||||||
bool _isRememberMe = false;
|
|
||||||
bool get isRememberMe => _isRememberMe;
|
|
||||||
|
|
||||||
String errorMessage = '';
|
|
||||||
|
|
||||||
ResultState loginState = ResultState.initial;
|
|
||||||
|
|
||||||
void controllerClear() {
|
void controllerClear() {
|
||||||
emailController.clear();
|
emailController.clear();
|
||||||
passwordController.clear();
|
passwordController.clear();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRememberMe(bool value) {
|
|
||||||
_isRememberMe = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> login(context,
|
|
||||||
{required String email, required String password}) async {
|
|
||||||
loginState = ResultState.loading;
|
|
||||||
notifyListeners();
|
|
||||||
try {
|
|
||||||
final result = await AppService().login(
|
|
||||||
username: email,
|
|
||||||
password: password,
|
|
||||||
rememberMe: isRememberMe.toString(),
|
|
||||||
);
|
|
||||||
if (result.data != null) {
|
|
||||||
loginState = ResultState.hasData;
|
|
||||||
notifyListeners();
|
|
||||||
} else {
|
|
||||||
errorMessage = 'Login gagal, data tidak ditemukan';
|
|
||||||
loginState = ResultState.error;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('Error'),
|
|
||||||
content: Text('$e'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: const Text('OK'),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
loginState = ResultState.error;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool validateInputs() {
|
|
||||||
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
|
|
||||||
errorMessage = 'Email dan password tidak boleh kosong';
|
|
||||||
loginState = ResultState.error;
|
|
||||||
notifyListeners();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,9 +1,8 @@
|
||||||
|
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/core/widgets/app_button.dart';
|
import 'package:agrilink_vocpro/core/widgets/app_button.dart';
|
||||||
import 'package:agrilink_vocpro/core/widgets/app_textfield.dart';
|
import 'package:agrilink_vocpro/core/widgets/app_textfield.dart';
|
||||||
import 'package:agrilink_vocpro/core/widgets/password_textfield.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
|
import 'package:agrilink_vocpro/features/auth/provider/auth_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
@ -15,15 +14,18 @@ class LoginScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: GestureDetector(
|
||||||
child: Consumer<AuthProvider>(
|
onTap: () {
|
||||||
builder: (context, authP, child) {
|
FocusScope.of(context).unfocus();
|
||||||
|
},
|
||||||
|
child: SafeArea(
|
||||||
|
child: Consumer<AuthProvider>(builder: (context, authP, child) {
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 40),
|
const SizedBox(height: 40),
|
||||||
Text(
|
Text(
|
||||||
'Hello, Welcome back 👋',
|
'Hello Wellcome back 👋',
|
||||||
style: AppTheme.titleLarge,
|
style: AppTheme.titleLarge,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -35,56 +37,36 @@ class LoginScreen extends StatelessWidget {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
AppTextfield(
|
AppTextfield(
|
||||||
controller: authP.emailController,
|
controller: authP.emailController,
|
||||||
hintText: 'Masukkan email',
|
hintText: 'Masukkan username',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text('Password', style: AppTheme.labelLarge),
|
Text('Password', style: AppTheme.labelLarge),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
PasswordTextfield(
|
AppTextfield(
|
||||||
controller: authP.passwordController,
|
controller: authP.passwordController,
|
||||||
hintText: 'Masukkan password',
|
hintText: 'Masukkan password',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Row(
|
GestureDetector(
|
||||||
children: [
|
onTap: () {
|
||||||
Checkbox(
|
print('Forgot password?');
|
||||||
value: authP.isRememberMe,
|
},
|
||||||
onChanged: (value) {
|
child: Text(
|
||||||
authP.setRememberMe(value!);
|
'Forgot password?',
|
||||||
},
|
textAlign: TextAlign.end,
|
||||||
),
|
style: AppTheme.labelMedium
|
||||||
Text('Remember me', style: AppTheme.labelLarge),
|
.copyWith(color: AppColor.secondary),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
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(
|
AppButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
authP.getAccessToken();
|
GoRouter.of(context).go(AppRoute.dashboard);
|
||||||
},
|
},
|
||||||
text: 'Get Access Token'),
|
text: 'Login'),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
||||||
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
import 'package:agrilink_vocpro/domain/service/app_service.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ControlProvider extends ChangeNotifier {
|
class ControlProvider extends ChangeNotifier {
|
||||||
final AppService _appService = AppService();
|
final AppService _appService = AppService();
|
||||||
|
|
@ -52,9 +54,6 @@ class ControlProvider extends ChangeNotifier {
|
||||||
relayState = ResultState.loading;
|
relayState = ResultState.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
if (kDebugMode) {
|
|
||||||
print('try to get relay status...');
|
|
||||||
}
|
|
||||||
final result = await _appService.getRelayStatus();
|
final result = await _appService.getRelayStatus();
|
||||||
if (result.success == true) {
|
if (result.success == true) {
|
||||||
for (var element in result.data!) {
|
for (var element in result.data!) {
|
||||||
|
|
@ -74,44 +73,26 @@ class ControlProvider extends ChangeNotifier {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
relayState = ResultState.error;
|
relayState = ResultState.error;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
if (kDebugMode) {
|
print(e);
|
||||||
print(e);
|
rethrow;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> switchRelay(int relayNumber, bool state) async {
|
// Future<void> disconnectMqtt() async {
|
||||||
relayState = ResultState.loading;
|
// try {
|
||||||
notifyListeners();
|
// await _mqttService.disconnectMqtt();
|
||||||
|
// } catch (e) {
|
||||||
|
// print(e);
|
||||||
|
// rethrow;
|
||||||
|
// }
|
||||||
|
// notifyListeners();
|
||||||
|
// }
|
||||||
|
|
||||||
final int stateConverted;
|
// @override
|
||||||
if (state == true) {
|
// void dispose() {
|
||||||
stateConverted = 1;
|
// disconnectMqtt();
|
||||||
} else {
|
// super.dispose();
|
||||||
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) {
|
void switchControl1(bool value) {
|
||||||
_control_1 = 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/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.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/provider/control_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/control/widgets/control_button_widget.dart';
|
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -22,80 +22,36 @@ class ControlScreen extends StatelessWidget {
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
displacement: 10,
|
|
||||||
onRefresh: () async =>
|
onRefresh: () async =>
|
||||||
await context.read<ControlProvider>().getRelayStatus(),
|
await context.read<ControlProvider>().getRelayStatus(),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
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),
|
SizedBox(height: 16.h),
|
||||||
GridView(
|
GridView(
|
||||||
padding: EdgeInsets.all(16.r),
|
padding: EdgeInsets.all(16.r),
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: 16.r,
|
crossAxisSpacing: 16.r,
|
||||||
mainAxisSpacing: 16.r,
|
mainAxisSpacing: 16.r,
|
||||||
childAspectRatio: 1.35.h,
|
childAspectRatio: 1.4.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);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
ControlButtonWidget(
|
children: [
|
||||||
title: 'Lampu Utama',
|
ControlButtonWidget(
|
||||||
subtitle: 'Relay 2',
|
title: 'Katup Air',
|
||||||
isActive: provider.control_2,
|
subtitle: 'Relay 1',
|
||||||
onTap: () {
|
isActive: provider.control_1,
|
||||||
provider.control_2 != true
|
onTap: () {},
|
||||||
? provider.switchControl2(true)
|
),
|
||||||
: provider.switchControl2(false);
|
ControlButtonWidget(
|
||||||
},
|
title: 'Lampu Utama',
|
||||||
),
|
subtitle: 'Relay 2',
|
||||||
],
|
isActive: provider.control_2,
|
||||||
),
|
onTap: () {},
|
||||||
|
),
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -104,46 +60,74 @@ class ControlScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatusBarWidget extends StatelessWidget {
|
class ControlButtonWidget extends StatelessWidget {
|
||||||
const StatusBarWidget({
|
const ControlButtonWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.title,
|
||||||
required this.icon,
|
required this.subtitle,
|
||||||
required this.color,
|
required this.isActive,
|
||||||
this.isLoading = false,
|
required this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String text;
|
final String title;
|
||||||
final IconData icon;
|
final String subtitle;
|
||||||
final Color color;
|
final bool isActive;
|
||||||
final bool isLoading;
|
final Function() onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
|
padding: EdgeInsets.all(16.r),
|
||||||
margin: EdgeInsets.all(16.r),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withAlpha(50),
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(8.r),
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
),
|
boxShadow: [
|
||||||
child: Row(
|
BoxShadow(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
color: Colors.grey.withOpacity(0.2),
|
||||||
children: [
|
spreadRadius: 1.r,
|
||||||
Icon(
|
blurRadius: 16.r,
|
||||||
icon,
|
offset: Offset(0, 12.r),
|
||||||
color: color,
|
|
||||||
size: 16.r,
|
|
||||||
),
|
),
|
||||||
SizedBox(width: 8.w),
|
],
|
||||||
Text(text, style: AppTheme.titleSmall.copyWith(color: color)),
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title, style: AppTheme.labelMedium),
|
||||||
|
Text(subtitle, style: AppTheme.labelSmall),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (isLoading)
|
Row(
|
||||||
Center(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
child: CupertinoActivityIndicator(
|
children: [
|
||||||
radius: 8.r,
|
Consumer<ControlProvider>(
|
||||||
))
|
builder: (context, provider, child) {
|
||||||
else
|
switch (provider.relayState) {
|
||||||
const SizedBox.shrink(),
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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,159 +1,84 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/conductivity/provider/conductivity_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class ConductivityScreen extends StatelessWidget {
|
class ConductivityScreen extends StatelessWidget {
|
||||||
const ConductivityScreen(
|
const ConductivityScreen({super.key, this.conductivity = 0.0});
|
||||||
{super.key, this.conductivityNpk1 = 0.0, this.conductivityNpk2 = 0.0});
|
|
||||||
|
|
||||||
final double conductivityNpk1;
|
final double conductivity;
|
||||||
final double conductivityNpk2;
|
double get value => conductivity;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => ConductivityProvider(),
|
appBar: AppBar(
|
||||||
child: DefaultTabController(
|
title: Text('Conductivity', style: AppTheme.labelMedium),
|
||||||
length: 2,
|
centerTitle: true,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.white,
|
||||||
appBar: AppBar(
|
scrolledUnderElevation: 0,
|
||||||
title: Text('Soil Conductivity', style: AppTheme.labelMedium),
|
actions: const [
|
||||||
centerTitle: true,
|
Padding(
|
||||||
backgroundColor: Colors.white,
|
padding: EdgeInsets.only(right: 16),
|
||||||
scrolledUnderElevation: 0,
|
child: Icon(
|
||||||
actions: const [
|
Icons.electric_bolt_rounded,
|
||||||
Padding(
|
color: Colors.teal,
|
||||||
padding: EdgeInsets.only(right: 16),
|
),
|
||||||
child: Icon(
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 32.h),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
Icons.electric_bolt_rounded,
|
Icons.electric_bolt_rounded,
|
||||||
|
size: 64.r,
|
||||||
color: Colors.teal,
|
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),
|
||||||
body: TabBarView(
|
Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
buildTabContent(context, conductivityNpk1, 'NPK 1', true),
|
children: [
|
||||||
buildTabContent(context, conductivityNpk2, 'NPK 2', false),
|
Text(
|
||||||
],
|
'Daya Arus Listrik',
|
||||||
),
|
style: AppTheme.labelMedium,
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
IconButton(
|
||||||
}
|
iconSize: 20.r,
|
||||||
|
color: Colors.blue,
|
||||||
SafeArea buildTabContent(
|
onPressed: () {},
|
||||||
BuildContext context, double value, String label, bool isNpk1) {
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
return SafeArea(
|
],
|
||||||
child: ListView(
|
),
|
||||||
padding: EdgeInsets.all(16.w),
|
SizedBox(height: 16.h),
|
||||||
children: [
|
const Text('Grafik'),
|
||||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
SizedBox(height: 16.h),
|
||||||
buildSoilInfo(context, value),
|
AspectRatio(
|
||||||
SizedBox(height: 16.h),
|
aspectRatio: 1.6.h,
|
||||||
buildInfoRow(context),
|
child: Container(
|
||||||
SizedBox(height: 16.h),
|
decoration: BoxDecoration(
|
||||||
const Text('Grafik'),
|
color: Colors.white,
|
||||||
SizedBox(height: 16.h),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
buildGraphicContent(context, isNpk1),
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
],
|
),
|
||||||
),
|
child: const GarphicWidget(
|
||||||
);
|
gradientColors: [
|
||||||
}
|
Colors.cyan,
|
||||||
|
Colors.teal,
|
||||||
Widget buildSoilInfo(BuildContext context, double value) {
|
],
|
||||||
return Center(
|
),
|
||||||
child: Column(
|
),
|
||||||
children: [
|
)
|
||||||
Icon(
|
],
|
||||||
Icons.electric_bolt_rounded,
|
|
||||||
size: 64.r,
|
|
||||||
color: Colors.teal,
|
|
||||||
),
|
|
||||||
Text('$value µS/cm', style: AppTheme.headline1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildInfoRow(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Soil Condutivity',
|
|
||||||
style: AppTheme.labelMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 20.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(BootstrapIcons.info_circle),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
|
||||||
return AspectRatio(
|
|
||||||
aspectRatio: 1.6.h,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
|
||||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
|
||||||
),
|
|
||||||
child: Consumer<ConductivityProvider>(
|
|
||||||
builder: (context, provider, child) {
|
|
||||||
final dataState = provider.dataState;
|
|
||||||
|
|
||||||
switch (dataState) {
|
|
||||||
case ResultState.loading:
|
|
||||||
return const Center(child: CupertinoActivityIndicator());
|
|
||||||
case ResultState.hasData:
|
|
||||||
return GarphicWidget(
|
|
||||||
gradientColors: const [Colors.teal, Colors.greenAccent],
|
|
||||||
hour: List.generate(
|
|
||||||
isNpk1
|
|
||||||
? provider.dataFetchedNpk1.length
|
|
||||||
: provider.dataFetchedNpk2.length,
|
|
||||||
(index) => isNpk1
|
|
||||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
|
||||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
|
||||||
),
|
|
||||||
data: List.generate(
|
|
||||||
isNpk1
|
|
||||||
? provider.dataFetchedNpk1.length
|
|
||||||
: provider.dataFetchedNpk2.length,
|
|
||||||
(index) => isNpk1
|
|
||||||
? provider.dataFetchedNpk1[index].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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
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,6 +1,4 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/humidity/provider/humidity_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
|
|
@ -18,185 +16,153 @@ class HumidityScreen extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => HumidityProvider(),
|
appBar: AppBar(
|
||||||
child: Scaffold(
|
title: Text('Humidity', style: AppTheme.labelMedium),
|
||||||
appBar: AppBar(
|
centerTitle: true,
|
||||||
title: Text('Humidity', style: AppTheme.labelMedium),
|
scrolledUnderElevation: 0,
|
||||||
centerTitle: true,
|
leading: IconButton(
|
||||||
scrolledUnderElevation: 0,
|
icon: const Icon(CupertinoIcons.back),
|
||||||
leading: IconButton(
|
onPressed: () => Navigator.pop(context),
|
||||||
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(
|
backgroundColor: Colors.white,
|
||||||
child: Consumer<HomeProvider>(builder: (context, provider, child) {
|
actions: const [
|
||||||
return ListView(
|
Padding(
|
||||||
children: [
|
padding: EdgeInsets.only(right: 16),
|
||||||
SizedBox(
|
child: Icon(
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
BootstrapIcons.droplet_half,
|
||||||
),
|
color: Colors.blue,
|
||||||
SizedBox(
|
),
|
||||||
height: 280.h,
|
)
|
||||||
child: Stack(
|
],
|
||||||
fit: StackFit.expand,
|
),
|
||||||
children: [
|
body: SafeArea(
|
||||||
Center(
|
child: Consumer<HomeProvider>(builder: (context, provider, child) {
|
||||||
child: Column(
|
return ListView(
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
SizedBox(
|
||||||
const Icon(BootstrapIcons.droplet_half,
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
size: 32, color: Colors.blue),
|
),
|
||||||
Text('$humidity %', style: AppTheme.headline1),
|
SizedBox(
|
||||||
],
|
height: 280.h,
|
||||||
),
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(BootstrapIcons.droplet_half,
|
||||||
|
size: 32, color: Colors.blue),
|
||||||
|
Text('60 %', style: AppTheme.headline1),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
RotatedBox(
|
),
|
||||||
quarterTurns: 2,
|
RotatedBox(
|
||||||
child: AnimatedRadialGauge(
|
quarterTurns: 2,
|
||||||
duration: const Duration(seconds: 3),
|
child: AnimatedRadialGauge(
|
||||||
curve: Curves.easeOut,
|
duration: const Duration(seconds: 3),
|
||||||
value: humidity,
|
curve: Curves.easeOut,
|
||||||
axis: GaugeAxis(
|
value: 60,
|
||||||
degrees: 360,
|
axis: GaugeAxis(
|
||||||
min: 0,
|
degrees: 360,
|
||||||
max: 100,
|
min: 0,
|
||||||
pointer: null,
|
max: 100,
|
||||||
style: GaugeAxisStyle(
|
pointer: null,
|
||||||
background: Colors.grey.shade100,
|
style: GaugeAxisStyle(
|
||||||
thickness: 50,
|
background: Colors.grey.shade100,
|
||||||
),
|
thickness: 50,
|
||||||
progressBar: GaugeBasicProgressBar(
|
),
|
||||||
gradient: GaugeAxisGradient(colors: [
|
progressBar: GaugeBasicProgressBar(
|
||||||
Colors.blue.shade200,
|
gradient: GaugeAxisGradient(colors: [
|
||||||
Colors.blue,
|
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,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Row(
|
SizedBox(height: 16.h),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
Padding(
|
||||||
children: [
|
padding: EdgeInsets.only(left: 16.w),
|
||||||
Text(
|
child: const Text('Deskripsi'),
|
||||||
'Humidity',
|
),
|
||||||
style: AppTheme.labelMedium,
|
// ListView.builder(
|
||||||
textAlign: TextAlign.center,
|
// shrinkWrap: true,
|
||||||
),
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
IconButton(
|
// itemCount: provider.humidtyRules.length,
|
||||||
iconSize: 20.r,
|
// itemBuilder: (context, index) {
|
||||||
color: Colors.blue,
|
// final item = provider.humidtyRules[index];
|
||||||
onPressed: () {},
|
// return Theme(
|
||||||
icon: const Icon(BootstrapIcons.info_circle))
|
// data: Theme.of(context)
|
||||||
],
|
// .copyWith(dividerColor: Colors.transparent),
|
||||||
),
|
// child: ExpansionTile(
|
||||||
const SizedBox(height: 16),
|
// trailing: Text(
|
||||||
Padding(
|
// item.censorText,
|
||||||
padding: EdgeInsets.only(left: 16.w),
|
// style: TextStyle(color: item.color),
|
||||||
child: const Text('Grafik dalam 7 hari terakhir'),
|
// ),
|
||||||
),
|
// expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(height: 16),
|
// childrenPadding: EdgeInsets.all(16.r),
|
||||||
AspectRatio(
|
// title: Text(
|
||||||
aspectRatio: 2.h,
|
// 'Kelembaban ${item.minPercentage}% - ${item.maxPercentage}%'),
|
||||||
child: Container(
|
// children: [
|
||||||
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
// Text(
|
||||||
decoration: BoxDecoration(
|
// item.description,
|
||||||
color: Colors.white,
|
// style: AppTheme.labelMedium,
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
// ),
|
||||||
border: Border.all(
|
// SizedBox(height: 8.h),
|
||||||
color: Colors.grey.shade300, width: 1.w)),
|
// Text('Tindakan', style: AppTheme.labelSmall),
|
||||||
child: Consumer<HumidityProvider>(
|
// SizedBox(height: 8.h),
|
||||||
builder: (context, provider, child) {
|
// Text(item.action),
|
||||||
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'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
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,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// )
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,191 +0,0 @@
|
||||||
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'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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,159 +1,85 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/nitrogen/provider/nitrogen_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class NitrogenScreen extends StatelessWidget {
|
class NitrogenScreen extends StatelessWidget {
|
||||||
const NitrogenScreen(
|
const NitrogenScreen({super.key, this.nitrogen = 0.0});
|
||||||
{super.key, this.nitrogenNpk1 = 0.0, this.nitrogenNpk2 = 0.0});
|
|
||||||
|
|
||||||
final double nitrogenNpk1;
|
final double nitrogen;
|
||||||
final double nitrogenNpk2;
|
double get value => nitrogen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => NitrogenProvider(),
|
appBar: AppBar(
|
||||||
child: DefaultTabController(
|
title: Text('Nitrogen', style: AppTheme.labelMedium),
|
||||||
length: 2,
|
centerTitle: true,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.white,
|
||||||
appBar: AppBar(
|
scrolledUnderElevation: 0,
|
||||||
title: Text('Soil Nitrogen', style: AppTheme.labelMedium),
|
actions: const [
|
||||||
centerTitle: true,
|
Padding(
|
||||||
backgroundColor: Colors.white,
|
padding: EdgeInsets.only(right: 16),
|
||||||
scrolledUnderElevation: 0,
|
child: Icon(
|
||||||
actions: const [
|
CupertinoIcons.eyedropper,
|
||||||
Padding(
|
color: Colors.blue,
|
||||||
padding: EdgeInsets.only(right: 16),
|
),
|
||||||
child: Icon(
|
)
|
||||||
BootstrapIcons.eyedropper,
|
],
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 32.h),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
CupertinoIcons.eyedropper,
|
||||||
|
size: 64.r,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
)
|
Text('$value ppm', style: AppTheme.headline1),
|
||||||
],
|
|
||||||
bottom: const TabBar(
|
|
||||||
tabs: [
|
|
||||||
Tab(text: 'NPK 1'),
|
|
||||||
Tab(text: 'NPK 2'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 32.h),
|
||||||
body: TabBarView(
|
Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
buildTabContent(context, nitrogenNpk1, 'NPK 1', true),
|
children: [
|
||||||
buildTabContent(context, nitrogenNpk2, 'NPK 2', false),
|
Text(
|
||||||
],
|
'Nitrogen',
|
||||||
),
|
style: AppTheme.labelMedium,
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
IconButton(
|
||||||
}
|
iconSize: 20.r,
|
||||||
|
color: Colors.blue,
|
||||||
SafeArea buildTabContent(
|
onPressed: () {},
|
||||||
BuildContext context, double value, String label, bool isNpk1) {
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
return SafeArea(
|
],
|
||||||
child: ListView(
|
),
|
||||||
padding: EdgeInsets.all(16.w),
|
SizedBox(height: 16.h),
|
||||||
children: [
|
const Text('Grafik'),
|
||||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
SizedBox(height: 16.h),
|
||||||
buildSoilInfo(context, value),
|
AspectRatio(
|
||||||
SizedBox(height: 16.h),
|
aspectRatio: 1.6.h,
|
||||||
buildInfoRow(context),
|
child: Container(
|
||||||
SizedBox(height: 16.h),
|
decoration: BoxDecoration(
|
||||||
const Text('Grafik'),
|
color: Colors.white,
|
||||||
SizedBox(height: 16.h),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
buildGraphicContent(context, isNpk1),
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
],
|
),
|
||||||
),
|
child: GarphicWidget(
|
||||||
);
|
gradientColors: [
|
||||||
}
|
Colors.blue.shade200,
|
||||||
|
Colors.blue,
|
||||||
Widget buildSoilInfo(BuildContext context, double value) {
|
],
|
||||||
return Center(
|
),
|
||||||
child: Column(
|
),
|
||||||
children: [
|
)
|
||||||
Icon(
|
],
|
||||||
CupertinoIcons.eyedropper,
|
|
||||||
size: 64.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
Text('$value ppm', style: AppTheme.headline1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildInfoRow(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Soil Nitrogen',
|
|
||||||
style: AppTheme.labelMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 20.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(BootstrapIcons.info_circle),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
|
||||||
return AspectRatio(
|
|
||||||
aspectRatio: 1.6.h,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
|
||||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
|
||||||
),
|
|
||||||
child: Consumer<NitrogenProvider>(
|
|
||||||
builder: (context, provider, child) {
|
|
||||||
final dataState = provider.dataState;
|
|
||||||
|
|
||||||
switch (dataState) {
|
|
||||||
case ResultState.loading:
|
|
||||||
return const Center(child: CupertinoActivityIndicator());
|
|
||||||
case ResultState.hasData:
|
|
||||||
return GarphicWidget(
|
|
||||||
gradientColors: const [Colors.blue, Colors.blueAccent],
|
|
||||||
hour: List.generate(
|
|
||||||
isNpk1
|
|
||||||
? provider.dataFetchedNpk1.length
|
|
||||||
: provider.dataFetchedNpk2.length,
|
|
||||||
(index) => isNpk1
|
|
||||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
|
||||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
|
||||||
),
|
|
||||||
data: List.generate(
|
|
||||||
isNpk1
|
|
||||||
? provider.dataFetchedNpk1.length
|
|
||||||
: provider.dataFetchedNpk2.length,
|
|
||||||
(index) => isNpk1
|
|
||||||
? provider.dataFetchedNpk1[index].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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
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,150 +1,181 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/ph/provider/ph_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/ph/widget/ph_bar_pointer.dart';
|
import 'package:agrilink_vocpro/features/home/pages/ph/widget/ph_bar_pointer.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class PhScreen extends StatelessWidget {
|
class PhScreen extends StatelessWidget {
|
||||||
const PhScreen({super.key, this.phValueNpk1 = 0, this.phValueNpk2 = 0});
|
const PhScreen({super.key, required this.phValue});
|
||||||
|
|
||||||
final double phValueNpk1;
|
final double phValue;
|
||||||
final double phValueNpk2;
|
|
||||||
|
double get value => phValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => PhProvider(),
|
appBar: AppBar(
|
||||||
child: DefaultTabController(
|
title: Text('pH Tanah', style: AppTheme.labelMedium),
|
||||||
length: 2,
|
centerTitle: true,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.white,
|
||||||
appBar: AppBar(
|
scrolledUnderElevation: 0,
|
||||||
title: Text('Soil Temperature', style: AppTheme.labelMedium),
|
actions: const [
|
||||||
centerTitle: true,
|
Padding(
|
||||||
backgroundColor: Colors.white,
|
padding: EdgeInsets.only(right: 16),
|
||||||
scrolledUnderElevation: 0,
|
child: Icon(
|
||||||
actions: const [
|
BootstrapIcons.pie_chart,
|
||||||
Padding(
|
color: Colors.orange,
|
||||||
padding: EdgeInsets.only(right: 16),
|
|
||||||
child: Icon(
|
|
||||||
BootstrapIcons.pie_chart,
|
|
||||||
color: Colors.amber,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
bottom: const TabBar(
|
|
||||||
tabs: [
|
|
||||||
Tab(text: 'NPK 1'),
|
|
||||||
Tab(text: 'NPK 2'),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
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),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
body: SafeArea(
|
||||||
}
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
Widget buildSoilInfo(BuildContext context, double value) {
|
children: [
|
||||||
return Center(
|
SizedBox(
|
||||||
child: PhIndicator(phValue: value),
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
);
|
),
|
||||||
}
|
Center(
|
||||||
|
child: PhIndicator(phValue: value), // Set nilai pH di sini
|
||||||
Widget buildInfoRow(BuildContext context) {
|
),
|
||||||
return Row(
|
const SizedBox(height: 16),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
const SizedBox(height: 16),
|
||||||
children: [
|
Row(
|
||||||
Text(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
'Soil Acidity',
|
children: [
|
||||||
style: AppTheme.labelMedium,
|
Text(
|
||||||
textAlign: TextAlign.center,
|
'pH',
|
||||||
),
|
style: AppTheme.labelMedium,
|
||||||
IconButton(
|
textAlign: TextAlign.center,
|
||||||
iconSize: 20.r,
|
),
|
||||||
color: Colors.blue,
|
IconButton(
|
||||||
onPressed: () {},
|
iconSize: 20.r,
|
||||||
icon: const Icon(BootstrapIcons.info_circle),
|
color: Colors.blue,
|
||||||
),
|
onPressed: () {},
|
||||||
],
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
);
|
],
|
||||||
}
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
// Row(
|
||||||
return AspectRatio(
|
// mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
aspectRatio: 1.6.h,
|
// children: [
|
||||||
child: Container(
|
// Container(
|
||||||
decoration: BoxDecoration(
|
// height: 100.h,
|
||||||
color: Colors.white,
|
// width: 100.w,
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
// decoration: BoxDecoration(
|
||||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
// borderRadius: BorderRadius.circular(16),
|
||||||
),
|
// color: Colors.blue.withOpacity(0.1),
|
||||||
child: Consumer<PhProvider>(
|
// border: Border.all(
|
||||||
builder: (context, provider, child) {
|
// color: Colors.blue,
|
||||||
final dataState = provider.dataState;
|
// width: 2,
|
||||||
|
// ),
|
||||||
switch (dataState) {
|
// ),
|
||||||
case ResultState.loading:
|
// child: Column(
|
||||||
return const Center(child: CupertinoActivityIndicator());
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
case ResultState.hasData:
|
// children: [
|
||||||
return GarphicWidget(
|
// Text('Low',
|
||||||
gradientColors: const [Colors.cyan, Colors.amber],
|
// style: AppTheme.labelMedium
|
||||||
hour: List.generate(
|
// .copyWith(color: Colors.blue)),
|
||||||
isNpk1
|
// // SizedBox(height: 8.h),
|
||||||
? provider.dataFetchedNpk1.length
|
// // const Icon(
|
||||||
: provider.dataFetchedNpk2.length,
|
// // BootstrapIcons.thermometer_low,
|
||||||
(index) => isNpk1
|
// // color: Colors.blue,
|
||||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
// // ),
|
||||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
// SizedBox(height: 8.h),
|
||||||
),
|
// Text(
|
||||||
data: List.generate(
|
// '<20°C',
|
||||||
isNpk1
|
// style: AppTheme.labelMedium,
|
||||||
? provider.dataFetchedNpk1.length
|
// textAlign: TextAlign.center,
|
||||||
: provider.dataFetchedNpk2.length,
|
// ),
|
||||||
(index) => isNpk1
|
// ],
|
||||||
? provider.dataFetchedNpk1[index].soilph ?? 0
|
// ),
|
||||||
: provider.dataFetchedNpk2[index].soilph ?? 0,
|
// ),
|
||||||
),
|
// Container(
|
||||||
maxValue: 14,
|
// height: 100.h,
|
||||||
);
|
// width: 100.w,
|
||||||
case ResultState.error:
|
// decoration: BoxDecoration(
|
||||||
return const GraphicErrorWidget(message: 'Terjadi Kesalahan');
|
// color: Colors.green.withOpacity(0.1),
|
||||||
case ResultState.noData:
|
// borderRadius: BorderRadius.circular(16),
|
||||||
return const GraphicErrorWidget(message: 'Tidak Ada Data');
|
// border: Border.all(
|
||||||
case ResultState.initial:
|
// color: Colors.green,
|
||||||
default:
|
// width: 2,
|
||||||
return const SizedBox.shrink();
|
// ),
|
||||||
}
|
// ),
|
||||||
},
|
// 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,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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,159 +1,85 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/phosphorus/provider/phosporus_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class PhosphorusScreen extends StatelessWidget {
|
class PhosphorusScreen extends StatelessWidget {
|
||||||
const PhosphorusScreen(
|
const PhosphorusScreen({super.key, this.phosphorus = 0.0});
|
||||||
{super.key, this.phosphorusNpk1 = 0.0, this.phosphorusNpk2 = 0.0});
|
|
||||||
|
|
||||||
final double phosphorusNpk1;
|
final double phosphorus;
|
||||||
final double phosphorusNpk2;
|
double get value => phosphorus;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => PhosporusProvider(),
|
appBar: AppBar(
|
||||||
child: DefaultTabController(
|
title: Text('Phosphorus', style: AppTheme.labelMedium),
|
||||||
length: 2,
|
centerTitle: true,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.white,
|
||||||
appBar: AppBar(
|
scrolledUnderElevation: 0,
|
||||||
title: Text('Soil Phosphorus', style: AppTheme.labelMedium),
|
actions: const [
|
||||||
centerTitle: true,
|
Padding(
|
||||||
backgroundColor: Colors.white,
|
padding: EdgeInsets.only(right: 16),
|
||||||
scrolledUnderElevation: 0,
|
child: Icon(
|
||||||
actions: const [
|
CupertinoIcons.eyedropper,
|
||||||
Padding(
|
color: Colors.blue,
|
||||||
padding: EdgeInsets.only(right: 16),
|
),
|
||||||
child: Icon(
|
)
|
||||||
BootstrapIcons.eyedropper,
|
],
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 32.h),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
CupertinoIcons.eyedropper,
|
||||||
|
size: 64.r,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
)
|
Text('$value ppm', style: AppTheme.headline1),
|
||||||
],
|
|
||||||
bottom: const TabBar(
|
|
||||||
tabs: [
|
|
||||||
Tab(text: 'NPK 1'),
|
|
||||||
Tab(text: 'NPK 2'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 32.h),
|
||||||
body: TabBarView(
|
Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
buildTabContent(context, phosphorusNpk1, 'NPK 1', true),
|
children: [
|
||||||
buildTabContent(context, phosphorusNpk2, 'NPK 2', false),
|
Text(
|
||||||
],
|
'Fosfor',
|
||||||
),
|
style: AppTheme.labelMedium,
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
IconButton(
|
||||||
}
|
iconSize: 20.r,
|
||||||
|
color: Colors.blue,
|
||||||
SafeArea buildTabContent(
|
onPressed: () {},
|
||||||
BuildContext context, double value, String label, bool isNpk1) {
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
return SafeArea(
|
],
|
||||||
child: ListView(
|
),
|
||||||
padding: EdgeInsets.all(16.w),
|
SizedBox(height: 16.h),
|
||||||
children: [
|
const Text('Grafik'),
|
||||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
SizedBox(height: 16.h),
|
||||||
buildSoilInfo(context, value),
|
AspectRatio(
|
||||||
SizedBox(height: 16.h),
|
aspectRatio: 1.6.h,
|
||||||
buildInfoRow(context),
|
child: Container(
|
||||||
SizedBox(height: 16.h),
|
decoration: BoxDecoration(
|
||||||
const Text('Grafik'),
|
color: Colors.white,
|
||||||
SizedBox(height: 16.h),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
buildGraphicContent(context, isNpk1),
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
],
|
),
|
||||||
),
|
child: GarphicWidget(
|
||||||
);
|
gradientColors: [
|
||||||
}
|
Colors.blue.shade200,
|
||||||
|
Colors.blue,
|
||||||
Widget buildSoilInfo(BuildContext context, double value) {
|
],
|
||||||
return Center(
|
),
|
||||||
child: Column(
|
),
|
||||||
children: [
|
)
|
||||||
Icon(
|
],
|
||||||
CupertinoIcons.eyedropper,
|
|
||||||
size: 64.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
Text('$value ppm', style: AppTheme.headline1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildInfoRow(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Soil Phosphorus',
|
|
||||||
style: AppTheme.labelMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 20.r,
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(BootstrapIcons.info_circle),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
|
||||||
return AspectRatio(
|
|
||||||
aspectRatio: 1.6.h,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
|
||||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
|
||||||
),
|
|
||||||
child: Consumer<PhosporusProvider>(
|
|
||||||
builder: (context, provider, child) {
|
|
||||||
final dataState = provider.dataState;
|
|
||||||
|
|
||||||
switch (dataState) {
|
|
||||||
case ResultState.loading:
|
|
||||||
return const Center(child: CupertinoActivityIndicator());
|
|
||||||
case ResultState.hasData:
|
|
||||||
return GarphicWidget(
|
|
||||||
gradientColors: const [Colors.blue, Colors.blueAccent],
|
|
||||||
hour: List.generate(
|
|
||||||
isNpk1
|
|
||||||
? provider.dataFetchedNpk1.length
|
|
||||||
: provider.dataFetchedNpk2.length,
|
|
||||||
(index) => isNpk1
|
|
||||||
? provider.dataFetchedNpk1[index].hour ?? 0
|
|
||||||
: provider.dataFetchedNpk2[index].hour ?? 0,
|
|
||||||
),
|
|
||||||
data: List.generate(
|
|
||||||
isNpk1
|
|
||||||
? provider.dataFetchedNpk1.length
|
|
||||||
: provider.dataFetchedNpk2.length,
|
|
||||||
(index) => isNpk1
|
|
||||||
? provider.dataFetchedNpk1[index].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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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,159 +1,85 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/potassium/provider/potassium_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class PotassiumScreen extends StatelessWidget {
|
class PotassiumScreen extends StatelessWidget {
|
||||||
const PotassiumScreen(
|
const PotassiumScreen({super.key, this.potassium = 0.0});
|
||||||
{super.key, this.potassiumNpk1 = 0.0, this.potassiumNpk2 = 0.0});
|
|
||||||
|
|
||||||
final double potassiumNpk1;
|
final double potassium;
|
||||||
final double potassiumNpk2;
|
double get value => potassium;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => PotassiumProvider(),
|
appBar: AppBar(
|
||||||
child: DefaultTabController(
|
title: Text('Potassium', style: AppTheme.labelMedium),
|
||||||
length: 2,
|
centerTitle: true,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.white,
|
||||||
appBar: AppBar(
|
scrolledUnderElevation: 0,
|
||||||
title: Text('Soil Potassium', style: AppTheme.labelMedium),
|
actions: const [
|
||||||
centerTitle: true,
|
Padding(
|
||||||
backgroundColor: Colors.white,
|
padding: EdgeInsets.only(right: 16),
|
||||||
scrolledUnderElevation: 0,
|
child: Icon(
|
||||||
actions: const [
|
CupertinoIcons.eyedropper,
|
||||||
Padding(
|
color: Colors.green,
|
||||||
padding: EdgeInsets.only(right: 16),
|
),
|
||||||
child: Icon(
|
)
|
||||||
BootstrapIcons.eyedropper,
|
],
|
||||||
color: Colors.red,
|
),
|
||||||
|
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),
|
||||||
],
|
|
||||||
bottom: const TabBar(
|
|
||||||
tabs: [
|
|
||||||
Tab(text: 'NPK 1'),
|
|
||||||
Tab(text: 'NPK 2'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 32.h),
|
||||||
body: TabBarView(
|
Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
buildTabContent(context, potassiumNpk1, 'NPK 1', true),
|
children: [
|
||||||
buildTabContent(context, potassiumNpk2, 'NPK 2', false),
|
Text(
|
||||||
],
|
'Kalium',
|
||||||
),
|
style: AppTheme.labelMedium,
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
IconButton(
|
||||||
}
|
iconSize: 20.r,
|
||||||
|
color: Colors.blue,
|
||||||
SafeArea buildTabContent(
|
onPressed: () {},
|
||||||
BuildContext context, double value, String label, bool isNpk1) {
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
return SafeArea(
|
],
|
||||||
child: ListView(
|
),
|
||||||
padding: EdgeInsets.all(16.w),
|
SizedBox(height: 16.h),
|
||||||
children: [
|
const Text('Grafik'),
|
||||||
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
|
SizedBox(height: 16.h),
|
||||||
buildSoilInfo(context, value),
|
AspectRatio(
|
||||||
SizedBox(height: 16.h),
|
aspectRatio: 1.6.h,
|
||||||
buildInfoRow(context),
|
child: Container(
|
||||||
SizedBox(height: 16.h),
|
decoration: BoxDecoration(
|
||||||
const Text('Grafik'),
|
color: Colors.white,
|
||||||
SizedBox(height: 16.h),
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
buildGraphicContent(context, isNpk1),
|
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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,189 +1,128 @@
|
||||||
|
import 'package:agrilink_vocpro/core/constant/app_constant.dart';
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/widgets/show_info.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/pages/soil_moisture/provider/soil_moisture_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_error_widget.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:gauge_indicator/gauge_indicator.dart';
|
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class SoilMoistureScreen extends StatelessWidget {
|
class SoilMoistureScreen extends StatelessWidget {
|
||||||
const SoilMoistureScreen(
|
const SoilMoistureScreen({super.key, this.moisture = 0});
|
||||||
{super.key, this.moistureNpk1 = 0, this.moistureNpk2 = 0});
|
|
||||||
|
|
||||||
final double moistureNpk1;
|
final double moisture;
|
||||||
final double moistureNpk2;
|
double get value => moisture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => SoilMoistureProvider(),
|
appBar: AppBar(
|
||||||
child: DefaultTabController(
|
title: Text('Soil Moisture', style: AppTheme.labelMedium),
|
||||||
length: 2,
|
centerTitle: true,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.white,
|
||||||
appBar: AppBar(
|
leading: IconButton(
|
||||||
title: Text('Soil Moisture', style: AppTheme.labelMedium),
|
icon: const Icon(CupertinoIcons.back),
|
||||||
centerTitle: true,
|
onPressed: () => Navigator.pop(context),
|
||||||
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, moistureNpk1, 'NPK 1', true),
|
|
||||||
buildTabContent(context, moistureNpk2, 'NPK 2', false),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
actions: const [
|
||||||
);
|
Padding(
|
||||||
}
|
padding: EdgeInsets.only(right: 16),
|
||||||
|
child: Icon(
|
||||||
SafeArea buildTabContent(
|
Icons.water_outlined,
|
||||||
BuildContext context, double value, String label, bool isNpk1) {
|
color: Colors.blue,
|
||||||
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),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
body: SafeArea(
|
||||||
}
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.w),
|
||||||
Widget buildSoilMoistureInfo(BuildContext context, double value) {
|
children: [
|
||||||
return SizedBox(
|
SizedBox(
|
||||||
height: 280.h,
|
height: 280.h,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(BootstrapIcons.water, size: 32, color: Colors.blue),
|
const Icon(BootstrapIcons.water,
|
||||||
Text('$value %', style: AppTheme.headline1),
|
size: 32, color: Colors.blue),
|
||||||
],
|
Text('60 %', style: AppTheme.headline1),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
RotatedBox(
|
),
|
||||||
quarterTurns: 2,
|
RotatedBox(
|
||||||
child: AnimatedRadialGauge(
|
quarterTurns: 2,
|
||||||
duration: const Duration(seconds: 3),
|
child: AnimatedRadialGauge(
|
||||||
curve: Curves.easeOut,
|
duration: const Duration(seconds: 3),
|
||||||
value: value,
|
curve: Curves.easeOut,
|
||||||
axis: GaugeAxis(
|
value: value,
|
||||||
degrees: 360,
|
axis: GaugeAxis(
|
||||||
min: 0,
|
degrees: 360,
|
||||||
max: 100,
|
min: 0,
|
||||||
pointer: null,
|
max: 100,
|
||||||
style: GaugeAxisStyle(
|
pointer: null,
|
||||||
background: Colors.grey.shade100,
|
style: GaugeAxisStyle(
|
||||||
thickness: 50,
|
background: Colors.grey.shade100,
|
||||||
),
|
thickness: 50,
|
||||||
progressBar: GaugeBasicProgressBar(
|
),
|
||||||
gradient: GaugeAxisGradient(colors: [
|
progressBar: GaugeBasicProgressBar(
|
||||||
Colors.blue.shade200,
|
gradient: GaugeAxisGradient(colors: [
|
||||||
Colors.blue,
|
Colors.blue.shade200,
|
||||||
]),
|
Colors.blue,
|
||||||
),
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 16.h),
|
||||||
],
|
Row(
|
||||||
),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
);
|
children: [
|
||||||
}
|
Text(
|
||||||
|
'Soil Temperature',
|
||||||
Widget buildInfoRow(BuildContext context) {
|
style: AppTheme.labelMedium,
|
||||||
return Row(
|
textAlign: TextAlign.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: [
|
IconButton(
|
||||||
Text(
|
iconSize: 20.r,
|
||||||
'Soil Moisture',
|
color: Colors.blue,
|
||||||
style: AppTheme.labelMedium,
|
onPressed: () {
|
||||||
textAlign: TextAlign.center,
|
showInfo(
|
||||||
),
|
context,
|
||||||
IconButton(
|
'Soil Temperature',
|
||||||
iconSize: 20.r,
|
AppConstant.soilTempInfo,
|
||||||
color: Colors.blue,
|
);
|
||||||
onPressed: () {},
|
},
|
||||||
icon: const Icon(BootstrapIcons.info_circle),
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
SizedBox(height: 16.h),
|
||||||
}
|
const Text('Grafik'),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
Widget buildGraphicContent(BuildContext context, bool isNpk1) {
|
AspectRatio(
|
||||||
return AspectRatio(
|
aspectRatio: 1.8.h,
|
||||||
aspectRatio: 1.6.h,
|
child: Container(
|
||||||
child: Container(
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
color: Colors.white,
|
||||||
color: Colors.white,
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
),
|
||||||
),
|
child: GarphicWidget(
|
||||||
child: Consumer<SoilMoistureProvider>(
|
gradientColors: [
|
||||||
builder: (context, provider, child) {
|
Colors.blue.shade200,
|
||||||
final dataState = provider.dataState;
|
Colors.blue,
|
||||||
|
],
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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,239 +1,232 @@
|
||||||
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_constant.dart';
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/core/widgets/show_info.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: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:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:gauge_indicator/gauge_indicator.dart';
|
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||||
|
|
||||||
class SoilTemperatureScreen extends StatelessWidget {
|
class SoilTemperatureScreen extends StatelessWidget {
|
||||||
const SoilTemperatureScreen({
|
const SoilTemperatureScreen({super.key, this.temperature = 0});
|
||||||
super.key,
|
|
||||||
this.npk1Temperature = 0,
|
|
||||||
this.npk2Temperature = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
final double npk1Temperature;
|
final double temperature;
|
||||||
final double npk2Temperature;
|
double get value => temperature;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => SoilTempProvider(),
|
appBar: AppBar(
|
||||||
child: DefaultTabController(
|
title: Text('Soil Temperature', style: AppTheme.labelMedium),
|
||||||
length: 2,
|
centerTitle: true,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.white,
|
||||||
appBar: AppBar(
|
scrolledUnderElevation: 0,
|
||||||
title: Text('Soil Temperature', style: AppTheme.labelMedium),
|
actions: const [
|
||||||
centerTitle: true,
|
Padding(
|
||||||
backgroundColor: Colors.white,
|
padding: EdgeInsets.only(right: 16),
|
||||||
scrolledUnderElevation: 0,
|
child: Icon(
|
||||||
actions: const [
|
BootstrapIcons.water,
|
||||||
Padding(
|
color: Colors.green,
|
||||||
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),
|
||||||
Widget buildTemperatureInfo(BuildContext context, double value) {
|
children: [
|
||||||
return SizedBox(
|
SizedBox(
|
||||||
height: 240.h,
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
child: Stack(
|
),
|
||||||
fit: StackFit.expand,
|
SizedBox(
|
||||||
children: [
|
height: 240.h,
|
||||||
Center(
|
child: Stack(
|
||||||
child: Column(
|
fit: StackFit.expand,
|
||||||
mainAxisSize: MainAxisSize.min,
|
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,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 80.h),
|
|
||||||
const Icon(BootstrapIcons.water, size: 32, color: Colors.green),
|
|
||||||
Text(
|
Text(
|
||||||
'$value°C', // Display temperature
|
'Soil Temperature',
|
||||||
style: const TextStyle(
|
style: AppTheme.labelMedium,
|
||||||
fontSize: 28,
|
textAlign: TextAlign.center,
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
color: Colors.black,
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 16.h),
|
||||||
AnimatedRadialGauge(
|
const Text('Grafik'),
|
||||||
duration: const Duration(seconds: 2),
|
SizedBox(height: 16.h),
|
||||||
curve: Curves.easeOut,
|
AspectRatio(
|
||||||
value: value,
|
aspectRatio: 1.6.h,
|
||||||
axis: GaugeAxis(
|
child: Container(
|
||||||
degrees: 240,
|
decoration: BoxDecoration(
|
||||||
min: 0,
|
color: Colors.white,
|
||||||
max: 56.7,
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
style: GaugeAxisStyle(
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
background: Colors.grey.shade100,
|
),
|
||||||
thickness: 50,
|
child: const GarphicWidget(
|
||||||
|
gradientColors: [
|
||||||
|
Colors.cyan,
|
||||||
|
Colors.amber,
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
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,13 +1,9 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/pages/temperature/provider/temp_provider.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/graphic_widget.dart';
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:gauge_indicator/gauge_indicator.dart';
|
import 'package:gauge_indicator/gauge_indicator.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class TemperatureScreen extends StatelessWidget {
|
class TemperatureScreen extends StatelessWidget {
|
||||||
const TemperatureScreen({super.key, this.temperature = 0});
|
const TemperatureScreen({super.key, this.temperature = 0});
|
||||||
|
|
@ -17,263 +13,213 @@ class TemperatureScreen extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => TempProvider(),
|
appBar: AppBar(
|
||||||
child: Scaffold(
|
title: Text('Temperature', style: AppTheme.labelMedium),
|
||||||
appBar: AppBar(
|
centerTitle: true,
|
||||||
title: Text('Temperature', style: AppTheme.labelMedium),
|
backgroundColor: Colors.white,
|
||||||
centerTitle: true,
|
scrolledUnderElevation: 0,
|
||||||
backgroundColor: Colors.white,
|
actions: const [
|
||||||
scrolledUnderElevation: 0,
|
Padding(
|
||||||
actions: const [
|
padding: EdgeInsets.only(right: 16),
|
||||||
Padding(
|
child: Icon(
|
||||||
padding: EdgeInsets.only(right: 16),
|
BootstrapIcons.thermometer_half,
|
||||||
child: Icon(
|
color: Colors.red,
|
||||||
BootstrapIcons.thermometer_half,
|
),
|
||||||
color: Colors.red,
|
)
|
||||||
),
|
],
|
||||||
)
|
),
|
||||||
],
|
body: SafeArea(
|
||||||
),
|
child: ListView(
|
||||||
body: SafeArea(
|
padding: EdgeInsets.all(16.w),
|
||||||
child: ListView(
|
children: [
|
||||||
padding: EdgeInsets.all(16.w),
|
SizedBox(
|
||||||
children: [
|
height: MediaQuery.of(context).size.height * 0.05,
|
||||||
SizedBox(
|
),
|
||||||
height: MediaQuery.of(context).size.height * 0.05,
|
SizedBox(
|
||||||
),
|
height: 240.h,
|
||||||
SizedBox(
|
child: Stack(
|
||||||
height: 240.h,
|
fit: StackFit.expand,
|
||||||
child: Stack(
|
children: [
|
||||||
fit: StackFit.expand,
|
Center(
|
||||||
children: [
|
child: Column(
|
||||||
Center(
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
SizedBox(
|
||||||
children: [
|
height: 80.h,
|
||||||
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,
|
||||||
),
|
),
|
||||||
const Icon(BootstrapIcons.thermometer_half,
|
),
|
||||||
size: 32, color: Colors.orange),
|
],
|
||||||
Text(
|
),
|
||||||
'${value.toStringAsFixed(0)}°C', // Animated percentage text
|
),
|
||||||
style: const TextStyle(
|
AnimatedRadialGauge(
|
||||||
fontSize: 28,
|
duration: const Duration(seconds: 2),
|
||||||
fontWeight: FontWeight.bold,
|
curve: Curves.easeOut,
|
||||||
color: Colors.black,
|
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,
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedRadialGauge(
|
),
|
||||||
duration: const Duration(seconds: 2),
|
],
|
||||||
curve: Curves.easeOut,
|
),
|
||||||
value: value,
|
),
|
||||||
axis: GaugeAxis(
|
const SizedBox(height: 16),
|
||||||
degrees: 240,
|
const SizedBox(height: 16),
|
||||||
min: 0,
|
Row(
|
||||||
max: 56.7,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
style: GaugeAxisStyle(
|
children: [
|
||||||
background: Colors.grey.shade100,
|
Text(
|
||||||
thickness: 50,
|
'Temperature',
|
||||||
),
|
style: AppTheme.labelMedium,
|
||||||
progressBar: const GaugeBasicProgressBar(
|
textAlign: TextAlign.center,
|
||||||
gradient: GaugeAxisGradient(colors: [
|
),
|
||||||
Colors.white12,
|
IconButton(
|
||||||
Colors.orange,
|
iconSize: 20.r,
|
||||||
]),
|
color: Colors.blue,
|
||||||
),
|
onPressed: () {},
|
||||||
),
|
icon: const Icon(BootstrapIcons.info_circle))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 100.h,
|
||||||
|
width: 100.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: Colors.blue.withOpacity(0.1),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.blue,
|
||||||
|
width: 2,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Low',
|
||||||
|
style: AppTheme.labelMedium
|
||||||
|
.copyWith(color: Colors.blue)),
|
||||||
|
// SizedBox(height: 8.h),
|
||||||
|
// const Icon(
|
||||||
|
// BootstrapIcons.thermometer_low,
|
||||||
|
// color: Colors.blue,
|
||||||
|
// ),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'<20°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 100.h,
|
||||||
|
width: 100.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.green.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.green,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Ideal',
|
||||||
|
style: AppTheme.labelMedium
|
||||||
|
.copyWith(color: Colors.green)),
|
||||||
|
// SizedBox(height: 8.h),
|
||||||
|
// const Icon(
|
||||||
|
// BootstrapIcons.thermometer_half,
|
||||||
|
// color: Colors.green,
|
||||||
|
// ),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'20-30°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 100.h,
|
||||||
|
width: 100.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.orange.shade800,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('high',
|
||||||
|
style: AppTheme.labelMedium
|
||||||
|
.copyWith(color: Colors.orange)),
|
||||||
|
// SizedBox(height: 8.h),
|
||||||
|
// const Icon(
|
||||||
|
// BootstrapIcons.thermometer_high,
|
||||||
|
// color: Colors.orange,
|
||||||
|
// ),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'>30°C',
|
||||||
|
style: AppTheme.labelMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
const Text('Grafik'),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 1.6.h,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16.w),
|
||||||
|
border: Border.all(color: Colors.grey.shade300, width: 1.w),
|
||||||
|
),
|
||||||
|
child: const GarphicWidget(
|
||||||
|
gradientColors: [
|
||||||
|
Colors.cyan,
|
||||||
|
Colors.amber,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
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: 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'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,25 @@
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
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 HomeProvider extends ChangeNotifier {
|
class HomeProvider extends ChangeNotifier {
|
||||||
final DateTime currentDate = DateTime.now();
|
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() {
|
HomeProvider() {
|
||||||
getLatestData();
|
getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultState dataState = ResultState.initial;
|
ResultState dataState = ResultState.initial;
|
||||||
|
|
||||||
Future<void> getLatestData() async {
|
Future<void> getData() async {
|
||||||
dataState = ResultState.loading;
|
dataState = ResultState.loading;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
final result = await AppService().getLatestData();
|
print('Fetching data...');
|
||||||
if (result.data == null) {
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
dataState = ResultState.noData;
|
print('Data fetched');
|
||||||
notifyListeners();
|
dataState = ResultState.hasData;
|
||||||
} else {
|
notifyListeners();
|
||||||
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) {
|
} catch (e) {
|
||||||
if (kDebugMode) {
|
|
||||||
print('Get Latest Error: $e');
|
|
||||||
}
|
|
||||||
dataState = ResultState.error;
|
dataState = ResultState.error;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
@ -109,3 +31,57 @@ class HomeProvider extends ChangeNotifier {
|
||||||
super.dispose();
|
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,
|
||||||
|
// ),
|
||||||
|
// ];
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ 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_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_npk2.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/list_data_from_censor_dht.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:animated_segmented_tab_control/animated_segmented_tab_control.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
@ -30,99 +29,96 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
flexibleSpace: Padding(
|
flexibleSpace: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child:
|
child: Column(
|
||||||
Consumer<SettingProvider>(builder: (context, settingP, child) {
|
children: [
|
||||||
return Column(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
CircleAvatar(
|
||||||
children: [
|
radius: 24.r,
|
||||||
CircleAvatar(
|
backgroundColor: AppColor.primary,
|
||||||
radius: 24.r,
|
child: Icon(
|
||||||
backgroundColor: AppColor.primary,
|
Icons.person,
|
||||||
child: Icon(
|
color: Colors.white,
|
||||||
Icons.person,
|
size: 24.r,
|
||||||
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,
|
|
||||||
),
|
|
||||||
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(
|
SizedBox(width: 16.w),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Text(
|
||||||
decoration: BoxDecoration(
|
getGreeting(DateTime.now().toString()),
|
||||||
color: AppColor.ternary.withAlpha(200),
|
style: AppTheme.labelSmall,
|
||||||
borderRadius: BorderRadius.circular(32),
|
),
|
||||||
),
|
Text(
|
||||||
padding: const EdgeInsets.symmetric(
|
'Fikril Mahesaputra',
|
||||||
vertical: 8, horizontal: 16),
|
style: AppTheme.labelMedium,
|
||||||
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(
|
body: DefaultTabController(
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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,17 +4,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
class GarphicWidget extends StatelessWidget {
|
class GarphicWidget extends StatelessWidget {
|
||||||
const GarphicWidget(
|
const GarphicWidget({super.key, required this.gradientColors});
|
||||||
{super.key,
|
|
||||||
required this.gradientColors,
|
|
||||||
this.data,
|
|
||||||
this.hour,
|
|
||||||
this.maxValue = 100});
|
|
||||||
|
|
||||||
final List<Color> gradientColors;
|
final List<Color> gradientColors;
|
||||||
final List<num>? hour;
|
|
||||||
final List<num>? data;
|
|
||||||
final double maxValue;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -88,16 +80,18 @@ class GarphicWidget extends StatelessWidget {
|
||||||
minX: 0,
|
minX: 0,
|
||||||
maxX: 24,
|
maxX: 24,
|
||||||
minY: 0,
|
minY: 0,
|
||||||
maxY: data == null ? 0 : maxValue,
|
maxY: 100,
|
||||||
lineBarsData: [
|
lineBarsData: [
|
||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
spots: data == null && hour == null
|
spots: const [
|
||||||
? [FlSpot(0, 0)]
|
FlSpot(0, 38),
|
||||||
: List.generate(
|
FlSpot(1, 42),
|
||||||
hour!.length,
|
FlSpot(2, 50),
|
||||||
(index) =>
|
FlSpot(3, 53),
|
||||||
FlSpot(hour![index].toDouble(), data![index].toDouble()),
|
FlSpot(4, 58),
|
||||||
),
|
FlSpot(5, 64),
|
||||||
|
FlSpot(7, 49),
|
||||||
|
],
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: gradientColors,
|
colors: gradientColors,
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ class ListDataFromCensorDht extends StatelessWidget {
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Humidity',
|
title: 'Humidity',
|
||||||
subtitle: 'kelembaban udara',
|
subtitle: 'kelembaban udara',
|
||||||
value: provider.dhtHumidity.toString(),
|
value: '60',
|
||||||
unit: '%',
|
unit: '%',
|
||||||
icon: BootstrapIcons.droplet_half,
|
icon: BootstrapIcons.droplet_half,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
|
|
@ -58,32 +58,29 @@ class ListDataFromCensorDht extends StatelessWidget {
|
||||||
iconColor: Colors.white,
|
iconColor: Colors.white,
|
||||||
censorIdentifier: 'NPK 1',
|
censorIdentifier: 'NPK 1',
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context
|
await context.push('${AppRoute.humidity}/60');
|
||||||
.push('${AppRoute.humidity}/${provider.dhtHumidity}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Temperature',
|
title: 'Temperature',
|
||||||
subtitle: 'suhu greenhouse',
|
subtitle: 'suhu greenhouse',
|
||||||
value: provider.dhtTemperature.toString(),
|
value: '43',
|
||||||
unit: '°C',
|
unit: '°C',
|
||||||
icon: BootstrapIcons.thermometer_half,
|
icon: BootstrapIcons.thermometer_half,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push('${AppRoute.temperature}/43');
|
||||||
'${AppRoute.temperature}/${provider.dhtTemperature}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Light',
|
title: 'Light',
|
||||||
subtitle: 'intensitas cahaya',
|
subtitle: 'intensitas cahaya',
|
||||||
value: provider.dhtLuminosity.toString(),
|
value: '320.5',
|
||||||
unit: 'lux',
|
unit: 'lux',
|
||||||
icon: BootstrapIcons.sun,
|
icon: BootstrapIcons.sun,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context
|
await context.push('${AppRoute.light}/320.5');
|
||||||
.push('${AppRoute.light}/${provider.dhtLuminosity}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Temperature',
|
title: 'Temperature',
|
||||||
subtitle: 'Suhu tanah',
|
subtitle: 'Suhu tanah',
|
||||||
value: provider.npk1Temperature.toString(),
|
value: '28',
|
||||||
unit: '°C',
|
unit: '°C',
|
||||||
icon: BootstrapIcons.thermometer_half,
|
icon: BootstrapIcons.thermometer_half,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
|
|
@ -57,83 +57,76 @@ class ListDataFromCensorNpk1 extends StatelessWidget {
|
||||||
iconColor: Colors.white,
|
iconColor: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push('${AppRoute.soilTemperature}/28');
|
||||||
'${AppRoute.soilTemperature}/${provider.npk1Temperature}/${provider.npk2Temperature}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Soil Moisture',
|
title: 'Soil Moisture',
|
||||||
subtitle: 'kelembaban tanah',
|
subtitle: 'kelembaban tanah',
|
||||||
value: provider.npk1SoilMoisture.toString(),
|
value: '40',
|
||||||
unit: '%',
|
unit: '%',
|
||||||
icon: Icons.water_outlined,
|
icon: Icons.water_outlined,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push('${AppRoute.soilMoisture}/40');
|
||||||
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Acid Level (PH)',
|
title: 'Acid Level (PH)',
|
||||||
subtitle: 'tingkat keasaman',
|
subtitle: 'tingkat keasaman',
|
||||||
value: provider.npk1SoilPh.toString(),
|
value: '6.5',
|
||||||
unit: 'pH',
|
unit: 'pH',
|
||||||
icon: BootstrapIcons.pie_chart,
|
icon: BootstrapIcons.pie_chart,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () {
|
||||||
context.push(
|
context.push('${AppRoute.ph}/6.5');
|
||||||
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Conductivity',
|
title: 'Conductivity',
|
||||||
subtitle: 'Daya Arus Listrik',
|
subtitle: 'Daya Arus Listrik',
|
||||||
value: provider.npk1SoilEc.toString(),
|
value: '234',
|
||||||
unit: 'µS/cm',
|
unit: 'µS/cm',
|
||||||
icon: Icons.electric_bolt,
|
icon: Icons.electric_bolt,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push('${AppRoute.conductivity}/234');
|
||||||
'${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Nitrogen',
|
title: 'Nitrogen',
|
||||||
subtitle: 'Kadar Nitrogen',
|
subtitle: 'Kadar Nitrogen',
|
||||||
value: provider.npk1SoilNitrogen.toString(),
|
value: '30',
|
||||||
unit: 'ppm',
|
unit: 'ppm',
|
||||||
icon: CupertinoIcons.eyedropper,
|
icon: CupertinoIcons.eyedropper,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push('${AppRoute.nitrogen}/30');
|
||||||
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Potassium',
|
title: 'Potassium',
|
||||||
subtitle: 'Kadar kalium',
|
subtitle: 'Kadar kalium',
|
||||||
value: provider.npk1SoilPotassium.toString(),
|
value: '20',
|
||||||
unit: 'ppm',
|
unit: 'ppm',
|
||||||
icon: CupertinoIcons.eyedropper,
|
icon: CupertinoIcons.eyedropper,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push('${AppRoute.potassium}/20');
|
||||||
'${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Phosphorus',
|
title: 'Phosphorus',
|
||||||
subtitle: 'Kadar Fosfor',
|
subtitle: 'Kadar Fosfor',
|
||||||
value: provider.npk1SoilPhosphorus.toString(),
|
value: '54',
|
||||||
unit: 'ppm',
|
unit: 'ppm',
|
||||||
icon: CupertinoIcons.eyedropper,
|
icon: CupertinoIcons.eyedropper,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await context.push('${AppRoute.phosphorus}/54');
|
||||||
'${AppRoute.phosphorus}/${provider.npk1SoilPhosphorus}/${provider.npk2SoilPhosphorus}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
import 'package:agrilink_vocpro/core/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/provider/home_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/censor_item_loading_widgets.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/censor_item_loading_widgets.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart';
|
import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart';
|
||||||
|
|
@ -49,7 +51,7 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Temperature',
|
title: 'Temperature',
|
||||||
subtitle: 'Suhu tanah',
|
subtitle: 'Suhu tanah',
|
||||||
value: provider.npk2Temperature.toString(),
|
value: '28',
|
||||||
unit: '°C',
|
unit: '°C',
|
||||||
icon: BootstrapIcons.thermometer_half,
|
icon: BootstrapIcons.thermometer_half,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
|
|
@ -57,84 +59,77 @@ class ListDataFromCensorNpk2 extends StatelessWidget {
|
||||||
iconColor: Colors.white,
|
iconColor: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await Navigator.push(
|
||||||
'${AppRoute.soilTemperature}/${provider.npk1Temperature}/${provider.npk2Temperature}');
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const TemperatureScreen()));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Soil Moisture',
|
title: 'Soil Moisture',
|
||||||
subtitle: 'kelembaban tanah',
|
subtitle: 'kelembaban tanah',
|
||||||
value: provider.npk2SoilMoisture.toString(),
|
value: '40',
|
||||||
unit: '%',
|
unit: '%',
|
||||||
icon: Icons.water_outlined,
|
icon: Icons.water_outlined,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
await Navigator.push(
|
||||||
'${AppRoute.soilMoisture}/${provider.npk1SoilMoisture}/${provider.npk2SoilMoisture}');
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const SoilMoistureScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Acid Level (PH)',
|
title: 'Acid Level (PH)',
|
||||||
subtitle: 'tingkat keasaman',
|
subtitle: 'tingkat keasaman',
|
||||||
value: provider.npk2SoilPh.toString(),
|
value: '6.5',
|
||||||
unit: 'pH',
|
unit: 'pH',
|
||||||
icon: BootstrapIcons.pie_chart,
|
icon: BootstrapIcons.pie_chart,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () {},
|
||||||
context.push(
|
|
||||||
'${AppRoute.ph}/${provider.npk1SoilPh}/${provider.npk2SoilPh}');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Conductivity',
|
title: 'Conductivity',
|
||||||
subtitle: 'Daya Arus Listrik',
|
subtitle: 'Daya Arus Listrik',
|
||||||
value: provider.npk2SoilEc.toString(),
|
value: '234',
|
||||||
unit: 'µS/cm',
|
unit: 'µS/cm',
|
||||||
icon: Icons.electric_bolt,
|
icon: Icons.electric_bolt,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
censorIdentifier: censorIdentifier,
|
censorIdentifier: censorIdentifier,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(
|
context.push(AppRoute.humidity, extra: '60');
|
||||||
'${AppRoute.conductivity}/${provider.npk1SoilEc}/${provider.npk2SoilEc}');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Nitrogen',
|
title: 'Nitrogen',
|
||||||
subtitle: 'Kadar Nitrogen',
|
subtitle: 'Kadar Nitrogen',
|
||||||
value: provider.npk2SoilNitrogen.toString(),
|
value: '30',
|
||||||
unit: 'ppm',
|
unit: 'ppm',
|
||||||
icon: CupertinoIcons.eyedropper,
|
icon: CupertinoIcons.eyedropper,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () {},
|
||||||
await context.push(
|
|
||||||
'${AppRoute.nitrogen}/${provider.npk2SoilNitrogen}/${provider.npk1SoilNitrogen}');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Potassium',
|
title: 'Potassium',
|
||||||
subtitle: 'Kadar kalium',
|
subtitle: 'Kadar kalium',
|
||||||
value: provider.npk2SoilPotassium.toString(),
|
value: '20',
|
||||||
unit: 'ppm',
|
unit: 'ppm',
|
||||||
icon: CupertinoIcons.eyedropper,
|
icon: CupertinoIcons.eyedropper,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () {},
|
||||||
await context.push(
|
|
||||||
'${AppRoute.potassium}/${provider.npk1SoilPotassium}/${provider.npk2SoilPotassium}');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
DataDisplayerWidget(
|
DataDisplayerWidget(
|
||||||
title: 'Phosphorus',
|
title: 'Phosphorus',
|
||||||
subtitle: 'Kadar Fosfor',
|
subtitle: 'Kadar Fosfor',
|
||||||
value: provider.npk2SoilPhosphorus.toString(),
|
value: '54',
|
||||||
unit: 'ppm',
|
unit: 'ppm',
|
||||||
icon: CupertinoIcons.eyedropper,
|
icon: CupertinoIcons.eyedropper,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onTap: () async {
|
onTap: () {},
|
||||||
await context.push(
|
|
||||||
'${AppRoute.phosphorus}/${provider.npk1SoilPhosphorus}/${provider.npk2SoilPhosphorus}');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
||||||
|
|
||||||
class PlantsScreen extends StatelessWidget {
|
class PlantsScreen extends StatelessWidget {
|
||||||
const PlantsScreen({super.key});
|
const PlantsScreen({super.key});
|
||||||
|
|
@ -11,19 +9,7 @@ class PlantsScreen extends StatelessWidget {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Text('Coming Soon', style: AppTheme.labelMedium),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
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,13 +1,10 @@
|
||||||
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
import 'package:agrilink_vocpro/core/constant/app_color.dart';
|
||||||
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
import 'package:agrilink_vocpro/core/constant/app_theme.dart';
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
import 'package:agrilink_vocpro/core/state/result_state.dart';
|
|
||||||
import 'package:agrilink_vocpro/features/setting/provider/setting_provider.dart';
|
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class SettingScreen extends StatelessWidget {
|
class SettingScreen extends StatelessWidget {
|
||||||
const SettingScreen({super.key});
|
const SettingScreen({super.key});
|
||||||
|
|
@ -21,72 +18,71 @@ class SettingScreen extends StatelessWidget {
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
),
|
),
|
||||||
body: Consumer<SettingProvider>(builder: (context, provider, child) {
|
body: SafeArea(
|
||||||
return SafeArea(
|
child: ListView(
|
||||||
child: ListView(
|
padding: EdgeInsets.all(16.r),
|
||||||
padding: EdgeInsets.all(16.r),
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
const CircleAvatar(
|
||||||
const CircleAvatar(
|
radius: 30,
|
||||||
radius: 30,
|
backgroundColor: AppColor.secondary,
|
||||||
backgroundColor: AppColor.secondary,
|
child: Icon(BootstrapIcons.person_fill, color: Colors.white),
|
||||||
child: Icon(BootstrapIcons.person_fill, color: Colors.white),
|
|
||||||
),
|
|
||||||
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(
|
SizedBox(width: 8.w),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
Text('User Name', style: AppTheme.labelMedium),
|
||||||
tileColor: Colors.white,
|
Text('useremail@gmail.com', style: AppTheme.labelSmall)
|
||||||
title: Text('Account',
|
],
|
||||||
style: AppTheme.labelSmall
|
)
|
||||||
.copyWith(color: Colors.black87)),
|
],
|
||||||
leading: const Icon(BootstrapIcons.person),
|
),
|
||||||
trailing: Icon(
|
SizedBox(height: 16.h),
|
||||||
Icons.arrow_forward_ios,
|
Container(
|
||||||
size: 16.r,
|
decoration: BoxDecoration(
|
||||||
),
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
onTap: () {},
|
),
|
||||||
|
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,
|
||||||
),
|
),
|
||||||
ListTile(
|
onTap: () {},
|
||||||
tileColor: Colors.white,
|
),
|
||||||
title: Text('Kebijakan & privasi',
|
ListTile(
|
||||||
style: AppTheme.labelSmall
|
tileColor: Colors.white,
|
||||||
.copyWith(color: Colors.black87)),
|
title: Text('Kebijakan & privasi',
|
||||||
leading: const Icon(BootstrapIcons.shield_check),
|
style:
|
||||||
trailing: Icon(
|
AppTheme.labelSmall.copyWith(color: Colors.black87)),
|
||||||
Icons.arrow_forward_ios,
|
leading: const Icon(BootstrapIcons.shield_check),
|
||||||
size: 16.r,
|
trailing: Icon(
|
||||||
),
|
Icons.arrow_forward_ios,
|
||||||
onTap: () {},
|
size: 16.r,
|
||||||
),
|
),
|
||||||
ListTile(
|
onTap: () {},
|
||||||
tileColor: Colors.white,
|
),
|
||||||
title: Text('Syarat & ketentuan',
|
ListTile(
|
||||||
style: AppTheme.labelSmall
|
tileColor: Colors.white,
|
||||||
.copyWith(color: Colors.black87)),
|
title: Text('Syarat & ketentuan',
|
||||||
leading: const Icon(BootstrapIcons.file_text),
|
style:
|
||||||
trailing: Icon(
|
AppTheme.labelSmall.copyWith(color: Colors.black87)),
|
||||||
Icons.arrow_forward_ios,
|
leading: const Icon(BootstrapIcons.file_text),
|
||||||
size: 16.r,
|
trailing: Icon(
|
||||||
),
|
Icons.arrow_forward_ios,
|
||||||
onTap: () {},
|
size: 16.r,
|
||||||
),
|
),
|
||||||
ListTile(
|
onTap: () {},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
tileColor: Colors.white,
|
tileColor: Colors.white,
|
||||||
title: Text('Logout',
|
title: Text('Logout',
|
||||||
style: AppTheme.labelSmall.copyWith(color: Colors.red)),
|
style: AppTheme.labelSmall.copyWith(color: Colors.red)),
|
||||||
|
|
@ -113,27 +109,19 @@ class SettingScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text('Ya'),
|
child: Text('Ya'),
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
await provider.logout();
|
context.go(AppRoute.root);
|
||||||
if (context.mounted) {
|
|
||||||
if (provider.logoutState ==
|
|
||||||
ResultState.hasData) {
|
|
||||||
context.go(AppRoute.root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
));
|
],
|
||||||
}),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
class SplashScreen extends StatefulWidget {
|
class SplashScreen extends StatefulWidget {
|
||||||
const SplashScreen({super.key});
|
const SplashScreen({super.key});
|
||||||
|
|
@ -21,41 +17,37 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
FirebaseMessaging.instance.subscribeToTopic('smartfarm');
|
|
||||||
_initialize();
|
_initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initialize() async {
|
Future<void> _initialize() async {
|
||||||
bool isLoggedIn = await _checkLoginStatus();
|
// final authProvider = Provider.of<AuthProvider>(context, listen: false);
|
||||||
|
// bool isLoggedIn = await _checkLoginStatus(authProvider);
|
||||||
_navigateAfterSplash(isLoggedIn);
|
_navigateAfterSplash(isLoggedIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _checkLoginStatus() async {
|
// Future<bool> _checkLoginStatus(AuthProvider authProvider) async {
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
// SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
if (prefs.getKeys().isEmpty) return false;
|
// if (prefs.getKeys().isEmpty) return false;
|
||||||
|
|
||||||
if (prefs.getBool('isLoggedIn') == true) {
|
// if (prefs.getBool('isLoggedIn') == true) {
|
||||||
String? token = prefs.getString('jwtToken');
|
// String? token = prefs.getString('token');
|
||||||
|
// String? refreshToken = prefs.getString('refreshToken');
|
||||||
|
|
||||||
if (token != null && !JwtDecoder.isExpired(token)) {
|
// if (token != null && !JwtDecoder.isExpired(token)) {
|
||||||
if (kDebugMode) {
|
// return true;
|
||||||
print('Token : ${prefs.getString('token')}');
|
// } else if (refreshToken != null && !JwtDecoder.isExpired(refreshToken)) {
|
||||||
}
|
// final result = await authProvider.refreshToken();
|
||||||
return true;
|
// return result == ResultState.hasData;
|
||||||
} else {
|
// } else {
|
||||||
prefs.remove('token');
|
// prefs.remove('token');
|
||||||
prefs.remove('jwtToken');
|
// return false;
|
||||||
prefs.remove('username');
|
// }
|
||||||
prefs.remove('email');
|
// }
|
||||||
prefs.remove('fullName');
|
|
||||||
prefs.remove('isLoggedIn');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
void _navigateAfterSplash(bool isLoggedIn) {
|
void _navigateAfterSplash(bool isLoggedIn) {
|
||||||
Timer(const Duration(seconds: 2), () {
|
Timer(const Duration(seconds: 2), () {
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
// 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,21 +1,15 @@
|
||||||
import 'package:agrilink_vocpro/core/route/app_route.dart';
|
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/auth/provider/auth_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
import 'package:agrilink_vocpro/features/control/provider/control_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/dashboard/provider/dashboard_provider.dart';
|
import 'package:agrilink_vocpro/features/dashboard/provider/dashboard_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
import 'package:agrilink_vocpro/features/home/provider/home_provider.dart';
|
||||||
import 'package:agrilink_vocpro/features/setting/provider/setting_provider.dart';
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp();
|
|
||||||
final firebaseApi = FirebaseApi();
|
|
||||||
await firebaseApi.initNotification();
|
|
||||||
SystemChrome.setPreferredOrientations([
|
SystemChrome.setPreferredOrientations([
|
||||||
DeviceOrientation.portraitUp, // Locks the app in portrait mode
|
DeviceOrientation.portraitUp, // Locks the app in portrait mode
|
||||||
]).then((_) {
|
]).then((_) {
|
||||||
|
|
@ -35,24 +29,22 @@ class MyApp extends StatelessWidget {
|
||||||
ChangeNotifierProvider(create: (context) => HomeProvider()),
|
ChangeNotifierProvider(create: (context) => HomeProvider()),
|
||||||
ChangeNotifierProvider(create: (context) => DashboardProvider()),
|
ChangeNotifierProvider(create: (context) => DashboardProvider()),
|
||||||
ChangeNotifierProvider(create: (context) => ControlProvider()),
|
ChangeNotifierProvider(create: (context) => ControlProvider()),
|
||||||
ChangeNotifierProvider(create: (context) => SettingProvider()),
|
|
||||||
],
|
],
|
||||||
child: ScreenUtilInit(
|
child: ScreenUtilInit(
|
||||||
designSize: const Size(360, 800),
|
designSize: const Size(360, 800),
|
||||||
minTextAdapt: true,
|
minTextAdapt: true,
|
||||||
builder: (_, context) {
|
builder: (_, context) {
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Demo',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
scaffoldBackgroundColor: Colors.white,
|
scaffoldBackgroundColor: Colors.white,
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
routerConfig: AppRoute.router,
|
routerConfig: AppRoute.router,
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,10 @@
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import firebase_core
|
|
||||||
import firebase_messaging
|
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
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"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
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:
|
animated_segmented_tab_control:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -17,14 +9,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
args:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: args
|
|
||||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.6.0"
|
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -157,58 +141,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.0"
|
||||||
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:
|
fl_chart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -226,10 +162,10 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "4.0.0"
|
||||||
flutter_screenutil:
|
flutter_screenutil:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -260,10 +196,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc"
|
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.0"
|
version: "14.2.7"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -272,22 +208,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
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:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -297,7 +217,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.4"
|
version: "0.15.4"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
|
|
@ -320,14 +240,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.19.0"
|
||||||
jwt_decoder:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: jwt_decoder
|
|
||||||
sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.1"
|
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -356,10 +268,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "4.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -428,10 +340,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
|
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.12"
|
version: "2.2.10"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -500,18 +412,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
|
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.3"
|
version: "2.3.2"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.3"
|
version: "2.5.2"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -649,18 +561,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.0.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.0.4"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.1 <4.0.0"
|
dart: ">=3.5.1 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.22.0"
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,6 @@ dependencies:
|
||||||
gauge_indicator: ^0.4.3
|
gauge_indicator: ^0.4.3
|
||||||
mqtt_client: ^10.5.1
|
mqtt_client: ^10.5.1
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
jwt_decoder: ^2.0.1
|
|
||||||
firebase_core: ^3.6.0
|
|
||||||
firebase_messaging: ^15.1.3
|
|
||||||
googleapis_auth: ^1.6.0
|
|
||||||
http: ^1.2.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
@ -62,7 +57,7 @@ dev_dependencies:
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^4.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
@ -78,7 +73,6 @@ flutter:
|
||||||
assets:
|
assets:
|
||||||
- assets/images/
|
- assets/images/
|
||||||
- assets/icons/
|
- assets/icons/
|
||||||
- assets/json/
|
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
firebase_core
|
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user