Compare commits

...

97 commits
v.3.7 ... main

Author SHA1 Message Date
Yoshi
9ba80c3609 Refactor code 2025-05-28 17:42:15 +03:00
Yoshi
33be8dcdc6 Update dependencies 2025-04-25 18:57:26 +03:00
Yoshi
089f3ae0b7 issue #201 2025-03-16 21:24:22 +03:00
Yoshi
fb150421a6 Fix bugs 2025-03-15 23:40:48 +03:00
Yoshi
d169f6237f Update README.md 2025-03-13 22:24:56 +03:00
Yoshi
e14dc03524 Update README.md 2025-03-12 22:21:22 +03:00
Yoshi
97aa5024b4 Update README.md 2025-03-11 21:01:16 +03:00
Yoshi
f880c5d274 Update dependencies and delete support 2025-03-02 15:58:04 +03:00
Yoshi
0973fb5230 Update dependencies 2025-02-08 13:09:25 +03:00
Yoshi
416f77765c Update dependencies 2025-01-08 14:26:35 +03:00
Yoshi
a59c6b7338 Android 15 2025-01-08 12:22:43 +03:00
Yoshi
0552a84e6f Fix dependencies 2024-12-15 13:41:23 +03:00
Yoshi
3a23dd6288 Update java 2024-11-10 19:55:18 +03:00
Yoshi
b7d0e8012a Update dependencies 2024-11-03 20:51:49 +03:00
Yoshi
fa0921d1b0 Fix settings gradle 2024-10-12 22:27:27 +03:00
Yoshi
7a00917ca2 Update build.gradle 2024-10-05 22:23:21 +03:00
Yoshi
ddf7115579 Update gradle 2024-10-05 17:49:08 +03:00
Yoshi
014a52c215 Rename folders 2024-09-06 22:07:50 +03:00
Yoshi
07142e25a7 Hide map 2024-08-30 22:31:42 +03:00
Yoshi
55e813749a Fix widget 2024-08-29 22:00:08 +03:00
Yoshi
1d7cbbd1bf Fix map and add new button 2024-08-28 21:52:42 +03:00
Yoshi
1ea344c69a Settings widget 2024-08-26 21:27:15 +03:00
Yoshi
5cd6e2e6c8 Delete 2gis 2024-08-23 07:15:37 +03:00
Yoshi
c0c15bb811 Fix ExpandableFab 2024-08-22 23:34:56 +03:00
Yoshi
d5a34ea11b Fix map 2024-08-22 22:49:37 +03:00
Yoshi
f174c0f336 Add buttons to the map 2024-08-21 23:20:50 +03:00
Yoshi
d0791279a2 Fix animation markers 2024-08-21 21:55:29 +03:00
Yoshi
88a7bafffb Add clear cache 2024-08-20 22:19:02 +03:00
Yoshi
27e276a092 Add search the map 2024-08-18 18:35:30 +03:00
Yoshi
383fb24881 Fix markers 2024-08-18 14:08:54 +03:00
Yoshi
8ed047a1aa Add markers 2024-08-15 22:39:51 +03:00
Yoshi
6c7da7b28d Add map 2024-08-12 21:03:35 +03:00
Yoshi
b2e843c5d9 Update dependencies 2024-08-09 22:19:37 +03:00
Yoshi
91e1757a20 Replacing SizedBox with Gap 2024-08-04 11:48:25 +03:00
Yoshi
8bce420296 Fix UI 2024-08-03 18:55:26 +03:00
Yoshi
2cc70f9221 Add mmHg and m/s 2024-08-02 22:52:33 +03:00
Yoshi
dd57938153 Fix anim new add form && Add feels to the top tile 2024-07-29 22:37:08 +03:00
Yoshi
fc246ac5a2 Add new form create_card_weather 2024-07-24 23:07:35 +03:00
Yoshi
33ceb30885 Fix api code 2024-07-13 07:00:58 +03:00
Yoshi
46e1546e5b Two weather display options 2024-07-10 21:59:36 +03:00
Yoshi
dd3339bc3b issue #185 2024-07-09 23:03:40 +03:00
Yoshi
9a7858f279 Fix widget 2024-07-07 22:26:29 +03:00
Yoshi
6d0d0efe36 Fix refresh 2024-07-07 15:01:58 +03:00
Yoshi
aa125fcdc1 Add search cards 2024-07-07 14:49:45 +03:00
Yoshi
0d565752d5 Fix redesign 2024-06-24 20:39:57 +03:00
Yoshi
6646f657d2 Redesign 2024-06-23 23:23:43 +03:00
Yoshi
647d620d45
Merge pull request #182 from Regu-Miabyss/patch-1
Update zh_tw.dart
2024-06-15 08:51:52 +03:00
Regu-Miabyss
b459a50f60
Update zh_tw.dart
Chinese (Traditional) translation fix
2024-06-15 08:12:12 +08:00
Yoshi
da64975e68 issue #179 2024-06-08 18:03:59 +03:00
Yoshi
58dbbe52ce Add locale ko_KR 2024-05-31 10:40:10 +03:00
Yoshi
275bbba853
Merge pull request #181 from tsyqax/main
Add Korean Translation
2024-05-31 10:32:51 +03:00
tsyqax6
ceb27da369
Translation (Start But End) 2024-05-31 12:34:23 +09:00
tsyqax6
b8600ab472
Create ko_kr.dart 2024-05-31 12:09:44 +09:00
Yoshi
3cfd4f8248 Fix flutter_native_splash 2024-05-26 19:09:28 +03:00
Yoshi
8eae7203f0 Update dependencies 2024-05-18 21:58:53 +03:00
Yoshi
42f7a2590e
Merge pull request #180 from jash13desai/main
Some UI Fixes
2024-05-14 11:54:04 +03:00
jash-desai
4b1536579d minor changes 2024-05-11 23:01:43 +05:30
jash-desai
f3400916d9 fixed horizontal padding in validator text in MyTextForm widget 2024-05-11 22:49:11 +05:30
jash-desai
696634c08f settings_full label text in bottom navbar fixed 2024-05-11 22:31:43 +05:30
Yoshi
a00abd4f3a Fix bug 2024-04-10 21:25:23 +03:00
Yoshi
1342079b6b Update dependencies 2024-04-05 22:52:00 +03:00
Yoshi
4d310776ce Add locale zh_TW 2024-03-11 23:42:29 +03:00
Yoshi
cb7367c04f
Merge pull request #173 from Regu-Miabyss/main
I made the Chinese(Traditional) translate.
2024-03-11 23:38:05 +03:00
Regu-Miabyss
51f72b4e7b
Update main.dart 2024-03-10 17:22:53 +08:00
Regu-Miabyss
400614a8f1
Add Chinese Traditional translation 2024-03-10 17:20:47 +08:00
Regu-Miabyss
1e11b6b775
Added Chinese Traditional translation 2024-03-10 17:19:04 +08:00
Yoshi
902bc45207 Add locale fa_IR 2024-03-03 16:11:30 +03:00
Yoshi
8275ec8f9e
Merge pull request #170 from behdanisohrab/main
Added Persian Translation
2024-03-02 17:13:47 +03:00
Sohrab Behdani
441a5ee680 Added Persian Translation 2024-03-02 10:44:42 +03:30
Yoshi
a46986f050 Update dependencies 2024-02-15 22:55:14 +03:00
Yoshi
fa5fbb8ce6 Add da_DK locale and fix error 2024-02-13 22:32:28 +03:00
Yoshi
cda285ea15
Merge pull request #168 from HJMVR/main
Danish translations
2024-02-13 22:05:22 +03:00
Yoshi
f08c459bc1
Update da_dk.dart 2024-02-13 22:04:57 +03:00
Hans-Jørgen Martinus Vork Rosenwein
1f945c4dfd
last update 2024-02-13 12:43:24 +01:00
Hans-Jørgen Martinus Vork Rosenwein
c5b55a291b
more danish 2024-02-13 12:34:29 +01:00
Hans-Jørgen Martinus Vork Rosenwein
013493c0dd
More danish updates 2024-02-13 12:21:08 +01:00
Hans-Jørgen Martinus Vork Rosenwein
58373c7c9c
UPdate 2024-02-13 11:26:30 +01:00
Hans-Jørgen Martinus Vork Rosenwein
504a77f6ad
Merge branch 'darkmoonight:main' into main 2024-02-13 11:21:43 +01:00
Hans-Jørgen Martinus Vork Rosenwein
0f29d2bdad
Update da_dk.dart 2024-02-12 20:04:55 +01:00
Yoshi
da07dc23c7 Add advanced search settings 2024-02-12 19:04:42 +03:00
Hans-Jørgen Martinus Vork Rosenwein
265ba143ab
Create da_dk.dart 2024-02-12 10:23:56 +01:00
Yoshi
f77d09d147 Add start search city 2024-02-11 22:00:37 +03:00
Yoshi
b160fc8fa4 minor fixes 2024-02-10 15:02:22 +03:00
Yoshi
225a5240b2
Merge pull request #166 from trueberryless/main
Fix: TabBar Changes delayed (1s) when scrolling
2024-02-10 14:58:59 +03:00
Yoshi
dc3f90e346
fix 2024-02-10 14:56:28 +03:00
trueberryless
e391c074c4 potential fix, not tested 2024-02-10 11:30:18 +01:00
Yoshi
d0464a5727 issue #158 2024-01-28 20:16:12 +03:00
Yoshi
42ad98da43
Update issue templates 2024-01-27 20:03:20 +03:00
Yoshi
6f195dc791 Add CODE_OF_CONDUCT and CONTRIBUTING 2024-01-27 20:00:48 +03:00
Yoshi
4c79b5e8c2 issue #156 2024-01-27 19:27:03 +03:00
Yoshi
2807fc08fb update dependencies 2024-01-27 18:44:14 +03:00
Yoshi
78c7519673 compress images 2024-01-05 19:24:21 +03:00
Yoshi
8435b7e2ce Fix UI 2024-01-05 18:55:14 +03:00
Yoshi
bf7ed5f9da Edit OnBording 2023-12-29 21:18:59 +03:00
Yoshi
7e13bf32b9 split up translations to separate files 2023-12-24 15:58:09 +03:00
Yoshi
c7798bc4d6 Update dependencies 2023-12-03 16:19:42 +03:00
Yoshi
6d249bd7a6 Update flutter and settings android 2023-11-18 13:51:52 +03:00
199 changed files with 12152 additions and 7257 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

3
.gitignore vendored
View file

@ -15,6 +15,7 @@ migrate_working_dir/
*.ipr
*.iws
.idea/
.cxx/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
@ -41,4 +42,4 @@ app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
/android/app/release

View file

@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
# This file should be version controlled and should not be manually edited.
version:
revision: 135454af32477f815a7525073027a3ff9eff1bfd
channel: stable
revision: "2663184aa79047d0a33a14a3b607954f8fdd8730"
channel: "stable"
project_type: app
@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: android
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: ios
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: linux
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: macos
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: web
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: windows
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
# User provided section

43
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,43 @@
# Code of Conduct
## Our Commitment
In the interest of fostering an open and friendly community, we, as contributors and maintainers, pledge to make participation in our project and our community a harassment-free experience for everyone.
## Standards of Behavior
1. **Respect**: Be respectful and considerate of different viewpoints and experiences.
2. **Collaboration**: Collaborate with community members, uphold principles of openness, and assist each other.
3. **Friendliness**: Remember that diversity of opinions and experiences is valuable for our community. Be friendly and supportive.
4. **Responsibility**: Be mindful of your words and actions. If you make a commitment, strive to fulfill it.
## Unacceptable Behavior
Unacceptable behavior includes but is not limited to:
1. **Discrimination**: No forms of discrimination or harassment based on race, gender, sexual orientation, nationality, religion, or other characteristics.
2. **Insults and Threats**: No offensive comments, threats, or bullying.
3. **Hostile Conduct**: No actions intended to create a hostile atmosphere in the community.
## Reporting Procedure
If you believe you're experiencing a violation of the code of conduct, please report it by sending an email to darkmoonight2022@gmail.com.
## Conclusion
We encourage all participants to adhere to this code of conduct. Violation of these principles may result in temporary or permanent exclusion from the community.
Thank you for your contribution and cooperation in creating an open and friendly community!
## Changes
This code of conduct may be revised from time to time. The most current version will always be available in this file.
## Attribution
This code of conduct is adapted from [Contributor Covenant](https://www.contributor-covenant.org/).

37
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,37 @@
# Contribution Guidelines
Thank you for considering contributing to our project! We welcome your input and appreciate your efforts. To ensure a smooth collaboration, please take a moment to review the following guidelines.
## Code of Conduct
This project and everyone participating in it are governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [project email].
## How to Contribute
1. Fork the repository and clone it locally.
2. Create a new branch for your feature or bug fix.
3. Make your changes and commit them.
4. Push your changes to your fork on GitHub.
5. Open a pull request in the original repository. Provide a clear title and description of your changes.
## Coding Standards
Follow the coding standards and style guide used in the project. If there's no specific guide, stick to the existing code style.
## Testing
Ensure that your code changes do not break existing functionality. Write tests for new features or bug fixes if applicable.
## Documentation
Update the documentation if your changes affect it. This includes the README.md file and any additional documentation files.
## Issues
Before starting work on a new feature or bug fix, check the issue tracker for related discussions or known issues.
## License
By contributing, you agree that your contributions will be licensed under the project's license.
Thank you for your contribution!

View file

@ -1,15 +1,15 @@
<div align='center'>
<img src='/assets/icons/icon.png' width='150'/>
<h2>🌦️ Rain</h2>
<img src='/readme/icon.png' width='150'/>
<h2>🌦️ Rain</h2>
</div>
<p align='center'>
<p align='center'>
<a href='https://github.com/darkmoonight/Rain/stargazers'><img alt='Stars' src='https://img.shields.io/github/stars/darkmoonight/Rain?style=flat-square&color=abc0d3'/></a>
<a href='https://github.com/darkmoonight/Rain/forks'><img alt='Forks' src='https://img.shields.io/github/forks/darkmoonight/Rain?style=flat-square&color=abc0d3'/></a>
<a href='https://github.com/darkmoonight/Rain/releases'><img alt='GitHub release' src='https://img.shields.io/github/v/release/darkmoonight/Rain?color=abc0d3'/></a>
<a href='https://github.com/darkmoonight/Rain/blob/main/LICENSE'><img alt='License' src='https://img.shields.io/github/license/darkmoonight/Rain?color=abc0d3'/></a>
</p>
<p align='center'>
<a href='https://github.com/darkmoonight/Rain/stargazers'><img alt='Stars' src='https://img.shields.io/github/stars/darkmoonight/Rain?color=abc0d3'/></a>
<a href='https://github.com/darkmoonight/Rain/forks'><img alt='Forks' src='https://img.shields.io/github/forks/darkmoonight/Rain?color=abc0d3'/></a>
<a href='https://github.com/darkmoonight/Rain/releases'><img alt='GitHub release' src='https://img.shields.io/github/v/release/darkmoonight/Rain?color=abc0d3'/></a>
<a href='https://github.com/darkmoonight/Rain/blob/main/LICENSE'><img alt='License' src='https://img.shields.io/github/license/darkmoonight/Rain?color=abc0d3'/></a>
</p>
</p>
<p align='center'> Tired of unpredictable weather? Rain's got you covered! Get ready for any forecast. 🌦️ </p>
@ -40,7 +40,7 @@ We fetch weather data from [Open-Meteo](https://open-meteo.com/en/docs) and use
### 📸 Screenshots
<img src='/readme/1.png' width='200'/> <img src='/readme/2.png' width='200'/> <img src='/readme/3.png' width='200'/> <img src='/readme/4.png' width='200'/>
<img src='/readme/1.png' width='200'/> <img src='/readme/2.png' width='200'/> <img src='/readme/3.png' width='200'/> <img src='/readme/4.png' width='200'/> <img src='/readme/5.png' width='200'/> <img src='/readme/6.png' width='200'/> <img src='/readme/7.png' width='200'/> <img src='/readme/8.png' width='200'/>
### 💰 Support Us
@ -52,7 +52,6 @@ If you find Rain valuable and worthy for future innovation, consider supporting
### 📥 Get Rain Now
[![Play Store](https://img.shields.io/badge/Google_Play-414141?style=for-the-badge&logo=google-play&logoColor=white)](https://play.google.com/store/apps/details?id=com.yoshi.rain)
[![RuStore](https://img.shields.io/badge/RuStore-blue?style=for-the-badge&logo=vk&logoColor=white)](https://apps.rustore.ru/app/com.yoshi.rain)
Or get the latest APK from the [Releases Section](https://github.com/DarkMooNight/Rain/releases/latest). You can also find the app on IzzyOnDroid via a F-Droid client [here](https://apt.izzysoft.de/fdroid/index/apk/com.yoshi.rain).
@ -63,5 +62,5 @@ This project is licensed under the [MIT License](./LICENSE).
### 👨‍💻 Our Contributors
<a href='https://github.com/darkmoonight/Rain/graphs/contributors'>
<img src='https://contrib.rocks/image?repo=darkmoonight/Rain' />
<img src='https://contrib.rocks/image?repo=darkmoonight/Rain'/>
</a>

View file

@ -1,30 +1,9 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
@ -32,64 +11,73 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdk 34
ndkVersion flutter.ndkVersion
namespace = 'com.yoshi.rain'
compileSdk = 35
ndkVersion = '29.0.13113456'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
coreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = JavaVersion.VERSION_17
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
defaultConfig {
applicationId "com.yoshi.rain"
minSdkVersion 23
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
applicationId = 'com.yoshi.rain'
minSdk = 23
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
release {
keyAlias = keystoreProperties['keyAlias']
keyPassword = keystoreProperties['keyPassword']
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword = keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
signingConfig = signingConfigs.release
}
debug {
signingConfig signingConfigs.debug
minifyEnabled true
signingConfig = signingConfigs.debug
minifyEnabled = true
}
}
buildFeatures {
viewBinding true
viewBinding = true
}
namespace 'com.yoshi.rain'
}
flutter {
source '../..'
source = "../.."
}
dependencies {
implementation "androidx.core:core-remoteviews:1.0.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation("androidx.core:core-remoteviews:1.1.0")
implementation("com.google.android.material:material:1.12.0")
implementation('androidx.work:work-runtime-ktx:2.10.0')
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}
// Remove this for FLOSS version

View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
@ -12,16 +11,6 @@
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:extractNativeLibs="true">
<receiver
android:name=".OreoWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/oreo_widget_info" />
</receiver>
<activity
android:name=".MainActivity"
android:exported="true"
@ -29,6 +18,7 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:enableOnBackInvokedCallback="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
@ -37,7 +27,32 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>
</activity>
<receiver
android:name=".OreoWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/oreo_widget_info" />
</receiver>
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
android:exported="true">
<intent-filter>
<action android:name="es.antonborri.home_widget.action.BACKGROUND" />
</intent-filter>
</receiver>
<service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
@ -53,4 +68,4 @@
</intent-filter>
</receiver>
</application>
</manifest>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

View file

@ -3,6 +3,7 @@
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSecondaryContainer"
android:theme="@style/Theme.Android.AppWidgetContainer"
android:id="@+id/widget_day_oreo">
@ -27,7 +28,7 @@
android:shadowRadius="1"
android:textSize="@dimen/widget_large_title_text_size"
tools:ignore="ObsoleteLayoutParam"
tools:text="Saturday, September 30 " />
tools:text="Saturday, September 30 " />
<ImageView
android:id="@+id/widget_day_icon"

View file

@ -3,6 +3,7 @@
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSecondaryContainer"
android:theme="@style/Theme.Android.AppWidgetContainer"
android:id="@+id/widget_day_oreo">
@ -27,7 +28,7 @@
android:shadowRadius="1"
android:textSize="@dimen/widget_large_title_text_size"
tools:ignore="ObsoleteLayoutParam"
tools:text="Saturday, September 30 " />
tools:text="Saturday, September 30 " />
<ImageView
android:id="@+id/widget_day_icon"

View file

@ -3,6 +3,7 @@
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSecondaryContainer"
android:theme="@style/Theme.Android.AppWidgetContainer"
android:id="@+id/widget_day_oreo">
@ -32,8 +33,8 @@
android:shadowRadius="1"
android:textAppearance="@android:style/TextAppearance.Material.Large"
android:textSize="@dimen/widget_title_text_size"
tools:text="21°"
tools:ignore="ObsoleteLayoutParam" />
tools:ignore="ObsoleteLayoutParam"
tools:text="21°" />
</LinearLayout>

View file

@ -3,6 +3,7 @@
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSecondaryContainer"
android:theme="@style/Theme.Android.AppWidgetContainer"
android:id="@+id/widget_day_oreo">

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="LaunchTheme" parent="@style/Theme.Material3.DynamicColors.Dark.NoActionBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
@ -14,7 +14,7 @@
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="NormalTheme" parent="@style/Theme.Material3.DynamicColors.Dark.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -3,7 +3,7 @@
<!--
Having themes.xml for night-v31 because of the priority order of the resource qualifiers.
-->
<style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
<style name="Theme.Android.AppWidgetContainerParent" parent="@style/Theme.Material3.DynamicColors.DayNight">
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
</style>

View file

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="LaunchTheme" parent="@style/Theme.Material3.DynamicColors.Dark.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowDrawsSystemBarBackgrounds" >false</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
@ -16,7 +16,7 @@
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="NormalTheme" parent="@style/Theme.Material3.DynamicColors.Dark.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<style name="LaunchTheme" parent="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
@ -14,17 +14,15 @@
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<style name="NormalTheme" parent="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_background</item>
<item name="android:clipToOutline">true</item>
</style>
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_inner_view_background</item>

View file

@ -5,7 +5,7 @@
and @android:dimen/system_app_widget_internal_padding requires API level 31
-->
<style name="Theme.Android.AppWidgetContainerParent"
parent="@android:style/Theme.DeviceDefault.DayNight">
parent="@style/Theme.Material3.DynamicColors.DayNight">
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
</style>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_widget_description">Oreo Widget</string>
<string name="app_widget_description">Rain Widget</string>
<string name="date_format_widget_oreo_style">EEE, d MMM │</string>
<string name="date_format_widget_oreo_big_style">EEEE, d MMM │</string>
</resources>

View file

@ -2,14 +2,14 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode
setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<style name="LaunchTheme" parent="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed" tools:ignore="NewApi">false</item>
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:ignore="NewApi">shortEdges</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
@ -17,18 +17,15 @@
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<style name="NormalTheme" parent="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:background">?android:attr/colorBackground</item>
<item name="android:background">?attr/colorSurface</item>
</style>
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
<item name="android:background">?android:attr/colorBackground</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:background">?attr/colorSurface</item>
<item name="android:textColor">?attr/itemTextColor</item>
</style>
</resources>
</resources>

View file

@ -1,5 +1,5 @@
<resources>
<style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
<style name="Theme.Android.AppWidgetContainerParent" parent="@style/Theme.Material3.DynamicColors.DayNight">
<!-- Radius of the outer bound of widgets to make the rounded corners -->
<item name="appWidgetRadius">16dp</item>
<!--

View file

@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '1.9.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
@ -21,6 +8,8 @@ allprojects {
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

View file

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
android.enableR8.fullMode = false

View file

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip

View file

@ -1,11 +1,25 @@
include ':app'
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.9.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
}
include ":app"

BIN
assets/icons/City.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
assets/icons/Design.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
assets/icons/Rain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
assets/icons/Team.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 1,011 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 912 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

View file

@ -25,11 +25,11 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>The Rain App requires access to the device&apos;s location.</string>
<string>The Rain App requires access to the device's location.</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>TemporaryPreciseAccuracy</key>
<string>The Rain App requires temporary access to the device&apos;s precise location.</string>
<string>The Rain App requires temporary access to the device's precise location.</string>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>

View file

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

237
lib/app/api/api.dart Normal file → Executable file
View file

@ -1,66 +1,38 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:rain/app/api/city.dart';
import 'package:rain/app/api/weather.dart';
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/api/city_api.dart';
import 'package:rain/app/api/weather_api.dart';
import 'package:rain/app/data/db.dart';
import 'package:rain/main.dart';
class WeatherAPI {
final Dio dio = Dio()..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?';
final Dio dio =
Dio()..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?';
final Dio dioLocation = Dio();
Future<MainWeatherCache> getWeatherData(double? lat, double? lon) async {
String url =
'latitude=$lat&longitude=$lon&hourly=temperature_2m,relativehumidity_2m,apparent_temperature,precipitation,rain,weathercode,surface_pressure,visibility,evapotranspiration,windspeed_10m,winddirection_10m,windgusts_10m,cloudcover,uv_index,dewpoint_2m,precipitation_probability,shortwave_radiation&daily=weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,sunrise,sunset,precipitation_sum,precipitation_probability_max,windspeed_10m_max,windgusts_10m_max,uv_index_max,rain_sum,winddirection_10m_dominant&forecast_days=12&timezone=auto';
String urlWeather;
settings.measurements == 'imperial' && settings.degrees == 'fahrenheit'
? urlWeather = '$url&temperature_unit=fahrenheit&windspeed_unit=mph&precipitation_unit=inch'
: settings.measurements == 'imperial'
? urlWeather = '$url&windspeed_unit=mph&precipitation_unit=inch'
: settings.degrees == 'fahrenheit'
? urlWeather = '$url&temperature_unit=fahrenheit'
: urlWeather = url;
static const String _weatherParams =
'hourly=temperature_2m,relativehumidity_2m,apparent_temperature,precipitation,rain,weathercode,surface_pressure,visibility,evapotranspiration,windspeed_10m,winddirection_10m,windgusts_10m,cloudcover,uv_index,dewpoint_2m,precipitation_probability,shortwave_radiation'
'&daily=weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,sunrise,sunset,precipitation_sum,precipitation_probability_max,windspeed_10m_max,windgusts_10m_max,uv_index_max,rain_sum,winddirection_10m_dominant'
'&forecast_days=12&timezone=auto';
String _buildWeatherUrl(double lat, double lon) {
String url = 'latitude=$lat&longitude=$lon&$_weatherParams';
if (settings.measurements == 'imperial') {
url += '&windspeed_unit=mph&precipitation_unit=inch';
}
if (settings.degrees == 'fahrenheit') {
url += '&temperature_unit=fahrenheit';
}
return url;
}
Future<MainWeatherCache> getWeatherData(double lat, double lon) async {
final String urlWeather = _buildWeatherUrl(lat, lon);
try {
Response response = await dio.get(urlWeather);
WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data);
return MainWeatherCache(
time: weatherData.hourly.time,
temperature2M: weatherData.hourly.temperature2M,
relativehumidity2M: weatherData.hourly.relativeHumidity2M,
apparentTemperature: weatherData.hourly.apparentTemperature,
precipitation: weatherData.hourly.precipitation,
rain: weatherData.hourly.rain,
weathercode: weatherData.hourly.weatherCode,
surfacePressure: weatherData.hourly.surfacePressure,
visibility: weatherData.hourly.visibility,
evapotranspiration: weatherData.hourly.evapotranspiration,
windspeed10M: weatherData.hourly.windSpeed10M,
winddirection10M: weatherData.hourly.windDirection10M,
windgusts10M: weatherData.hourly.windGusts10M,
cloudcover: weatherData.hourly.cloudCover,
uvIndex: weatherData.hourly.uvIndex,
dewpoint2M: weatherData.hourly.dewpoint2M,
precipitationProbability: weatherData.hourly.precipitationProbability,
shortwaveRadiation: weatherData.hourly.shortwaveRadiation,
timeDaily: weatherData.daily.time,
weathercodeDaily: weatherData.daily.weatherCode,
temperature2MMax: weatherData.daily.temperature2MMax,
temperature2MMin: weatherData.daily.temperature2MMin,
apparentTemperatureMax: weatherData.daily.apparentTemperatureMax,
apparentTemperatureMin: weatherData.daily.apparentTemperatureMin,
sunrise: weatherData.daily.sunrise,
sunset: weatherData.daily.sunset,
precipitationSum: weatherData.daily.precipitationSum,
precipitationProbabilityMax: weatherData.daily.precipitationProbabilityMax,
windspeed10MMax: weatherData.daily.windSpeed10MMax,
windgusts10MMax: weatherData.daily.windGusts10MMax,
uvIndexMax: weatherData.daily.uvIndexMax,
rainSum: weatherData.daily.rainSum,
winddirection10MDominant: weatherData.daily.windDirection10MDominant,
timezone: weatherData.timezone,
timestamp: DateTime.now(),
);
return _mapWeatherDataToCache(weatherData);
} on DioException catch (e) {
if (kDebugMode) {
print(e);
@ -69,60 +41,24 @@ class WeatherAPI {
}
}
Future<WeatherCard> getWeatherCard(double? lat, double? lon, String city, String district, String timezone) async {
String url =
'latitude=$lat&longitude=$lon&hourly=temperature_2m,relativehumidity_2m,apparent_temperature,precipitation,rain,weathercode,surface_pressure,visibility,evapotranspiration,windspeed_10m,winddirection_10m,windgusts_10m,cloudcover,uv_index,dewpoint_2m,precipitation_probability,shortwave_radiation&daily=weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,sunrise,sunset,precipitation_sum,precipitation_probability_max,windspeed_10m_max,windgusts_10m_max,uv_index_max,rain_sum,winddirection_10m_dominant&forecast_days=12&timezone=auto';
String urlWeather;
settings.measurements == 'imperial' && settings.degrees == 'fahrenheit'
? urlWeather = '$url&temperature_unit=fahrenheit&windspeed_unit=mph&precipitation_unit=inch'
: settings.measurements == 'imperial'
? urlWeather = '$url&windspeed_unit=mph&precipitation_unit=inch'
: settings.degrees == 'fahrenheit'
? urlWeather = '$url&temperature_unit=fahrenheit'
: urlWeather = url;
Future<WeatherCard> getWeatherCard(
double lat,
double lon,
String city,
String district,
String timezone,
) async {
final String urlWeather = _buildWeatherUrl(lat, lon);
try {
Response response = await dio.get(urlWeather);
WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data);
return WeatherCard(
time: weatherData.hourly.time,
temperature2M: weatherData.hourly.temperature2M,
relativehumidity2M: weatherData.hourly.relativeHumidity2M,
apparentTemperature: weatherData.hourly.apparentTemperature,
precipitation: weatherData.hourly.precipitation,
rain: weatherData.hourly.rain,
weathercode: weatherData.hourly.weatherCode,
surfacePressure: weatherData.hourly.surfacePressure,
visibility: weatherData.hourly.visibility,
evapotranspiration: weatherData.hourly.evapotranspiration,
windspeed10M: weatherData.hourly.windSpeed10M,
winddirection10M: weatherData.hourly.windDirection10M,
windgusts10M: weatherData.hourly.windGusts10M,
cloudcover: weatherData.hourly.cloudCover,
uvIndex: weatherData.hourly.uvIndex,
dewpoint2M: weatherData.hourly.dewpoint2M,
precipitationProbability: weatherData.hourly.precipitationProbability,
shortwaveRadiation: weatherData.hourly.shortwaveRadiation,
timeDaily: weatherData.daily.time,
weathercodeDaily: weatherData.daily.weatherCode,
temperature2MMax: weatherData.daily.temperature2MMax,
temperature2MMin: weatherData.daily.temperature2MMin,
apparentTemperatureMax: weatherData.daily.apparentTemperatureMax,
apparentTemperatureMin: weatherData.daily.apparentTemperatureMin,
sunrise: weatherData.daily.sunrise,
sunset: weatherData.daily.sunset,
precipitationSum: weatherData.daily.precipitationSum,
precipitationProbabilityMax: weatherData.daily.precipitationProbabilityMax,
windspeed10MMax: weatherData.daily.windSpeed10MMax,
windgusts10MMax: weatherData.daily.windGusts10MMax,
uvIndexMax: weatherData.daily.uvIndexMax,
rainSum: weatherData.daily.rainSum,
winddirection10MDominant: weatherData.daily.windDirection10MDominant,
lat: lat,
lon: lon,
city: city,
district: district,
timezone: timezone,
timestamp: DateTime.now(),
return _mapWeatherDataToCard(
weatherData,
lat,
lon,
city,
district,
timezone,
);
} on DioException catch (e) {
if (kDebugMode) {
@ -133,7 +69,7 @@ class WeatherAPI {
}
Future<Iterable<Result>> getCity(String query, Locale? locale) async {
final url =
final String url =
'https://geocoding-api.open-meteo.com/v1/search?name=$query&count=5&language=${locale?.languageCode}&format=json';
try {
Response response = await dioLocation.get(url);
@ -157,4 +93,97 @@ class WeatherAPI {
rethrow;
}
}
MainWeatherCache _mapWeatherDataToCache(WeatherDataApi weatherData) {
return MainWeatherCache(
time: weatherData.hourly.time,
temperature2M: weatherData.hourly.temperature2M,
relativehumidity2M: weatherData.hourly.relativeHumidity2M,
apparentTemperature: weatherData.hourly.apparentTemperature,
precipitation: weatherData.hourly.precipitation,
rain: weatherData.hourly.rain,
weathercode: weatherData.hourly.weatherCode,
surfacePressure: weatherData.hourly.surfacePressure,
visibility: weatherData.hourly.visibility,
evapotranspiration: weatherData.hourly.evapotranspiration,
windspeed10M: weatherData.hourly.windSpeed10M,
winddirection10M: weatherData.hourly.windDirection10M,
windgusts10M: weatherData.hourly.windGusts10M,
cloudcover: weatherData.hourly.cloudCover,
uvIndex: weatherData.hourly.uvIndex,
dewpoint2M: weatherData.hourly.dewpoint2M,
precipitationProbability: weatherData.hourly.precipitationProbability,
shortwaveRadiation: weatherData.hourly.shortwaveRadiation,
timeDaily: weatherData.daily.time,
weathercodeDaily: weatherData.daily.weatherCode,
temperature2MMax: weatherData.daily.temperature2MMax,
temperature2MMin: weatherData.daily.temperature2MMin,
apparentTemperatureMax: weatherData.daily.apparentTemperatureMax,
apparentTemperatureMin: weatherData.daily.apparentTemperatureMin,
sunrise: weatherData.daily.sunrise,
sunset: weatherData.daily.sunset,
precipitationSum: weatherData.daily.precipitationSum,
precipitationProbabilityMax:
weatherData.daily.precipitationProbabilityMax,
windspeed10MMax: weatherData.daily.windSpeed10MMax,
windgusts10MMax: weatherData.daily.windGusts10MMax,
uvIndexMax: weatherData.daily.uvIndexMax,
rainSum: weatherData.daily.rainSum,
winddirection10MDominant: weatherData.daily.windDirection10MDominant,
timezone: weatherData.timezone,
timestamp: DateTime.now(),
);
}
WeatherCard _mapWeatherDataToCard(
WeatherDataApi weatherData,
double lat,
double lon,
String city,
String district,
String timezone,
) {
return WeatherCard(
time: weatherData.hourly.time,
temperature2M: weatherData.hourly.temperature2M,
relativehumidity2M: weatherData.hourly.relativeHumidity2M,
apparentTemperature: weatherData.hourly.apparentTemperature,
precipitation: weatherData.hourly.precipitation,
rain: weatherData.hourly.rain,
weathercode: weatherData.hourly.weatherCode,
surfacePressure: weatherData.hourly.surfacePressure,
visibility: weatherData.hourly.visibility,
evapotranspiration: weatherData.hourly.evapotranspiration,
windspeed10M: weatherData.hourly.windSpeed10M,
winddirection10M: weatherData.hourly.windDirection10M,
windgusts10M: weatherData.hourly.windGusts10M,
cloudcover: weatherData.hourly.cloudCover,
uvIndex: weatherData.hourly.uvIndex,
dewpoint2M: weatherData.hourly.dewpoint2M,
precipitationProbability: weatherData.hourly.precipitationProbability,
shortwaveRadiation: weatherData.hourly.shortwaveRadiation,
timeDaily: weatherData.daily.time,
weathercodeDaily: weatherData.daily.weatherCode,
temperature2MMax: weatherData.daily.temperature2MMax,
temperature2MMin: weatherData.daily.temperature2MMin,
apparentTemperatureMax: weatherData.daily.apparentTemperatureMax,
apparentTemperatureMin: weatherData.daily.apparentTemperatureMin,
sunrise: weatherData.daily.sunrise,
sunset: weatherData.daily.sunset,
precipitationSum: weatherData.daily.precipitationSum,
precipitationProbabilityMax:
weatherData.daily.precipitationProbabilityMax,
windspeed10MMax: weatherData.daily.windSpeed10MMax,
windgusts10MMax: weatherData.daily.windGusts10MMax,
uvIndexMax: weatherData.daily.uvIndexMax,
rainSum: weatherData.daily.rainSum,
winddirection10MDominant: weatherData.daily.windDirection10MDominant,
lat: lat,
lon: lon,
city: city,
district: district,
timezone: timezone,
timestamp: DateTime.now(),
);
}
}

19
lib/app/api/city.dart → lib/app/api/city_api.dart Normal file → Executable file
View file

@ -1,15 +1,14 @@
class CityApi {
CityApi({
required this.results,
});
CityApi({required this.results});
List<Result> results;
factory CityApi.fromJson(Map<String, dynamic> json) => CityApi(
results: json['results'] == null
results:
json['results'] == null
? List<Result>.empty()
: List<Result>.from(json['results'].map((x) => Result.fromJson(x))),
);
);
}
class Result {
@ -26,9 +25,9 @@ class Result {
double longitude;
factory Result.fromJson(Map<String, dynamic> json) => Result(
admin1: json['admin1'] ?? '',
name: json['name'],
latitude: json['latitude'],
longitude: json['longitude'],
);
admin1: json['admin1'] ?? '',
name: json['name'],
latitude: json['latitude'],
longitude: json['longitude'],
);
}

View file

@ -2,8 +2,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
//ignore_for_file: invalid_annotation_target
part 'weather.freezed.dart';
part 'weather.g.dart';
part 'weather_api.freezed.dart';
part 'weather_api.g.dart';
@freezed
class WeatherDataApi with _$WeatherDataApi {
@ -13,7 +13,8 @@ class WeatherDataApi with _$WeatherDataApi {
required String timezone,
}) = _WeatherDataApi;
factory WeatherDataApi.fromJson(Map<String, dynamic> json) => _$WeatherDataApiFromJson(json);
factory WeatherDataApi.fromJson(Map<String, dynamic> json) =>
_$WeatherDataApiFromJson(json);
}
@freezed
@ -35,14 +36,16 @@ class Hourly with _$Hourly {
@JsonKey(name: 'cloudcover') List<int?>? cloudCover,
@JsonKey(name: 'uv_index') List<double?>? uvIndex,
@JsonKey(name: 'dewpoint_2m') List<double?>? dewpoint2M,
@JsonKey(name: 'precipitation_probability') List<int?>? precipitationProbability,
@JsonKey(name: 'precipitation_probability')
List<int?>? precipitationProbability,
@JsonKey(name: 'shortwave_radiation') List<double?>? shortwaveRadiation,
}) = _Hourly;
factory Hourly.fromJson(Map<String, dynamic> json) => _$HourlyFromJson(json);
}
List<DateTime> _dateTimeFromJson(List<dynamic>? json) => json?.map((x) => DateTime.parse(x)).toList() ?? [];
List<DateTime> _dateTimeFromJson(List<dynamic>? json) =>
json?.map((x) => DateTime.parse(x)).toList() ?? [];
@freezed
class Daily with _$Daily {
@ -51,17 +54,21 @@ class Daily with _$Daily {
@JsonKey(name: 'weathercode') List<int?>? weatherCode,
@JsonKey(name: 'temperature_2m_max') List<double?>? temperature2MMax,
@JsonKey(name: 'temperature_2m_min') List<double?>? temperature2MMin,
@JsonKey(name: 'apparent_temperature_max') List<double?>? apparentTemperatureMax,
@JsonKey(name: 'apparent_temperature_min') List<double?>? apparentTemperatureMin,
@JsonKey(name: 'apparent_temperature_max')
List<double?>? apparentTemperatureMax,
@JsonKey(name: 'apparent_temperature_min')
List<double?>? apparentTemperatureMin,
@JsonKey(name: 'precipitation_sum') List<double?>? precipitationSum,
List<String>? sunrise,
List<String>? sunset,
@JsonKey(name: 'precipitation_probability_max') List<int?>? precipitationProbabilityMax,
@JsonKey(name: 'precipitation_probability_max')
List<int?>? precipitationProbabilityMax,
@JsonKey(name: 'windspeed_10m_max') List<double?>? windSpeed10MMax,
@JsonKey(name: 'windgusts_10m_max') List<double?>? windGusts10MMax,
@JsonKey(name: 'uv_index_max') List<double?>? uvIndexMax,
@JsonKey(name: 'rain_sum') List<double?>? rainSum,
@JsonKey(name: 'winddirection_10m_dominant') List<int?>? windDirection10MDominant,
@JsonKey(name: 'winddirection_10m_dominant')
List<int?>? windDirection10MDominant,
}) = _Daily;
factory Daily.fromJson(Map<String, dynamic> json) => _$DailyFromJson(json);

View file

@ -3,7 +3,7 @@
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'weather.dart';
part of 'weather_api.dart';
// **************************************************************************
// FreezedGenerator
@ -12,7 +12,7 @@ part of 'weather.dart';
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
WeatherDataApi _$WeatherDataApiFromJson(Map<String, dynamic> json) {
return _WeatherDataApi.fromJson(json);
@ -24,8 +24,12 @@ mixin _$WeatherDataApi {
Daily get daily => throw _privateConstructorUsedError;
String get timezone => throw _privateConstructorUsedError;
/// Serializes this WeatherDataApi to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of WeatherDataApi
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$WeatherDataApiCopyWith<WeatherDataApi> get copyWith =>
throw _privateConstructorUsedError;
}
@ -52,6 +56,8 @@ class _$WeatherDataApiCopyWithImpl<$Res, $Val extends WeatherDataApi>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of WeatherDataApi
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -75,6 +81,8 @@ class _$WeatherDataApiCopyWithImpl<$Res, $Val extends WeatherDataApi>
) as $Val);
}
/// Create a copy of WeatherDataApi
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$HourlyCopyWith<$Res> get hourly {
@ -83,6 +91,8 @@ class _$WeatherDataApiCopyWithImpl<$Res, $Val extends WeatherDataApi>
});
}
/// Create a copy of WeatherDataApi
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DailyCopyWith<$Res> get daily {
@ -116,6 +126,8 @@ class __$$WeatherDataApiImplCopyWithImpl<$Res>
_$WeatherDataApiImpl _value, $Res Function(_$WeatherDataApiImpl) _then)
: super(_value, _then);
/// Create a copy of WeatherDataApi
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -162,7 +174,7 @@ class _$WeatherDataApiImpl implements _WeatherDataApi {
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$WeatherDataApiImpl &&
@ -172,11 +184,13 @@ class _$WeatherDataApiImpl implements _WeatherDataApi {
other.timezone == timezone));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, hourly, daily, timezone);
@JsonKey(ignore: true)
/// Create a copy of WeatherDataApi
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith =>
@ -206,8 +220,11 @@ abstract class _WeatherDataApi implements WeatherDataApi {
Daily get daily;
@override
String get timezone;
/// Create a copy of WeatherDataApi
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -251,8 +268,12 @@ mixin _$Hourly {
@JsonKey(name: 'shortwave_radiation')
List<double?>? get shortwaveRadiation => throw _privateConstructorUsedError;
/// Serializes this Hourly to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Hourly
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$HourlyCopyWith<Hourly> get copyWith => throw _privateConstructorUsedError;
}
@ -293,6 +314,8 @@ class _$HourlyCopyWithImpl<$Res, $Val extends Hourly>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Hourly
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -429,6 +452,8 @@ class __$$HourlyImplCopyWithImpl<$Res>
_$HourlyImpl _value, $Res Function(_$HourlyImpl) _then)
: super(_value, _then);
/// Create a copy of Hourly
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -781,7 +806,7 @@ class _$HourlyImpl implements _Hourly {
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HourlyImpl &&
@ -820,7 +845,7 @@ class _$HourlyImpl implements _Hourly {
.equals(other._shortwaveRadiation, _shortwaveRadiation));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@ -843,7 +868,9 @@ class _$HourlyImpl implements _Hourly {
const DeepCollectionEquality().hash(_precipitationProbability),
const DeepCollectionEquality().hash(_shortwaveRadiation));
@JsonKey(ignore: true)
/// Create a copy of Hourly
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$HourlyImplCopyWith<_$HourlyImpl> get copyWith =>
@ -933,8 +960,11 @@ abstract class _Hourly implements Hourly {
@override
@JsonKey(name: 'shortwave_radiation')
List<double?>? get shortwaveRadiation;
/// Create a copy of Hourly
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$HourlyImplCopyWith<_$HourlyImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -978,8 +1008,12 @@ mixin _$Daily {
List<int?>? get windDirection10MDominant =>
throw _privateConstructorUsedError;
/// Serializes this Daily to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Daily
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$DailyCopyWith<Daily> get copyWith => throw _privateConstructorUsedError;
}
@ -1020,6 +1054,8 @@ class _$DailyCopyWithImpl<$Res, $Val extends Daily>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Daily
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1141,6 +1177,8 @@ class __$$DailyImplCopyWithImpl<$Res>
_$DailyImpl _value, $Res Function(_$DailyImpl) _then)
: super(_value, _then);
/// Create a copy of Daily
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1445,7 +1483,7 @@ class _$DailyImpl implements _Daily {
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$DailyImpl &&
@ -1478,7 +1516,7 @@ class _$DailyImpl implements _Daily {
other._windDirection10MDominant, _windDirection10MDominant));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@ -1498,7 +1536,9 @@ class _$DailyImpl implements _Daily {
const DeepCollectionEquality().hash(_rainSum),
const DeepCollectionEquality().hash(_windDirection10MDominant));
@JsonKey(ignore: true)
/// Create a copy of Daily
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DailyImplCopyWith<_$DailyImpl> get copyWith =>
@ -1581,8 +1621,11 @@ abstract class _Daily implements Daily {
@override
@JsonKey(name: 'winddirection_10m_dominant')
List<int?>? get windDirection10MDominant;
/// Create a copy of Daily
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$DailyImplCopyWith<_$DailyImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'weather.dart';
part of 'weather_api.dart';
// **************************************************************************
// JsonSerializableGenerator
@ -24,7 +24,7 @@ Map<String, dynamic> _$$WeatherDataApiImplToJson(
_$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
time: (json['time'] as List<dynamic>?)?.map((e) => e as String).toList(),
weatherCode: (json['weathercode'] as List<dynamic>?)
?.map((e) => e as int)
?.map((e) => (e as num).toInt())
.toList(),
temperature2M: (json['temperature_2m'] as List<dynamic>?)
?.map((e) => (e as num).toDouble())
@ -39,7 +39,7 @@ _$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
?.map((e) => (e as num?)?.toDouble())
.toList(),
relativeHumidity2M: (json['relativehumidity_2m'] as List<dynamic>?)
?.map((e) => e as int?)
?.map((e) => (e as num?)?.toInt())
.toList(),
surfacePressure: (json['surface_pressure'] as List<dynamic>?)
?.map((e) => (e as num?)?.toDouble())
@ -54,13 +54,13 @@ _$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
?.map((e) => (e as num?)?.toDouble())
.toList(),
windDirection10M: (json['winddirection_10m'] as List<dynamic>?)
?.map((e) => e as int?)
?.map((e) => (e as num?)?.toInt())
.toList(),
windGusts10M: (json['windgusts_10m'] as List<dynamic>?)
?.map((e) => (e as num?)?.toDouble())
.toList(),
cloudCover: (json['cloudcover'] as List<dynamic>?)
?.map((e) => e as int?)
?.map((e) => (e as num?)?.toInt())
.toList(),
uvIndex: (json['uv_index'] as List<dynamic>?)
?.map((e) => (e as num?)?.toDouble())
@ -70,7 +70,7 @@ _$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
.toList(),
precipitationProbability:
(json['precipitation_probability'] as List<dynamic>?)
?.map((e) => e as int?)
?.map((e) => (e as num?)?.toInt())
.toList(),
shortwaveRadiation: (json['shortwave_radiation'] as List<dynamic>?)
?.map((e) => (e as num?)?.toDouble())
@ -102,7 +102,7 @@ Map<String, dynamic> _$$HourlyImplToJson(_$HourlyImpl instance) =>
_$DailyImpl _$$DailyImplFromJson(Map<String, dynamic> json) => _$DailyImpl(
time: _dateTimeFromJson(json['time'] as List?),
weatherCode: (json['weathercode'] as List<dynamic>?)
?.map((e) => e as int?)
?.map((e) => (e as num?)?.toInt())
.toList(),
temperature2MMax: (json['temperature_2m_max'] as List<dynamic>?)
?.map((e) => (e as num?)?.toDouble())
@ -127,7 +127,7 @@ _$DailyImpl _$$DailyImplFromJson(Map<String, dynamic> json) => _$DailyImpl(
(json['sunset'] as List<dynamic>?)?.map((e) => e as String).toList(),
precipitationProbabilityMax:
(json['precipitation_probability_max'] as List<dynamic>?)
?.map((e) => e as int?)
?.map((e) => (e as num?)?.toInt())
.toList(),
windSpeed10MMax: (json['windspeed_10m_max'] as List<dynamic>?)
?.map((e) => (e as num?)?.toDouble())
@ -143,7 +143,7 @@ _$DailyImpl _$$DailyImplFromJson(Map<String, dynamic> json) => _$DailyImpl(
.toList(),
windDirection10MDominant:
(json['winddirection_10m_dominant'] as List<dynamic>?)
?.map((e) => e as int?)
?.map((e) => (e as num?)?.toInt())
.toList(),
);

430
lib/app/controller/controller.dart Normal file → Executable file
View file

@ -1,7 +1,6 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
@ -11,16 +10,17 @@ import 'package:isar/isar.dart';
import 'package:lat_lng_to_timezone/lat_lng_to_timezone.dart' as tzmap;
import 'package:path_provider/path_provider.dart';
import 'package:rain/app/api/api.dart';
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/services/notification.dart';
import 'package:rain/app/services/utils.dart';
import 'package:rain/app/widgets/status/status_data.dart';
import 'package:rain/app/widgets/status/status_weather.dart';
import 'package:rain/app/data/db.dart';
import 'package:rain/app/utils/notification.dart';
import 'package:rain/app/utils/show_snack_bar.dart';
import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
import 'package:rain/main.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/standalone.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'package:url_launcher/url_launcher.dart';
import 'package:workmanager/workmanager.dart';
class WeatherController extends GetxController {
@ -52,15 +52,14 @@ class WeatherController extends GetxController {
@override
void onInit() {
weatherCards
.assignAll(isar.weatherCards.where().sortByIndex().findAllSync());
weatherCards.assignAll(
isar.weatherCards.where().sortByIndex().findAllSync(),
);
super.onInit();
}
Future<Position> determinePosition() async {
LocationPermission permission;
permission = await Geolocator.checkPermission();
Future<Position> _determinePosition() async {
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
@ -70,35 +69,36 @@ class WeatherController extends GetxController {
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
'Location permissions are permanently denied, we cannot request permissions.',
);
}
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
return await Geolocator.getCurrentPosition();
}
Future<void> setLocation() async {
if (settings.location) {
await getCurrentLocation();
} else {
if ((isar.locationCaches.where().findAllSync()).isNotEmpty) {
LocationCache locationCity =
(isar.locationCaches.where().findFirstSync())!;
await getLocation(locationCity.lat!, locationCity.lon!,
locationCity.district!, locationCity.city!);
final locationCity = isar.locationCaches.where().findFirstSync();
if (locationCity != null) {
await getLocation(
locationCity.lat!,
locationCity.lon!,
locationCity.district!,
locationCity.city!,
);
}
}
}
Future<void> getCurrentLocation() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!isOnline) {
if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr);
await readCache();
return;
}
if (!serviceEnabled) {
if (!await Geolocator.isLocationServiceEnabled()) {
showSnackBar(
content: 'no_location'.tr,
onPressed: () => Geolocator.openLocationSettings(),
@ -107,39 +107,73 @@ class WeatherController extends GetxController {
return;
}
if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) {
if (isar.mainWeatherCaches.where().findAllSync().isNotEmpty) {
await readCache();
return;
}
Position position = await determinePosition();
List<Placemark> placemarks =
await placemarkFromCoordinates(position.latitude, position.longitude);
Placemark place = placemarks[0];
final position = await _determinePosition();
final placemarks = await placemarkFromCoordinates(
position.latitude,
position.longitude,
);
final place = placemarks[0];
_latitude.value = position.latitude;
_longitude.value = position.longitude;
_district.value = '${place.administrativeArea}';
_city.value = '${place.locality}';
_district.value = place.administrativeArea ?? '';
_city.value = place.locality ?? '';
_mainWeather.value =
await WeatherAPI().getWeatherData(_latitude.value, _longitude.value);
_mainWeather.value = await WeatherAPI().getWeatherData(
_latitude.value,
_longitude.value,
);
notificationCheck();
await writeCache();
await readCache();
}
Future<void> getLocation(double latitude, double longitude, String district,
String locality) async {
if (!isOnline) {
Future<Map<String, dynamic>> getCurrentLocationSearch() async {
if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr);
}
if (!await Geolocator.isLocationServiceEnabled()) {
showSnackBar(
content: 'no_location'.tr,
onPressed: () => Geolocator.openLocationSettings(),
);
}
final position = await _determinePosition();
final placemarks = await placemarkFromCoordinates(
position.latitude,
position.longitude,
);
final place = placemarks[0];
return {
'lat': position.latitude,
'lon': position.longitude,
'city': place.administrativeArea ?? '',
'district': place.locality ?? '',
};
}
Future<void> getLocation(
double latitude,
double longitude,
String district,
String locality,
) async {
if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr);
await readCache();
return;
}
if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) {
if (isar.mainWeatherCaches.where().findAllSync().isNotEmpty) {
await readCache();
return;
}
@ -149,11 +183,12 @@ class WeatherController extends GetxController {
_district.value = district;
_city.value = locality;
_mainWeather.value =
await WeatherAPI().getWeatherData(_latitude.value, _longitude.value);
_mainWeather.value = await WeatherAPI().getWeatherData(
_latitude.value,
_longitude.value,
);
notificationCheck();
await writeCache();
await readCache();
}
@ -170,10 +205,14 @@ class WeatherController extends GetxController {
_mainWeather.value = mainWeatherCache;
_location.value = locationCache;
hourOfDay.value =
getTime(_mainWeather.value.time!, _mainWeather.value.timezone!);
dayOfNow.value =
getDay(_mainWeather.value.timeDaily!, _mainWeather.value.timezone!);
hourOfDay.value = getTime(
_mainWeather.value.time!,
_mainWeather.value.timezone!,
);
dayOfNow.value = getDay(
_mainWeather.value.timeDaily!,
_mainWeather.value.timezone!,
);
if (Platform.isAndroid) {
Workmanager().registerPeriodicTask(
@ -204,23 +243,17 @@ class WeatherController extends GetxController {
);
isar.writeTxnSync(() {
final mainWeatherCachesIsEmpty =
(isar.mainWeatherCaches.where().findAllSync()).isEmpty;
final locationCachesIsEmpty =
(isar.locationCaches.where().findAllSync()).isEmpty;
if (mainWeatherCachesIsEmpty) {
if (isar.mainWeatherCaches.where().findAllSync().isEmpty) {
isar.mainWeatherCaches.putSync(_mainWeather.value);
}
if (locationCachesIsEmpty) {
if (isar.locationCaches.where().findAllSync().isEmpty) {
isar.locationCaches.putSync(locationCaches);
}
});
}
Future<void> deleteCache() async {
if (!isOnline) {
if (!(await isOnline.value)) {
return;
}
@ -230,41 +263,49 @@ class WeatherController extends GetxController {
.timestampLessThan(cacheExpiry)
.deleteAllSync();
});
if ((isar.mainWeatherCaches.where().findAllSync()).isEmpty) {
if (isar.mainWeatherCaches.where().findAllSync().isEmpty) {
await flutterLocalNotificationsPlugin.cancelAll();
}
}
Future<void> deleteAll(bool changeCity) async {
if (!isOnline) {
if (!(await isOnline.value)) {
return;
}
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
await flutterLocalNotificationsPlugin.cancelAll();
isar.writeTxnSync(() {
if (!settings.location) {
isar.mainWeatherCaches.where().deleteAllSync();
}
if ((settings.location && serviceEnabled) || changeCity) {
if (settings.location && serviceEnabled || changeCity) {
isar.mainWeatherCaches.where().deleteAllSync();
isar.locationCaches.where().deleteAllSync();
}
});
}
// Card Weather
Future<void> addCardWeather(
double latitude, double longitude, String city, String district) async {
if (!isOnline) {
double latitude,
double longitude,
String city,
String district,
) async {
if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr);
return;
}
String tz = tzmap.latLngToTimezoneString(latitude, longitude);
_weatherCard.value = await WeatherAPI()
.getWeatherCard(latitude, longitude, city, district, tz);
final tz = tzmap.latLngToTimezoneString(latitude, longitude);
_weatherCard.value = await WeatherAPI().getWeatherCard(
latitude,
longitude,
city,
district,
tz,
);
isar.writeTxnSync(() {
weatherCards.add(_weatherCard.value);
isar.weatherCards.putSync(_weatherCard.value);
@ -272,120 +313,88 @@ class WeatherController extends GetxController {
}
Future<void> updateCacheCard(bool refresh) async {
List<WeatherCard> weatherCard = refresh
final weatherCard = refresh
? isar.weatherCards.where().sortByIndex().findAllSync()
: isar.weatherCards
.filter()
.timestampLessThan(cacheExpiry)
.sortByIndex()
.findAllSync();
.filter()
.timestampLessThan(cacheExpiry)
.sortByIndex()
.findAllSync();
if (!isOnline || weatherCard.isEmpty) {
if (!(await isOnline.value) || weatherCard.isEmpty) {
return;
}
for (var oldCard in weatherCard) {
var updatedCard = await WeatherAPI().getWeatherCard(oldCard.lat,
oldCard.lon, oldCard.city!, oldCard.district!, oldCard.timezone!);
final updatedCard = await WeatherAPI().getWeatherCard(
oldCard.lat!,
oldCard.lon!,
oldCard.city!,
oldCard.district!,
oldCard.timezone!,
);
isar.writeTxnSync(() {
oldCard
..time = updatedCard.time
..weathercode = updatedCard.weathercode
..temperature2M = updatedCard.temperature2M
..apparentTemperature = updatedCard.apparentTemperature
..relativehumidity2M = updatedCard.relativehumidity2M
..precipitation = updatedCard.precipitation
..rain = updatedCard.rain
..surfacePressure = updatedCard.surfacePressure
..visibility = updatedCard.visibility
..evapotranspiration = updatedCard.evapotranspiration
..windspeed10M = updatedCard.windspeed10M
..winddirection10M = updatedCard.winddirection10M
..windgusts10M = updatedCard.windgusts10M
..cloudcover = updatedCard.cloudcover
..uvIndex = updatedCard.uvIndex
..dewpoint2M = updatedCard.dewpoint2M
..precipitationProbability = updatedCard.precipitationProbability
..shortwaveRadiation = updatedCard.shortwaveRadiation
..timeDaily = updatedCard.timeDaily
..weathercodeDaily = updatedCard.weathercodeDaily
..temperature2MMax = updatedCard.temperature2MMax
..temperature2MMin = updatedCard.temperature2MMin
..apparentTemperatureMax = updatedCard.apparentTemperatureMax
..apparentTemperatureMin = updatedCard.apparentTemperatureMin
..sunrise = updatedCard.sunrise
..sunset = updatedCard.sunset
..precipitationSum = updatedCard.precipitationSum
..precipitationProbabilityMax =
updatedCard.precipitationProbabilityMax
..windspeed10MMax = updatedCard.windspeed10MMax
..windgusts10MMax = updatedCard.windgusts10MMax
..uvIndexMax = updatedCard.uvIndexMax
..rainSum = updatedCard.rainSum
..winddirection10MDominant = updatedCard.winddirection10MDominant
..timestamp = DateTime.now();
isar.weatherCards.putSync(oldCard);
var newCard = oldCard;
int oldIdx = weatherCard.indexOf(oldCard);
weatherCards[oldIdx] = newCard;
_updateWeatherCard(oldCard, updatedCard);
weatherCards.refresh();
});
}
}
void _updateWeatherCard(WeatherCard oldCard, WeatherCard updatedCard) {
oldCard
..time = updatedCard.time
..weathercode = updatedCard.weathercode
..temperature2M = updatedCard.temperature2M
..apparentTemperature = updatedCard.apparentTemperature
..relativehumidity2M = updatedCard.relativehumidity2M
..precipitation = updatedCard.precipitation
..rain = updatedCard.rain
..surfacePressure = updatedCard.surfacePressure
..visibility = updatedCard.visibility
..evapotranspiration = updatedCard.evapotranspiration
..windspeed10M = updatedCard.windspeed10M
..winddirection10M = updatedCard.winddirection10M
..windgusts10M = updatedCard.windgusts10M
..cloudcover = updatedCard.cloudcover
..uvIndex = updatedCard.uvIndex
..dewpoint2M = updatedCard.dewpoint2M
..precipitationProbability = updatedCard.precipitationProbability
..shortwaveRadiation = updatedCard.shortwaveRadiation
..timeDaily = updatedCard.timeDaily
..weathercodeDaily = updatedCard.weathercodeDaily
..temperature2MMax = updatedCard.temperature2MMax
..temperature2MMin = updatedCard.temperature2MMin
..apparentTemperatureMax = updatedCard.apparentTemperatureMax
..apparentTemperatureMin = updatedCard.apparentTemperatureMin
..sunrise = updatedCard.sunrise
..sunset = updatedCard.sunset
..precipitationSum = updatedCard.precipitationSum
..precipitationProbabilityMax = updatedCard.precipitationProbabilityMax
..windspeed10MMax = updatedCard.windspeed10MMax
..windgusts10MMax = updatedCard.windgusts10MMax
..uvIndexMax = updatedCard.uvIndexMax
..rainSum = updatedCard.rainSum
..winddirection10MDominant = updatedCard.winddirection10MDominant
..timestamp = DateTime.now();
isar.weatherCards.putSync(oldCard);
}
Future<void> updateCard(WeatherCard weatherCard) async {
if (!isOnline) {
if (!(await isOnline.value)) {
return;
}
final updatedCard = await WeatherAPI().getWeatherCard(
weatherCard.lat,
weatherCard.lon,
weatherCard.lat!,
weatherCard.lon!,
weatherCard.city!,
weatherCard.district!,
weatherCard.timezone!,
);
isar.writeTxnSync(() {
weatherCard
..time = updatedCard.time
..weathercode = updatedCard.weathercode
..temperature2M = updatedCard.temperature2M
..apparentTemperature = updatedCard.apparentTemperature
..relativehumidity2M = updatedCard.relativehumidity2M
..precipitation = updatedCard.precipitation
..rain = updatedCard.rain
..surfacePressure = updatedCard.surfacePressure
..visibility = updatedCard.visibility
..evapotranspiration = updatedCard.evapotranspiration
..windspeed10M = updatedCard.windspeed10M
..winddirection10M = updatedCard.winddirection10M
..windgusts10M = updatedCard.windgusts10M
..cloudcover = updatedCard.cloudcover
..uvIndex = updatedCard.uvIndex
..dewpoint2M = updatedCard.dewpoint2M
..precipitationProbability = updatedCard.precipitationProbability
..shortwaveRadiation = updatedCard.shortwaveRadiation
..timeDaily = updatedCard.timeDaily
..weathercodeDaily = updatedCard.weathercodeDaily
..temperature2MMax = updatedCard.temperature2MMax
..temperature2MMin = updatedCard.temperature2MMin
..apparentTemperatureMax = updatedCard.apparentTemperatureMax
..apparentTemperatureMin = updatedCard.apparentTemperatureMin
..sunrise = updatedCard.sunrise
..sunset = updatedCard.sunset
..precipitationSum = updatedCard.precipitationSum
..precipitationProbabilityMax = updatedCard.precipitationProbabilityMax
..windspeed10MMax = updatedCard.windspeed10MMax
..windgusts10MMax = updatedCard.windgusts10MMax
..uvIndexMax = updatedCard.uvIndexMax
..rainSum = updatedCard.rainSum
..winddirection10MDominant = updatedCard.winddirection10MDominant
..timestamp = DateTime.now();
isar.weatherCards.putSync(weatherCard);
_updateWeatherCard(weatherCard, updatedCard);
});
}
@ -397,35 +406,26 @@ class WeatherController extends GetxController {
}
int getTime(List<String> time, String timezone) {
int getTime = 0;
for (var i = 0; i < time.length; i++) {
if (tz.TZDateTime.now(tz.getLocation(timezone)).hour ==
DateTime.parse(time[i]).hour &&
tz.TZDateTime.now(tz.getLocation(timezone)).day ==
DateTime.parse(time[i]).day) {
getTime = i;
}
}
return getTime;
return time.indexWhere((t) {
final dateTime = DateTime.parse(t);
return tz.TZDateTime.now(tz.getLocation(timezone)).hour ==
dateTime.hour &&
tz.TZDateTime.now(tz.getLocation(timezone)).day == dateTime.day;
});
}
int getDay(List<DateTime> time, String timezone) {
int getDay = 0;
for (var i = 0; i < time.length; i++) {
if (tz.TZDateTime.now(tz.getLocation(timezone)).day == time[i].day) {
getDay = i;
}
}
return getDay;
return time.indexWhere(
(t) => tz.TZDateTime.now(tz.getLocation(timezone)).day == t.day,
);
}
TimeOfDay timeConvert(String normTime) {
int hh = 0;
if (normTime.endsWith('PM')) hh = 12;
normTime = normTime.split(' ')[0];
final hh = normTime.endsWith('PM') ? 12 : 0;
final timeParts = normTime.split(' ')[0].split(':');
return TimeOfDay(
hour: hh + int.parse(normTime.split(':')[0]) % 24,
minute: int.parse(normTime.split(':')[1]) % 60,
hour: hh + int.parse(timeParts[0]) % 24,
minute: int.parse(timeParts[1]) % 60,
);
}
@ -433,21 +433,21 @@ class WeatherController extends GetxController {
final directory = await getTemporaryDirectory();
final imagePath = '${directory.path}/$icon';
final ByteData data = await rootBundle.load('assets/images/$icon');
final List<int> bytes = data.buffer.asUint8List();
final data = await rootBundle.load('assets/images/$icon');
final bytes = data.buffer.asUint8List();
await File(imagePath).writeAsBytes(bytes);
return imagePath;
}
void notlification(MainWeatherCache mainWeatherCache) async {
DateTime now = DateTime.now();
int startHour = timeConvert(timeStart).hour;
int endHour = timeConvert(timeEnd).hour;
void notification(MainWeatherCache mainWeatherCache) async {
final now = DateTime.now();
final startHour = timeConvert(timeStart).hour;
final endHour = timeConvert(timeEnd).hour;
for (var i = 0; i < mainWeatherCache.time!.length; i += timeRange) {
DateTime notificationTime = DateTime.parse(mainWeatherCache.time![i]);
final notificationTime = DateTime.parse(mainWeatherCache.time![i]);
if (notificationTime.isAfter(now) &&
notificationTime.hour >= startHour &&
@ -474,15 +474,15 @@ class WeatherController extends GetxController {
void notificationCheck() async {
if (settings.notifications) {
final List<PendingNotificationRequest> pendingNotificationRequests =
await flutterLocalNotificationsPlugin.pendingNotificationRequests();
final pendingNotificationRequests = await flutterLocalNotificationsPlugin
.pendingNotificationRequests();
if (pendingNotificationRequests.isEmpty) {
notlification(_mainWeather.value);
notification(_mainWeather.value);
}
}
}
void reorder(oldIndex, newIndex) {
void reorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
@ -502,15 +502,11 @@ class WeatherController extends GetxController {
isar.settings.putSync(settings);
});
return Future.wait<bool?>([
HomeWidget.saveWidgetData(
'background_color',
color,
),
final results = await Future.wait<bool?>([
HomeWidget.saveWidgetData('background_color', color),
HomeWidget.updateWidget(androidName: androidWidgetName),
]).then((value) {
return !value.contains(false);
});
]);
return !results.contains(false);
}
Future<bool> updateWidgetTextColor(String color) async {
@ -519,15 +515,11 @@ class WeatherController extends GetxController {
isar.settings.putSync(settings);
});
return Future.wait<bool?>([
HomeWidget.saveWidgetData(
'text_color',
color,
),
final results = await Future.wait<bool?>([
HomeWidget.saveWidgetData('text_color', color),
HomeWidget.updateWidget(androidName: androidWidgetName),
]).then((value) {
return !value.contains(false);
});
]);
return !results.contains(false);
}
Future<bool> updateWidget() async {
@ -542,29 +534,39 @@ class WeatherController extends GetxController {
WeatherCardSchema,
], directory: (await getApplicationSupportDirectory()).path);
MainWeatherCache? mainWeatherCache;
mainWeatherCache = isarWidget.mainWeatherCaches.where().findFirstSync();
final mainWeatherCache = isarWidget.mainWeatherCaches
.where()
.findFirstSync();
if (mainWeatherCache == null) return false;
int hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!);
int day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!);
final hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!);
final day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!);
return Future.wait<bool?>([
final results = await Future.wait<bool?>([
HomeWidget.saveWidgetData(
'weather_icon',
await getLocalImagePath(StatusWeather().getImageNotification(
'weather_icon',
await getLocalImagePath(
StatusWeather().getImageNotification(
mainWeatherCache.weathercode![hour],
mainWeatherCache.time![hour],
mainWeatherCache.sunrise![day],
mainWeatherCache.sunset![day],
))),
),
),
),
HomeWidget.saveWidgetData(
'weather_degree',
'${mainWeatherCache.temperature2M?[hour].round()}°',
),
HomeWidget.updateWidget(androidName: androidWidgetName),
]).then((value) {
return !value.contains(false);
});
]);
return !results.contains(false);
}
void urlLauncher(String uri) async {
final url = Uri.parse(uri);
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
throw Exception('Could not launch $url');
}
}
}

204
lib/app/data/weather.dart → lib/app/data/db.dart Normal file → Executable file
View file

@ -1,6 +1,6 @@
import 'package:isar/isar.dart';
part 'weather.g.dart';
part 'db.g.dart';
@collection
class Settings {
@ -11,10 +11,15 @@ class Settings {
bool notifications = false;
bool materialColor = false;
bool amoledTheme = false;
bool roundDegree = false;
bool largeElement = false;
bool hideMap = false;
String? widgetBackgroundColor;
String? widgetTextColor;
String measurements = 'metric';
String degrees = 'celsius';
String wind = 'kph';
String pressure = 'hPa';
String timeformat = '24';
String? language;
int? timeRange;
@ -100,43 +105,43 @@ class MainWeatherCache {
});
Map<String, dynamic> toJson() => {
'id': id,
'time': time,
'weathercode': weathercode,
'temperature2M': temperature2M,
'apparentTemperature': apparentTemperature,
'relativehumidity2M': relativehumidity2M,
'precipitation': precipitation,
'rain': rain,
'surfacePressure': surfacePressure,
'visibility': visibility,
'evapotranspiration': evapotranspiration,
'windspeed10M': windspeed10M,
'winddirection10M': winddirection10M,
'windgusts10M': windgusts10M,
'cloudcover': cloudcover,
'uvIndex': uvIndex,
'dewpoint2M': dewpoint2M,
'precipitationProbability': precipitationProbability,
'shortwaveRadiation': shortwaveRadiation,
'timeDaily': timeDaily,
'weathercodeDaily': weathercodeDaily,
'temperature2MMax': temperature2MMax,
'temperature2MMin': temperature2MMin,
'apparentTemperatureMax': apparentTemperatureMax,
'apparentTemperatureMin': apparentTemperatureMin,
'sunrise': sunrise,
'sunset': sunset,
'precipitationSum': precipitationSum,
'precipitationProbabilityMax': precipitationProbabilityMax,
'windspeed10MMax': windspeed10MMax,
'windgusts10MMax': windgusts10MMax,
'uvIndexMax': uvIndexMax,
'rainSum': rainSum,
'winddirection10MDominant': winddirection10MDominant,
'timezone': timezone,
'timestamp': timestamp,
};
'id': id,
'time': time,
'weathercode': weathercode,
'temperature2M': temperature2M,
'apparentTemperature': apparentTemperature,
'relativehumidity2M': relativehumidity2M,
'precipitation': precipitation,
'rain': rain,
'surfacePressure': surfacePressure,
'visibility': visibility,
'evapotranspiration': evapotranspiration,
'windspeed10M': windspeed10M,
'winddirection10M': winddirection10M,
'windgusts10M': windgusts10M,
'cloudcover': cloudcover,
'uvIndex': uvIndex,
'dewpoint2M': dewpoint2M,
'precipitationProbability': precipitationProbability,
'shortwaveRadiation': shortwaveRadiation,
'timeDaily': timeDaily,
'weathercodeDaily': weathercodeDaily,
'temperature2MMax': temperature2MMax,
'temperature2MMin': temperature2MMin,
'apparentTemperatureMax': apparentTemperatureMax,
'apparentTemperatureMin': apparentTemperatureMin,
'sunrise': sunrise,
'sunset': sunset,
'precipitationSum': precipitationSum,
'precipitationProbabilityMax': precipitationProbabilityMax,
'windspeed10MMax': windspeed10MMax,
'windgusts10MMax': windgusts10MMax,
'uvIndexMax': uvIndexMax,
'rainSum': rainSum,
'winddirection10MDominant': winddirection10MDominant,
'timezone': timezone,
'timestamp': timestamp,
};
}
@collection
@ -147,12 +152,15 @@ class LocationCache {
String? city;
String? district;
LocationCache({
this.lat,
this.lon,
this.city,
this.district,
});
LocationCache({this.lat, this.lon, this.city, this.district});
Map<String, dynamic> toJson() => {
'id': id,
'lat': lat,
'lon': lon,
'city': city,
'district': district,
};
}
@collection
@ -243,55 +251,57 @@ class WeatherCard {
});
Map<String, dynamic> toJson() => {
'id': id,
'time': time,
'weathercode': weathercode,
'temperature2M': temperature2M,
'apparentTemperature': apparentTemperature,
'relativehumidity2M': relativehumidity2M,
'precipitation': precipitation,
'rain': rain,
'surfacePressure': surfacePressure,
'visibility': visibility,
'evapotranspiration': evapotranspiration,
'windspeed10M': windspeed10M,
'winddirection10M': winddirection10M,
'windgusts10M': windgusts10M,
'cloudcover': cloudcover,
'uvIndex': uvIndex,
'dewpoint2M': dewpoint2M,
'precipitationProbability': precipitationProbability,
'shortwaveRadiation': shortwaveRadiation,
'timeDaily': timeDaily,
'weathercodeDaily': weathercodeDaily,
'temperature2MMax': temperature2MMax,
'temperature2MMin': temperature2MMin,
'apparentTemperatureMax': apparentTemperatureMax,
'apparentTemperatureMin': apparentTemperatureMin,
'sunrise': sunrise,
'sunset': sunset,
'precipitationSum': precipitationSum,
'precipitationProbabilityMax': precipitationProbabilityMax,
'windspeed10MMax': windspeed10MMax,
'windgusts10MMax': windgusts10MMax,
'uvIndexMax': uvIndexMax,
'rainSum': rainSum,
'winddirection10MDominant': winddirection10MDominant,
'timezone': timezone,
'timestamp': timestamp,
'lat': lat,
'lon': lon,
'city': city,
'district': district,
'index': index,
};
'id': id,
'time': time,
'weathercode': weathercode,
'temperature2M': temperature2M,
'apparentTemperature': apparentTemperature,
'relativehumidity2M': relativehumidity2M,
'precipitation': precipitation,
'rain': rain,
'surfacePressure': surfacePressure,
'visibility': visibility,
'evapotranspiration': evapotranspiration,
'windspeed10M': windspeed10M,
'winddirection10M': winddirection10M,
'windgusts10M': windgusts10M,
'cloudcover': cloudcover,
'uvIndex': uvIndex,
'dewpoint2M': dewpoint2M,
'precipitationProbability': precipitationProbability,
'shortwaveRadiation': shortwaveRadiation,
'timeDaily': timeDaily,
'weathercodeDaily': weathercodeDaily,
'temperature2MMax': temperature2MMax,
'temperature2MMin': temperature2MMin,
'apparentTemperatureMax': apparentTemperatureMax,
'apparentTemperatureMin': apparentTemperatureMin,
'sunrise': sunrise,
'sunset': sunset,
'precipitationSum': precipitationSum,
'precipitationProbabilityMax': precipitationProbabilityMax,
'windspeed10MMax': windspeed10MMax,
'windgusts10MMax': windgusts10MMax,
'uvIndexMax': uvIndexMax,
'rainSum': rainSum,
'winddirection10MDominant': winddirection10MDominant,
'timezone': timezone,
'timestamp': timestamp,
'lat': lat,
'lon': lon,
'city': city,
'district': district,
'index': index,
};
factory WeatherCard.fromJson(Map<String, dynamic> json) {
return WeatherCard(
time: List<String>.from(json['time'] ?? []),
weathercode: List<int>.from(json['weathercode'] ?? []),
temperature2M: List<double>.from(json['temperature2M'] ?? []),
apparentTemperature: List<double?>.from(json['apparentTemperature'] ?? []),
apparentTemperature: List<double?>.from(
json['apparentTemperature'] ?? [],
),
relativehumidity2M: List<int?>.from(json['relativehumidity2M'] ?? []),
precipitation: List<double>.from(json['precipitation'] ?? []),
rain: List<double?>.from(json['rain'] ?? []),
@ -304,21 +314,31 @@ class WeatherCard {
cloudcover: List<int?>.from(json['cloudcover'] ?? []),
uvIndex: List<double?>.from(json['uvIndex'] ?? []),
dewpoint2M: List<double?>.from(json['dewpoint2M'] ?? []),
precipitationProbability: List<int?>.from(json['precipitationProbability'] ?? []),
precipitationProbability: List<int?>.from(
json['precipitationProbability'] ?? [],
),
shortwaveRadiation: List<double?>.from(json['shortwaveRadiation'] ?? []),
timeDaily: List<DateTime>.from(json['timeDaily'] ?? []),
weathercodeDaily: List<int?>.from(json['weathercodeDaily'] ?? []),
temperature2MMax: List<double?>.from(json['temperature2MMax'] ?? []),
temperature2MMin: List<double?>.from(json['temperature2MMin'] ?? []),
apparentTemperatureMax: List<double?>.from(json['apparentTemperatureMax'] ?? []),
apparentTemperatureMin: List<double?>.from(json['apparentTemperatureMin'] ?? []),
apparentTemperatureMax: List<double?>.from(
json['apparentTemperatureMax'] ?? [],
),
apparentTemperatureMin: List<double?>.from(
json['apparentTemperatureMin'] ?? [],
),
windspeed10MMax: List<double?>.from(json['windspeed10MMax'] ?? []),
windgusts10MMax: List<double?>.from(json['windgusts10MMax'] ?? []),
uvIndexMax: List<double?>.from(json['uvIndexMax'] ?? []),
rainSum: List<double?>.from(json['rainSum'] ?? []),
winddirection10MDominant: List<int?>.from(json['winddirection10MDominant'] ?? []),
winddirection10MDominant: List<int?>.from(
json['winddirection10MDominant'] ?? [],
),
precipitationSum: List<double?>.from(json['precipitationSum'] ?? []),
precipitationProbabilityMax: List<int?>.from(json['precipitationProbabilityMax'] ?? []),
precipitationProbabilityMax: List<int?>.from(
json['precipitationProbabilityMax'] ?? [],
),
sunrise: List<String>.from(json['sunrise'] ?? []),
sunset: List<String>.from(json['sunset'] ?? []),
lat: json['lat'],

633
lib/app/data/weather.g.dart → lib/app/data/db.g.dart Normal file → Executable file
View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'weather.dart';
part of 'db.dart';
// **************************************************************************
// IsarCollectionGenerator
@ -27,70 +27,95 @@ const SettingsSchema = CollectionSchema(
name: r'degrees',
type: IsarType.string,
),
r'language': PropertySchema(
r'hideMap': PropertySchema(
id: 2,
name: r'hideMap',
type: IsarType.bool,
),
r'language': PropertySchema(
id: 3,
name: r'language',
type: IsarType.string,
),
r'largeElement': PropertySchema(
id: 4,
name: r'largeElement',
type: IsarType.bool,
),
r'location': PropertySchema(
id: 3,
id: 5,
name: r'location',
type: IsarType.bool,
),
r'materialColor': PropertySchema(
id: 4,
id: 6,
name: r'materialColor',
type: IsarType.bool,
),
r'measurements': PropertySchema(
id: 5,
id: 7,
name: r'measurements',
type: IsarType.string,
),
r'notifications': PropertySchema(
id: 6,
id: 8,
name: r'notifications',
type: IsarType.bool,
),
r'onboard': PropertySchema(
id: 7,
id: 9,
name: r'onboard',
type: IsarType.bool,
),
r'pressure': PropertySchema(
id: 10,
name: r'pressure',
type: IsarType.string,
),
r'roundDegree': PropertySchema(
id: 11,
name: r'roundDegree',
type: IsarType.bool,
),
r'theme': PropertySchema(
id: 8,
id: 12,
name: r'theme',
type: IsarType.string,
),
r'timeEnd': PropertySchema(
id: 9,
id: 13,
name: r'timeEnd',
type: IsarType.string,
),
r'timeRange': PropertySchema(
id: 10,
id: 14,
name: r'timeRange',
type: IsarType.long,
),
r'timeStart': PropertySchema(
id: 11,
id: 15,
name: r'timeStart',
type: IsarType.string,
),
r'timeformat': PropertySchema(
id: 12,
id: 16,
name: r'timeformat',
type: IsarType.string,
),
r'widgetBackgroundColor': PropertySchema(
id: 13,
id: 17,
name: r'widgetBackgroundColor',
type: IsarType.string,
),
r'widgetTextColor': PropertySchema(
id: 14,
id: 18,
name: r'widgetTextColor',
type: IsarType.string,
),
r'wind': PropertySchema(
id: 19,
name: r'wind',
type: IsarType.string,
)
},
estimateSize: _settingsEstimateSize,
@ -104,7 +129,7 @@ const SettingsSchema = CollectionSchema(
getId: _settingsGetId,
getLinks: _settingsGetLinks,
attach: _settingsAttach,
version: '3.1.0+1',
version: '3.1.8',
);
int _settingsEstimateSize(
@ -121,6 +146,7 @@ int _settingsEstimateSize(
}
}
bytesCount += 3 + object.measurements.length * 3;
bytesCount += 3 + object.pressure.length * 3;
{
final value = object.theme;
if (value != null) {
@ -152,6 +178,7 @@ int _settingsEstimateSize(
bytesCount += 3 + value.length * 3;
}
}
bytesCount += 3 + object.wind.length * 3;
return bytesCount;
}
@ -163,19 +190,24 @@ void _settingsSerialize(
) {
writer.writeBool(offsets[0], object.amoledTheme);
writer.writeString(offsets[1], object.degrees);
writer.writeString(offsets[2], object.language);
writer.writeBool(offsets[3], object.location);
writer.writeBool(offsets[4], object.materialColor);
writer.writeString(offsets[5], object.measurements);
writer.writeBool(offsets[6], object.notifications);
writer.writeBool(offsets[7], object.onboard);
writer.writeString(offsets[8], object.theme);
writer.writeString(offsets[9], object.timeEnd);
writer.writeLong(offsets[10], object.timeRange);
writer.writeString(offsets[11], object.timeStart);
writer.writeString(offsets[12], object.timeformat);
writer.writeString(offsets[13], object.widgetBackgroundColor);
writer.writeString(offsets[14], object.widgetTextColor);
writer.writeBool(offsets[2], object.hideMap);
writer.writeString(offsets[3], object.language);
writer.writeBool(offsets[4], object.largeElement);
writer.writeBool(offsets[5], object.location);
writer.writeBool(offsets[6], object.materialColor);
writer.writeString(offsets[7], object.measurements);
writer.writeBool(offsets[8], object.notifications);
writer.writeBool(offsets[9], object.onboard);
writer.writeString(offsets[10], object.pressure);
writer.writeBool(offsets[11], object.roundDegree);
writer.writeString(offsets[12], object.theme);
writer.writeString(offsets[13], object.timeEnd);
writer.writeLong(offsets[14], object.timeRange);
writer.writeString(offsets[15], object.timeStart);
writer.writeString(offsets[16], object.timeformat);
writer.writeString(offsets[17], object.widgetBackgroundColor);
writer.writeString(offsets[18], object.widgetTextColor);
writer.writeString(offsets[19], object.wind);
}
Settings _settingsDeserialize(
@ -187,20 +219,25 @@ Settings _settingsDeserialize(
final object = Settings();
object.amoledTheme = reader.readBool(offsets[0]);
object.degrees = reader.readString(offsets[1]);
object.hideMap = reader.readBool(offsets[2]);
object.id = id;
object.language = reader.readStringOrNull(offsets[2]);
object.location = reader.readBool(offsets[3]);
object.materialColor = reader.readBool(offsets[4]);
object.measurements = reader.readString(offsets[5]);
object.notifications = reader.readBool(offsets[6]);
object.onboard = reader.readBool(offsets[7]);
object.theme = reader.readStringOrNull(offsets[8]);
object.timeEnd = reader.readStringOrNull(offsets[9]);
object.timeRange = reader.readLongOrNull(offsets[10]);
object.timeStart = reader.readStringOrNull(offsets[11]);
object.timeformat = reader.readString(offsets[12]);
object.widgetBackgroundColor = reader.readStringOrNull(offsets[13]);
object.widgetTextColor = reader.readStringOrNull(offsets[14]);
object.language = reader.readStringOrNull(offsets[3]);
object.largeElement = reader.readBool(offsets[4]);
object.location = reader.readBool(offsets[5]);
object.materialColor = reader.readBool(offsets[6]);
object.measurements = reader.readString(offsets[7]);
object.notifications = reader.readBool(offsets[8]);
object.onboard = reader.readBool(offsets[9]);
object.pressure = reader.readString(offsets[10]);
object.roundDegree = reader.readBool(offsets[11]);
object.theme = reader.readStringOrNull(offsets[12]);
object.timeEnd = reader.readStringOrNull(offsets[13]);
object.timeRange = reader.readLongOrNull(offsets[14]);
object.timeStart = reader.readStringOrNull(offsets[15]);
object.timeformat = reader.readString(offsets[16]);
object.widgetBackgroundColor = reader.readStringOrNull(offsets[17]);
object.widgetTextColor = reader.readStringOrNull(offsets[18]);
object.wind = reader.readString(offsets[19]);
return object;
}
@ -216,31 +253,41 @@ P _settingsDeserializeProp<P>(
case 1:
return (reader.readString(offset)) as P;
case 2:
return (reader.readStringOrNull(offset)) as P;
case 3:
return (reader.readBool(offset)) as P;
case 3:
return (reader.readStringOrNull(offset)) as P;
case 4:
return (reader.readBool(offset)) as P;
case 5:
return (reader.readString(offset)) as P;
return (reader.readBool(offset)) as P;
case 6:
return (reader.readBool(offset)) as P;
case 7:
return (reader.readBool(offset)) as P;
case 8:
return (reader.readStringOrNull(offset)) as P;
case 9:
return (reader.readStringOrNull(offset)) as P;
case 10:
return (reader.readLongOrNull(offset)) as P;
case 11:
return (reader.readStringOrNull(offset)) as P;
case 12:
return (reader.readString(offset)) as P;
case 8:
return (reader.readBool(offset)) as P;
case 9:
return (reader.readBool(offset)) as P;
case 10:
return (reader.readString(offset)) as P;
case 11:
return (reader.readBool(offset)) as P;
case 12:
return (reader.readStringOrNull(offset)) as P;
case 13:
return (reader.readStringOrNull(offset)) as P;
case 14:
return (reader.readLongOrNull(offset)) as P;
case 15:
return (reader.readStringOrNull(offset)) as P;
case 16:
return (reader.readString(offset)) as P;
case 17:
return (reader.readStringOrNull(offset)) as P;
case 18:
return (reader.readStringOrNull(offset)) as P;
case 19:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@ -475,6 +522,16 @@ extension SettingsQueryFilter
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> hideMapEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'hideMap',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
@ -673,6 +730,16 @@ extension SettingsQueryFilter
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> largeElementEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'largeElement',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> locationEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
@ -847,6 +914,146 @@ extension SettingsQueryFilter
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'pressure',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'pressure',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'pressure',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'pressure',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'pressure',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'pressure',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureContains(
String value,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'pressure',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureMatches(
String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'pressure',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'pressure',
value: '',
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> pressureIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'pressure',
value: '',
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> roundDegreeEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'roundDegree',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> themeIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@ -1794,6 +2001,136 @@ extension SettingsQueryFilter
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'wind',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'wind',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'wind',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'wind',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'wind',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'wind',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windContains(
String value,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'wind',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windMatches(
String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'wind',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'wind',
value: '',
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> windIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'wind',
value: '',
));
});
}
}
extension SettingsQueryObject
@ -1827,6 +2164,18 @@ extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByHideMap() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hideMap', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByHideMapDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hideMap', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByLanguage() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'language', Sort.asc);
@ -1839,6 +2188,18 @@ extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByLargeElement() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'largeElement', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByLargeElementDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'largeElement', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByLocation() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'location', Sort.asc);
@ -1899,6 +2260,30 @@ extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByPressure() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'pressure', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByPressureDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'pressure', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByRoundDegree() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'roundDegree', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByRoundDegreeDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'roundDegree', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByTheme() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'theme', Sort.asc);
@ -1983,6 +2368,18 @@ extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
return query.addSortBy(r'widgetTextColor', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByWind() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'wind', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByWindDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'wind', Sort.desc);
});
}
}
extension SettingsQuerySortThenBy
@ -2011,6 +2408,18 @@ extension SettingsQuerySortThenBy
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByHideMap() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hideMap', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByHideMapDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'hideMap', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
@ -2035,6 +2444,18 @@ extension SettingsQuerySortThenBy
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByLargeElement() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'largeElement', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByLargeElementDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'largeElement', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByLocation() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'location', Sort.asc);
@ -2095,6 +2516,30 @@ extension SettingsQuerySortThenBy
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByPressure() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'pressure', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByPressureDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'pressure', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByRoundDegree() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'roundDegree', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByRoundDegreeDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'roundDegree', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByTheme() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'theme', Sort.asc);
@ -2179,6 +2624,18 @@ extension SettingsQuerySortThenBy
return query.addSortBy(r'widgetTextColor', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByWind() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'wind', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByWindDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'wind', Sort.desc);
});
}
}
extension SettingsQueryWhereDistinct
@ -2196,6 +2653,12 @@ extension SettingsQueryWhereDistinct
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByHideMap() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'hideMap');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByLanguage(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@ -2203,6 +2666,12 @@ extension SettingsQueryWhereDistinct
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByLargeElement() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'largeElement');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByLocation() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'location');
@ -2234,6 +2703,19 @@ extension SettingsQueryWhereDistinct
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByPressure(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'pressure', caseSensitive: caseSensitive);
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByRoundDegree() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'roundDegree');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByTheme(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@ -2283,6 +2765,13 @@ extension SettingsQueryWhereDistinct
caseSensitive: caseSensitive);
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByWind(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'wind', caseSensitive: caseSensitive);
});
}
}
extension SettingsQueryProperty
@ -2305,12 +2794,24 @@ extension SettingsQueryProperty
});
}
QueryBuilder<Settings, bool, QQueryOperations> hideMapProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'hideMap');
});
}
QueryBuilder<Settings, String?, QQueryOperations> languageProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'language');
});
}
QueryBuilder<Settings, bool, QQueryOperations> largeElementProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'largeElement');
});
}
QueryBuilder<Settings, bool, QQueryOperations> locationProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'location');
@ -2341,6 +2842,18 @@ extension SettingsQueryProperty
});
}
QueryBuilder<Settings, String, QQueryOperations> pressureProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'pressure');
});
}
QueryBuilder<Settings, bool, QQueryOperations> roundDegreeProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'roundDegree');
});
}
QueryBuilder<Settings, String?, QQueryOperations> themeProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'theme');
@ -2383,6 +2896,12 @@ extension SettingsQueryProperty
return query.addPropertyName(r'widgetTextColor');
});
}
QueryBuilder<Settings, String, QQueryOperations> windProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'wind');
});
}
}
// coverage:ignore-file
@ -2583,7 +3102,7 @@ const MainWeatherCacheSchema = CollectionSchema(
getId: _mainWeatherCacheGetId,
getLinks: _mainWeatherCacheGetLinks,
attach: _mainWeatherCacheAttach,
version: '3.1.0+1',
version: '3.1.8',
);
int _mainWeatherCacheEstimateSize(
@ -10306,7 +10825,7 @@ const LocationCacheSchema = CollectionSchema(
getId: _locationCacheGetId,
getLinks: _locationCacheGetLinks,
attach: _locationCacheAttach,
version: '3.1.0+1',
version: '3.1.8',
);
int _locationCacheEstimateSize(
@ -11405,7 +11924,7 @@ const WeatherCardSchema = CollectionSchema(
getId: _weatherCardGetId,
getLinks: _weatherCardGetLinks,
attach: _weatherCardAttach,
version: '3.1.0+1',
version: '3.1.8',
);
int _weatherCardEstimateSize(

View file

@ -1,182 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:iconsax/iconsax.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/widgets/daily/weather_daily.dart';
import 'package:rain/app/widgets/daily/weather_more.dart';
import 'package:rain/app/widgets/desc/desc_container.dart';
import 'package:rain/app/widgets/hourly/weather_hourly.dart';
import 'package:rain/app/widgets/now/weather_now.dart';
import 'package:rain/app/widgets/sun_moon/sunset_sunrise.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class InfoWeatherCard extends StatefulWidget {
const InfoWeatherCard({
super.key,
required this.weatherCard,
});
final WeatherCard weatherCard;
@override
State<InfoWeatherCard> createState() => _InfoWeatherCardState();
}
class _InfoWeatherCardState extends State<InfoWeatherCard> {
int timeNow = 0;
int dayNow = 0;
final weatherController = Get.put(WeatherController());
final itemScrollController = ItemScrollController();
@override
void initState() {
getTime();
super.initState();
}
void getTime() {
final weatherCard = widget.weatherCard;
timeNow = weatherController.getTime(weatherCard.time!, weatherCard.timezone!);
dayNow = weatherController.getDay(weatherCard.timeDaily!, weatherCard.timezone!);
Future.delayed(const Duration(milliseconds: 30), () {
itemScrollController.scrollTo(
index: timeNow,
duration: const Duration(seconds: 2),
curve: Curves.easeInOutCubic,
);
});
}
@override
Widget build(BuildContext context) {
final weatherCard = widget.weatherCard;
return RefreshIndicator(
onRefresh: () async {
await weatherController.updateCard(weatherCard);
getTime();
setState(() {});
},
child: Scaffold(
appBar: AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
leading: IconButton(
onPressed: () => Get.back(),
icon: const Icon(
Iconsax.arrow_left_1,
size: 20,
),
),
title: Text(
weatherCard.district!.isNotEmpty
? '${weatherCard.city}'
', ${weatherCard.district}'
: '${weatherCard.city}',
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ListView(
children: [
WeatherNow(
time: weatherCard.time![timeNow],
weather: weatherCard.weathercode![timeNow],
degree: weatherCard.temperature2M![timeNow],
timeDay: weatherCard.sunrise![dayNow],
timeNight: weatherCard.sunset![dayNow],
),
Card(
margin: const EdgeInsets.symmetric(vertical: 15),
child: SizedBox(
height: 136,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: ScrollablePositionedList.separated(
key: const PageStorageKey(1),
physics: const AlwaysScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, int index) {
return const VerticalDivider(
width: 10,
indent: 40,
endIndent: 40,
);
},
scrollDirection: Axis.horizontal,
itemScrollController: itemScrollController,
itemCount: weatherCard.time!.length,
itemBuilder: (ctx, i) => GestureDetector(
onTap: () {
timeNow = i;
dayNow = (i / 24).floor();
setState(() {});
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 5),
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
decoration: BoxDecoration(
color: i == timeNow ? context.theme.colorScheme.primaryContainer : Colors.transparent,
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
),
child: WeatherHourly(
time: weatherCard.time![i],
weather: weatherCard.weathercode![i],
degree: weatherCard.temperature2M![i],
timeDay: weatherCard.sunrise![(i / 24).floor()],
timeNight: weatherCard.sunset![(i / 24).floor()],
),
),
),
),
),
),
),
SunsetSunrise(
timeSunrise: weatherCard.sunrise![dayNow],
timeSunset: weatherCard.sunset![dayNow],
),
DescContainer(
humidity: weatherCard.relativehumidity2M?[timeNow],
wind: weatherCard.windspeed10M?[timeNow],
visibility: weatherCard.visibility?[timeNow],
feels: weatherCard.apparentTemperature?[timeNow],
evaporation: weatherCard.evapotranspiration?[timeNow],
precipitation: weatherCard.precipitation?[timeNow],
direction: weatherCard.winddirection10M?[timeNow],
pressure: weatherCard.surfacePressure?[timeNow],
rain: weatherCard.rain?[timeNow],
cloudcover: weatherCard.cloudcover?[timeNow],
windgusts: weatherCard.windgusts10M?[timeNow],
uvIndex: weatherCard.uvIndex?[timeNow],
dewpoint2M: weatherCard.dewpoint2M?[timeNow],
precipitationProbability: weatherCard.precipitationProbability?[timeNow],
shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow],
),
WeatherDaily(
weatherData: weatherCard,
onTap: () => Get.to(
() => WeatherMore(
weatherData: weatherCard,
),
transition: Transition.downToUp,
),
),
],
),
),
),
),
);
}
}

View file

@ -1,128 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:iconsax/iconsax.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/app/modules/cards/view/info_weather_card.dart';
import 'package:rain/app/modules/cards/widgets/weather_card_container.dart';
class ListWeatherCard extends StatefulWidget {
const ListWeatherCard({super.key});
@override
State<ListWeatherCard> createState() => _ListWeatherCardState();
}
class _ListWeatherCardState extends State<ListWeatherCard> {
final weatherController = Get.put(WeatherController());
@override
Widget build(BuildContext context) {
final textTheme = context.textTheme;
final titleMedium = textTheme.titleMedium;
return RefreshIndicator(
onRefresh: () async {
await weatherController.updateCacheCard(true);
setState(() {});
},
child: Obx(
() => weatherController.weatherCards.isEmpty
? Center(
child: SingleChildScrollView(
child: Column(
children: [
Image.asset(
'assets/images/add_weather.png',
scale: 6,
),
SizedBox(
width: Get.size.width * 0.8,
child: Text(
'noWeatherCard'.tr,
textAlign: TextAlign.center,
style: titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
),
],
),
),
)
: ReorderableListView(
onReorder: (oldIndex, newIndex) => weatherController.reorder(oldIndex, newIndex),
children: [
...weatherController.weatherCards.map(
(weatherCardList) => Dismissible(
key: ValueKey(weatherCardList),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
child: const Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(
Iconsax.trush_square,
color: Colors.red,
),
),
),
confirmDismiss: (DismissDirection direction) async {
return await showAdaptiveDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog.adaptive(
title: Text(
'deletedCardWeather'.tr,
style: textTheme.titleLarge,
),
content: Text('deletedCardWeatherQuery'.tr, style: titleMedium),
actions: [
TextButton(
onPressed: () => Get.back(result: false),
child: Text(
'cancel'.tr,
style: titleMedium?.copyWith(color: Colors.blueAccent),
),
),
TextButton(
onPressed: () => Get.back(result: true),
child: Text(
'delete'.tr,
style: titleMedium?.copyWith(color: Colors.red),
),
),
],
);
},
);
},
onDismissed: (DismissDirection direction) async {
await weatherController.deleteCardWeather(weatherCardList);
},
child: GestureDetector(
onTap: () => Get.to(
() => InfoWeatherCard(
weatherCard: weatherCardList,
),
transition: Transition.downToUp,
),
child: WeatherCardContainer(
time: weatherCardList.time!,
timeDaily: weatherCardList.timeDaily!,
timeDay: weatherCardList.sunrise!,
timeNight: weatherCardList.sunset!,
weather: weatherCardList.weathercode!,
degree: weatherCardList.temperature2M!,
district: weatherCardList.district!,
city: weatherCardList.city!,
timezone: weatherCardList.timezone!,
),
),
),
),
],
),
),
);
}
}

View file

@ -1,239 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:iconsax/iconsax.dart';
import 'package:rain/app/api/api.dart';
import 'package:rain/app/api/city.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/app/widgets/text_form.dart';
import 'package:rain/main.dart';
class CreateWeatherCard extends StatefulWidget {
const CreateWeatherCard({super.key});
@override
State<CreateWeatherCard> createState() => _CreateWeatherCardState();
}
class _CreateWeatherCardState extends State<CreateWeatherCard> {
bool isLoading = false;
final formKey = GlobalKey<FormState>();
final _focusNode = FocusNode();
final weatherController = Get.put(WeatherController());
final _controller = TextEditingController();
final _controllerLat = TextEditingController();
final _controllerLon = TextEditingController();
final _controllerCity = TextEditingController();
final _controllerDistrict = TextEditingController();
textTrim(value) {
value.text = value.text.trim();
while (value.text.contains(' ')) {
value.text = value.text.replaceAll(' ', ' ');
}
}
void fillController(selection) {
_controllerLat.text = '${selection.latitude}';
_controllerLon.text = '${selection.longitude}';
_controllerCity.text = selection.name;
_controllerDistrict.text = selection.admin1;
_controller.clear();
_focusNode.unfocus();
setState(() {});
}
@override
Widget build(BuildContext context) {
const kTextFieldElevation = 4.0;
return Form(
key: formKey,
child: SingleChildScrollView(
child: Stack(
children: [
Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(
Iconsax.close_square,
size: 18,
),
),
Text(
'create'.tr,
style: context.textTheme.titleLarge?.copyWith(
fontSize: 20,
),
textAlign: TextAlign.center,
),
IconButton(
onPressed: () async {
if (formKey.currentState!.validate()) {
textTrim(_controllerLat);
textTrim(_controllerLon);
textTrim(_controllerCity);
textTrim(_controllerDistrict);
setState(() => isLoading = true);
await weatherController.addCardWeather(
double.parse(_controllerLat.text),
double.parse(_controllerLon.text),
_controllerCity.text,
_controllerDistrict.text,
);
setState(() => isLoading = false);
Get.back();
}
},
icon: const Icon(
Iconsax.tick_square,
size: 18,
),
),
],
),
),
RawAutocomplete<Result>(
focusNode: _focusNode,
textEditingController: _controller,
fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) {
return MyTextForm(
elevation: kTextFieldElevation,
labelText: 'search'.tr,
type: TextInputType.text,
icon: const Icon(Iconsax.global_search),
controller: _controller,
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
focusNode: _focusNode,
);
},
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text.isEmpty) {
return const Iterable<Result>.empty();
}
return WeatherAPI().getCity(textEditingValue.text, locale);
},
onSelected: (Result selection) => fillController(selection),
displayStringForOption: (Result option) => '${option.name}, ${option.admin1}',
optionsViewBuilder:
(BuildContext context, AutocompleteOnSelected<Result> onSelected, Iterable<Result> options) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Align(
alignment: Alignment.topCenter,
child: Material(
borderRadius: BorderRadius.circular(20),
elevation: 4.0,
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final Result option = options.elementAt(index);
return InkWell(
onTap: () => onSelected(option),
child: ListTile(
title: Text(
'${option.name}, ${option.admin1}',
style: context.textTheme.labelLarge,
),
),
);
},
),
),
),
);
},
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerLat,
labelText: 'lat'.tr,
type: TextInputType.number,
icon: const Icon(Iconsax.location),
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
validator: (value) {
if (value == null || value.isEmpty) {
return 'validateValue'.tr;
}
double? numericValue = double.tryParse(value);
if (numericValue == null) {
return 'validateNumber'.tr;
}
if (numericValue < -90 || numericValue > 90) {
return 'validate90'.tr;
}
return null;
},
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerLon,
labelText: 'lon'.tr,
type: TextInputType.number,
icon: const Icon(Iconsax.location),
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
validator: (value) {
if (value == null || value.isEmpty) {
return 'validateValue'.tr;
}
double? numericValue = double.tryParse(value);
if (numericValue == null) {
return 'validateNumber'.tr;
}
if (numericValue < -180 || numericValue > 180) {
return 'validate180'.tr;
}
return null;
},
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerCity,
labelText: 'city'.tr,
type: TextInputType.name,
icon: const Icon(Icons.location_city_rounded),
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
validator: (value) {
if (value == null || value.isEmpty) {
return 'validateName'.tr;
}
return null;
},
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerDistrict,
labelText: 'district'.tr,
type: TextInputType.streetAddress,
icon: const Icon(Iconsax.global),
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
),
const SizedBox(height: 20),
],
),
),
if (isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
),
);
}
}

View file

@ -1,124 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/app/widgets/status/status_weather.dart';
import 'package:rain/app/widgets/status/status_data.dart';
import 'package:timezone/standalone.dart' as tz;
class WeatherCardContainer extends StatefulWidget {
const WeatherCardContainer({
super.key,
required this.time,
required this.weather,
required this.degree,
required this.district,
required this.city,
required this.timezone,
required this.timeDay,
required this.timeNight,
required this.timeDaily,
});
final List<String> time;
final List<String> timeDay;
final List<String> timeNight;
final List<DateTime> timeDaily;
final String district;
final String city;
final List<int> weather;
final List<double> degree;
final String timezone;
@override
State<WeatherCardContainer> createState() => _WeatherCardContainerState();
}
class _WeatherCardContainerState extends State<WeatherCardContainer> {
final statusWeather = StatusWeather();
final statusData = StatusData();
final weatherController = Get.put(WeatherController());
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
statusData.getDegree(widget.degree[weatherController
.getTime(widget.time, widget.timezone)]
.round()
.toInt()),
style: context.textTheme.titleLarge?.copyWith(
fontSize: 22,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 7),
Text(
statusWeather.getText(widget.weather[weatherController
.getTime(widget.time, widget.timezone)]),
style: context.textTheme.titleMedium?.copyWith(
color: Colors.grey,
fontWeight: FontWeight.w400,
),
),
],
),
const SizedBox(height: 10),
Text(
widget.district.isEmpty
? widget.city
: widget.city.isEmpty
? widget.district
: widget.city == widget.district
? widget.city
: '${widget.city}'
', ${widget.district}',
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 5),
StreamBuilder(
stream: Stream.periodic(const Duration(seconds: 1)),
builder: (context, snapshot) {
return Text(
'${'time'.tr}: ${statusData.getTimeFormatTz(tz.TZDateTime.now(tz.getLocation(widget.timezone)))}',
style: context.textTheme.titleMedium?.copyWith(
color: Colors.grey,
fontWeight: FontWeight.w400,
),
);
},
),
],
),
),
const SizedBox(width: 5),
Image.asset(
statusWeather.getImageNow(
widget.weather[
weatherController.getTime(widget.time, widget.timezone)],
widget.time[
weatherController.getTime(widget.time, widget.timezone)],
widget.timeDay[weatherController.getDay(
widget.timeDaily, widget.timezone)],
widget.timeNight[weatherController.getDay(
widget.timeDaily, widget.timezone)]),
scale: 6.5,
),
],
),
),
);
}
}

View file

@ -1,242 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:iconsax/iconsax.dart';
import 'package:isar/isar.dart';
import 'package:rain/app/api/api.dart';
import 'package:rain/app/api/city.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/modules/cards/view/list_weather_card.dart';
import 'package:rain/app/modules/cards/widgets/create_card_weather.dart';
import 'package:rain/app/modules/main/view/weather.dart';
import 'package:rain/app/modules/settings/view/settings.dart';
import 'package:rain/app/services/utils.dart';
import 'package:rain/main.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
int tabIndex = 0;
bool visible = false;
final _focusNode = FocusNode();
late TabController tabController;
final weatherController = Get.put(WeatherController());
final _controller = TextEditingController();
final pages = [
const WeatherPage(),
const ListWeatherCard(),
const SettingsPage(),
];
@override
void initState() {
getData();
tabController = TabController(
initialIndex: tabIndex,
length: pages.length,
vsync: this,
);
tabController.addListener(() {
setState(() {
tabIndex = tabController.index;
});
});
super.initState();
}
void getData() async {
await weatherController.deleteCache();
await weatherController.updateCacheCard(false);
await weatherController.setLocation();
}
void changeTabIndex(int index) {
setState(() {
tabIndex = index;
});
tabController.animateTo(tabIndex);
}
@override
Widget build(BuildContext context) {
final textTheme = context.textTheme;
final labelLarge = textTheme.labelLarge;
return DefaultTabController(
length: pages.length,
child: ScaffoldMessenger(
key: globalKey,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
leading: const Icon(
Iconsax.location,
size: 18,
),
title: visible
? RawAutocomplete<Result>(
focusNode: _focusNode,
textEditingController: _controller,
fieldViewBuilder: (_, __, ___, ____) {
return TextField(
controller: _controller,
focusNode: _focusNode,
style: labelLarge?.copyWith(fontSize: 16),
decoration: InputDecoration(
hintText: 'search'.tr,
),
);
},
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text.isEmpty) {
return const Iterable<Result>.empty();
}
return WeatherAPI()
.getCity(textEditingValue.text, locale);
},
onSelected: (Result selection) async {
await weatherController.deleteAll(true);
await weatherController.getLocation(
double.parse('${selection.latitude}'),
double.parse('${selection.longitude}'),
selection.admin1,
selection.name,
);
visible = false;
_controller.clear();
_focusNode.unfocus();
setState(() {});
},
displayStringForOption: (Result option) =>
'${option.name}, ${option.admin1}',
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<Result> onSelected,
Iterable<Result> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
borderRadius: BorderRadius.circular(20),
elevation: 4.0,
child: SizedBox(
width: 250,
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final Result option = options.elementAt(index);
return InkWell(
onTap: () => onSelected(option),
child: ListTile(
title: Text(
'${option.name}, ${option.admin1}',
style: labelLarge,
),
),
);
},
),
),
),
);
},
)
: Obx(
() {
final location = weatherController.location;
final city = location.city;
final district = location.district;
return Text(
weatherController.isLoading.isFalse
? district!.isEmpty
? '$city'
: city!.isEmpty
? district
: city == district
? city
: '$city' ', $district'
: settings.location
? 'search'.tr
: (isar.locationCaches.where().findAllSync())
.isNotEmpty
? 'loading'.tr
: 'searchCity'.tr,
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
);
},
),
actions: [
IconButton(
onPressed: () {
if (visible) {
_controller.clear();
_focusNode.unfocus();
visible = false;
} else {
visible = true;
}
setState(() {});
},
icon: Icon(
visible ? Icons.close : Iconsax.search_normal_1,
size: 18,
),
),
],
),
body: SafeArea(
child: TabBarView(
controller: tabController,
children: pages,
),
),
bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) => changeTabIndex(index),
selectedIndex: tabIndex,
destinations: [
NavigationDestination(
icon: const Icon(Iconsax.cloud_sunny),
selectedIcon: const Icon(Iconsax.cloud_sunny5),
label: 'name'.tr,
),
NavigationDestination(
icon: const Icon(Iconsax.map_1),
selectedIcon: const Icon(Iconsax.map5),
label: 'city'.tr,
),
NavigationDestination(
icon: const Icon(Iconsax.category),
selectedIcon: const Icon(Iconsax.category5),
label: 'settings'.tr,
),
],
),
floatingActionButton: tabIndex == 1
? FloatingActionButton(
onPressed: () => showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: false,
builder: (BuildContext context) =>
const CreateWeatherCard(),
),
child: const Icon(
Iconsax.add,
),
)
: null,
),
),
);
}
}

View file

@ -1,173 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/widgets/daily/weather_daily.dart';
import 'package:rain/app/widgets/daily/weather_more.dart';
import 'package:rain/app/widgets/desc/desc_container.dart';
import 'package:rain/app/widgets/hourly/weather_hourly.dart';
import 'package:rain/app/widgets/now/weather_now.dart';
import 'package:rain/app/widgets/shimmer.dart';
import 'package:rain/app/widgets/sun_moon/sunset_sunrise.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class WeatherPage extends StatefulWidget {
const WeatherPage({super.key});
@override
State<WeatherPage> createState() => _WeatherPageState();
}
class _WeatherPageState extends State<WeatherPage> {
final weatherController = Get.put(WeatherController());
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
await weatherController.deleteAll(false);
await weatherController.setLocation();
setState(() {});
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ListView(
children: [
Obx(() {
if (weatherController.isLoading.isTrue) {
return const Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MyShimmer(
hight: 350,
),
MyShimmer(
hight: 130,
edgeInsetsMargin: EdgeInsets.symmetric(vertical: 15),
),
MyShimmer(
hight: 90,
edgeInsetsMargin: EdgeInsets.only(bottom: 15),
),
MyShimmer(
hight: 400,
edgeInsetsMargin: EdgeInsets.only(bottom: 15),
),
MyShimmer(
hight: 450,
edgeInsetsMargin: EdgeInsets.only(bottom: 15),
)
],
);
}
final mainWeather = weatherController.mainWeather;
final weatherCard = WeatherCard.fromJson(mainWeather.toJson());
final hourOfDay = weatherController.hourOfDay.value;
final dayOfNow = weatherController.dayOfNow.value;
final sunrise = mainWeather.sunrise![dayOfNow];
final sunset = mainWeather.sunset![dayOfNow];
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
WeatherNow(
time: mainWeather.time![hourOfDay],
weather: mainWeather.weathercode![hourOfDay],
degree: mainWeather.temperature2M![hourOfDay],
timeDay: sunrise,
timeNight: sunset,
),
Card(
margin: const EdgeInsets.symmetric(vertical: 15),
child: SizedBox(
height: 136,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: ScrollablePositionedList.separated(
key: const PageStorageKey(0),
physics: const AlwaysScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, int index) {
return const VerticalDivider(
width: 10,
indent: 40,
endIndent: 40,
);
},
scrollDirection: Axis.horizontal,
itemScrollController: weatherController.itemScrollController,
itemCount: mainWeather.time!.length,
itemBuilder: (ctx, i) {
final i24 = (i / 24).floor();
return GestureDetector(
onTap: () {
weatherController.hourOfDay.value = i;
weatherController.dayOfNow.value = i24;
setState(() {});
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 5),
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
decoration: BoxDecoration(
color:
i == hourOfDay ? context.theme.colorScheme.primaryContainer : Colors.transparent,
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
),
child: WeatherHourly(
time: mainWeather.time![i],
weather: mainWeather.weathercode![i],
degree: mainWeather.temperature2M![i],
timeDay: mainWeather.sunrise![i24],
timeNight: mainWeather.sunset![i24],
),
),
);
},
),
),
),
),
SunsetSunrise(
timeSunrise: sunrise,
timeSunset: sunset,
),
DescContainer(
humidity: mainWeather.relativehumidity2M?[hourOfDay],
wind: mainWeather.windspeed10M?[hourOfDay],
visibility: mainWeather.visibility?[hourOfDay],
feels: mainWeather.apparentTemperature?[hourOfDay],
evaporation: mainWeather.evapotranspiration?[hourOfDay],
precipitation: mainWeather.precipitation?[hourOfDay],
direction: mainWeather.winddirection10M?[hourOfDay],
pressure: mainWeather.surfacePressure?[hourOfDay],
rain: mainWeather.rain?[hourOfDay],
cloudcover: mainWeather.cloudcover?[hourOfDay],
windgusts: mainWeather.windgusts10M?[hourOfDay],
uvIndex: mainWeather.uvIndex?[hourOfDay],
dewpoint2M: mainWeather.dewpoint2M?[hourOfDay],
precipitationProbability: mainWeather.precipitationProbability?[hourOfDay],
shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay],
),
WeatherDaily(
weatherData: weatherCard,
onTap: () => Get.to(
() => WeatherMore(
weatherData: weatherCard,
),
transition: Transition.downToUp,
),
)
],
);
}),
],
),
),
);
}
}

View file

@ -1,71 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/modules/home.dart';
import 'package:rain/app/widgets/button.dart';
import 'package:rain/main.dart';
class OnboardingPage extends StatefulWidget {
const OnboardingPage({super.key});
@override
State<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends State<OnboardingPage> {
@override
Widget build(BuildContext context) {
final textTheme = context.textTheme;
return Scaffold(
body: SafeArea(
child: Column(
children: [
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/weather.png',
scale: 7,
),
const SizedBox(height: 15),
Text(
'Rain - ${'name'.tr}',
style: textTheme.titleLarge?.copyWith(
fontSize: 32,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 5),
SizedBox(
width: 300,
child: Text(
'description'.tr,
style: textTheme.labelMedium?.copyWith(fontSize: 14),
textAlign: TextAlign.center,
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: MyTextButton(
buttonName: 'start'.tr,
onPressed: () async {
settings.onboard = true;
isar.writeTxnSync(() => isar.settings.putSync(settings));
Get.off(
() => const HomePage(),
transition: Transition.downToUp,
);
},
),
),
],
),
),
);
}
}

View file

@ -1,806 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:iconsax/iconsax.dart';
import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/modules/settings/widgets/setting_card.dart';
import 'package:rain/main.dart';
import 'package:rain/theme/theme_controller.dart';
import 'package:rain/utils/color_converter.dart';
import 'package:url_launcher/url_launcher.dart';
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
final themeController = Get.put(ThemeController());
final weatherController = Get.put(WeatherController());
String? appVersion;
String? colorBackground;
String? colorText;
Future<void> infoVersion() async {
final packageInfo = await PackageInfo.fromPlatform();
setState(() {
appVersion = packageInfo.version;
});
}
void urlLauncher(String uri) async {
final Uri url = Uri.parse(uri);
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
throw Exception('Could not launch $url');
}
}
@override
void initState() {
infoVersion();
super.initState();
}
updateLanguage(Locale locale) {
settings.language = '$locale';
isar.writeTxnSync(() => isar.settings.putSync(settings));
Get.updateLocale(locale);
Get.back();
}
@override
Widget build(BuildContext context) {
final theme = context.theme;
final textTheme = context.textTheme;
final titleMedium = textTheme.titleMedium;
final titleLarge = textTheme.titleLarge;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SettingCard(
icon: const Icon(Iconsax.brush_1),
text: 'appearance'.tr,
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, setState) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'appearance'.tr,
style: titleLarge?.copyWith(
fontSize: 20,
),
),
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.moon),
text: 'theme'.tr,
dropdown: true,
dropdownName: settings.theme?.tr,
dropdownList: <String>['system'.tr, 'dark'.tr, 'light'.tr],
dropdownCange: (String? newValue) {
final newThemeMode = newValue?.tr;
final darkTheme = 'dark'.tr;
final systemTheme = 'system'.tr;
ThemeMode themeMode = newThemeMode == systemTheme
? ThemeMode.system
: newThemeMode == darkTheme
? ThemeMode.dark
: ThemeMode.light;
String theme = newThemeMode == systemTheme
? 'system'
: newThemeMode == darkTheme
? 'dark'
: 'light';
themeController.saveTheme(theme);
themeController.changeThemeMode(themeMode);
setState(() {});
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.mobile),
text: 'amoledTheme'.tr,
switcher: true,
value: settings.amoledTheme,
onChange: (value) {
themeController.saveOledTheme(value);
MyApp.updateAppState(context, newAmoledTheme: value);
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.colorfilter),
text: 'materialColor'.tr,
switcher: true,
value: settings.materialColor,
onChange: (value) {
themeController.saveMaterialTheme(value);
MyApp.updateAppState(context, newMaterialColor: value);
},
),
const SizedBox(height: 10),
],
),
);
},
);
},
);
},
),
SettingCard(
icon: const Icon(Iconsax.code),
text: 'functions'.tr,
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, setState) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'functions'.tr,
style: titleLarge?.copyWith(
fontSize: 20,
),
),
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.map_1),
text: 'location'.tr,
switcher: true,
value: settings.location,
onChange: (value) async {
if (value) {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
if (!mounted) return;
await showAdaptiveDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog.adaptive(
title: Text(
'location'.tr,
style: titleLarge,
),
content: Text('no_location'.tr, style: titleMedium),
actions: [
TextButton(
onPressed: () => Get.back(result: false),
child: Text(
'cancel'.tr,
style: titleMedium?.copyWith(color: Colors.blueAccent),
),
),
TextButton(
onPressed: () {
Geolocator.openLocationSettings();
Get.back(result: true);
},
child: Text(
'settings'.tr,
style: titleMedium?.copyWith(color: Colors.green),
),
),
],
);
},
);
return;
}
weatherController.getCurrentLocation();
}
isar.writeTxnSync(() {
settings.location = value;
isar.settings.putSync(settings);
});
setState(() {});
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.notification_1),
text: 'notifications'.tr,
switcher: true,
value: settings.notifications,
onChange: (value) async {
final resultExact = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.requestExactAlarmsPermission();
final result = Platform.isIOS
? await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions()
: await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission();
if (result != null && resultExact != null) {
isar.writeTxnSync(() {
settings.notifications = value;
isar.settings.putSync(settings);
});
if (value) {
weatherController.notlification(weatherController.mainWeather);
} else {
flutterLocalNotificationsPlugin.cancelAll();
}
setState(() {});
}
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.notification_status),
text: 'timeRange'.tr,
dropdown: true,
dropdownName: '$timeRange',
dropdownList: const <String>[
'1',
'2',
'3',
'4',
'5',
],
dropdownCange: (String? newValue) {
isar.writeTxnSync(() {
settings.timeRange = int.parse(newValue!);
isar.settings.putSync(settings);
});
MyApp.updateAppState(context, newTimeRange: int.parse(newValue!));
if (settings.notifications) {
flutterLocalNotificationsPlugin.cancelAll();
weatherController.notlification(weatherController.mainWeather);
}
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.timer_start),
text: 'timeStart'.tr,
info: true,
infoSettings: true,
infoWidget: _TextInfo(
info: settings.timeformat == '12'
? DateFormat.jm().format(
DateFormat.Hm().parse(weatherController.timeConvert(timeStart).format(context)))
: DateFormat.Hm().format(DateFormat.Hm()
.parse(weatherController.timeConvert(timeStart).format(context))),
),
onPressed: () async {
final TimeOfDay? timeStartPicker = await showTimePicker(
context: context,
initialTime: weatherController.timeConvert(timeStart),
builder: (context, child) {
final Widget mediaQueryWrapper = MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: settings.timeformat == '12' ? false : true,
),
child: child!,
);
return mediaQueryWrapper;
},
);
if (timeStartPicker != null) {
isar.writeTxnSync(() {
settings.timeStart = timeStartPicker.format(context);
isar.settings.putSync(settings);
});
if (!mounted) return;
MyApp.updateAppState(context, newTimeStart: timeStartPicker.format(context));
if (settings.notifications) {
flutterLocalNotificationsPlugin.cancelAll();
weatherController.notlification(weatherController.mainWeather);
}
}
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.timer_pause),
text: 'timeEnd'.tr,
info: true,
infoSettings: true,
infoWidget: _TextInfo(
info: settings.timeformat == '12'
? DateFormat.jm().format(
DateFormat.Hm().parse(weatherController.timeConvert(timeEnd).format(context)))
: DateFormat.Hm().format(
DateFormat.Hm().parse(weatherController.timeConvert(timeEnd).format(context))),
),
onPressed: () async {
final TimeOfDay? timeEndPicker = await showTimePicker(
context: context,
initialTime: weatherController.timeConvert(timeEnd),
builder: (context, child) {
final Widget mediaQueryWrapper = MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: settings.timeformat == '12' ? false : true,
),
child: child!,
);
return mediaQueryWrapper;
},
);
if (timeEndPicker != null) {
isar.writeTxnSync(() {
settings.timeEnd = timeEndPicker.format(context);
isar.settings.putSync(settings);
});
if (!mounted) return;
MyApp.updateAppState(context, newTimeEnd: timeEndPicker.format(context));
if (settings.notifications) {
flutterLocalNotificationsPlugin.cancelAll();
weatherController.notlification(weatherController.mainWeather);
}
}
},
),
const SizedBox(height: 10),
],
),
);
},
);
},
);
},
),
SettingCard(
icon: const Icon(Iconsax.d_square),
text: 'data'.tr,
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, setState) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'data'.tr,
style: titleLarge?.copyWith(
fontSize: 20,
),
),
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.sun_1),
text: 'degrees'.tr,
dropdown: true,
dropdownName: settings.degrees.tr,
dropdownList: <String>['celsius'.tr, 'fahrenheit'.tr],
dropdownCange: (String? newValue) async {
isar.writeTxnSync(() {
settings.degrees = newValue == 'celsius'.tr ? 'celsius' : 'fahrenheit';
isar.settings.putSync(settings);
});
await weatherController.deleteAll(false);
await weatherController.setLocation();
await weatherController.updateCacheCard(true);
setState(() {});
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.rulerpen),
text: 'measurements'.tr,
dropdown: true,
dropdownName: settings.measurements.tr,
dropdownList: <String>['metric'.tr, 'imperial'.tr],
dropdownCange: (String? newValue) async {
isar.writeTxnSync(() {
settings.measurements = newValue == 'metric'.tr ? 'metric' : 'imperial';
isar.settings.putSync(settings);
});
await weatherController.deleteAll(false);
await weatherController.setLocation();
await weatherController.updateCacheCard(true);
setState(() {});
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.clock),
text: 'timeformat'.tr,
dropdown: true,
dropdownName: settings.timeformat.tr,
dropdownList: <String>['12'.tr, '24'.tr],
dropdownCange: (String? newValue) {
isar.writeTxnSync(() {
settings.timeformat = newValue == '12'.tr ? '12' : '24';
isar.settings.putSync(settings);
});
setState(() {});
},
),
const SizedBox(height: 10),
],
),
);
},
);
},
);
},
),
SettingCard(
icon: const Icon(Iconsax.setting_3),
text: 'widget'.tr,
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, setState) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'widget'.tr,
style: titleLarge?.copyWith(
fontSize: 20,
),
),
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.bucket_square),
text: 'widgetBackground'.tr,
info: true,
infoWidget: CircleAvatar(
backgroundColor: theme.indicatorColor,
radius: 11,
child: CircleAvatar(
backgroundColor: widgetBackgroundColor.isEmpty
? theme.primaryColor
: HexColor.fromHex(widgetBackgroundColor),
radius: 10,
),
),
onPressed: () {
colorBackground = null;
showDialog(
context: context,
builder: (context) => Dialog(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'widgetBackground'.tr,
style: context.textTheme.titleMedium?.copyWith(fontSize: 18),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Theme(
data: theme.copyWith(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
child: ColorPicker(
pickerColor: widgetBackgroundColor.isEmpty
? theme.primaryColor
: HexColor.fromHex(widgetBackgroundColor),
onColorChanged: (pickedColor) {
colorBackground = pickedColor.toHex();
},
hexInputBar: true,
labelTypes: const [],
pickerAreaHeightPercent: 0.7,
pickerAreaBorderRadius: BorderRadius.circular(20),
),
),
),
IconButton(
icon: const Icon(
Iconsax.tick_square,
),
onPressed: () {
if (colorBackground == null) {
return;
}
weatherController.updateWidgetBackgroundColor(colorBackground!);
MyApp.updateAppState(context, newWidgetBackgroundColor: colorBackground);
Get.back();
},
),
],
),
),
),
);
},
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.text_block),
text: 'widgetText'.tr,
info: true,
infoWidget: CircleAvatar(
backgroundColor: theme.indicatorColor,
radius: 11,
child: CircleAvatar(
backgroundColor:
widgetTextColor.isEmpty ? theme.primaryColor : HexColor.fromHex(widgetTextColor),
radius: 10,
),
),
onPressed: () {
colorText = null;
showDialog(
context: context,
builder: (context) => Dialog(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'widgetText'.tr,
style: context.textTheme.titleMedium?.copyWith(fontSize: 18),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Theme(
data: theme.copyWith(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
child: ColorPicker(
pickerColor: widgetTextColor.isEmpty
? theme.primaryColor
: HexColor.fromHex(widgetTextColor),
onColorChanged: (pickedColor) {
colorText = pickedColor.toHex();
},
hexInputBar: true,
labelTypes: const [],
pickerAreaHeightPercent: 0.7,
pickerAreaBorderRadius: BorderRadius.circular(20),
),
),
),
IconButton(
icon: const Icon(
Iconsax.tick_square,
),
onPressed: () {
if (colorText == null) return;
weatherController.updateWidgetTextColor(colorText!);
MyApp.updateAppState(context, newWidgetTextColor: colorText);
Get.back();
},
),
],
),
),
),
);
},
),
const SizedBox(height: 10),
],
),
);
},
);
},
);
},
),
SettingCard(
icon: const Icon(Iconsax.language_square),
text: 'language'.tr,
info: true,
infoSettings: true,
infoWidget: _TextInfo(
info: appLanguages.firstWhere((element) => (element['locale'] == locale),
orElse: () => appLanguages.first)['name'],
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, setState) {
return ListView(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'language'.tr,
style: titleLarge?.copyWith(
fontSize: 20,
),
textAlign: TextAlign.center,
),
),
ListView.builder(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
itemCount: appLanguages.length,
itemBuilder: (context, index) {
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: ListTile(
title: Text(
appLanguages[index]['name'],
style: textTheme.labelLarge,
textAlign: TextAlign.center,
),
onTap: () {
MyApp.updateAppState(context, newLocale: appLanguages[index]['locale']);
updateLanguage(appLanguages[index]['locale']);
},
),
);
},
),
const SizedBox(height: 10),
],
);
},
);
},
);
},
),
SettingCard(
icon: const Icon(Iconsax.dollar_square),
text: 'support'.tr,
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, setState) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
'support'.tr,
style: titleLarge?.copyWith(
fontSize: 20,
),
),
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.card),
text: 'DonationAlerts',
onPressed: () => urlLauncher('https://www.donationalerts.com/r/darkmoonight'),
),
SettingCard(
elevation: 4,
icon: const Icon(Iconsax.wallet),
text: 'ЮMoney',
onPressed: () => urlLauncher('https://yoomoney.ru/to/4100117672775961'),
),
const SizedBox(height: 10),
],
),
);
},
);
},
);
},
),
SettingCard(
icon: const Icon(Iconsax.document),
text: 'license'.tr,
onPressed: () => Get.to(
LicensePage(
applicationIcon: Container(
width: 100,
height: 100,
margin: const EdgeInsets.symmetric(vertical: 5),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
image: DecorationImage(
image: AssetImage('assets/icons/icon.png'),
),
),
),
applicationName: 'Rain',
applicationVersion: appVersion,
),
transition: Transition.downToUp,
),
),
SettingCard(
icon: const Icon(Iconsax.hierarchy_square_2),
text: 'version'.tr,
info: true,
infoWidget: _TextInfo(
info: '$appVersion',
),
),
SettingCard(
icon: Image.asset(
'assets/images/github.png',
scale: 20,
),
text: '${'project'.tr} GitHub',
onPressed: () => urlLauncher('https://github.com/darkmoonight/Rain'),
),
const SizedBox(height: 10),
],
),
);
}
}
class _TextInfo extends StatelessWidget {
const _TextInfo({required this.info});
final String info;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 5),
child: Text(
info,
style: context.textTheme.bodyMedium,
overflow: TextOverflow.visible,
),
);
}
}

View file

@ -1,94 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:iconsax/iconsax.dart';
class SettingCard extends StatelessWidget {
const SettingCard({
super.key,
required this.icon,
required this.text,
this.switcher = false,
this.dropdown = false,
this.info = false,
this.infoSettings = false,
this.elevation,
this.dropdownName,
this.dropdownList,
this.dropdownCange,
this.value,
this.onPressed,
this.onChange,
this.infoWidget,
});
final Widget icon;
final String text;
final bool switcher;
final bool dropdown;
final bool info;
final bool infoSettings;
final Widget? infoWidget;
final String? dropdownName;
final List<String>? dropdownList;
final Function(String?)? dropdownCange;
final bool? value;
final Function()? onPressed;
final Function(bool)? onChange;
final double? elevation;
@override
Widget build(BuildContext context) {
return Card(
elevation: elevation ?? 1,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onTap: onPressed,
leading: icon,
title: Text(
text,
style: context.textTheme.titleMedium,
overflow: TextOverflow.visible,
),
trailing: switcher
? Transform.scale(
scale: 0.8,
child: Switch(
value: value!,
onChanged: onChange,
),
)
: dropdown
? DropdownButton<String>(
underline: Container(),
value: dropdownName,
items: dropdownList!
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: dropdownCange,
)
: info
? infoSettings
? Wrap(
children: [
infoWidget!,
const Icon(
Iconsax.arrow_right_3,
size: 18,
),
],
)
: infoWidget!
: const Icon(
Iconsax.arrow_right_3,
size: 18,
),
),
);
}
}

View file

@ -1,42 +0,0 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:rain/app/controller/controller.dart';
import 'package:rain/main.dart';
import 'package:timezone/timezone.dart' as tz;
class NotificationShow {
Future showNotification(
int id,
String title,
String body,
DateTime date,
String icon,
) async {
final imagePath = await WeatherController().getLocalImagePath(icon);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'Rain',
'DARK NIGHT',
priority: Priority.high,
importance: Importance.max,
playSound: false,
enableVibration: false,
largeIcon: FilePathAndroidBitmap(imagePath),
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
var scheduledTime = tz.TZDateTime.from(date, tz.local);
flutterLocalNotificationsPlugin.zonedSchedule(
id,
title,
body,
scheduledTime,
notificationDetails,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
payload: imagePath,
);
}
}

Some files were not shown because too many files have changed in this diff Show more