Compare commits
86 commits
Author | SHA1 | Date | |
---|---|---|---|
|
9ba80c3609 | ||
|
33be8dcdc6 | ||
|
089f3ae0b7 | ||
|
fb150421a6 | ||
|
d169f6237f | ||
|
e14dc03524 | ||
|
97aa5024b4 | ||
|
f880c5d274 | ||
|
0973fb5230 | ||
|
416f77765c | ||
|
a59c6b7338 | ||
|
0552a84e6f | ||
|
3a23dd6288 | ||
|
b7d0e8012a | ||
|
fa0921d1b0 | ||
|
7a00917ca2 | ||
|
ddf7115579 | ||
|
014a52c215 | ||
|
07142e25a7 | ||
|
55e813749a | ||
|
1d7cbbd1bf | ||
|
1ea344c69a | ||
|
5cd6e2e6c8 | ||
|
c0c15bb811 | ||
|
d5a34ea11b | ||
|
f174c0f336 | ||
|
d0791279a2 | ||
|
88a7bafffb | ||
|
27e276a092 | ||
|
383fb24881 | ||
|
8ed047a1aa | ||
|
6c7da7b28d | ||
|
b2e843c5d9 | ||
|
91e1757a20 | ||
|
8bce420296 | ||
|
2cc70f9221 | ||
|
dd57938153 | ||
|
fc246ac5a2 | ||
|
33ceb30885 | ||
|
46e1546e5b | ||
|
dd3339bc3b | ||
|
9a7858f279 | ||
|
6d0d0efe36 | ||
|
aa125fcdc1 | ||
|
0d565752d5 | ||
|
6646f657d2 | ||
|
647d620d45 | ||
|
b459a50f60 | ||
|
da64975e68 | ||
|
58dbbe52ce | ||
|
275bbba853 | ||
|
ceb27da369 | ||
|
b8600ab472 | ||
|
3cfd4f8248 | ||
|
8eae7203f0 | ||
|
42f7a2590e | ||
|
4b1536579d | ||
|
f3400916d9 | ||
|
696634c08f | ||
|
a00abd4f3a | ||
|
1342079b6b | ||
|
4d310776ce | ||
|
cb7367c04f | ||
|
51f72b4e7b | ||
|
400614a8f1 | ||
|
1e11b6b775 | ||
|
902bc45207 | ||
|
8275ec8f9e | ||
|
441a5ee680 | ||
|
a46986f050 | ||
|
fa5fbb8ce6 | ||
|
cda285ea15 | ||
|
f08c459bc1 | ||
|
1f945c4dfd | ||
|
c5b55a291b | ||
|
013493c0dd | ||
|
58373c7c9c | ||
|
504a77f6ad | ||
|
0f29d2bdad | ||
|
da07dc23c7 | ||
|
265ba143ab | ||
|
f77d09d147 | ||
|
b160fc8fa4 | ||
|
225a5240b2 | ||
|
dc3f90e346 | ||
|
e391c074c4 |
3
.gitignore
vendored
|
@ -15,6 +15,7 @@ migrate_working_dir/
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
.idea/
|
.idea/
|
||||||
|
.cxx/
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# 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
|
# 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 Studio will place build artifacts here
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
34
.metadata
|
@ -1,11 +1,11 @@
|
||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# 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:
|
version:
|
||||||
revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
revision: "2663184aa79047d0a33a14a3b607954f8fdd8730"
|
||||||
channel: stable
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
@ -13,26 +13,26 @@ project_type: app
|
||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
- platform: android
|
- platform: android
|
||||||
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
- platform: ios
|
- platform: ios
|
||||||
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
- platform: linux
|
- platform: linux
|
||||||
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
- platform: macos
|
- platform: macos
|
||||||
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
- platform: web
|
- platform: web
|
||||||
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
- platform: windows
|
- platform: windows
|
||||||
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
|
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|
21
README.md
|
@ -1,15 +1,15 @@
|
||||||
<div align='center'>
|
<div align='center'>
|
||||||
<img src='/assets/icons/icon.png' width='150'/>
|
<img src='/readme/icon.png' width='150'/>
|
||||||
<h2>🌦️ Rain</h2>
|
<h2>🌦️ Rain</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align='center'>
|
<p align='center'>
|
||||||
<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?color=abc0d3'/></a>
|
<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/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/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>
|
<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>
|
</p>
|
||||||
|
|
||||||
<p align='center'> Tired of unpredictable weather? Rain's got you covered! Get ready for any forecast. 🌦️ </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
|
### 📸 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
|
### 💰 Support Us
|
||||||
|
|
||||||
|
@ -52,7 +52,6 @@ If you find Rain valuable and worthy for future innovation, consider supporting
|
||||||
### 📥 Get Rain Now
|
### 📥 Get Rain Now
|
||||||
|
|
||||||
[](https://play.google.com/store/apps/details?id=com.yoshi.rain)
|
[](https://play.google.com/store/apps/details?id=com.yoshi.rain)
|
||||||
[](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).
|
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
|
### 👨💻 Our Contributors
|
||||||
|
|
||||||
<a href='https://github.com/darkmoonight/Rain/graphs/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>
|
</a>
|
||||||
|
|
|
@ -4,24 +4,6 @@ plugins {
|
||||||
id "dev.flutter.flutter-gradle-plugin"
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
}
|
}
|
||||||
|
|
||||||
def localProperties = new Properties()
|
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
|
||||||
if (localPropertiesFile.exists()) {
|
|
||||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
|
||||||
localProperties.load(reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
|
||||||
if (flutterVersionCode == null) {
|
|
||||||
flutterVersionCode = '1'
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
|
||||||
if (flutterVersionName == null) {
|
|
||||||
flutterVersionName = '1.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||||
if (keystorePropertiesFile.exists()) {
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
@ -29,62 +11,73 @@ if (keystorePropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'com.yoshi.rain'
|
namespace = 'com.yoshi.rain'
|
||||||
compileSdkVersion 34
|
compileSdk = 35
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion = '29.0.13113456'
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
coreLibraryDesugaringEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
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 {
|
defaultConfig {
|
||||||
applicationId "com.yoshi.rain"
|
applicationId = 'com.yoshi.rain'
|
||||||
minSdkVersion 23
|
minSdk = 23
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode = flutter.versionCode
|
||||||
versionName flutterVersionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
keyAlias keystoreProperties['keyAlias']
|
keyAlias = keystoreProperties['keyAlias']
|
||||||
keyPassword keystoreProperties['keyPassword']
|
keyPassword = keystoreProperties['keyPassword']
|
||||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||||
storePassword keystoreProperties['storePassword']
|
storePassword = keystoreProperties['storePassword']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig = signingConfigs.release
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
signingConfig signingConfigs.debug
|
signingConfig = signingConfigs.debug
|
||||||
minifyEnabled true
|
minifyEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source '../..'
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.core:core-remoteviews:1.0.0"
|
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
|
// Remove this for FLOSS version
|
||||||
|
|
|
@ -11,16 +11,6 @@
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:extractNativeLibs="true">
|
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
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -28,6 +18,7 @@
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
@ -36,7 +27,32 @@
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</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
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
@ -52,4 +68,4 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
@ -3,6 +3,7 @@
|
||||||
style="@style/Widget.Android.AppWidget.Container"
|
style="@style/Widget.Android.AppWidget.Container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSecondaryContainer"
|
||||||
android:theme="@style/Theme.Android.AppWidgetContainer"
|
android:theme="@style/Theme.Android.AppWidgetContainer"
|
||||||
android:id="@+id/widget_day_oreo">
|
android:id="@+id/widget_day_oreo">
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
android:shadowRadius="1"
|
android:shadowRadius="1"
|
||||||
android:textSize="@dimen/widget_large_title_text_size"
|
android:textSize="@dimen/widget_large_title_text_size"
|
||||||
tools:ignore="ObsoleteLayoutParam"
|
tools:ignore="ObsoleteLayoutParam"
|
||||||
tools:text="Saturday, September 30 │ " />
|
tools:text="Saturday, September 30 " />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/widget_day_icon"
|
android:id="@+id/widget_day_icon"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
style="@style/Widget.Android.AppWidget.Container"
|
style="@style/Widget.Android.AppWidget.Container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSecondaryContainer"
|
||||||
android:theme="@style/Theme.Android.AppWidgetContainer"
|
android:theme="@style/Theme.Android.AppWidgetContainer"
|
||||||
android:id="@+id/widget_day_oreo">
|
android:id="@+id/widget_day_oreo">
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
android:shadowRadius="1"
|
android:shadowRadius="1"
|
||||||
android:textSize="@dimen/widget_large_title_text_size"
|
android:textSize="@dimen/widget_large_title_text_size"
|
||||||
tools:ignore="ObsoleteLayoutParam"
|
tools:ignore="ObsoleteLayoutParam"
|
||||||
tools:text="Saturday, September 30 │ " />
|
tools:text="Saturday, September 30 " />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/widget_day_icon"
|
android:id="@+id/widget_day_icon"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
style="@style/Widget.Android.AppWidget.Container"
|
style="@style/Widget.Android.AppWidget.Container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSecondaryContainer"
|
||||||
android:theme="@style/Theme.Android.AppWidgetContainer"
|
android:theme="@style/Theme.Android.AppWidgetContainer"
|
||||||
android:id="@+id/widget_day_oreo">
|
android:id="@+id/widget_day_oreo">
|
||||||
|
|
||||||
|
@ -32,8 +33,8 @@
|
||||||
android:shadowRadius="1"
|
android:shadowRadius="1"
|
||||||
android:textAppearance="@android:style/TextAppearance.Material.Large"
|
android:textAppearance="@android:style/TextAppearance.Material.Large"
|
||||||
android:textSize="@dimen/widget_title_text_size"
|
android:textSize="@dimen/widget_title_text_size"
|
||||||
tools:text="21°"
|
tools:ignore="ObsoleteLayoutParam"
|
||||||
tools:ignore="ObsoleteLayoutParam" />
|
tools:text="21°" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
style="@style/Widget.Android.AppWidget.Container"
|
style="@style/Widget.Android.AppWidget.Container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSecondaryContainer"
|
||||||
android:theme="@style/Theme.Android.AppWidgetContainer"
|
android:theme="@style/Theme.Android.AppWidgetContainer"
|
||||||
android:id="@+id/widget_day_oreo">
|
android:id="@+id/widget_day_oreo">
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- 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:forceDarkAllowed">false</item>
|
||||||
<item name="android:windowFullscreen">false</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
running.
|
running.
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
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>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<!--
|
<!--
|
||||||
Having themes.xml for night-v31 because of the priority order of the resource qualifiers.
|
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="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
|
||||||
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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 -->
|
<!-- 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
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<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:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowDrawsSystemBarBackgrounds" >false</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
running.
|
running.
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
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>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- 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:forceDarkAllowed">false</item>
|
||||||
<item name="android:windowFullscreen">false</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
@ -14,17 +14,15 @@
|
||||||
running.
|
running.
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
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>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
||||||
<item name="android:id">@android:id/background</item>
|
<item name="android:id">@android:id/background</item>
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||||
<item name="android:background">@drawable/app_widget_background</item>
|
<item name="android:background">@drawable/app_widget_background</item>
|
||||||
<item name="android:clipToOutline">true</item>
|
<item name="android:clipToOutline">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
|
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||||
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
and @android:dimen/system_app_widget_internal_padding requires API level 31
|
and @android:dimen/system_app_widget_internal_padding requires API level 31
|
||||||
-->
|
-->
|
||||||
<style name="Theme.Android.AppWidgetContainerParent"
|
<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="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
|
||||||
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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_style">EEE, d MMM │</string>
|
||||||
<string name="date_format_widget_oreo_big_style">EEEE, d MMM │</string>
|
<string name="date_format_widget_oreo_big_style">EEEE, d MMM │</string>
|
||||||
</resources>
|
</resources>
|
|
@ -2,14 +2,14 @@
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<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
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode
|
||||||
setting is off -->
|
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
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<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:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">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>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
@ -17,18 +17,15 @@
|
||||||
running.
|
running.
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
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>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
||||||
<item name="android:id">@android:id/background</item>
|
<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>
|
||||||
|
|
||||||
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
|
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
|
||||||
<item name="android:background">?android:attr/colorBackground</item>
|
<item name="android:background">?attr/colorSurface</item>
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
<item name="android:textColor">?attr/itemTextColor</item>
|
||||||
</style>
|
</style>
|
||||||
|
</resources>
|
||||||
</resources>
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<resources>
|
<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 -->
|
<!-- Radius of the outer bound of widgets to make the rounded corners -->
|
||||||
<item name="appWidgetRadius">16dp</item>
|
<item name="appWidgetRadius">16dp</item>
|
||||||
<!--
|
<!--
|
||||||
|
|
|
@ -1,15 +1,3 @@
|
||||||
buildscript {
|
|
||||||
ext.kotlin_version = '1.9.22'
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
@ -20,6 +8,8 @@ allprojects {
|
||||||
rootProject.buildDir = '../build'
|
rootProject.buildDir = '../build'
|
||||||
subprojects {
|
subprojects {
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
org.gradle.jvmargs=-Xmx4G
|
org.gradle.jvmargs=-Xmx4G
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
android.enableR8.fullMode = false
|
||||||
|
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
||||||
|
|
|
@ -5,25 +5,21 @@ pluginManagement {
|
||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
return flutterSdkPath
|
return flutterSdkPath
|
||||||
}
|
}()
|
||||||
settings.ext.flutterSdkPath = flutterSdkPath()
|
|
||||||
|
|
||||||
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
|
||||||
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version '7.4.2' apply false
|
id "com.android.application" version "8.9.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
@ -25,11 +25,11 @@
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string>The Rain App requires access to the device's location.</string>
|
<string>The Rain App requires access to the device's location.</string>
|
||||||
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
|
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>TemporaryPreciseAccuracy</key>
|
<key>TemporaryPreciseAccuracy</key>
|
||||||
<string>The Rain App requires temporary access to the device's precise location.</string>
|
<string>The Rain App requires temporary access to the device's precise location.</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
|
|
12
ios/RunnerTests/RunnerTests.swift
Normal 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
243
lib/app/api/api.dart
Normal file → Executable file
|
@ -1,69 +1,38 @@
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:rain/app/api/city.dart';
|
import 'package:rain/app/api/city_api.dart';
|
||||||
import 'package:rain/app/api/weather.dart';
|
import 'package:rain/app/api/weather_api.dart';
|
||||||
import 'package:rain/app/data/weather.dart';
|
import 'package:rain/app/data/db.dart';
|
||||||
import 'package:rain/main.dart';
|
import 'package:rain/main.dart';
|
||||||
|
|
||||||
class WeatherAPI {
|
class WeatherAPI {
|
||||||
final Dio dio = Dio()
|
final Dio dio =
|
||||||
..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?';
|
Dio()..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?';
|
||||||
final Dio dioLocation = Dio();
|
final Dio dioLocation = Dio();
|
||||||
|
|
||||||
Future<MainWeatherCache> getWeatherData(double? lat, double? lon) async {
|
static const String _weatherParams =
|
||||||
String url =
|
'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'
|
||||||
'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';
|
'&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'
|
||||||
String urlWeather;
|
'&forecast_days=12&timezone=auto';
|
||||||
settings.measurements == 'imperial' && settings.degrees == 'fahrenheit'
|
|
||||||
? urlWeather =
|
String _buildWeatherUrl(double lat, double lon) {
|
||||||
'$url&temperature_unit=fahrenheit&windspeed_unit=mph&precipitation_unit=inch'
|
String url = 'latitude=$lat&longitude=$lon&$_weatherParams';
|
||||||
: settings.measurements == 'imperial'
|
if (settings.measurements == 'imperial') {
|
||||||
? urlWeather = '$url&windspeed_unit=mph&precipitation_unit=inch'
|
url += '&windspeed_unit=mph&precipitation_unit=inch';
|
||||||
: settings.degrees == 'fahrenheit'
|
}
|
||||||
? urlWeather = '$url&temperature_unit=fahrenheit'
|
if (settings.degrees == 'fahrenheit') {
|
||||||
: urlWeather = url;
|
url += '&temperature_unit=fahrenheit';
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MainWeatherCache> getWeatherData(double lat, double lon) async {
|
||||||
|
final String urlWeather = _buildWeatherUrl(lat, lon);
|
||||||
try {
|
try {
|
||||||
Response response = await dio.get(urlWeather);
|
Response response = await dio.get(urlWeather);
|
||||||
WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data);
|
WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data);
|
||||||
return MainWeatherCache(
|
return _mapWeatherDataToCache(weatherData);
|
||||||
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(),
|
|
||||||
);
|
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
|
@ -72,63 +41,24 @@ class WeatherAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<WeatherCard> getWeatherCard(double? lat, double? lon, String city,
|
Future<WeatherCard> getWeatherCard(
|
||||||
String district, String timezone) async {
|
double lat,
|
||||||
String url =
|
double lon,
|
||||||
'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 city,
|
||||||
String urlWeather;
|
String district,
|
||||||
settings.measurements == 'imperial' && settings.degrees == 'fahrenheit'
|
String timezone,
|
||||||
? urlWeather =
|
) async {
|
||||||
'$url&temperature_unit=fahrenheit&windspeed_unit=mph&precipitation_unit=inch'
|
final String urlWeather = _buildWeatherUrl(lat, lon);
|
||||||
: settings.measurements == 'imperial'
|
|
||||||
? urlWeather = '$url&windspeed_unit=mph&precipitation_unit=inch'
|
|
||||||
: settings.degrees == 'fahrenheit'
|
|
||||||
? urlWeather = '$url&temperature_unit=fahrenheit'
|
|
||||||
: urlWeather = url;
|
|
||||||
try {
|
try {
|
||||||
Response response = await dio.get(urlWeather);
|
Response response = await dio.get(urlWeather);
|
||||||
WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data);
|
WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data);
|
||||||
return WeatherCard(
|
return _mapWeatherDataToCard(
|
||||||
time: weatherData.hourly.time,
|
weatherData,
|
||||||
temperature2M: weatherData.hourly.temperature2M,
|
lat,
|
||||||
relativehumidity2M: weatherData.hourly.relativeHumidity2M,
|
lon,
|
||||||
apparentTemperature: weatherData.hourly.apparentTemperature,
|
city,
|
||||||
precipitation: weatherData.hourly.precipitation,
|
district,
|
||||||
rain: weatherData.hourly.rain,
|
timezone,
|
||||||
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(),
|
|
||||||
);
|
);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
|
@ -139,7 +69,7 @@ class WeatherAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Iterable<Result>> getCity(String query, Locale? locale) async {
|
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';
|
'https://geocoding-api.open-meteo.com/v1/search?name=$query&count=5&language=${locale?.languageCode}&format=json';
|
||||||
try {
|
try {
|
||||||
Response response = await dioLocation.get(url);
|
Response response = await dioLocation.get(url);
|
||||||
|
@ -163,4 +93,97 @@ class WeatherAPI {
|
||||||
rethrow;
|
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
|
@ -1,15 +1,14 @@
|
||||||
class CityApi {
|
class CityApi {
|
||||||
CityApi({
|
CityApi({required this.results});
|
||||||
required this.results,
|
|
||||||
});
|
|
||||||
|
|
||||||
List<Result> results;
|
List<Result> results;
|
||||||
|
|
||||||
factory CityApi.fromJson(Map<String, dynamic> json) => CityApi(
|
factory CityApi.fromJson(Map<String, dynamic> json) => CityApi(
|
||||||
results: json['results'] == null
|
results:
|
||||||
|
json['results'] == null
|
||||||
? List<Result>.empty()
|
? List<Result>.empty()
|
||||||
: List<Result>.from(json['results'].map((x) => Result.fromJson(x))),
|
: List<Result>.from(json['results'].map((x) => Result.fromJson(x))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Result {
|
class Result {
|
||||||
|
@ -26,9 +25,9 @@ class Result {
|
||||||
double longitude;
|
double longitude;
|
||||||
|
|
||||||
factory Result.fromJson(Map<String, dynamic> json) => Result(
|
factory Result.fromJson(Map<String, dynamic> json) => Result(
|
||||||
admin1: json['admin1'] ?? '',
|
admin1: json['admin1'] ?? '',
|
||||||
name: json['name'],
|
name: json['name'],
|
||||||
latitude: json['latitude'],
|
latitude: json['latitude'],
|
||||||
longitude: json['longitude'],
|
longitude: json['longitude'],
|
||||||
);
|
);
|
||||||
}
|
}
|
4
lib/app/api/weather.dart → lib/app/api/weather_api.dart
Normal file → Executable file
|
@ -2,8 +2,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
//ignore_for_file: invalid_annotation_target
|
//ignore_for_file: invalid_annotation_target
|
||||||
|
|
||||||
part 'weather.freezed.dart';
|
part 'weather_api.freezed.dart';
|
||||||
part 'weather.g.dart';
|
part 'weather_api.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class WeatherDataApi with _$WeatherDataApi {
|
class WeatherDataApi with _$WeatherDataApi {
|
71
lib/app/api/weather.freezed.dart → lib/app/api/weather_api.freezed.dart
Normal file → Executable file
|
@ -3,7 +3,7 @@
|
||||||
// ignore_for_file: type=lint
|
// 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
|
// 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
|
// FreezedGenerator
|
||||||
|
@ -12,7 +12,7 @@ part of 'weather.dart';
|
||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
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) {
|
WeatherDataApi _$WeatherDataApiFromJson(Map<String, dynamic> json) {
|
||||||
return _WeatherDataApi.fromJson(json);
|
return _WeatherDataApi.fromJson(json);
|
||||||
|
@ -24,8 +24,12 @@ mixin _$WeatherDataApi {
|
||||||
Daily get daily => throw _privateConstructorUsedError;
|
Daily get daily => throw _privateConstructorUsedError;
|
||||||
String get timezone => throw _privateConstructorUsedError;
|
String get timezone => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this WeatherDataApi to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
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 =>
|
$WeatherDataApiCopyWith<WeatherDataApi> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
@ -52,6 +56,8 @@ class _$WeatherDataApiCopyWithImpl<$Res, $Val extends WeatherDataApi>
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Res Function($Val) _then;
|
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')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
@ -75,6 +81,8 @@ class _$WeatherDataApiCopyWithImpl<$Res, $Val extends WeatherDataApi>
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of WeatherDataApi
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$HourlyCopyWith<$Res> get hourly {
|
$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
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$DailyCopyWith<$Res> get daily {
|
$DailyCopyWith<$Res> get daily {
|
||||||
|
@ -116,6 +126,8 @@ class __$$WeatherDataApiImplCopyWithImpl<$Res>
|
||||||
_$WeatherDataApiImpl _value, $Res Function(_$WeatherDataApiImpl) _then)
|
_$WeatherDataApiImpl _value, $Res Function(_$WeatherDataApiImpl) _then)
|
||||||
: super(_value, _then);
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of WeatherDataApi
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
@ -172,11 +184,13 @@ class _$WeatherDataApiImpl implements _WeatherDataApi {
|
||||||
other.timezone == timezone));
|
other.timezone == timezone));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, hourly, daily, timezone);
|
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
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith =>
|
_$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith =>
|
||||||
|
@ -206,8 +220,11 @@ abstract class _WeatherDataApi implements WeatherDataApi {
|
||||||
Daily get daily;
|
Daily get daily;
|
||||||
@override
|
@override
|
||||||
String get timezone;
|
String get timezone;
|
||||||
|
|
||||||
|
/// Create a copy of WeatherDataApi
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
_$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith =>
|
_$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
@ -251,8 +268,12 @@ mixin _$Hourly {
|
||||||
@JsonKey(name: 'shortwave_radiation')
|
@JsonKey(name: 'shortwave_radiation')
|
||||||
List<double?>? get shortwaveRadiation => throw _privateConstructorUsedError;
|
List<double?>? get shortwaveRadiation => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this Hourly to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
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;
|
$HourlyCopyWith<Hourly> get copyWith => throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,6 +314,8 @@ class _$HourlyCopyWithImpl<$Res, $Val extends Hourly>
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Res Function($Val) _then;
|
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')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
@ -429,6 +452,8 @@ class __$$HourlyImplCopyWithImpl<$Res>
|
||||||
_$HourlyImpl _value, $Res Function(_$HourlyImpl) _then)
|
_$HourlyImpl _value, $Res Function(_$HourlyImpl) _then)
|
||||||
: super(_value, _then);
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of Hourly
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
@ -820,7 +845,7 @@ class _$HourlyImpl implements _Hourly {
|
||||||
.equals(other._shortwaveRadiation, _shortwaveRadiation));
|
.equals(other._shortwaveRadiation, _shortwaveRadiation));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(
|
||||||
runtimeType,
|
runtimeType,
|
||||||
|
@ -843,7 +868,9 @@ class _$HourlyImpl implements _Hourly {
|
||||||
const DeepCollectionEquality().hash(_precipitationProbability),
|
const DeepCollectionEquality().hash(_precipitationProbability),
|
||||||
const DeepCollectionEquality().hash(_shortwaveRadiation));
|
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
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$$HourlyImplCopyWith<_$HourlyImpl> get copyWith =>
|
_$$HourlyImplCopyWith<_$HourlyImpl> get copyWith =>
|
||||||
|
@ -933,8 +960,11 @@ abstract class _Hourly implements Hourly {
|
||||||
@override
|
@override
|
||||||
@JsonKey(name: 'shortwave_radiation')
|
@JsonKey(name: 'shortwave_radiation')
|
||||||
List<double?>? get shortwaveRadiation;
|
List<double?>? get shortwaveRadiation;
|
||||||
|
|
||||||
|
/// Create a copy of Hourly
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
_$$HourlyImplCopyWith<_$HourlyImpl> get copyWith =>
|
_$$HourlyImplCopyWith<_$HourlyImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
@ -978,8 +1008,12 @@ mixin _$Daily {
|
||||||
List<int?>? get windDirection10MDominant =>
|
List<int?>? get windDirection10MDominant =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this Daily to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
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;
|
$DailyCopyWith<Daily> get copyWith => throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1020,6 +1054,8 @@ class _$DailyCopyWithImpl<$Res, $Val extends Daily>
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final $Res Function($Val) _then;
|
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')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
@ -1141,6 +1177,8 @@ class __$$DailyImplCopyWithImpl<$Res>
|
||||||
_$DailyImpl _value, $Res Function(_$DailyImpl) _then)
|
_$DailyImpl _value, $Res Function(_$DailyImpl) _then)
|
||||||
: super(_value, _then);
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of Daily
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
|
@ -1478,7 +1516,7 @@ class _$DailyImpl implements _Daily {
|
||||||
other._windDirection10MDominant, _windDirection10MDominant));
|
other._windDirection10MDominant, _windDirection10MDominant));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(
|
||||||
runtimeType,
|
runtimeType,
|
||||||
|
@ -1498,7 +1536,9 @@ class _$DailyImpl implements _Daily {
|
||||||
const DeepCollectionEquality().hash(_rainSum),
|
const DeepCollectionEquality().hash(_rainSum),
|
||||||
const DeepCollectionEquality().hash(_windDirection10MDominant));
|
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
|
@override
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
_$$DailyImplCopyWith<_$DailyImpl> get copyWith =>
|
_$$DailyImplCopyWith<_$DailyImpl> get copyWith =>
|
||||||
|
@ -1581,8 +1621,11 @@ abstract class _Daily implements Daily {
|
||||||
@override
|
@override
|
||||||
@JsonKey(name: 'winddirection_10m_dominant')
|
@JsonKey(name: 'winddirection_10m_dominant')
|
||||||
List<int?>? get windDirection10MDominant;
|
List<int?>? get windDirection10MDominant;
|
||||||
|
|
||||||
|
/// Create a copy of Daily
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
_$$DailyImplCopyWith<_$DailyImpl> get copyWith =>
|
_$$DailyImplCopyWith<_$DailyImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
18
lib/app/api/weather.g.dart → lib/app/api/weather_api.g.dart
Normal file → Executable file
|
@ -1,6 +1,6 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'weather.dart';
|
part of 'weather_api.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
|
@ -24,7 +24,7 @@ Map<String, dynamic> _$$WeatherDataApiImplToJson(
|
||||||
_$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
|
_$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
|
||||||
time: (json['time'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
time: (json['time'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||||
weatherCode: (json['weathercode'] as List<dynamic>?)
|
weatherCode: (json['weathercode'] as List<dynamic>?)
|
||||||
?.map((e) => e as int)
|
?.map((e) => (e as num).toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
temperature2M: (json['temperature_2m'] as List<dynamic>?)
|
temperature2M: (json['temperature_2m'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num).toDouble())
|
?.map((e) => (e as num).toDouble())
|
||||||
|
@ -39,7 +39,7 @@ _$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.map((e) => (e as num?)?.toDouble())
|
||||||
.toList(),
|
.toList(),
|
||||||
relativeHumidity2M: (json['relativehumidity_2m'] as List<dynamic>?)
|
relativeHumidity2M: (json['relativehumidity_2m'] as List<dynamic>?)
|
||||||
?.map((e) => e as int?)
|
?.map((e) => (e as num?)?.toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
surfacePressure: (json['surface_pressure'] as List<dynamic>?)
|
surfacePressure: (json['surface_pressure'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.map((e) => (e as num?)?.toDouble())
|
||||||
|
@ -54,13 +54,13 @@ _$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.map((e) => (e as num?)?.toDouble())
|
||||||
.toList(),
|
.toList(),
|
||||||
windDirection10M: (json['winddirection_10m'] as List<dynamic>?)
|
windDirection10M: (json['winddirection_10m'] as List<dynamic>?)
|
||||||
?.map((e) => e as int?)
|
?.map((e) => (e as num?)?.toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
windGusts10M: (json['windgusts_10m'] as List<dynamic>?)
|
windGusts10M: (json['windgusts_10m'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.map((e) => (e as num?)?.toDouble())
|
||||||
.toList(),
|
.toList(),
|
||||||
cloudCover: (json['cloudcover'] as List<dynamic>?)
|
cloudCover: (json['cloudcover'] as List<dynamic>?)
|
||||||
?.map((e) => e as int?)
|
?.map((e) => (e as num?)?.toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
uvIndex: (json['uv_index'] as List<dynamic>?)
|
uvIndex: (json['uv_index'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.map((e) => (e as num?)?.toDouble())
|
||||||
|
@ -70,7 +70,7 @@ _$HourlyImpl _$$HourlyImplFromJson(Map<String, dynamic> json) => _$HourlyImpl(
|
||||||
.toList(),
|
.toList(),
|
||||||
precipitationProbability:
|
precipitationProbability:
|
||||||
(json['precipitation_probability'] as List<dynamic>?)
|
(json['precipitation_probability'] as List<dynamic>?)
|
||||||
?.map((e) => e as int?)
|
?.map((e) => (e as num?)?.toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
shortwaveRadiation: (json['shortwave_radiation'] as List<dynamic>?)
|
shortwaveRadiation: (json['shortwave_radiation'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.map((e) => (e as num?)?.toDouble())
|
||||||
|
@ -102,7 +102,7 @@ Map<String, dynamic> _$$HourlyImplToJson(_$HourlyImpl instance) =>
|
||||||
_$DailyImpl _$$DailyImplFromJson(Map<String, dynamic> json) => _$DailyImpl(
|
_$DailyImpl _$$DailyImplFromJson(Map<String, dynamic> json) => _$DailyImpl(
|
||||||
time: _dateTimeFromJson(json['time'] as List?),
|
time: _dateTimeFromJson(json['time'] as List?),
|
||||||
weatherCode: (json['weathercode'] as List<dynamic>?)
|
weatherCode: (json['weathercode'] as List<dynamic>?)
|
||||||
?.map((e) => e as int?)
|
?.map((e) => (e as num?)?.toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
temperature2MMax: (json['temperature_2m_max'] as List<dynamic>?)
|
temperature2MMax: (json['temperature_2m_max'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.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(),
|
(json['sunset'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||||
precipitationProbabilityMax:
|
precipitationProbabilityMax:
|
||||||
(json['precipitation_probability_max'] as List<dynamic>?)
|
(json['precipitation_probability_max'] as List<dynamic>?)
|
||||||
?.map((e) => e as int?)
|
?.map((e) => (e as num?)?.toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
windSpeed10MMax: (json['windspeed_10m_max'] as List<dynamic>?)
|
windSpeed10MMax: (json['windspeed_10m_max'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num?)?.toDouble())
|
?.map((e) => (e as num?)?.toDouble())
|
||||||
|
@ -143,7 +143,7 @@ _$DailyImpl _$$DailyImplFromJson(Map<String, dynamic> json) => _$DailyImpl(
|
||||||
.toList(),
|
.toList(),
|
||||||
windDirection10MDominant:
|
windDirection10MDominant:
|
||||||
(json['winddirection_10m_dominant'] as List<dynamic>?)
|
(json['winddirection_10m_dominant'] as List<dynamic>?)
|
||||||
?.map((e) => e as int?)
|
?.map((e) => (e as num?)?.toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
430
lib/app/controller/controller.dart
Normal file → Executable file
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:flutter_timezone/flutter_timezone.dart';
|
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||||
import 'package:geocoding/geocoding.dart';
|
import 'package:geocoding/geocoding.dart';
|
||||||
import 'package:geolocator/geolocator.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:lat_lng_to_timezone/lat_lng_to_timezone.dart' as tzmap;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:rain/app/api/api.dart';
|
import 'package:rain/app/api/api.dart';
|
||||||
import 'package:rain/app/data/weather.dart';
|
import 'package:rain/app/data/db.dart';
|
||||||
import 'package:rain/app/services/notification.dart';
|
import 'package:rain/app/utils/notification.dart';
|
||||||
import 'package:rain/app/services/utils.dart';
|
import 'package:rain/app/utils/show_snack_bar.dart';
|
||||||
import 'package:rain/app/widgets/status/status_data.dart';
|
import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
|
||||||
import 'package:rain/app/widgets/status/status_weather.dart';
|
import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
|
||||||
import 'package:rain/main.dart';
|
import 'package:rain/main.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
import 'package:timezone/data/latest_all.dart' as tz;
|
import 'package:timezone/data/latest_all.dart' as tz;
|
||||||
import 'package:timezone/standalone.dart' as tz;
|
import 'package:timezone/standalone.dart' as tz;
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
|
|
||||||
class WeatherController extends GetxController {
|
class WeatherController extends GetxController {
|
||||||
|
@ -52,15 +52,14 @@ class WeatherController extends GetxController {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
weatherCards
|
weatherCards.assignAll(
|
||||||
.assignAll(isar.weatherCards.where().sortByIndex().findAllSync());
|
isar.weatherCards.where().sortByIndex().findAllSync(),
|
||||||
|
);
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Position> determinePosition() async {
|
Future<Position> _determinePosition() async {
|
||||||
LocationPermission permission;
|
LocationPermission permission = await Geolocator.checkPermission();
|
||||||
|
|
||||||
permission = await Geolocator.checkPermission();
|
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
permission = await Geolocator.requestPermission();
|
permission = await Geolocator.requestPermission();
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
|
@ -70,35 +69,36 @@ class WeatherController extends GetxController {
|
||||||
|
|
||||||
if (permission == LocationPermission.deniedForever) {
|
if (permission == LocationPermission.deniedForever) {
|
||||||
return Future.error(
|
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(
|
return await Geolocator.getCurrentPosition();
|
||||||
desiredAccuracy: LocationAccuracy.high);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setLocation() async {
|
Future<void> setLocation() async {
|
||||||
if (settings.location) {
|
if (settings.location) {
|
||||||
await getCurrentLocation();
|
await getCurrentLocation();
|
||||||
} else {
|
} else {
|
||||||
if ((isar.locationCaches.where().findAllSync()).isNotEmpty) {
|
final locationCity = isar.locationCaches.where().findFirstSync();
|
||||||
LocationCache locationCity =
|
if (locationCity != null) {
|
||||||
(isar.locationCaches.where().findFirstSync())!;
|
await getLocation(
|
||||||
await getLocation(locationCity.lat!, locationCity.lon!,
|
locationCity.lat!,
|
||||||
locationCity.district!, locationCity.city!);
|
locationCity.lon!,
|
||||||
|
locationCity.district!,
|
||||||
|
locationCity.city!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getCurrentLocation() async {
|
Future<void> getCurrentLocation() async {
|
||||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
if (!(await isOnline.value)) {
|
||||||
|
|
||||||
if (!isOnline) {
|
|
||||||
showSnackBar(content: 'no_inter'.tr);
|
showSnackBar(content: 'no_inter'.tr);
|
||||||
await readCache();
|
await readCache();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serviceEnabled) {
|
if (!await Geolocator.isLocationServiceEnabled()) {
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
content: 'no_location'.tr,
|
content: 'no_location'.tr,
|
||||||
onPressed: () => Geolocator.openLocationSettings(),
|
onPressed: () => Geolocator.openLocationSettings(),
|
||||||
|
@ -107,39 +107,73 @@ class WeatherController extends GetxController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) {
|
if (isar.mainWeatherCaches.where().findAllSync().isNotEmpty) {
|
||||||
await readCache();
|
await readCache();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Position position = await determinePosition();
|
final position = await _determinePosition();
|
||||||
List<Placemark> placemarks =
|
final placemarks = await placemarkFromCoordinates(
|
||||||
await placemarkFromCoordinates(position.latitude, position.longitude);
|
position.latitude,
|
||||||
Placemark place = placemarks[0];
|
position.longitude,
|
||||||
|
);
|
||||||
|
final place = placemarks[0];
|
||||||
|
|
||||||
_latitude.value = position.latitude;
|
_latitude.value = position.latitude;
|
||||||
_longitude.value = position.longitude;
|
_longitude.value = position.longitude;
|
||||||
_district.value = '${place.administrativeArea}';
|
_district.value = place.administrativeArea ?? '';
|
||||||
_city.value = '${place.locality}';
|
_city.value = place.locality ?? '';
|
||||||
|
|
||||||
_mainWeather.value =
|
_mainWeather.value = await WeatherAPI().getWeatherData(
|
||||||
await WeatherAPI().getWeatherData(_latitude.value, _longitude.value);
|
_latitude.value,
|
||||||
|
_longitude.value,
|
||||||
|
);
|
||||||
|
|
||||||
notificationCheck();
|
notificationCheck();
|
||||||
|
|
||||||
await writeCache();
|
await writeCache();
|
||||||
await readCache();
|
await readCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getLocation(double latitude, double longitude, String district,
|
Future<Map<String, dynamic>> getCurrentLocationSearch() async {
|
||||||
String locality) async {
|
if (!(await isOnline.value)) {
|
||||||
if (!isOnline) {
|
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);
|
showSnackBar(content: 'no_inter'.tr);
|
||||||
await readCache();
|
await readCache();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) {
|
if (isar.mainWeatherCaches.where().findAllSync().isNotEmpty) {
|
||||||
await readCache();
|
await readCache();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -149,11 +183,12 @@ class WeatherController extends GetxController {
|
||||||
_district.value = district;
|
_district.value = district;
|
||||||
_city.value = locality;
|
_city.value = locality;
|
||||||
|
|
||||||
_mainWeather.value =
|
_mainWeather.value = await WeatherAPI().getWeatherData(
|
||||||
await WeatherAPI().getWeatherData(_latitude.value, _longitude.value);
|
_latitude.value,
|
||||||
|
_longitude.value,
|
||||||
|
);
|
||||||
|
|
||||||
notificationCheck();
|
notificationCheck();
|
||||||
|
|
||||||
await writeCache();
|
await writeCache();
|
||||||
await readCache();
|
await readCache();
|
||||||
}
|
}
|
||||||
|
@ -170,10 +205,14 @@ class WeatherController extends GetxController {
|
||||||
_mainWeather.value = mainWeatherCache;
|
_mainWeather.value = mainWeatherCache;
|
||||||
_location.value = locationCache;
|
_location.value = locationCache;
|
||||||
|
|
||||||
hourOfDay.value =
|
hourOfDay.value = getTime(
|
||||||
getTime(_mainWeather.value.time!, _mainWeather.value.timezone!);
|
_mainWeather.value.time!,
|
||||||
dayOfNow.value =
|
_mainWeather.value.timezone!,
|
||||||
getDay(_mainWeather.value.timeDaily!, _mainWeather.value.timezone!);
|
);
|
||||||
|
dayOfNow.value = getDay(
|
||||||
|
_mainWeather.value.timeDaily!,
|
||||||
|
_mainWeather.value.timezone!,
|
||||||
|
);
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
Workmanager().registerPeriodicTask(
|
Workmanager().registerPeriodicTask(
|
||||||
|
@ -204,23 +243,17 @@ class WeatherController extends GetxController {
|
||||||
);
|
);
|
||||||
|
|
||||||
isar.writeTxnSync(() {
|
isar.writeTxnSync(() {
|
||||||
final mainWeatherCachesIsEmpty =
|
if (isar.mainWeatherCaches.where().findAllSync().isEmpty) {
|
||||||
(isar.mainWeatherCaches.where().findAllSync()).isEmpty;
|
|
||||||
final locationCachesIsEmpty =
|
|
||||||
(isar.locationCaches.where().findAllSync()).isEmpty;
|
|
||||||
|
|
||||||
if (mainWeatherCachesIsEmpty) {
|
|
||||||
isar.mainWeatherCaches.putSync(_mainWeather.value);
|
isar.mainWeatherCaches.putSync(_mainWeather.value);
|
||||||
}
|
}
|
||||||
|
if (isar.locationCaches.where().findAllSync().isEmpty) {
|
||||||
if (locationCachesIsEmpty) {
|
|
||||||
isar.locationCaches.putSync(locationCaches);
|
isar.locationCaches.putSync(locationCaches);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteCache() async {
|
Future<void> deleteCache() async {
|
||||||
if (!isOnline) {
|
if (!(await isOnline.value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,41 +263,49 @@ class WeatherController extends GetxController {
|
||||||
.timestampLessThan(cacheExpiry)
|
.timestampLessThan(cacheExpiry)
|
||||||
.deleteAllSync();
|
.deleteAllSync();
|
||||||
});
|
});
|
||||||
if ((isar.mainWeatherCaches.where().findAllSync()).isEmpty) {
|
if (isar.mainWeatherCaches.where().findAllSync().isEmpty) {
|
||||||
await flutterLocalNotificationsPlugin.cancelAll();
|
await flutterLocalNotificationsPlugin.cancelAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteAll(bool changeCity) async {
|
Future<void> deleteAll(bool changeCity) async {
|
||||||
if (!isOnline) {
|
if (!(await isOnline.value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||||
await flutterLocalNotificationsPlugin.cancelAll();
|
await flutterLocalNotificationsPlugin.cancelAll();
|
||||||
|
|
||||||
isar.writeTxnSync(() {
|
isar.writeTxnSync(() {
|
||||||
if (!settings.location) {
|
if (!settings.location) {
|
||||||
isar.mainWeatherCaches.where().deleteAllSync();
|
isar.mainWeatherCaches.where().deleteAllSync();
|
||||||
}
|
}
|
||||||
if ((settings.location && serviceEnabled) || changeCity) {
|
if (settings.location && serviceEnabled || changeCity) {
|
||||||
isar.mainWeatherCaches.where().deleteAllSync();
|
isar.mainWeatherCaches.where().deleteAllSync();
|
||||||
isar.locationCaches.where().deleteAllSync();
|
isar.locationCaches.where().deleteAllSync();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Card Weather
|
|
||||||
Future<void> addCardWeather(
|
Future<void> addCardWeather(
|
||||||
double latitude, double longitude, String city, String district) async {
|
double latitude,
|
||||||
if (!isOnline) {
|
double longitude,
|
||||||
|
String city,
|
||||||
|
String district,
|
||||||
|
) async {
|
||||||
|
if (!(await isOnline.value)) {
|
||||||
showSnackBar(content: 'no_inter'.tr);
|
showSnackBar(content: 'no_inter'.tr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String tz = tzmap.latLngToTimezoneString(latitude, longitude);
|
final tz = tzmap.latLngToTimezoneString(latitude, longitude);
|
||||||
_weatherCard.value = await WeatherAPI()
|
_weatherCard.value = await WeatherAPI().getWeatherCard(
|
||||||
.getWeatherCard(latitude, longitude, city, district, tz);
|
latitude,
|
||||||
|
longitude,
|
||||||
|
city,
|
||||||
|
district,
|
||||||
|
tz,
|
||||||
|
);
|
||||||
isar.writeTxnSync(() {
|
isar.writeTxnSync(() {
|
||||||
weatherCards.add(_weatherCard.value);
|
weatherCards.add(_weatherCard.value);
|
||||||
isar.weatherCards.putSync(_weatherCard.value);
|
isar.weatherCards.putSync(_weatherCard.value);
|
||||||
|
@ -272,120 +313,88 @@ class WeatherController extends GetxController {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateCacheCard(bool refresh) async {
|
Future<void> updateCacheCard(bool refresh) async {
|
||||||
List<WeatherCard> weatherCard = refresh
|
final weatherCard = refresh
|
||||||
? isar.weatherCards.where().sortByIndex().findAllSync()
|
? isar.weatherCards.where().sortByIndex().findAllSync()
|
||||||
: isar.weatherCards
|
: isar.weatherCards
|
||||||
.filter()
|
.filter()
|
||||||
.timestampLessThan(cacheExpiry)
|
.timestampLessThan(cacheExpiry)
|
||||||
.sortByIndex()
|
.sortByIndex()
|
||||||
.findAllSync();
|
.findAllSync();
|
||||||
|
|
||||||
if (!isOnline || weatherCard.isEmpty) {
|
if (!(await isOnline.value) || weatherCard.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var oldCard in weatherCard) {
|
for (var oldCard in weatherCard) {
|
||||||
var updatedCard = await WeatherAPI().getWeatherCard(oldCard.lat,
|
final updatedCard = await WeatherAPI().getWeatherCard(
|
||||||
oldCard.lon, oldCard.city!, oldCard.district!, oldCard.timezone!);
|
oldCard.lat!,
|
||||||
|
oldCard.lon!,
|
||||||
|
oldCard.city!,
|
||||||
|
oldCard.district!,
|
||||||
|
oldCard.timezone!,
|
||||||
|
);
|
||||||
isar.writeTxnSync(() {
|
isar.writeTxnSync(() {
|
||||||
oldCard
|
_updateWeatherCard(oldCard, updatedCard);
|
||||||
..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;
|
|
||||||
weatherCards.refresh();
|
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 {
|
Future<void> updateCard(WeatherCard weatherCard) async {
|
||||||
if (!isOnline) {
|
if (!(await isOnline.value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final updatedCard = await WeatherAPI().getWeatherCard(
|
final updatedCard = await WeatherAPI().getWeatherCard(
|
||||||
weatherCard.lat,
|
weatherCard.lat!,
|
||||||
weatherCard.lon,
|
weatherCard.lon!,
|
||||||
weatherCard.city!,
|
weatherCard.city!,
|
||||||
weatherCard.district!,
|
weatherCard.district!,
|
||||||
weatherCard.timezone!,
|
weatherCard.timezone!,
|
||||||
);
|
);
|
||||||
|
|
||||||
isar.writeTxnSync(() {
|
isar.writeTxnSync(() {
|
||||||
weatherCard
|
_updateWeatherCard(weatherCard, updatedCard);
|
||||||
..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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,35 +406,26 @@ class WeatherController extends GetxController {
|
||||||
}
|
}
|
||||||
|
|
||||||
int getTime(List<String> time, String timezone) {
|
int getTime(List<String> time, String timezone) {
|
||||||
int getTime = 0;
|
return time.indexWhere((t) {
|
||||||
for (var i = 0; i < time.length; i++) {
|
final dateTime = DateTime.parse(t);
|
||||||
if (tz.TZDateTime.now(tz.getLocation(timezone)).hour ==
|
return tz.TZDateTime.now(tz.getLocation(timezone)).hour ==
|
||||||
DateTime.parse(time[i]).hour &&
|
dateTime.hour &&
|
||||||
tz.TZDateTime.now(tz.getLocation(timezone)).day ==
|
tz.TZDateTime.now(tz.getLocation(timezone)).day == dateTime.day;
|
||||||
DateTime.parse(time[i]).day) {
|
});
|
||||||
getTime = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDay(List<DateTime> time, String timezone) {
|
int getDay(List<DateTime> time, String timezone) {
|
||||||
int getDay = 0;
|
return time.indexWhere(
|
||||||
for (var i = 0; i < time.length; i++) {
|
(t) => tz.TZDateTime.now(tz.getLocation(timezone)).day == t.day,
|
||||||
if (tz.TZDateTime.now(tz.getLocation(timezone)).day == time[i].day) {
|
);
|
||||||
getDay = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getDay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeOfDay timeConvert(String normTime) {
|
TimeOfDay timeConvert(String normTime) {
|
||||||
int hh = 0;
|
final hh = normTime.endsWith('PM') ? 12 : 0;
|
||||||
if (normTime.endsWith('PM')) hh = 12;
|
final timeParts = normTime.split(' ')[0].split(':');
|
||||||
normTime = normTime.split(' ')[0];
|
|
||||||
return TimeOfDay(
|
return TimeOfDay(
|
||||||
hour: hh + int.parse(normTime.split(':')[0]) % 24,
|
hour: hh + int.parse(timeParts[0]) % 24,
|
||||||
minute: int.parse(normTime.split(':')[1]) % 60,
|
minute: int.parse(timeParts[1]) % 60,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,21 +433,21 @@ class WeatherController extends GetxController {
|
||||||
final directory = await getTemporaryDirectory();
|
final directory = await getTemporaryDirectory();
|
||||||
final imagePath = '${directory.path}/$icon';
|
final imagePath = '${directory.path}/$icon';
|
||||||
|
|
||||||
final ByteData data = await rootBundle.load('assets/images/$icon');
|
final data = await rootBundle.load('assets/images/$icon');
|
||||||
final List<int> bytes = data.buffer.asUint8List();
|
final bytes = data.buffer.asUint8List();
|
||||||
|
|
||||||
await File(imagePath).writeAsBytes(bytes);
|
await File(imagePath).writeAsBytes(bytes);
|
||||||
|
|
||||||
return imagePath;
|
return imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
void notlification(MainWeatherCache mainWeatherCache) async {
|
void notification(MainWeatherCache mainWeatherCache) async {
|
||||||
DateTime now = DateTime.now();
|
final now = DateTime.now();
|
||||||
int startHour = timeConvert(timeStart).hour;
|
final startHour = timeConvert(timeStart).hour;
|
||||||
int endHour = timeConvert(timeEnd).hour;
|
final endHour = timeConvert(timeEnd).hour;
|
||||||
|
|
||||||
for (var i = 0; i < mainWeatherCache.time!.length; i += timeRange) {
|
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) &&
|
if (notificationTime.isAfter(now) &&
|
||||||
notificationTime.hour >= startHour &&
|
notificationTime.hour >= startHour &&
|
||||||
|
@ -474,15 +474,15 @@ class WeatherController extends GetxController {
|
||||||
|
|
||||||
void notificationCheck() async {
|
void notificationCheck() async {
|
||||||
if (settings.notifications) {
|
if (settings.notifications) {
|
||||||
final List<PendingNotificationRequest> pendingNotificationRequests =
|
final pendingNotificationRequests = await flutterLocalNotificationsPlugin
|
||||||
await flutterLocalNotificationsPlugin.pendingNotificationRequests();
|
.pendingNotificationRequests();
|
||||||
if (pendingNotificationRequests.isEmpty) {
|
if (pendingNotificationRequests.isEmpty) {
|
||||||
notlification(_mainWeather.value);
|
notification(_mainWeather.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reorder(oldIndex, newIndex) {
|
void reorder(int oldIndex, int newIndex) {
|
||||||
if (newIndex > oldIndex) {
|
if (newIndex > oldIndex) {
|
||||||
newIndex -= 1;
|
newIndex -= 1;
|
||||||
}
|
}
|
||||||
|
@ -502,15 +502,11 @@ class WeatherController extends GetxController {
|
||||||
isar.settings.putSync(settings);
|
isar.settings.putSync(settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Future.wait<bool?>([
|
final results = await Future.wait<bool?>([
|
||||||
HomeWidget.saveWidgetData(
|
HomeWidget.saveWidgetData('background_color', color),
|
||||||
'background_color',
|
|
||||||
color,
|
|
||||||
),
|
|
||||||
HomeWidget.updateWidget(androidName: androidWidgetName),
|
HomeWidget.updateWidget(androidName: androidWidgetName),
|
||||||
]).then((value) {
|
]);
|
||||||
return !value.contains(false);
|
return !results.contains(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updateWidgetTextColor(String color) async {
|
Future<bool> updateWidgetTextColor(String color) async {
|
||||||
|
@ -519,15 +515,11 @@ class WeatherController extends GetxController {
|
||||||
isar.settings.putSync(settings);
|
isar.settings.putSync(settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Future.wait<bool?>([
|
final results = await Future.wait<bool?>([
|
||||||
HomeWidget.saveWidgetData(
|
HomeWidget.saveWidgetData('text_color', color),
|
||||||
'text_color',
|
|
||||||
color,
|
|
||||||
),
|
|
||||||
HomeWidget.updateWidget(androidName: androidWidgetName),
|
HomeWidget.updateWidget(androidName: androidWidgetName),
|
||||||
]).then((value) {
|
]);
|
||||||
return !value.contains(false);
|
return !results.contains(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updateWidget() async {
|
Future<bool> updateWidget() async {
|
||||||
|
@ -542,29 +534,39 @@ class WeatherController extends GetxController {
|
||||||
WeatherCardSchema,
|
WeatherCardSchema,
|
||||||
], directory: (await getApplicationSupportDirectory()).path);
|
], directory: (await getApplicationSupportDirectory()).path);
|
||||||
|
|
||||||
MainWeatherCache? mainWeatherCache;
|
final mainWeatherCache = isarWidget.mainWeatherCaches
|
||||||
mainWeatherCache = isarWidget.mainWeatherCaches.where().findFirstSync();
|
.where()
|
||||||
|
.findFirstSync();
|
||||||
if (mainWeatherCache == null) return false;
|
if (mainWeatherCache == null) return false;
|
||||||
|
|
||||||
int hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!);
|
final hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!);
|
||||||
int day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!);
|
final day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!);
|
||||||
|
|
||||||
return Future.wait<bool?>([
|
final results = await Future.wait<bool?>([
|
||||||
HomeWidget.saveWidgetData(
|
HomeWidget.saveWidgetData(
|
||||||
'weather_icon',
|
'weather_icon',
|
||||||
await getLocalImagePath(StatusWeather().getImageNotification(
|
await getLocalImagePath(
|
||||||
|
StatusWeather().getImageNotification(
|
||||||
mainWeatherCache.weathercode![hour],
|
mainWeatherCache.weathercode![hour],
|
||||||
mainWeatherCache.time![hour],
|
mainWeatherCache.time![hour],
|
||||||
mainWeatherCache.sunrise![day],
|
mainWeatherCache.sunrise![day],
|
||||||
mainWeatherCache.sunset![day],
|
mainWeatherCache.sunset![day],
|
||||||
))),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
HomeWidget.saveWidgetData(
|
HomeWidget.saveWidgetData(
|
||||||
'weather_degree',
|
'weather_degree',
|
||||||
'${mainWeatherCache.temperature2M?[hour].round()}°',
|
'${mainWeatherCache.temperature2M?[hour].round()}°',
|
||||||
),
|
),
|
||||||
HomeWidget.updateWidget(androidName: androidWidgetName),
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
209
lib/app/data/weather.dart → lib/app/data/db.dart
Normal file → Executable file
|
@ -1,6 +1,6 @@
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
part 'weather.g.dart';
|
part 'db.g.dart';
|
||||||
|
|
||||||
@collection
|
@collection
|
||||||
class Settings {
|
class Settings {
|
||||||
|
@ -12,10 +12,14 @@ class Settings {
|
||||||
bool materialColor = false;
|
bool materialColor = false;
|
||||||
bool amoledTheme = false;
|
bool amoledTheme = false;
|
||||||
bool roundDegree = false;
|
bool roundDegree = false;
|
||||||
|
bool largeElement = false;
|
||||||
|
bool hideMap = false;
|
||||||
String? widgetBackgroundColor;
|
String? widgetBackgroundColor;
|
||||||
String? widgetTextColor;
|
String? widgetTextColor;
|
||||||
String measurements = 'metric';
|
String measurements = 'metric';
|
||||||
String degrees = 'celsius';
|
String degrees = 'celsius';
|
||||||
|
String wind = 'kph';
|
||||||
|
String pressure = 'hPa';
|
||||||
String timeformat = '24';
|
String timeformat = '24';
|
||||||
String? language;
|
String? language;
|
||||||
int? timeRange;
|
int? timeRange;
|
||||||
|
@ -101,43 +105,43 @@ class MainWeatherCache {
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'time': time,
|
'time': time,
|
||||||
'weathercode': weathercode,
|
'weathercode': weathercode,
|
||||||
'temperature2M': temperature2M,
|
'temperature2M': temperature2M,
|
||||||
'apparentTemperature': apparentTemperature,
|
'apparentTemperature': apparentTemperature,
|
||||||
'relativehumidity2M': relativehumidity2M,
|
'relativehumidity2M': relativehumidity2M,
|
||||||
'precipitation': precipitation,
|
'precipitation': precipitation,
|
||||||
'rain': rain,
|
'rain': rain,
|
||||||
'surfacePressure': surfacePressure,
|
'surfacePressure': surfacePressure,
|
||||||
'visibility': visibility,
|
'visibility': visibility,
|
||||||
'evapotranspiration': evapotranspiration,
|
'evapotranspiration': evapotranspiration,
|
||||||
'windspeed10M': windspeed10M,
|
'windspeed10M': windspeed10M,
|
||||||
'winddirection10M': winddirection10M,
|
'winddirection10M': winddirection10M,
|
||||||
'windgusts10M': windgusts10M,
|
'windgusts10M': windgusts10M,
|
||||||
'cloudcover': cloudcover,
|
'cloudcover': cloudcover,
|
||||||
'uvIndex': uvIndex,
|
'uvIndex': uvIndex,
|
||||||
'dewpoint2M': dewpoint2M,
|
'dewpoint2M': dewpoint2M,
|
||||||
'precipitationProbability': precipitationProbability,
|
'precipitationProbability': precipitationProbability,
|
||||||
'shortwaveRadiation': shortwaveRadiation,
|
'shortwaveRadiation': shortwaveRadiation,
|
||||||
'timeDaily': timeDaily,
|
'timeDaily': timeDaily,
|
||||||
'weathercodeDaily': weathercodeDaily,
|
'weathercodeDaily': weathercodeDaily,
|
||||||
'temperature2MMax': temperature2MMax,
|
'temperature2MMax': temperature2MMax,
|
||||||
'temperature2MMin': temperature2MMin,
|
'temperature2MMin': temperature2MMin,
|
||||||
'apparentTemperatureMax': apparentTemperatureMax,
|
'apparentTemperatureMax': apparentTemperatureMax,
|
||||||
'apparentTemperatureMin': apparentTemperatureMin,
|
'apparentTemperatureMin': apparentTemperatureMin,
|
||||||
'sunrise': sunrise,
|
'sunrise': sunrise,
|
||||||
'sunset': sunset,
|
'sunset': sunset,
|
||||||
'precipitationSum': precipitationSum,
|
'precipitationSum': precipitationSum,
|
||||||
'precipitationProbabilityMax': precipitationProbabilityMax,
|
'precipitationProbabilityMax': precipitationProbabilityMax,
|
||||||
'windspeed10MMax': windspeed10MMax,
|
'windspeed10MMax': windspeed10MMax,
|
||||||
'windgusts10MMax': windgusts10MMax,
|
'windgusts10MMax': windgusts10MMax,
|
||||||
'uvIndexMax': uvIndexMax,
|
'uvIndexMax': uvIndexMax,
|
||||||
'rainSum': rainSum,
|
'rainSum': rainSum,
|
||||||
'winddirection10MDominant': winddirection10MDominant,
|
'winddirection10MDominant': winddirection10MDominant,
|
||||||
'timezone': timezone,
|
'timezone': timezone,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@collection
|
@collection
|
||||||
|
@ -148,12 +152,15 @@ class LocationCache {
|
||||||
String? city;
|
String? city;
|
||||||
String? district;
|
String? district;
|
||||||
|
|
||||||
LocationCache({
|
LocationCache({this.lat, this.lon, this.city, this.district});
|
||||||
this.lat,
|
|
||||||
this.lon,
|
Map<String, dynamic> toJson() => {
|
||||||
this.city,
|
'id': id,
|
||||||
this.district,
|
'lat': lat,
|
||||||
});
|
'lon': lon,
|
||||||
|
'city': city,
|
||||||
|
'district': district,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@collection
|
@collection
|
||||||
|
@ -244,56 +251,57 @@ class WeatherCard {
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'time': time,
|
'time': time,
|
||||||
'weathercode': weathercode,
|
'weathercode': weathercode,
|
||||||
'temperature2M': temperature2M,
|
'temperature2M': temperature2M,
|
||||||
'apparentTemperature': apparentTemperature,
|
'apparentTemperature': apparentTemperature,
|
||||||
'relativehumidity2M': relativehumidity2M,
|
'relativehumidity2M': relativehumidity2M,
|
||||||
'precipitation': precipitation,
|
'precipitation': precipitation,
|
||||||
'rain': rain,
|
'rain': rain,
|
||||||
'surfacePressure': surfacePressure,
|
'surfacePressure': surfacePressure,
|
||||||
'visibility': visibility,
|
'visibility': visibility,
|
||||||
'evapotranspiration': evapotranspiration,
|
'evapotranspiration': evapotranspiration,
|
||||||
'windspeed10M': windspeed10M,
|
'windspeed10M': windspeed10M,
|
||||||
'winddirection10M': winddirection10M,
|
'winddirection10M': winddirection10M,
|
||||||
'windgusts10M': windgusts10M,
|
'windgusts10M': windgusts10M,
|
||||||
'cloudcover': cloudcover,
|
'cloudcover': cloudcover,
|
||||||
'uvIndex': uvIndex,
|
'uvIndex': uvIndex,
|
||||||
'dewpoint2M': dewpoint2M,
|
'dewpoint2M': dewpoint2M,
|
||||||
'precipitationProbability': precipitationProbability,
|
'precipitationProbability': precipitationProbability,
|
||||||
'shortwaveRadiation': shortwaveRadiation,
|
'shortwaveRadiation': shortwaveRadiation,
|
||||||
'timeDaily': timeDaily,
|
'timeDaily': timeDaily,
|
||||||
'weathercodeDaily': weathercodeDaily,
|
'weathercodeDaily': weathercodeDaily,
|
||||||
'temperature2MMax': temperature2MMax,
|
'temperature2MMax': temperature2MMax,
|
||||||
'temperature2MMin': temperature2MMin,
|
'temperature2MMin': temperature2MMin,
|
||||||
'apparentTemperatureMax': apparentTemperatureMax,
|
'apparentTemperatureMax': apparentTemperatureMax,
|
||||||
'apparentTemperatureMin': apparentTemperatureMin,
|
'apparentTemperatureMin': apparentTemperatureMin,
|
||||||
'sunrise': sunrise,
|
'sunrise': sunrise,
|
||||||
'sunset': sunset,
|
'sunset': sunset,
|
||||||
'precipitationSum': precipitationSum,
|
'precipitationSum': precipitationSum,
|
||||||
'precipitationProbabilityMax': precipitationProbabilityMax,
|
'precipitationProbabilityMax': precipitationProbabilityMax,
|
||||||
'windspeed10MMax': windspeed10MMax,
|
'windspeed10MMax': windspeed10MMax,
|
||||||
'windgusts10MMax': windgusts10MMax,
|
'windgusts10MMax': windgusts10MMax,
|
||||||
'uvIndexMax': uvIndexMax,
|
'uvIndexMax': uvIndexMax,
|
||||||
'rainSum': rainSum,
|
'rainSum': rainSum,
|
||||||
'winddirection10MDominant': winddirection10MDominant,
|
'winddirection10MDominant': winddirection10MDominant,
|
||||||
'timezone': timezone,
|
'timezone': timezone,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'lat': lat,
|
'lat': lat,
|
||||||
'lon': lon,
|
'lon': lon,
|
||||||
'city': city,
|
'city': city,
|
||||||
'district': district,
|
'district': district,
|
||||||
'index': index,
|
'index': index,
|
||||||
};
|
};
|
||||||
|
|
||||||
factory WeatherCard.fromJson(Map<String, dynamic> json) {
|
factory WeatherCard.fromJson(Map<String, dynamic> json) {
|
||||||
return WeatherCard(
|
return WeatherCard(
|
||||||
time: List<String>.from(json['time'] ?? []),
|
time: List<String>.from(json['time'] ?? []),
|
||||||
weathercode: List<int>.from(json['weathercode'] ?? []),
|
weathercode: List<int>.from(json['weathercode'] ?? []),
|
||||||
temperature2M: List<double>.from(json['temperature2M'] ?? []),
|
temperature2M: List<double>.from(json['temperature2M'] ?? []),
|
||||||
apparentTemperature:
|
apparentTemperature: List<double?>.from(
|
||||||
List<double?>.from(json['apparentTemperature'] ?? []),
|
json['apparentTemperature'] ?? [],
|
||||||
|
),
|
||||||
relativehumidity2M: List<int?>.from(json['relativehumidity2M'] ?? []),
|
relativehumidity2M: List<int?>.from(json['relativehumidity2M'] ?? []),
|
||||||
precipitation: List<double>.from(json['precipitation'] ?? []),
|
precipitation: List<double>.from(json['precipitation'] ?? []),
|
||||||
rain: List<double?>.from(json['rain'] ?? []),
|
rain: List<double?>.from(json['rain'] ?? []),
|
||||||
|
@ -306,26 +314,31 @@ class WeatherCard {
|
||||||
cloudcover: List<int?>.from(json['cloudcover'] ?? []),
|
cloudcover: List<int?>.from(json['cloudcover'] ?? []),
|
||||||
uvIndex: List<double?>.from(json['uvIndex'] ?? []),
|
uvIndex: List<double?>.from(json['uvIndex'] ?? []),
|
||||||
dewpoint2M: List<double?>.from(json['dewpoint2M'] ?? []),
|
dewpoint2M: List<double?>.from(json['dewpoint2M'] ?? []),
|
||||||
precipitationProbability:
|
precipitationProbability: List<int?>.from(
|
||||||
List<int?>.from(json['precipitationProbability'] ?? []),
|
json['precipitationProbability'] ?? [],
|
||||||
|
),
|
||||||
shortwaveRadiation: List<double?>.from(json['shortwaveRadiation'] ?? []),
|
shortwaveRadiation: List<double?>.from(json['shortwaveRadiation'] ?? []),
|
||||||
timeDaily: List<DateTime>.from(json['timeDaily'] ?? []),
|
timeDaily: List<DateTime>.from(json['timeDaily'] ?? []),
|
||||||
weathercodeDaily: List<int?>.from(json['weathercodeDaily'] ?? []),
|
weathercodeDaily: List<int?>.from(json['weathercodeDaily'] ?? []),
|
||||||
temperature2MMax: List<double?>.from(json['temperature2MMax'] ?? []),
|
temperature2MMax: List<double?>.from(json['temperature2MMax'] ?? []),
|
||||||
temperature2MMin: List<double?>.from(json['temperature2MMin'] ?? []),
|
temperature2MMin: List<double?>.from(json['temperature2MMin'] ?? []),
|
||||||
apparentTemperatureMax:
|
apparentTemperatureMax: List<double?>.from(
|
||||||
List<double?>.from(json['apparentTemperatureMax'] ?? []),
|
json['apparentTemperatureMax'] ?? [],
|
||||||
apparentTemperatureMin:
|
),
|
||||||
List<double?>.from(json['apparentTemperatureMin'] ?? []),
|
apparentTemperatureMin: List<double?>.from(
|
||||||
|
json['apparentTemperatureMin'] ?? [],
|
||||||
|
),
|
||||||
windspeed10MMax: List<double?>.from(json['windspeed10MMax'] ?? []),
|
windspeed10MMax: List<double?>.from(json['windspeed10MMax'] ?? []),
|
||||||
windgusts10MMax: List<double?>.from(json['windgusts10MMax'] ?? []),
|
windgusts10MMax: List<double?>.from(json['windgusts10MMax'] ?? []),
|
||||||
uvIndexMax: List<double?>.from(json['uvIndexMax'] ?? []),
|
uvIndexMax: List<double?>.from(json['uvIndexMax'] ?? []),
|
||||||
rainSum: List<double?>.from(json['rainSum'] ?? []),
|
rainSum: List<double?>.from(json['rainSum'] ?? []),
|
||||||
winddirection10MDominant:
|
winddirection10MDominant: List<int?>.from(
|
||||||
List<int?>.from(json['winddirection10MDominant'] ?? []),
|
json['winddirection10MDominant'] ?? [],
|
||||||
|
),
|
||||||
precipitationSum: List<double?>.from(json['precipitationSum'] ?? []),
|
precipitationSum: List<double?>.from(json['precipitationSum'] ?? []),
|
||||||
precipitationProbabilityMax:
|
precipitationProbabilityMax: List<int?>.from(
|
||||||
List<int?>.from(json['precipitationProbabilityMax'] ?? []),
|
json['precipitationProbabilityMax'] ?? [],
|
||||||
|
),
|
||||||
sunrise: List<String>.from(json['sunrise'] ?? []),
|
sunrise: List<String>.from(json['sunrise'] ?? []),
|
||||||
sunset: List<String>.from(json['sunset'] ?? []),
|
sunset: List<String>.from(json['sunset'] ?? []),
|
||||||
lat: json['lat'],
|
lat: json['lat'],
|
576
lib/app/data/weather.g.dart → lib/app/data/db.g.dart
Normal file → Executable file
|
@ -1,6 +1,6 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'weather.dart';
|
part of 'db.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// IsarCollectionGenerator
|
// IsarCollectionGenerator
|
||||||
|
@ -27,75 +27,95 @@ const SettingsSchema = CollectionSchema(
|
||||||
name: r'degrees',
|
name: r'degrees',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'language': PropertySchema(
|
r'hideMap': PropertySchema(
|
||||||
id: 2,
|
id: 2,
|
||||||
|
name: r'hideMap',
|
||||||
|
type: IsarType.bool,
|
||||||
|
),
|
||||||
|
r'language': PropertySchema(
|
||||||
|
id: 3,
|
||||||
name: r'language',
|
name: r'language',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
|
r'largeElement': PropertySchema(
|
||||||
|
id: 4,
|
||||||
|
name: r'largeElement',
|
||||||
|
type: IsarType.bool,
|
||||||
|
),
|
||||||
r'location': PropertySchema(
|
r'location': PropertySchema(
|
||||||
id: 3,
|
id: 5,
|
||||||
name: r'location',
|
name: r'location',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'materialColor': PropertySchema(
|
r'materialColor': PropertySchema(
|
||||||
id: 4,
|
id: 6,
|
||||||
name: r'materialColor',
|
name: r'materialColor',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'measurements': PropertySchema(
|
r'measurements': PropertySchema(
|
||||||
id: 5,
|
id: 7,
|
||||||
name: r'measurements',
|
name: r'measurements',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'notifications': PropertySchema(
|
r'notifications': PropertySchema(
|
||||||
id: 6,
|
id: 8,
|
||||||
name: r'notifications',
|
name: r'notifications',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'onboard': PropertySchema(
|
r'onboard': PropertySchema(
|
||||||
id: 7,
|
id: 9,
|
||||||
name: r'onboard',
|
name: r'onboard',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
|
r'pressure': PropertySchema(
|
||||||
|
id: 10,
|
||||||
|
name: r'pressure',
|
||||||
|
type: IsarType.string,
|
||||||
|
),
|
||||||
r'roundDegree': PropertySchema(
|
r'roundDegree': PropertySchema(
|
||||||
id: 8,
|
id: 11,
|
||||||
name: r'roundDegree',
|
name: r'roundDegree',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'theme': PropertySchema(
|
r'theme': PropertySchema(
|
||||||
id: 9,
|
id: 12,
|
||||||
name: r'theme',
|
name: r'theme',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'timeEnd': PropertySchema(
|
r'timeEnd': PropertySchema(
|
||||||
id: 10,
|
id: 13,
|
||||||
name: r'timeEnd',
|
name: r'timeEnd',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'timeRange': PropertySchema(
|
r'timeRange': PropertySchema(
|
||||||
id: 11,
|
id: 14,
|
||||||
name: r'timeRange',
|
name: r'timeRange',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'timeStart': PropertySchema(
|
r'timeStart': PropertySchema(
|
||||||
id: 12,
|
id: 15,
|
||||||
name: r'timeStart',
|
name: r'timeStart',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'timeformat': PropertySchema(
|
r'timeformat': PropertySchema(
|
||||||
id: 13,
|
id: 16,
|
||||||
name: r'timeformat',
|
name: r'timeformat',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'widgetBackgroundColor': PropertySchema(
|
r'widgetBackgroundColor': PropertySchema(
|
||||||
id: 14,
|
id: 17,
|
||||||
name: r'widgetBackgroundColor',
|
name: r'widgetBackgroundColor',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'widgetTextColor': PropertySchema(
|
r'widgetTextColor': PropertySchema(
|
||||||
id: 15,
|
id: 18,
|
||||||
name: r'widgetTextColor',
|
name: r'widgetTextColor',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
|
),
|
||||||
|
r'wind': PropertySchema(
|
||||||
|
id: 19,
|
||||||
|
name: r'wind',
|
||||||
|
type: IsarType.string,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
estimateSize: _settingsEstimateSize,
|
estimateSize: _settingsEstimateSize,
|
||||||
|
@ -109,7 +129,7 @@ const SettingsSchema = CollectionSchema(
|
||||||
getId: _settingsGetId,
|
getId: _settingsGetId,
|
||||||
getLinks: _settingsGetLinks,
|
getLinks: _settingsGetLinks,
|
||||||
attach: _settingsAttach,
|
attach: _settingsAttach,
|
||||||
version: '3.1.0+1',
|
version: '3.1.8',
|
||||||
);
|
);
|
||||||
|
|
||||||
int _settingsEstimateSize(
|
int _settingsEstimateSize(
|
||||||
|
@ -126,6 +146,7 @@ int _settingsEstimateSize(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bytesCount += 3 + object.measurements.length * 3;
|
bytesCount += 3 + object.measurements.length * 3;
|
||||||
|
bytesCount += 3 + object.pressure.length * 3;
|
||||||
{
|
{
|
||||||
final value = object.theme;
|
final value = object.theme;
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
@ -157,6 +178,7 @@ int _settingsEstimateSize(
|
||||||
bytesCount += 3 + value.length * 3;
|
bytesCount += 3 + value.length * 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bytesCount += 3 + object.wind.length * 3;
|
||||||
return bytesCount;
|
return bytesCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,20 +190,24 @@ void _settingsSerialize(
|
||||||
) {
|
) {
|
||||||
writer.writeBool(offsets[0], object.amoledTheme);
|
writer.writeBool(offsets[0], object.amoledTheme);
|
||||||
writer.writeString(offsets[1], object.degrees);
|
writer.writeString(offsets[1], object.degrees);
|
||||||
writer.writeString(offsets[2], object.language);
|
writer.writeBool(offsets[2], object.hideMap);
|
||||||
writer.writeBool(offsets[3], object.location);
|
writer.writeString(offsets[3], object.language);
|
||||||
writer.writeBool(offsets[4], object.materialColor);
|
writer.writeBool(offsets[4], object.largeElement);
|
||||||
writer.writeString(offsets[5], object.measurements);
|
writer.writeBool(offsets[5], object.location);
|
||||||
writer.writeBool(offsets[6], object.notifications);
|
writer.writeBool(offsets[6], object.materialColor);
|
||||||
writer.writeBool(offsets[7], object.onboard);
|
writer.writeString(offsets[7], object.measurements);
|
||||||
writer.writeBool(offsets[8], object.roundDegree);
|
writer.writeBool(offsets[8], object.notifications);
|
||||||
writer.writeString(offsets[9], object.theme);
|
writer.writeBool(offsets[9], object.onboard);
|
||||||
writer.writeString(offsets[10], object.timeEnd);
|
writer.writeString(offsets[10], object.pressure);
|
||||||
writer.writeLong(offsets[11], object.timeRange);
|
writer.writeBool(offsets[11], object.roundDegree);
|
||||||
writer.writeString(offsets[12], object.timeStart);
|
writer.writeString(offsets[12], object.theme);
|
||||||
writer.writeString(offsets[13], object.timeformat);
|
writer.writeString(offsets[13], object.timeEnd);
|
||||||
writer.writeString(offsets[14], object.widgetBackgroundColor);
|
writer.writeLong(offsets[14], object.timeRange);
|
||||||
writer.writeString(offsets[15], object.widgetTextColor);
|
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(
|
Settings _settingsDeserialize(
|
||||||
|
@ -193,21 +219,25 @@ Settings _settingsDeserialize(
|
||||||
final object = Settings();
|
final object = Settings();
|
||||||
object.amoledTheme = reader.readBool(offsets[0]);
|
object.amoledTheme = reader.readBool(offsets[0]);
|
||||||
object.degrees = reader.readString(offsets[1]);
|
object.degrees = reader.readString(offsets[1]);
|
||||||
|
object.hideMap = reader.readBool(offsets[2]);
|
||||||
object.id = id;
|
object.id = id;
|
||||||
object.language = reader.readStringOrNull(offsets[2]);
|
object.language = reader.readStringOrNull(offsets[3]);
|
||||||
object.location = reader.readBool(offsets[3]);
|
object.largeElement = reader.readBool(offsets[4]);
|
||||||
object.materialColor = reader.readBool(offsets[4]);
|
object.location = reader.readBool(offsets[5]);
|
||||||
object.measurements = reader.readString(offsets[5]);
|
object.materialColor = reader.readBool(offsets[6]);
|
||||||
object.notifications = reader.readBool(offsets[6]);
|
object.measurements = reader.readString(offsets[7]);
|
||||||
object.onboard = reader.readBool(offsets[7]);
|
object.notifications = reader.readBool(offsets[8]);
|
||||||
object.roundDegree = reader.readBool(offsets[8]);
|
object.onboard = reader.readBool(offsets[9]);
|
||||||
object.theme = reader.readStringOrNull(offsets[9]);
|
object.pressure = reader.readString(offsets[10]);
|
||||||
object.timeEnd = reader.readStringOrNull(offsets[10]);
|
object.roundDegree = reader.readBool(offsets[11]);
|
||||||
object.timeRange = reader.readLongOrNull(offsets[11]);
|
object.theme = reader.readStringOrNull(offsets[12]);
|
||||||
object.timeStart = reader.readStringOrNull(offsets[12]);
|
object.timeEnd = reader.readStringOrNull(offsets[13]);
|
||||||
object.timeformat = reader.readString(offsets[13]);
|
object.timeRange = reader.readLongOrNull(offsets[14]);
|
||||||
object.widgetBackgroundColor = reader.readStringOrNull(offsets[14]);
|
object.timeStart = reader.readStringOrNull(offsets[15]);
|
||||||
object.widgetTextColor = 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;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,33 +253,41 @@ P _settingsDeserializeProp<P>(
|
||||||
case 1:
|
case 1:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 2:
|
case 2:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
|
||||||
case 3:
|
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
|
case 3:
|
||||||
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 4:
|
case 4:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 5:
|
case 5:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 6:
|
case 6:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 7:
|
case 7:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 8:
|
case 8:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 9:
|
case 9:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 10:
|
case 10:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 11:
|
case 11:
|
||||||
return (reader.readLongOrNull(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 12:
|
case 12:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 13:
|
case 13:
|
||||||
return (reader.readString(offset)) as P;
|
|
||||||
case 14:
|
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
|
case 14:
|
||||||
|
return (reader.readLongOrNull(offset)) as P;
|
||||||
case 15:
|
case 15:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
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:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
}
|
}
|
||||||
|
@ -484,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) {
|
QueryBuilder<Settings, Settings, QAfterFilterCondition> idEqualTo(Id value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
@ -682,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(
|
QueryBuilder<Settings, Settings, QAfterFilterCondition> locationEqualTo(
|
||||||
bool value) {
|
bool value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -856,6 +914,136 @@ 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(
|
QueryBuilder<Settings, Settings, QAfterFilterCondition> roundDegreeEqualTo(
|
||||||
bool value) {
|
bool value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -1813,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
|
extension SettingsQueryObject
|
||||||
|
@ -1846,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() {
|
QueryBuilder<Settings, Settings, QAfterSortBy> sortByLanguage() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'language', Sort.asc);
|
return query.addSortBy(r'language', Sort.asc);
|
||||||
|
@ -1858,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() {
|
QueryBuilder<Settings, Settings, QAfterSortBy> sortByLocation() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'location', Sort.asc);
|
return query.addSortBy(r'location', Sort.asc);
|
||||||
|
@ -1918,6 +2260,18 @@ 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() {
|
QueryBuilder<Settings, Settings, QAfterSortBy> sortByRoundDegree() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'roundDegree', Sort.asc);
|
return query.addSortBy(r'roundDegree', Sort.asc);
|
||||||
|
@ -2014,6 +2368,18 @@ extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
|
||||||
return query.addSortBy(r'widgetTextColor', Sort.desc);
|
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
|
extension SettingsQuerySortThenBy
|
||||||
|
@ -2042,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() {
|
QueryBuilder<Settings, Settings, QAfterSortBy> thenById() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'id', Sort.asc);
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
|
@ -2066,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() {
|
QueryBuilder<Settings, Settings, QAfterSortBy> thenByLocation() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'location', Sort.asc);
|
return query.addSortBy(r'location', Sort.asc);
|
||||||
|
@ -2126,6 +2516,18 @@ 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() {
|
QueryBuilder<Settings, Settings, QAfterSortBy> thenByRoundDegree() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'roundDegree', Sort.asc);
|
return query.addSortBy(r'roundDegree', Sort.asc);
|
||||||
|
@ -2222,6 +2624,18 @@ extension SettingsQuerySortThenBy
|
||||||
return query.addSortBy(r'widgetTextColor', Sort.desc);
|
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
|
extension SettingsQueryWhereDistinct
|
||||||
|
@ -2239,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(
|
QueryBuilder<Settings, Settings, QDistinct> distinctByLanguage(
|
||||||
{bool caseSensitive = true}) {
|
{bool caseSensitive = true}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -2246,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() {
|
QueryBuilder<Settings, Settings, QDistinct> distinctByLocation() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'location');
|
return query.addDistinctBy(r'location');
|
||||||
|
@ -2277,6 +2703,13 @@ 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() {
|
QueryBuilder<Settings, Settings, QDistinct> distinctByRoundDegree() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'roundDegree');
|
return query.addDistinctBy(r'roundDegree');
|
||||||
|
@ -2332,6 +2765,13 @@ extension SettingsQueryWhereDistinct
|
||||||
caseSensitive: caseSensitive);
|
caseSensitive: caseSensitive);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Settings, Settings, QDistinct> distinctByWind(
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'wind', caseSensitive: caseSensitive);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SettingsQueryProperty
|
extension SettingsQueryProperty
|
||||||
|
@ -2354,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() {
|
QueryBuilder<Settings, String?, QQueryOperations> languageProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'language');
|
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() {
|
QueryBuilder<Settings, bool, QQueryOperations> locationProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'location');
|
return query.addPropertyName(r'location');
|
||||||
|
@ -2390,6 +2842,12 @@ extension SettingsQueryProperty
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Settings, String, QQueryOperations> pressureProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'pressure');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Settings, bool, QQueryOperations> roundDegreeProperty() {
|
QueryBuilder<Settings, bool, QQueryOperations> roundDegreeProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'roundDegree');
|
return query.addPropertyName(r'roundDegree');
|
||||||
|
@ -2438,6 +2896,12 @@ extension SettingsQueryProperty
|
||||||
return query.addPropertyName(r'widgetTextColor');
|
return query.addPropertyName(r'widgetTextColor');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Settings, String, QQueryOperations> windProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'wind');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
|
@ -2638,7 +3102,7 @@ const MainWeatherCacheSchema = CollectionSchema(
|
||||||
getId: _mainWeatherCacheGetId,
|
getId: _mainWeatherCacheGetId,
|
||||||
getLinks: _mainWeatherCacheGetLinks,
|
getLinks: _mainWeatherCacheGetLinks,
|
||||||
attach: _mainWeatherCacheAttach,
|
attach: _mainWeatherCacheAttach,
|
||||||
version: '3.1.0+1',
|
version: '3.1.8',
|
||||||
);
|
);
|
||||||
|
|
||||||
int _mainWeatherCacheEstimateSize(
|
int _mainWeatherCacheEstimateSize(
|
||||||
|
@ -10361,7 +10825,7 @@ const LocationCacheSchema = CollectionSchema(
|
||||||
getId: _locationCacheGetId,
|
getId: _locationCacheGetId,
|
||||||
getLinks: _locationCacheGetLinks,
|
getLinks: _locationCacheGetLinks,
|
||||||
attach: _locationCacheAttach,
|
attach: _locationCacheAttach,
|
||||||
version: '3.1.0+1',
|
version: '3.1.8',
|
||||||
);
|
);
|
||||||
|
|
||||||
int _locationCacheEstimateSize(
|
int _locationCacheEstimateSize(
|
||||||
|
@ -11460,7 +11924,7 @@ const WeatherCardSchema = CollectionSchema(
|
||||||
getId: _weatherCardGetId,
|
getId: _weatherCardGetId,
|
||||||
getLinks: _weatherCardGetLinks,
|
getLinks: _weatherCardGetLinks,
|
||||||
attach: _weatherCardAttach,
|
attach: _weatherCardAttach,
|
||||||
version: '3.1.0+1',
|
version: '3.1.8',
|
||||||
);
|
);
|
||||||
|
|
||||||
int _weatherCardEstimateSize(
|
int _weatherCardEstimateSize(
|
|
@ -1,187 +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: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 135,
|
|
||||||
child: ScrollablePositionedList.separated(
|
|
||||||
key: const PageStorageKey(1),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,133 +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/icons/City.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!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,245 +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(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,171 +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: Obx(() {
|
|
||||||
if (weatherController.isLoading.isTrue) {
|
|
||||||
return ListView(
|
|
||||||
children: const [
|
|
||||||
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 ListView(
|
|
||||||
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: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 135,
|
|
||||||
child: ScrollablePositionedList.separated(
|
|
||||||
key: const PageStorageKey(0),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,922 +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) {
|
|
||||||
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: context.textTheme.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: context.textTheme.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: context.textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
content: Text('no_location'.tr,
|
|
||||||
style: context
|
|
||||||
.textTheme.titleMedium),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () =>
|
|
||||||
Get.back(result: false),
|
|
||||||
child: Text(
|
|
||||||
'cancel'.tr,
|
|
||||||
style: context
|
|
||||||
.textTheme.titleMedium
|
|
||||||
?.copyWith(
|
|
||||||
color:
|
|
||||||
Colors.blueAccent),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Geolocator
|
|
||||||
.openLocationSettings();
|
|
||||||
Get.back(result: true);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'settings'.tr,
|
|
||||||
style: context
|
|
||||||
.textTheme.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: context.textTheme.titleLarge?.copyWith(
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SettingCard(
|
|
||||||
elevation: 4,
|
|
||||||
icon: const Icon(Iconsax.cloud_notif),
|
|
||||||
text: 'roundDegree'.tr,
|
|
||||||
switcher: true,
|
|
||||||
value: settings.roundDegree,
|
|
||||||
onChange: (value) {
|
|
||||||
settings.roundDegree = value;
|
|
||||||
isar.writeTxnSync(
|
|
||||||
() => isar.settings.putSync(settings));
|
|
||||||
MyApp.updateAppState(context,
|
|
||||||
newRoundDegree: value);
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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: context.textTheme.titleLarge?.copyWith(
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SettingCard(
|
|
||||||
elevation: 4,
|
|
||||||
icon: const Icon(Iconsax.bucket_square),
|
|
||||||
text: 'widgetBackground'.tr,
|
|
||||||
info: true,
|
|
||||||
infoWidget: CircleAvatar(
|
|
||||||
backgroundColor: context.theme.indicatorColor,
|
|
||||||
radius: 11,
|
|
||||||
child: CircleAvatar(
|
|
||||||
backgroundColor: widgetBackgroundColor.isEmpty
|
|
||||||
? context.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: context.theme.copyWith(
|
|
||||||
inputDecorationTheme:
|
|
||||||
InputDecorationTheme(
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(
|
|
||||||
8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ColorPicker(
|
|
||||||
pickerColor: widgetBackgroundColor
|
|
||||||
.isEmpty
|
|
||||||
? context.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: context.theme.indicatorColor,
|
|
||||||
radius: 11,
|
|
||||||
child: CircleAvatar(
|
|
||||||
backgroundColor: widgetTextColor.isEmpty
|
|
||||||
? context.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: context.theme.copyWith(
|
|
||||||
inputDecorationTheme:
|
|
||||||
InputDecorationTheme(
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(
|
|
||||||
8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ColorPicker(
|
|
||||||
pickerColor: widgetTextColor
|
|
||||||
.isEmpty
|
|
||||||
? context.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: context.textTheme.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: context.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: context.textTheme.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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
final globalKey = GlobalKey<ScaffoldMessengerState>();
|
|
||||||
|
|
||||||
void showSnackBar({required String content, Function? onPressed}) {
|
|
||||||
globalKey.currentState?.showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(content),
|
|
||||||
action: onPressed != null
|
|
||||||
? SnackBarAction(
|
|
||||||
label: 'settings'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
onPressed();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
479
lib/app/ui/geolocation.dart
Executable file
|
@ -0,0 +1,479 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:rain/app/api/api.dart';
|
||||||
|
import 'package:rain/app/api/city_api.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/ui/home.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/button.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/text_form.dart';
|
||||||
|
import 'package:rain/main.dart';
|
||||||
|
|
||||||
|
class SelectGeolocation extends StatefulWidget {
|
||||||
|
const SelectGeolocation({super.key, required this.isStart});
|
||||||
|
final bool isStart;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SelectGeolocation> createState() => _SelectGeolocationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectGeolocationState extends State<SelectGeolocation> {
|
||||||
|
bool isLoading = false;
|
||||||
|
final formKeySearch = 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();
|
||||||
|
|
||||||
|
static const kTextFieldElevation = 4.0;
|
||||||
|
|
||||||
|
static const colorFilter = ColorFilter.matrix(<double>[
|
||||||
|
-0.2, -0.7, -0.08, 0, 255, // Red channel
|
||||||
|
-0.2, -0.7, -0.08, 0, 255, // Green channel
|
||||||
|
-0.2, -0.7, -0.08, 0, 255, // Blue channel
|
||||||
|
0, 0, 0, 1, 0, // Alpha channel
|
||||||
|
]);
|
||||||
|
|
||||||
|
final bool _isDarkMode = Get.theme.brightness == Brightness.dark;
|
||||||
|
final mapController = MapController();
|
||||||
|
|
||||||
|
void textTrim(TextEditingController value) {
|
||||||
|
value.text = value.text.trim();
|
||||||
|
while (value.text.contains(' ')) {
|
||||||
|
value.text = value.text.replaceAll(' ', ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillController(Result selection) {
|
||||||
|
_controllerLat.text = '${selection.latitude}';
|
||||||
|
_controllerLon.text = '${selection.longitude}';
|
||||||
|
_controllerCity.text = selection.name;
|
||||||
|
_controllerDistrict.text = selection.admin1;
|
||||||
|
_controller.clear();
|
||||||
|
_focusNode.unfocus();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillControllerGeo(Map<String, dynamic> location) {
|
||||||
|
_controllerLat.text = '${location['lat']}';
|
||||||
|
_controllerLon.text = '${location['lon']}';
|
||||||
|
_controllerCity.text = location['district'];
|
||||||
|
_controllerDistrict.text = location['city'];
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillMap(double latitude, double longitude) {
|
||||||
|
_controllerLat.text = '$latitude';
|
||||||
|
_controllerLon.text = '$longitude';
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMapTileLayer() {
|
||||||
|
return TileLayer(
|
||||||
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
|
userAgentPackageName: 'com.darkmoonight.rain',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMap() {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
child: SizedBox(
|
||||||
|
height: Get.size.height * 0.3,
|
||||||
|
child: FlutterMap(
|
||||||
|
mapController: mapController,
|
||||||
|
options: MapOptions(
|
||||||
|
backgroundColor: context.theme.colorScheme.surface,
|
||||||
|
initialCenter: const LatLng(55.7522, 37.6156),
|
||||||
|
initialZoom: 3,
|
||||||
|
interactionOptions: const InteractionOptions(
|
||||||
|
flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
|
||||||
|
),
|
||||||
|
cameraConstraint: CameraConstraint.contain(
|
||||||
|
bounds: LatLngBounds(
|
||||||
|
const LatLng(-90, -180),
|
||||||
|
const LatLng(90, 180),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onLongPress: (tapPosition, point) =>
|
||||||
|
fillMap(point.latitude, point.longitude),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
if (_isDarkMode)
|
||||||
|
ColorFiltered(
|
||||||
|
colorFilter: colorFilter,
|
||||||
|
child: _buildMapTileLayer(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_buildMapTileLayer(),
|
||||||
|
RichAttributionWidget(
|
||||||
|
animationConfig: const ScaleRAWA(),
|
||||||
|
attributions: [
|
||||||
|
TextSourceAttribution(
|
||||||
|
'OpenStreetMap contributors',
|
||||||
|
onTap: () => weatherController.urlLauncher(
|
||||||
|
'https://openstreetmap.org/copyright',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchField() {
|
||||||
|
return 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(IconsaxPlusLinear.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 _buildOptionsView(context, onSelected, options);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOptionsView(
|
||||||
|
BuildContext context,
|
||||||
|
AutocompleteOnSelected<Result> onSelected,
|
||||||
|
Iterable<Result> options,
|
||||||
|
) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLocationButton() {
|
||||||
|
return Card(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
margin: const EdgeInsets.only(top: 10, right: 10),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(2.5),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: _handleLocationButtonPress,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.location),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleLocationButtonPress() async {
|
||||||
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||||
|
if (!serviceEnabled) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
await _showLocationDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() => isLoading = true);
|
||||||
|
final location = await weatherController.getCurrentLocationSearch();
|
||||||
|
fillControllerGeo(location);
|
||||||
|
setState(() => isLoading = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showLocationDialog() async {
|
||||||
|
await showAdaptiveDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog.adaptive(
|
||||||
|
title: Text('location'.tr, style: context.textTheme.titleLarge),
|
||||||
|
content: Text('no_location'.tr, style: context.textTheme.titleMedium),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(result: false),
|
||||||
|
child: Text(
|
||||||
|
'cancel'.tr,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Geolocator.openLocationSettings();
|
||||||
|
Get.back(result: true);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'settings'.tr,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLatitudeField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerLat,
|
||||||
|
labelText: 'lat'.tr,
|
||||||
|
type: TextInputType.number,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.location),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
validator: (value) => _validateLatitude(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLongitudeField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerLon,
|
||||||
|
labelText: 'lon'.tr,
|
||||||
|
type: TextInputType.number,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.location),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
validator: (value) => _validateLongitude(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCityField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerCity,
|
||||||
|
labelText: 'city'.tr,
|
||||||
|
type: TextInputType.name,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.building_3),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
validator: (value) => _validateCity(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDistrictField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerDistrict,
|
||||||
|
labelText: 'district'.tr,
|
||||||
|
type: TextInputType.streetAddress,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.global),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSubmitButton() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
|
child: MyTextButton(buttonName: 'done'.tr, onPressed: _handleSubmit),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateLatitude(String? 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateLongitude(String? 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateCity(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'validateName'.tr;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleSubmit() async {
|
||||||
|
if (formKeySearch.currentState!.validate()) {
|
||||||
|
textTrim(_controllerLat);
|
||||||
|
textTrim(_controllerLon);
|
||||||
|
textTrim(_controllerCity);
|
||||||
|
textTrim(_controllerDistrict);
|
||||||
|
try {
|
||||||
|
await weatherController.deleteAll(true);
|
||||||
|
await weatherController.getLocation(
|
||||||
|
double.parse(_controllerLat.text),
|
||||||
|
double.parse(_controllerLon.text),
|
||||||
|
_controllerDistrict.text,
|
||||||
|
_controllerCity.text,
|
||||||
|
);
|
||||||
|
widget.isStart
|
||||||
|
? Get.off(() => const HomePage(), transition: Transition.downToUp)
|
||||||
|
: Get.back();
|
||||||
|
} catch (error) {
|
||||||
|
Future.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Form(
|
||||||
|
key: formKeySearch,
|
||||||
|
child: Scaffold(
|
||||||
|
resizeToAvoidBottomInset: true,
|
||||||
|
appBar: _buildAppBar(),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
),
|
||||||
|
child: _buildMap(),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
10,
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'searchMethod'.tr,
|
||||||
|
style: context.theme.textTheme.bodyLarge
|
||||||
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(child: _buildSearchField()),
|
||||||
|
_buildLocationButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_buildLatitudeField(),
|
||||||
|
_buildLongitudeField(),
|
||||||
|
_buildCityField(),
|
||||||
|
_buildDistrictField(),
|
||||||
|
const Gap(20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildSubmitButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isLoading)
|
||||||
|
BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
|
||||||
|
child: const Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppBar _buildAppBar() {
|
||||||
|
return AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
leading: widget.isStart
|
||||||
|
? null
|
||||||
|
: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
title: Text(
|
||||||
|
'searchCity'.tr,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
311
lib/app/ui/home.dart
Executable file
|
@ -0,0 +1,311 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:rain/app/api/api.dart';
|
||||||
|
import 'package:rain/app/api/city_api.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/places/view/place_list.dart';
|
||||||
|
import 'package:rain/app/ui/places/widgets/create_place.dart';
|
||||||
|
import 'package:rain/app/ui/geolocation.dart';
|
||||||
|
import 'package:rain/app/ui/main/view/main.dart';
|
||||||
|
import 'package:rain/app/ui/map/view/map.dart';
|
||||||
|
import 'package:rain/app/ui/settings/view/settings.dart';
|
||||||
|
import 'package:rain/app/utils/show_snack_bar.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 List<Widget> pages = [
|
||||||
|
const MainPage(),
|
||||||
|
const PlaceList(),
|
||||||
|
if (!settings.hideMap) const MapPage(),
|
||||||
|
const SettingsPage(),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getData();
|
||||||
|
setupTabController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupTabController() {
|
||||||
|
tabController = TabController(
|
||||||
|
initialIndex: tabIndex,
|
||||||
|
length: pages.length,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
tabController.animation?.addListener(() {
|
||||||
|
int value = (tabController.animation!.value).round();
|
||||||
|
if (value != tabIndex) setState(() => tabIndex = value);
|
||||||
|
});
|
||||||
|
|
||||||
|
tabController.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
tabIndex = tabController.index;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void getData() async {
|
||||||
|
await weatherController.deleteCache();
|
||||||
|
await weatherController.updateCacheCard(false);
|
||||||
|
await weatherController.setLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeTabIndex(int index) {
|
||||||
|
setState(() {
|
||||||
|
tabIndex = index;
|
||||||
|
});
|
||||||
|
tabController.animateTo(tabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAppBarTitle(
|
||||||
|
int tabIndex,
|
||||||
|
TextStyle? textStyle,
|
||||||
|
TextStyle? labelLarge,
|
||||||
|
) {
|
||||||
|
switch (tabIndex) {
|
||||||
|
case 0:
|
||||||
|
return visible
|
||||||
|
? _buildSearchField(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: textStyle,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
case 1:
|
||||||
|
return Text('cities'.tr, style: textStyle);
|
||||||
|
case 2:
|
||||||
|
return settings.hideMap
|
||||||
|
? Text('settings_full'.tr, style: textStyle)
|
||||||
|
: Text('map'.tr, style: textStyle);
|
||||||
|
case 3:
|
||||||
|
return Text('settings_full'.tr, style: textStyle);
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchField(TextStyle? labelLarge) {
|
||||||
|
return 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 _buildOptionsView(context, onSelected, options, labelLarge);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOptionsView(
|
||||||
|
BuildContext context,
|
||||||
|
AutocompleteOnSelected<Result> onSelected,
|
||||||
|
Iterable<Result> options,
|
||||||
|
TextStyle? labelLarge,
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchIconButton() {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (visible) {
|
||||||
|
_controller.clear();
|
||||||
|
_focusNode.unfocus();
|
||||||
|
visible = false;
|
||||||
|
} else {
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
visible
|
||||||
|
? IconsaxPlusLinear.close_circle
|
||||||
|
: IconsaxPlusLinear.search_normal_1,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = context.textTheme;
|
||||||
|
final labelLarge = textTheme.labelLarge;
|
||||||
|
|
||||||
|
final textStyle = textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 18,
|
||||||
|
);
|
||||||
|
|
||||||
|
return DefaultTabController(
|
||||||
|
length: pages.length,
|
||||||
|
child: ScaffoldMessenger(
|
||||||
|
key: globalKey,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
leading:
|
||||||
|
tabIndex == 0
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.to(
|
||||||
|
() => const SelectGeolocation(isStart: false),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
IconsaxPlusLinear.global_search,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
title: _buildAppBarTitle(tabIndex, textStyle, labelLarge),
|
||||||
|
actions: tabIndex == 0 ? [_buildSearchIconButton()] : null,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: TabBarView(controller: tabController, children: pages),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: NavigationBar(
|
||||||
|
onDestinationSelected: (int index) => changeTabIndex(index),
|
||||||
|
selectedIndex: tabIndex,
|
||||||
|
destinations: [
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(IconsaxPlusLinear.cloud_sunny),
|
||||||
|
selectedIcon: const Icon(IconsaxPlusBold.cloud_sunny),
|
||||||
|
label: 'name'.tr,
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(IconsaxPlusLinear.buildings),
|
||||||
|
selectedIcon: const Icon(IconsaxPlusBold.buildings),
|
||||||
|
label: 'cities'.tr,
|
||||||
|
),
|
||||||
|
if (!settings.hideMap)
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(IconsaxPlusLinear.map),
|
||||||
|
selectedIcon: const Icon(IconsaxPlusBold.map),
|
||||||
|
label: 'map'.tr,
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(IconsaxPlusLinear.category),
|
||||||
|
selectedIcon: const Icon(IconsaxPlusBold.category),
|
||||||
|
label: 'settings_full'.tr,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton:
|
||||||
|
tabIndex == 1
|
||||||
|
? FloatingActionButton(
|
||||||
|
onPressed:
|
||||||
|
() => showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
enableDrag: false,
|
||||||
|
builder:
|
||||||
|
(BuildContext context) => const CreatePlace(),
|
||||||
|
),
|
||||||
|
child: const Icon(IconsaxPlusLinear.add),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
242
lib/app/ui/main/view/main.dart
Executable file
|
@ -0,0 +1,242 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/daily/daily_card_list.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/daily/daily_container.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/hourly.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/now.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/shimmer.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
|
||||||
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
|
class MainPage extends StatefulWidget {
|
||||||
|
const MainPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MainPage> createState() => _MainPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainPageState extends State<MainPage> {
|
||||||
|
final weatherController = Get.put(WeatherController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: _handleRefresh,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: Obx(() {
|
||||||
|
if (weatherController.isLoading.isTrue) {
|
||||||
|
return _buildLoadingView();
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
final tempMax = mainWeather.temperature2MMax![dayOfNow];
|
||||||
|
final tempMin = mainWeather.temperature2MMin![dayOfNow];
|
||||||
|
|
||||||
|
return _buildMainView(
|
||||||
|
context,
|
||||||
|
mainWeather,
|
||||||
|
weatherCard,
|
||||||
|
hourOfDay,
|
||||||
|
dayOfNow,
|
||||||
|
sunrise,
|
||||||
|
sunset,
|
||||||
|
tempMax!,
|
||||||
|
tempMin!,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleRefresh() async {
|
||||||
|
await weatherController.deleteAll(false);
|
||||||
|
await weatherController.setLocation();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLoadingView() {
|
||||||
|
return ListView(
|
||||||
|
children: const [
|
||||||
|
MyShimmer(height: 200),
|
||||||
|
MyShimmer(height: 130, margin: EdgeInsets.symmetric(vertical: 15)),
|
||||||
|
MyShimmer(height: 90, margin: EdgeInsets.only(bottom: 15)),
|
||||||
|
MyShimmer(height: 400, margin: EdgeInsets.only(bottom: 15)),
|
||||||
|
MyShimmer(height: 450, margin: EdgeInsets.only(bottom: 15)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMainView(
|
||||||
|
BuildContext context,
|
||||||
|
MainWeatherCache mainWeather,
|
||||||
|
WeatherCard weatherCard,
|
||||||
|
int hourOfDay,
|
||||||
|
int dayOfNow,
|
||||||
|
String sunrise,
|
||||||
|
String sunset,
|
||||||
|
double tempMax,
|
||||||
|
double tempMin,
|
||||||
|
) {
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
_buildNowWidget(
|
||||||
|
mainWeather,
|
||||||
|
hourOfDay,
|
||||||
|
dayOfNow,
|
||||||
|
sunrise,
|
||||||
|
sunset,
|
||||||
|
tempMax,
|
||||||
|
tempMin,
|
||||||
|
),
|
||||||
|
_buildHourlyList(context, mainWeather, hourOfDay, dayOfNow),
|
||||||
|
_buildSunsetSunriseWidget(sunrise, sunset),
|
||||||
|
_buildHourlyDescContainer(mainWeather, hourOfDay),
|
||||||
|
_buildDailyContainer(weatherCard),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNowWidget(
|
||||||
|
MainWeatherCache mainWeather,
|
||||||
|
int hourOfDay,
|
||||||
|
int dayOfNow,
|
||||||
|
String sunrise,
|
||||||
|
String sunset,
|
||||||
|
double tempMax,
|
||||||
|
double tempMin,
|
||||||
|
) {
|
||||||
|
return Now(
|
||||||
|
time: mainWeather.time![hourOfDay],
|
||||||
|
weather: mainWeather.weathercode![hourOfDay],
|
||||||
|
degree: mainWeather.temperature2M![hourOfDay],
|
||||||
|
feels: mainWeather.apparentTemperature![hourOfDay]!,
|
||||||
|
timeDay: sunrise,
|
||||||
|
timeNight: sunset,
|
||||||
|
tempMax: tempMax,
|
||||||
|
tempMin: tempMin,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyList(
|
||||||
|
BuildContext context,
|
||||||
|
MainWeatherCache mainWeather,
|
||||||
|
int hourOfDay,
|
||||||
|
int dayOfNow,
|
||||||
|
) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 135,
|
||||||
|
child: ScrollablePositionedList.separated(
|
||||||
|
key: const PageStorageKey(0),
|
||||||
|
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) {
|
||||||
|
return _buildHourlyItem(
|
||||||
|
context,
|
||||||
|
mainWeather,
|
||||||
|
i,
|
||||||
|
hourOfDay,
|
||||||
|
dayOfNow,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyItem(
|
||||||
|
BuildContext context,
|
||||||
|
MainWeatherCache mainWeather,
|
||||||
|
int i,
|
||||||
|
int hourOfDay,
|
||||||
|
int dayOfNow,
|
||||||
|
) {
|
||||||
|
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.secondaryContainer
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Hourly(
|
||||||
|
time: mainWeather.time![i],
|
||||||
|
weather: mainWeather.weathercode![i],
|
||||||
|
degree: mainWeather.temperature2M![i],
|
||||||
|
timeDay: mainWeather.sunrise![i24],
|
||||||
|
timeNight: mainWeather.sunset![i24],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSunsetSunriseWidget(String sunrise, String sunset) {
|
||||||
|
return SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyDescContainer(
|
||||||
|
MainWeatherCache mainWeather,
|
||||||
|
int hourOfDay,
|
||||||
|
) {
|
||||||
|
return 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],
|
||||||
|
initiallyExpanded: false,
|
||||||
|
title: 'hourlyVariables'.tr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDailyContainer(WeatherCard weatherCard) {
|
||||||
|
return DailyContainer(
|
||||||
|
weatherData: weatherCard,
|
||||||
|
onTap: () => Get.to(
|
||||||
|
() => DailyCardList(weatherData: weatherCard),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
477
lib/app/ui/map/view/map.dart
Executable file
|
@ -0,0 +1,477 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
||||||
|
import 'package:flutter_map_cache/flutter_map_cache.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:http_cache_file_store/http_cache_file_store.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:rain/app/api/api.dart';
|
||||||
|
import 'package:rain/app/api/city_api.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/places/view/place_info.dart';
|
||||||
|
import 'package:rain/app/ui/places/widgets/create_place.dart';
|
||||||
|
import 'package:rain/app/ui/places/widgets/place_card.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/app/ui/widgets/text_form.dart';
|
||||||
|
import 'package:rain/main.dart';
|
||||||
|
|
||||||
|
class MapPage extends StatefulWidget {
|
||||||
|
const MapPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MapPage> createState() => _MapPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
||||||
|
late final AnimatedMapController _animatedMapController =
|
||||||
|
AnimatedMapController(vsync: this);
|
||||||
|
final weatherController = Get.put(WeatherController());
|
||||||
|
final statusWeather = StatusWeather();
|
||||||
|
final statusData = StatusData();
|
||||||
|
final Future<CacheStore> _cacheStoreFuture = _getCacheStore();
|
||||||
|
|
||||||
|
final GlobalKey<ExpandableFabState> _fabKey = GlobalKey<ExpandableFabState>();
|
||||||
|
|
||||||
|
final bool _isDarkMode = Get.theme.brightness == Brightness.dark;
|
||||||
|
WeatherCard? _selectedWeatherCard;
|
||||||
|
bool _isCardVisible = false;
|
||||||
|
late final AnimationController _animationController;
|
||||||
|
late final Animation<Offset> _offsetAnimation;
|
||||||
|
static const _useTransformerId = 'useTransformerId';
|
||||||
|
final bool _useTransformer = true;
|
||||||
|
|
||||||
|
final _focusNode = FocusNode();
|
||||||
|
late final TextEditingController _controllerSearch = TextEditingController();
|
||||||
|
|
||||||
|
static Future<CacheStore> _getCacheStore() async {
|
||||||
|
final dir = await getTemporaryDirectory();
|
||||||
|
return FileCacheStore('${dir.path}${Platform.pathSeparator}MapTiles');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
_offsetAnimation = Tween<Offset>(
|
||||||
|
begin: const Offset(0.0, 1.0),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(
|
||||||
|
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
||||||
|
);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animatedMapController.dispose();
|
||||||
|
_controllerSearch.dispose();
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetMapOrientation({LatLng? center, double? zoom}) {
|
||||||
|
_animatedMapController.animateTo(
|
||||||
|
customId: _useTransformer ? _useTransformerId : null,
|
||||||
|
dest: center,
|
||||||
|
zoom: zoom,
|
||||||
|
rotation: 0,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onMarkerTap(WeatherCard weatherCard) {
|
||||||
|
setState(() {
|
||||||
|
_selectedWeatherCard = weatherCard;
|
||||||
|
});
|
||||||
|
_animationController.forward();
|
||||||
|
_isCardVisible = true;
|
||||||
|
|
||||||
|
if (_fabKey.currentState?.isOpen == true) {
|
||||||
|
_fabKey.currentState?.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hideCard() {
|
||||||
|
_animationController.reverse().then((_) {
|
||||||
|
setState(() {
|
||||||
|
_isCardVisible = false;
|
||||||
|
_selectedWeatherCard = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_focusNode.unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStyleMarkers(
|
||||||
|
int weathercode,
|
||||||
|
String time,
|
||||||
|
String sunrise,
|
||||||
|
String sunset,
|
||||||
|
double temperature2M,
|
||||||
|
) {
|
||||||
|
return Card(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
statusWeather.getImageNow(weathercode, time, sunrise, sunset),
|
||||||
|
scale: 18,
|
||||||
|
),
|
||||||
|
const MaxGap(5),
|
||||||
|
Text(
|
||||||
|
statusData.getDegree(
|
||||||
|
roundDegree ? temperature2M.round() : temperature2M,
|
||||||
|
),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marker _buildMainLocationMarker(
|
||||||
|
WeatherCard weatherCard,
|
||||||
|
int hourOfDay,
|
||||||
|
int dayOfNow,
|
||||||
|
) {
|
||||||
|
return Marker(
|
||||||
|
height: 50,
|
||||||
|
width: 100,
|
||||||
|
point: LatLng(weatherCard.lat!, weatherCard.lon!),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _onMarkerTap(weatherCard),
|
||||||
|
child: _buildStyleMarkers(
|
||||||
|
weatherCard.weathercode![hourOfDay],
|
||||||
|
weatherCard.time![hourOfDay],
|
||||||
|
weatherCard.sunrise![dayOfNow],
|
||||||
|
weatherCard.sunset![dayOfNow],
|
||||||
|
weatherCard.temperature2M![hourOfDay],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marker _buildCardMarker(WeatherCard weatherCardList) {
|
||||||
|
final hourOfDay = weatherController.getTime(
|
||||||
|
weatherCardList.time!,
|
||||||
|
weatherCardList.timezone!,
|
||||||
|
);
|
||||||
|
final dayOfNow = weatherController.getDay(
|
||||||
|
weatherCardList.timeDaily!,
|
||||||
|
weatherCardList.timezone!,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Marker(
|
||||||
|
height: 50,
|
||||||
|
width: 100,
|
||||||
|
point: LatLng(weatherCardList.lat!, weatherCardList.lon!),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _onMarkerTap(weatherCardList),
|
||||||
|
child: _buildStyleMarkers(
|
||||||
|
weatherCardList.weathercode![hourOfDay],
|
||||||
|
weatherCardList.time![hourOfDay],
|
||||||
|
weatherCardList.sunrise![dayOfNow],
|
||||||
|
weatherCardList.sunset![dayOfNow],
|
||||||
|
weatherCardList.temperature2M![hourOfDay],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMapTileLayer(CacheStore cacheStore) {
|
||||||
|
return TileLayer(
|
||||||
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
|
userAgentPackageName: 'com.darkmoonight.rain',
|
||||||
|
tileProvider: CachedTileProvider(
|
||||||
|
store: cacheStore,
|
||||||
|
maxStale: const Duration(days: 30),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherCard() {
|
||||||
|
return _isCardVisible && _selectedWeatherCard != null
|
||||||
|
? SlideTransition(
|
||||||
|
position: _offsetAnimation,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap:
|
||||||
|
() => Get.to(
|
||||||
|
() => PlaceInfo(weatherCard: _selectedWeatherCard!),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
),
|
||||||
|
child: PlaceCard(
|
||||||
|
time: _selectedWeatherCard!.time!,
|
||||||
|
timeDaily: _selectedWeatherCard!.timeDaily!,
|
||||||
|
timeDay: _selectedWeatherCard!.sunrise!,
|
||||||
|
timeNight: _selectedWeatherCard!.sunset!,
|
||||||
|
weather: _selectedWeatherCard!.weathercode!,
|
||||||
|
degree: _selectedWeatherCard!.temperature2M!,
|
||||||
|
district: _selectedWeatherCard!.district!,
|
||||||
|
city: _selectedWeatherCard!.city!,
|
||||||
|
timezone: _selectedWeatherCard!.timezone!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchField() {
|
||||||
|
return RawAutocomplete<Result>(
|
||||||
|
focusNode: _focusNode,
|
||||||
|
textEditingController: _controllerSearch,
|
||||||
|
fieldViewBuilder: (
|
||||||
|
BuildContext context,
|
||||||
|
TextEditingController fieldTextEditingController,
|
||||||
|
FocusNode fieldFocusNode,
|
||||||
|
VoidCallback onFieldSubmitted,
|
||||||
|
) {
|
||||||
|
return MyTextForm(
|
||||||
|
labelText: 'search'.tr,
|
||||||
|
type: TextInputType.text,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.global_search),
|
||||||
|
controller: _controllerSearch,
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
focusNode: _focusNode,
|
||||||
|
onChanged: (value) => setState(() {}),
|
||||||
|
iconButton:
|
||||||
|
_controllerSearch.text.isNotEmpty
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_controllerSearch.clear();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
IconsaxPlusLinear.close_circle,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
|
if (textEditingValue.text.isEmpty) {
|
||||||
|
return const Iterable<Result>.empty();
|
||||||
|
}
|
||||||
|
return WeatherAPI().getCity(textEditingValue.text, locale);
|
||||||
|
},
|
||||||
|
onSelected: (Result selection) {
|
||||||
|
_animatedMapController.mapController.move(
|
||||||
|
LatLng(selection.latitude, selection.longitude),
|
||||||
|
14,
|
||||||
|
);
|
||||||
|
_controllerSearch.clear();
|
||||||
|
_focusNode.unfocus();
|
||||||
|
},
|
||||||
|
displayStringForOption:
|
||||||
|
(Result option) => '${option.name}, ${option.admin1}',
|
||||||
|
optionsViewBuilder: (
|
||||||
|
BuildContext context,
|
||||||
|
AutocompleteOnSelected<Result> onSelected,
|
||||||
|
Iterable<Result> options,
|
||||||
|
) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final mainLocation = weatherController.location;
|
||||||
|
final mainWeather = weatherController.mainWeather;
|
||||||
|
|
||||||
|
final hourOfDay = weatherController.hourOfDay.value;
|
||||||
|
final dayOfNow = weatherController.dayOfNow.value;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: FutureBuilder<CacheStore>(
|
||||||
|
future: _cacheStoreFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Center(child: Text(snapshot.error.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final cacheStore = snapshot.data!;
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
FlutterMap(
|
||||||
|
mapController: _animatedMapController.mapController,
|
||||||
|
options: MapOptions(
|
||||||
|
backgroundColor: context.theme.colorScheme.surface,
|
||||||
|
initialCenter: LatLng(mainLocation.lat!, mainLocation.lon!),
|
||||||
|
initialZoom: 8,
|
||||||
|
interactionOptions: const InteractionOptions(
|
||||||
|
flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
|
||||||
|
),
|
||||||
|
cameraConstraint: CameraConstraint.contain(
|
||||||
|
bounds: LatLngBounds(
|
||||||
|
const LatLng(-90, -180),
|
||||||
|
const LatLng(90, 180),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: (_, __) => _hideCard(),
|
||||||
|
onLongPress:
|
||||||
|
(tapPosition, point) => showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
enableDrag: false,
|
||||||
|
builder:
|
||||||
|
(BuildContext context) => CreatePlace(
|
||||||
|
latitude: '${point.latitude}',
|
||||||
|
longitude: '${point.longitude}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
if (_isDarkMode)
|
||||||
|
ColorFiltered(
|
||||||
|
colorFilter: const ColorFilter.matrix(<double>[
|
||||||
|
-0.2, -0.7, -0.08, 0, 255, // Red channel
|
||||||
|
-0.2, -0.7, -0.08, 0, 255, // Green channel
|
||||||
|
-0.2, -0.7, -0.08, 0, 255, // Blue channel
|
||||||
|
0, 0, 0, 1, 0, // Alpha channel
|
||||||
|
]),
|
||||||
|
child: _buildMapTileLayer(cacheStore),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_buildMapTileLayer(cacheStore),
|
||||||
|
RichAttributionWidget(
|
||||||
|
animationConfig: const ScaleRAWA(),
|
||||||
|
alignment: AttributionAlignment.bottomLeft,
|
||||||
|
attributions: [
|
||||||
|
TextSourceAttribution(
|
||||||
|
'OpenStreetMap contributors',
|
||||||
|
onTap:
|
||||||
|
() => weatherController.urlLauncher(
|
||||||
|
'https://openstreetmap.org/copyright',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
final mainMarker = _buildMainLocationMarker(
|
||||||
|
WeatherCard.fromJson({
|
||||||
|
...mainWeather.toJson(),
|
||||||
|
...mainLocation.toJson(),
|
||||||
|
}),
|
||||||
|
hourOfDay,
|
||||||
|
dayOfNow,
|
||||||
|
);
|
||||||
|
|
||||||
|
final cardMarkers =
|
||||||
|
weatherController.weatherCards
|
||||||
|
.map(
|
||||||
|
(weatherCardList) =>
|
||||||
|
_buildCardMarker(weatherCardList),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return MarkerLayer(markers: [mainMarker, ...cardMarkers]);
|
||||||
|
}),
|
||||||
|
ExpandableFab(
|
||||||
|
key: _fabKey,
|
||||||
|
pos: ExpandableFabPos.right,
|
||||||
|
type: ExpandableFabType.up,
|
||||||
|
distance: 70,
|
||||||
|
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||||
|
child: const Icon(IconsaxPlusLinear.menu),
|
||||||
|
fabSize: ExpandableFabSize.regular,
|
||||||
|
),
|
||||||
|
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
||||||
|
child: const Icon(Icons.close),
|
||||||
|
fabSize: ExpandableFabSize.regular,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
child: const Icon(IconsaxPlusLinear.home_2),
|
||||||
|
onPressed:
|
||||||
|
() => _resetMapOrientation(
|
||||||
|
center: LatLng(
|
||||||
|
mainLocation.lat!,
|
||||||
|
mainLocation.lon!,
|
||||||
|
),
|
||||||
|
zoom: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
child: const Icon(IconsaxPlusLinear.search_zoom_out_1),
|
||||||
|
onPressed:
|
||||||
|
() => _animatedMapController.animatedZoomOut(
|
||||||
|
customId:
|
||||||
|
_useTransformer ? _useTransformerId : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
child: const Icon(IconsaxPlusLinear.search_zoom_in),
|
||||||
|
onPressed:
|
||||||
|
() => _animatedMapController.animatedZoomIn(
|
||||||
|
customId:
|
||||||
|
_useTransformer ? _useTransformerId : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: _buildWeatherCard(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_buildSearchField(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
152
lib/app/modules/onboarding.dart → lib/app/ui/onboarding.dart
Normal file → Executable file
|
@ -1,8 +1,9 @@
|
||||||
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';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/geolocation.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/button.dart';
|
||||||
|
import 'package:rain/main.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class OnBording extends StatefulWidget {
|
class OnBording extends StatefulWidget {
|
||||||
|
@ -18,8 +19,8 @@ class _OnBordingState extends State<OnBording> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
pageController = PageController(initialPage: 0);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
pageController = PageController(initialPage: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,69 +32,82 @@ class _OnBordingState extends State<OnBording> {
|
||||||
void onBoardHome() {
|
void onBoardHome() {
|
||||||
settings.onboard = true;
|
settings.onboard = true;
|
||||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
||||||
Get.off(() => const HomePage(), transition: Transition.downToUp);
|
Get.off(
|
||||||
|
() => const SelectGeolocation(isStart: true),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
_buildPageView(),
|
||||||
child: PageView.builder(
|
_buildDotIndicators(),
|
||||||
controller: pageController,
|
_buildActionButton(),
|
||||||
itemCount: data.length,
|
|
||||||
onPageChanged: (index) {
|
|
||||||
setState(() {
|
|
||||||
pageIndex = index;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
itemBuilder: (context, index) => OnboardContent(
|
|
||||||
image: data[index].image,
|
|
||||||
title: data[index].title,
|
|
||||||
description: data[index].description,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
...List.generate(
|
|
||||||
data.length,
|
|
||||||
(index) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
|
||||||
child: DotIndicator(isActive: index == pageIndex),
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
|
||||||
child: MyTextButton(
|
|
||||||
buttonName:
|
|
||||||
pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
|
|
||||||
onPressed: () {
|
|
||||||
pageIndex == data.length - 1
|
|
||||||
? onBoardHome()
|
|
||||||
: pageController.nextPage(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.ease,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildPageView() {
|
||||||
|
return Expanded(
|
||||||
|
child: PageView.builder(
|
||||||
|
controller: pageController,
|
||||||
|
itemCount: data.length,
|
||||||
|
onPageChanged: (index) {
|
||||||
|
setState(() {
|
||||||
|
pageIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
itemBuilder: (context, index) => OnboardContent(
|
||||||
|
image: data[index].image,
|
||||||
|
title: data[index].title,
|
||||||
|
description: data[index].description,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDotIndicators() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: List.generate(
|
||||||
|
data.length,
|
||||||
|
(index) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||||
|
child: DotIndicator(isActive: index == pageIndex),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildActionButton() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
|
child: MyTextButton(
|
||||||
|
buttonName: pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
|
||||||
|
onPressed: () {
|
||||||
|
if (pageIndex == data.length - 1) {
|
||||||
|
onBoardHome();
|
||||||
|
} else {
|
||||||
|
pageController.nextPage(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DotIndicator extends StatelessWidget {
|
class DotIndicator extends StatelessWidget {
|
||||||
const DotIndicator({
|
const DotIndicator({super.key, this.isActive = false});
|
||||||
super.key,
|
|
||||||
this.isActive = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
|
||||||
|
@ -125,17 +139,20 @@ class Onboard {
|
||||||
|
|
||||||
final List<Onboard> data = [
|
final List<Onboard> data = [
|
||||||
Onboard(
|
Onboard(
|
||||||
image: 'assets/icons/Rain.png',
|
image: 'assets/icons/Rain.png',
|
||||||
title: 'name'.tr,
|
title: 'name'.tr,
|
||||||
description: 'description'.tr),
|
description: 'description'.tr,
|
||||||
|
),
|
||||||
Onboard(
|
Onboard(
|
||||||
image: 'assets/icons/Design.png',
|
image: 'assets/icons/Design.png',
|
||||||
title: 'name2'.tr,
|
title: 'name2'.tr,
|
||||||
description: 'description2'.tr),
|
description: 'description2'.tr,
|
||||||
|
),
|
||||||
Onboard(
|
Onboard(
|
||||||
image: 'assets/icons/Team.png',
|
image: 'assets/icons/Team.png',
|
||||||
title: 'name3'.tr,
|
title: 'name3'.tr,
|
||||||
description: 'description3'.tr),
|
description: 'description3'.tr,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
class OnboardContent extends StatelessWidget {
|
class OnboardContent extends StatelessWidget {
|
||||||
|
@ -145,6 +162,7 @@ class OnboardContent extends StatelessWidget {
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.description,
|
required this.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String image, title, description;
|
final String image, title, description;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -155,16 +173,14 @@ class OnboardContent extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Image.asset(image, scale: 5),
|
||||||
image,
|
|
||||||
scale: 5,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: context.textTheme.titleLarge
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
?.copyWith(fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const Gap(10),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 300,
|
width: 300,
|
||||||
child: Text(
|
child: Text(
|
212
lib/app/ui/places/view/place_info.dart
Executable file
|
@ -0,0 +1,212 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/daily/daily_card_list.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/daily/daily_container.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/hourly.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/now.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
|
||||||
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
|
class PlaceInfo extends StatefulWidget {
|
||||||
|
const PlaceInfo({super.key, required this.weatherCard});
|
||||||
|
final WeatherCard weatherCard;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlaceInfo> createState() => _PlaceInfoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaceInfoState extends State<PlaceInfo> {
|
||||||
|
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: _handleRefresh,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: _buildAppBar(context, weatherCard),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
_buildNowWidget(weatherCard),
|
||||||
|
_buildHourlyList(weatherCard),
|
||||||
|
_buildSunsetSunriseWidget(weatherCard),
|
||||||
|
_buildHourlyDescContainer(weatherCard),
|
||||||
|
_buildDailyContainer(weatherCard),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleRefresh() async {
|
||||||
|
await weatherController.updateCard(widget.weatherCard);
|
||||||
|
getTime();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
AppBar _buildAppBar(BuildContext context, WeatherCard weatherCard) {
|
||||||
|
return AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
weatherCard.district!.isNotEmpty
|
||||||
|
? '${weatherCard.city}, ${weatherCard.district}'
|
||||||
|
: '${weatherCard.city}',
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNowWidget(WeatherCard weatherCard) {
|
||||||
|
return Now(
|
||||||
|
time: weatherCard.time![timeNow],
|
||||||
|
weather: weatherCard.weathercode![timeNow],
|
||||||
|
degree: weatherCard.temperature2M![timeNow],
|
||||||
|
feels: weatherCard.apparentTemperature![timeNow]!,
|
||||||
|
timeDay: weatherCard.sunrise![dayNow],
|
||||||
|
timeNight: weatherCard.sunset![dayNow],
|
||||||
|
tempMax: weatherCard.temperature2MMax![dayNow]!,
|
||||||
|
tempMin: weatherCard.temperature2MMin![dayNow]!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyList(WeatherCard weatherCard) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 135,
|
||||||
|
child: ScrollablePositionedList.separated(
|
||||||
|
key: const PageStorageKey(1),
|
||||||
|
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) => _buildHourlyItem(weatherCard, i),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyItem(WeatherCard weatherCard, int i) {
|
||||||
|
return 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.secondaryContainer
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Hourly(
|
||||||
|
time: weatherCard.time![i],
|
||||||
|
weather: weatherCard.weathercode![i],
|
||||||
|
degree: weatherCard.temperature2M![i],
|
||||||
|
timeDay: weatherCard.sunrise![(i / 24).floor()],
|
||||||
|
timeNight: weatherCard.sunset![(i / 24).floor()],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSunsetSunriseWidget(WeatherCard weatherCard) {
|
||||||
|
return SunsetSunrise(
|
||||||
|
timeSunrise: weatherCard.sunrise![dayNow],
|
||||||
|
timeSunset: weatherCard.sunset![dayNow],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyDescContainer(WeatherCard weatherCard) {
|
||||||
|
return 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],
|
||||||
|
initiallyExpanded: false,
|
||||||
|
title: 'hourlyVariables'.tr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDailyContainer(WeatherCard weatherCard) {
|
||||||
|
return DailyContainer(
|
||||||
|
weatherData: weatherCard,
|
||||||
|
onTap:
|
||||||
|
() => Get.to(
|
||||||
|
() => DailyCardList(weatherData: weatherCard),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
116
lib/app/ui/places/view/place_list.dart
Executable file
|
@ -0,0 +1,116 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/ui/places/widgets/place_card_list.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/text_form.dart';
|
||||||
|
|
||||||
|
class PlaceList extends StatefulWidget {
|
||||||
|
const PlaceList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlaceList> createState() => _PlaceListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaceListState extends State<PlaceList> {
|
||||||
|
final weatherController = Get.put(WeatherController());
|
||||||
|
final TextEditingController searchTasks = TextEditingController();
|
||||||
|
String filter = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
applyFilter('');
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyFilter(String value) {
|
||||||
|
filter = value.toLowerCase();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearSearch() {
|
||||||
|
searchTasks.clear();
|
||||||
|
applyFilter('');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = context.textTheme;
|
||||||
|
final titleMedium = textTheme.titleMedium;
|
||||||
|
|
||||||
|
return Obx(() => _buildContent(context, titleMedium));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(BuildContext context, TextStyle? titleMedium) {
|
||||||
|
if (weatherController.weatherCards.isEmpty) {
|
||||||
|
return _buildEmptyState(context, titleMedium);
|
||||||
|
} else {
|
||||||
|
return _buildListView(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyState(BuildContext context, TextStyle? titleMedium) {
|
||||||
|
return Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.asset('assets/icons/City.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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListView(BuildContext context) {
|
||||||
|
return NestedScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||||
|
return [_buildSearchField(context)];
|
||||||
|
},
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: _handleRefresh,
|
||||||
|
child: PlaceCardList(searchCity: filter),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchField(BuildContext context) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: MyTextForm(
|
||||||
|
labelText: 'search'.tr,
|
||||||
|
type: TextInputType.text,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.search_normal_1, size: 20),
|
||||||
|
controller: searchTasks,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
onChanged: applyFilter,
|
||||||
|
iconButton:
|
||||||
|
searchTasks.text.isNotEmpty
|
||||||
|
? IconButton(
|
||||||
|
onPressed: clearSearch,
|
||||||
|
icon: const Icon(
|
||||||
|
IconsaxPlusLinear.close_circle,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleRefresh() async {
|
||||||
|
await weatherController.updateCacheCard(true);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
346
lib/app/ui/places/widgets/create_place.dart
Executable file
|
@ -0,0 +1,346 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:rain/app/api/api.dart';
|
||||||
|
import 'package:rain/app/api/city_api.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/button.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/text_form.dart';
|
||||||
|
import 'package:rain/main.dart';
|
||||||
|
|
||||||
|
class CreatePlace extends StatefulWidget {
|
||||||
|
const CreatePlace({super.key, this.latitude, this.longitude});
|
||||||
|
final String? latitude;
|
||||||
|
final String? longitude;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CreatePlace> createState() => _CreatePlaceState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreatePlaceState extends State<CreatePlace>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
bool isLoading = false;
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final _focusNode = FocusNode();
|
||||||
|
final weatherController = Get.put(WeatherController());
|
||||||
|
|
||||||
|
static const kTextFieldElevation = 4.0;
|
||||||
|
|
||||||
|
late TextEditingController _controller;
|
||||||
|
late TextEditingController _controllerLat;
|
||||||
|
late TextEditingController _controllerLon;
|
||||||
|
late TextEditingController _controllerCity;
|
||||||
|
late TextEditingController _controllerDistrict;
|
||||||
|
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = TextEditingController();
|
||||||
|
_controllerLat = TextEditingController(text: widget.latitude);
|
||||||
|
_controllerLon = TextEditingController(text: widget.longitude);
|
||||||
|
_controllerCity = TextEditingController();
|
||||||
|
_controllerDistrict = TextEditingController();
|
||||||
|
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_animation = CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
_controller.dispose();
|
||||||
|
_controllerLat.dispose();
|
||||||
|
_controllerLon.dispose();
|
||||||
|
_controllerCity.dispose();
|
||||||
|
_controllerDistrict.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void textTrim(TextEditingController value) {
|
||||||
|
value.text = value.text.trim();
|
||||||
|
while (value.text.contains(' ')) {
|
||||||
|
value.text = value.text.replaceAll(' ', ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillController(Result selection) {
|
||||||
|
_controllerLat.text = '${selection.latitude}';
|
||||||
|
_controllerLon.text = '${selection.longitude}';
|
||||||
|
_controllerCity.text = selection.name;
|
||||||
|
_controllerDistrict.text = selection.admin1;
|
||||||
|
_controller.clear();
|
||||||
|
_focusNode.unfocus();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get showButton {
|
||||||
|
return _controllerLon.text.isNotEmpty &&
|
||||||
|
_controllerLat.text.isNotEmpty &&
|
||||||
|
_controllerCity.text.isNotEmpty &&
|
||||||
|
_controllerDistrict.text.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateButtonVisibility() {
|
||||||
|
if (showButton) {
|
||||||
|
_animationController.forward();
|
||||||
|
} else {
|
||||||
|
_animationController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
updateButtonVisibility();
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
child: 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: [
|
||||||
|
_buildTitleText(),
|
||||||
|
_buildSearchField(),
|
||||||
|
_buildLatitudeField(),
|
||||||
|
_buildLongitudeField(),
|
||||||
|
_buildCityField(),
|
||||||
|
_buildDistrictField(),
|
||||||
|
_buildSubmitButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isLoading) const Center(child: CircularProgressIndicator()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTitleText() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 14, bottom: 7),
|
||||||
|
child: Text(
|
||||||
|
'create'.tr,
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchField() {
|
||||||
|
return 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(IconsaxPlusLinear.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 _buildOptionsView(context, onSelected, options);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOptionsView(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLatitudeField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerLat,
|
||||||
|
labelText: 'lat'.tr,
|
||||||
|
type: TextInputType.number,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.location),
|
||||||
|
onChanged: (value) => setState(() {}),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
validator: (value) => _validateLatitude(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLongitudeField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerLon,
|
||||||
|
labelText: 'lon'.tr,
|
||||||
|
type: TextInputType.number,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.location),
|
||||||
|
onChanged: (value) => setState(() {}),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
validator: (value) => _validateLongitude(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCityField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerCity,
|
||||||
|
labelText: 'city'.tr,
|
||||||
|
type: TextInputType.name,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.building_3),
|
||||||
|
onChanged: (value) => setState(() {}),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
validator: (value) => _validateCity(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDistrictField() {
|
||||||
|
return MyTextForm(
|
||||||
|
elevation: kTextFieldElevation,
|
||||||
|
controller: _controllerDistrict,
|
||||||
|
labelText: 'district'.tr,
|
||||||
|
type: TextInputType.streetAddress,
|
||||||
|
icon: const Icon(IconsaxPlusLinear.global),
|
||||||
|
onChanged: (value) => setState(() {}),
|
||||||
|
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||||
|
validator: (value) => _validateDistrict(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSubmitButton() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||||
|
child: SizeTransition(
|
||||||
|
sizeFactor: _animation,
|
||||||
|
axisAlignment: -1.0,
|
||||||
|
child: MyTextButton(buttonName: 'done'.tr, onPressed: _handleSubmit),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateLatitude(String? 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateLongitude(String? 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateCity(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'validateName'.tr;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateDistrict(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'validateName'.tr;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleSubmit() 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
155
lib/app/ui/places/widgets/place_card.dart
Executable file
|
@ -0,0 +1,155 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
|
||||||
|
import 'package:timezone/standalone.dart' as tz;
|
||||||
|
|
||||||
|
class PlaceCard extends StatefulWidget {
|
||||||
|
const PlaceCard({
|
||||||
|
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<PlaceCard> createState() => _PlaceCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaceCardState extends State<PlaceCard> {
|
||||||
|
final statusWeather = StatusWeather();
|
||||||
|
final statusData = StatusData();
|
||||||
|
final weatherController = Get.put(WeatherController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final currentTimeIndex = weatherController.getTime(
|
||||||
|
widget.time,
|
||||||
|
widget.timezone,
|
||||||
|
);
|
||||||
|
final currentDayIndex = weatherController.getDay(
|
||||||
|
widget.timeDaily,
|
||||||
|
widget.timezone,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildWeatherInfo(context, currentTimeIndex, currentDayIndex),
|
||||||
|
const Gap(5),
|
||||||
|
_buildWeatherImage(currentTimeIndex, currentDayIndex),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherInfo(
|
||||||
|
BuildContext context,
|
||||||
|
int currentTimeIndex,
|
||||||
|
int currentDayIndex,
|
||||||
|
) {
|
||||||
|
return Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
statusData.getDegree(
|
||||||
|
widget.degree[currentTimeIndex].round().toInt(),
|
||||||
|
),
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(7),
|
||||||
|
Text(
|
||||||
|
statusWeather.getText(widget.weather[currentTimeIndex]),
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Gap(10),
|
||||||
|
_buildLocationText(),
|
||||||
|
const Gap(5),
|
||||||
|
_buildCurrentTimeText(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLocationText() {
|
||||||
|
String locationText;
|
||||||
|
|
||||||
|
if (widget.district.isEmpty) {
|
||||||
|
locationText = widget.city;
|
||||||
|
} else if (widget.city.isEmpty) {
|
||||||
|
locationText = widget.district;
|
||||||
|
} else if (widget.city == widget.district) {
|
||||||
|
locationText = widget.city;
|
||||||
|
} else {
|
||||||
|
locationText = '${widget.city}, ${widget.district}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text(
|
||||||
|
locationText,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCurrentTimeText(BuildContext context) {
|
||||||
|
return 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherImage(int currentTimeIndex, int currentDayIndex) {
|
||||||
|
return Image.asset(
|
||||||
|
statusWeather.getImageNow(
|
||||||
|
widget.weather[currentTimeIndex],
|
||||||
|
widget.time[currentTimeIndex],
|
||||||
|
widget.timeDay[currentDayIndex],
|
||||||
|
widget.timeNight[currentDayIndex],
|
||||||
|
),
|
||||||
|
scale: 6.5,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
155
lib/app/ui/places/widgets/place_card_list.dart
Executable file
|
@ -0,0 +1,155 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:rain/app/controller/controller.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/places/view/place_info.dart';
|
||||||
|
import 'package:rain/app/ui/places/widgets/place_card.dart';
|
||||||
|
|
||||||
|
class PlaceCardList extends StatefulWidget {
|
||||||
|
const PlaceCardList({super.key, required this.searchCity});
|
||||||
|
final String searchCity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlaceCardList> createState() => _PlaceCardListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaceCardListState extends State<PlaceCardList> {
|
||||||
|
final weatherController = Get.put(WeatherController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = context.textTheme;
|
||||||
|
final titleMedium = textTheme.titleMedium;
|
||||||
|
|
||||||
|
final weatherCards = _filterWeatherCards(
|
||||||
|
weatherController.weatherCards,
|
||||||
|
widget.searchCity,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ReorderableListView(
|
||||||
|
onReorder:
|
||||||
|
(oldIndex, newIndex) => weatherController.reorder(oldIndex, newIndex),
|
||||||
|
children: _buildWeatherCardList(
|
||||||
|
weatherCards,
|
||||||
|
context,
|
||||||
|
textTheme,
|
||||||
|
titleMedium,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WeatherCard> _filterWeatherCards(
|
||||||
|
List<WeatherCard> weatherCards,
|
||||||
|
String searchCity,
|
||||||
|
) {
|
||||||
|
return weatherCards
|
||||||
|
.where(
|
||||||
|
(weatherCard) =>
|
||||||
|
(searchCity.isEmpty ||
|
||||||
|
weatherCard.city!.toLowerCase().contains(searchCity)),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildWeatherCardList(
|
||||||
|
List<WeatherCard> weatherCards,
|
||||||
|
BuildContext context,
|
||||||
|
TextTheme textTheme,
|
||||||
|
TextStyle? titleMedium,
|
||||||
|
) {
|
||||||
|
return weatherCards
|
||||||
|
.map(
|
||||||
|
(weatherCardList) => _buildDismissibleCard(
|
||||||
|
context,
|
||||||
|
weatherCardList,
|
||||||
|
textTheme,
|
||||||
|
titleMedium,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDismissibleCard(
|
||||||
|
BuildContext context,
|
||||||
|
WeatherCard weatherCardList,
|
||||||
|
TextTheme textTheme,
|
||||||
|
TextStyle? titleMedium,
|
||||||
|
) {
|
||||||
|
return Dismissible(
|
||||||
|
key: ValueKey(weatherCardList),
|
||||||
|
direction: DismissDirection.endToStart,
|
||||||
|
background: _buildDismissibleBackground(),
|
||||||
|
confirmDismiss:
|
||||||
|
(DismissDirection direction) =>
|
||||||
|
_showDeleteConfirmationDialog(context, textTheme, titleMedium),
|
||||||
|
onDismissed: (DismissDirection direction) async {
|
||||||
|
await weatherController.deleteCardWeather(weatherCardList);
|
||||||
|
},
|
||||||
|
child: _buildCardGestureDetector(weatherCardList),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDismissibleBackground() {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 15),
|
||||||
|
child: Icon(IconsaxPlusLinear.trash_square, color: Colors.red),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _showDeleteConfirmationDialog(
|
||||||
|
BuildContext context,
|
||||||
|
TextTheme textTheme,
|
||||||
|
TextStyle? titleMedium,
|
||||||
|
) 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCardGestureDetector(WeatherCard weatherCardList) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap:
|
||||||
|
() => Get.to(
|
||||||
|
() => PlaceInfo(weatherCard: weatherCardList),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
),
|
||||||
|
child: PlaceCard(
|
||||||
|
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!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1275
lib/app/ui/settings/view/settings.dart
Executable file
107
lib/app/ui/settings/widgets/setting_card.dart
Executable file
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.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.dropdownChange,
|
||||||
|
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 ValueChanged<String?>? dropdownChange;
|
||||||
|
final bool? value;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final ValueChanged<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(15)),
|
||||||
|
onTap: onPressed,
|
||||||
|
leading: icon,
|
||||||
|
title: Text(
|
||||||
|
text,
|
||||||
|
style: context.textTheme.titleMedium,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
),
|
||||||
|
trailing: _buildTrailingWidget(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTrailingWidget(BuildContext context) {
|
||||||
|
if (switcher) {
|
||||||
|
return _buildSwitchWidget();
|
||||||
|
} else if (dropdown) {
|
||||||
|
return _buildDropdownWidget();
|
||||||
|
} else if (info) {
|
||||||
|
return _buildInfoWidget();
|
||||||
|
} else {
|
||||||
|
return const Icon(IconsaxPlusLinear.arrow_right_3, size: 18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSwitchWidget() {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: 0.8,
|
||||||
|
child: Switch(value: value!, onChanged: onChange),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDropdownWidget() {
|
||||||
|
return DropdownButton<String>(
|
||||||
|
icon: const Padding(
|
||||||
|
padding: EdgeInsets.only(left: 7),
|
||||||
|
child: Icon(IconsaxPlusLinear.arrow_down),
|
||||||
|
),
|
||||||
|
iconSize: 15,
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
|
underline: Container(),
|
||||||
|
value: dropdownName,
|
||||||
|
items: dropdownList!.map<DropdownMenuItem<String>>((String value) {
|
||||||
|
return DropdownMenuItem<String>(value: value, child: Text(value));
|
||||||
|
}).toList(),
|
||||||
|
onChanged: dropdownChange,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoWidget() {
|
||||||
|
if (infoSettings) {
|
||||||
|
return Wrap(
|
||||||
|
children: [
|
||||||
|
infoWidget!,
|
||||||
|
const Icon(IconsaxPlusLinear.arrow_right_3, size: 18),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return infoWidget!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
lib/app/ui/widgets/button.dart
Executable file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class MyTextButton extends StatelessWidget {
|
||||||
|
const MyTextButton({
|
||||||
|
super.key,
|
||||||
|
required this.buttonName,
|
||||||
|
required this.onPressed,
|
||||||
|
this.height = 50.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String buttonName;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final double height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: height,
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: _buildButtonStyle(context),
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: Text(buttonName, style: context.textTheme.titleMedium),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonStyle _buildButtonStyle(BuildContext context) {
|
||||||
|
return ButtonStyle(
|
||||||
|
shadowColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
context.theme.colorScheme.secondaryContainer.withAlpha(80),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
lib/app/widgets/shimmer.dart → lib/app/ui/widgets/shimmer.dart
Normal file → Executable file
|
@ -3,25 +3,21 @@ import 'package:get/get.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
class MyShimmer extends StatelessWidget {
|
class MyShimmer extends StatelessWidget {
|
||||||
const MyShimmer({
|
const MyShimmer({super.key, required this.height, this.margin});
|
||||||
super.key,
|
|
||||||
required this.hight,
|
final double height;
|
||||||
this.edgeInsetsMargin,
|
final EdgeInsets? margin;
|
||||||
});
|
|
||||||
final double hight;
|
|
||||||
final EdgeInsets? edgeInsetsMargin;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Shimmer.fromColors(
|
return Shimmer.fromColors(
|
||||||
baseColor: context.theme.cardColor,
|
baseColor: context.theme.cardColor,
|
||||||
highlightColor: context.theme.primaryColor,
|
highlightColor: context.theme.primaryColor,
|
||||||
child: Card(
|
child: _buildShimmerCard(),
|
||||||
margin: edgeInsetsMargin,
|
|
||||||
child: SizedBox(
|
|
||||||
height: hight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildShimmerCard() {
|
||||||
|
return Card(margin: margin, child: SizedBox(height: height));
|
||||||
|
}
|
||||||
}
|
}
|
37
lib/app/widgets/text_form.dart → lib/app/ui/widgets/text_form.dart
Normal file → Executable file
|
@ -13,7 +13,9 @@ class MyTextForm extends StatelessWidget {
|
||||||
this.validator,
|
this.validator,
|
||||||
this.elevation,
|
this.elevation,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
|
this.onChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String labelText;
|
final String labelText;
|
||||||
final TextInputType type;
|
final TextInputType type;
|
||||||
final Icon icon;
|
final Icon icon;
|
||||||
|
@ -23,24 +25,35 @@ class MyTextForm extends StatelessWidget {
|
||||||
final String? Function(String?)? validator;
|
final String? Function(String?)? validator;
|
||||||
final double? elevation;
|
final double? elevation;
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
|
final Function(String)? onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
elevation: elevation,
|
elevation: elevation,
|
||||||
margin: margin,
|
margin: margin,
|
||||||
child: TextFormField(
|
child: _buildTextFormField(context),
|
||||||
focusNode: focusNode,
|
);
|
||||||
controller: controller,
|
}
|
||||||
keyboardType: type,
|
|
||||||
style: context.textTheme.labelLarge,
|
Widget _buildTextFormField(BuildContext context) {
|
||||||
decoration: InputDecoration(
|
return TextFormField(
|
||||||
prefixIcon: icon,
|
focusNode: focusNode,
|
||||||
suffixIcon: iconButton,
|
controller: controller,
|
||||||
labelText: labelText,
|
keyboardType: type,
|
||||||
),
|
style: context.textTheme.labelLarge,
|
||||||
validator: validator,
|
decoration: _buildInputDecoration(),
|
||||||
),
|
validator: validator,
|
||||||
|
onChanged: onChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputDecoration _buildInputDecoration() {
|
||||||
|
return InputDecoration(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12.5, vertical: 0),
|
||||||
|
prefixIcon: icon,
|
||||||
|
suffixIcon: iconButton,
|
||||||
|
labelText: labelText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
99
lib/app/ui/widgets/weather/daily/daily_card.dart
Executable file
|
@ -0,0 +1,99 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
|
||||||
|
import 'package:rain/main.dart';
|
||||||
|
|
||||||
|
class DailyCard extends StatefulWidget {
|
||||||
|
const DailyCard({
|
||||||
|
super.key,
|
||||||
|
required this.timeDaily,
|
||||||
|
required this.weathercodeDaily,
|
||||||
|
required this.temperature2MMax,
|
||||||
|
required this.temperature2MMin,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime timeDaily;
|
||||||
|
final int? weathercodeDaily;
|
||||||
|
final double? temperature2MMax;
|
||||||
|
final double? temperature2MMin;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DailyCard> createState() => _DailyCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DailyCardState extends State<DailyCard> {
|
||||||
|
final statusWeather = StatusWeather();
|
||||||
|
final statusData = StatusData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.weathercodeDaily == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildTemperatureInfo(context),
|
||||||
|
const Gap(5),
|
||||||
|
_buildWeatherImage(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTemperatureInfo(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${statusData.getDegree(widget.temperature2MMin?.round())} / ${statusData.getDegree(widget.temperature2MMax?.round())}',
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(5),
|
||||||
|
_buildDateText(context),
|
||||||
|
const Gap(5),
|
||||||
|
_buildWeatherDescription(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDateText(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
DateFormat.MMMMEEEEd(locale.languageCode).format(widget.timeDaily),
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherDescription(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
statusWeather.getText(widget.weathercodeDaily),
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherImage() {
|
||||||
|
return Image.asset(
|
||||||
|
statusWeather.getImageNowDaily(widget.weathercodeDaily),
|
||||||
|
scale: 6.5,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
348
lib/app/ui/widgets/weather/daily/daily_card_info.dart
Executable file
|
@ -0,0 +1,348 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/desc/message.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/hourly.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/now.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/app/ui/widgets/weather/sunset_sunrise.dart';
|
||||||
|
import 'package:rain/main.dart';
|
||||||
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
|
class DailyCardInfo extends StatefulWidget {
|
||||||
|
const DailyCardInfo({
|
||||||
|
super.key,
|
||||||
|
required this.weatherData,
|
||||||
|
required this.index,
|
||||||
|
});
|
||||||
|
|
||||||
|
final WeatherCard weatherData;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DailyCardInfo> createState() => _DailyCardInfoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DailyCardInfoState extends State<DailyCardInfo> {
|
||||||
|
final statusWeather = StatusWeather();
|
||||||
|
final statusData = StatusData();
|
||||||
|
final message = Message();
|
||||||
|
late PageController pageController;
|
||||||
|
int pageIndex = 0;
|
||||||
|
int hourOfDay = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
pageController = PageController(initialPage: widget.index);
|
||||||
|
pageIndex = widget.index;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
pageController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final weatherData = widget.weatherData;
|
||||||
|
final timeDaily = weatherData.timeDaily ?? [];
|
||||||
|
|
||||||
|
final textTheme = context.textTheme;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: _buildAppBar(context, textTheme, timeDaily),
|
||||||
|
body: SafeArea(
|
||||||
|
child: PageView.builder(
|
||||||
|
controller: pageController,
|
||||||
|
onPageChanged: (index) {
|
||||||
|
setState(() {
|
||||||
|
pageIndex = index;
|
||||||
|
hourOfDay = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
itemCount: timeDaily.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _buildPageContent(context, weatherData, index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppBar _buildAppBar(
|
||||||
|
BuildContext context,
|
||||||
|
TextTheme textTheme,
|
||||||
|
List<DateTime> timeDaily,
|
||||||
|
) {
|
||||||
|
return AppBar(
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
centerTitle: true,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
DateFormat.MMMMEEEEd(locale.languageCode).format(timeDaily[pageIndex]),
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPageContent(
|
||||||
|
BuildContext context,
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int index,
|
||||||
|
) {
|
||||||
|
final weatherCodeDaily = weatherData.weathercodeDaily?[index];
|
||||||
|
if (weatherCodeDaily == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
final startIndex = index * 24;
|
||||||
|
final temperature2MMin = weatherData.temperature2MMin?[index];
|
||||||
|
final temperature2MMax = weatherData.temperature2MMax?[index];
|
||||||
|
final apparentTemperatureMin = weatherData.apparentTemperatureMin?[index];
|
||||||
|
final apparentTemperatureMax = weatherData.apparentTemperatureMax?[index];
|
||||||
|
final uvIndexMax = weatherData.uvIndexMax?[index];
|
||||||
|
final windDirection10MDominant =
|
||||||
|
weatherData.winddirection10MDominant?[index];
|
||||||
|
final windSpeed10MMax = weatherData.windspeed10MMax?[index];
|
||||||
|
final windGusts10MMax = weatherData.windgusts10MMax?[index];
|
||||||
|
final precipitationProbabilityMax =
|
||||||
|
weatherData.precipitationProbabilityMax?[index];
|
||||||
|
final rainSum = weatherData.rainSum?[index];
|
||||||
|
final precipitationSum = weatherData.precipitationSum?[index];
|
||||||
|
final sunrise = weatherData.sunrise?[index];
|
||||||
|
final sunset = weatherData.sunset?[index];
|
||||||
|
|
||||||
|
if (sunrise == null || sunset == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
_buildNowWidget(
|
||||||
|
weatherData,
|
||||||
|
index,
|
||||||
|
startIndex,
|
||||||
|
hourOfDay,
|
||||||
|
sunrise,
|
||||||
|
sunset,
|
||||||
|
),
|
||||||
|
_buildHourlyList(context, weatherData, startIndex, sunrise, sunset),
|
||||||
|
_buildSunsetSunriseWidget(sunrise, sunset),
|
||||||
|
_buildHourlyDescContainer(weatherData, startIndex, hourOfDay),
|
||||||
|
_buildDailyDescContainer(
|
||||||
|
weatherData,
|
||||||
|
temperature2MMin,
|
||||||
|
temperature2MMax,
|
||||||
|
apparentTemperatureMin,
|
||||||
|
apparentTemperatureMax,
|
||||||
|
uvIndexMax,
|
||||||
|
windDirection10MDominant,
|
||||||
|
windSpeed10MMax,
|
||||||
|
windGusts10MMax,
|
||||||
|
precipitationProbabilityMax,
|
||||||
|
rainSum,
|
||||||
|
precipitationSum,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNowWidget(
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int index,
|
||||||
|
int startIndex,
|
||||||
|
int hourOfDay,
|
||||||
|
String sunrise,
|
||||||
|
String sunset,
|
||||||
|
) {
|
||||||
|
final weatherCode = weatherData.weathercode?[startIndex + hourOfDay];
|
||||||
|
final temperature = weatherData.temperature2M?[startIndex + hourOfDay];
|
||||||
|
final feels = weatherData.apparentTemperature?[startIndex + hourOfDay];
|
||||||
|
final time = weatherData.time?[startIndex + hourOfDay];
|
||||||
|
final tempMax = weatherData.temperature2MMax?[index];
|
||||||
|
final tempMin = weatherData.temperature2MMin?[index];
|
||||||
|
|
||||||
|
if (weatherCode == null ||
|
||||||
|
temperature == null ||
|
||||||
|
feels == null ||
|
||||||
|
time == null ||
|
||||||
|
tempMax == null ||
|
||||||
|
tempMin == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Now(
|
||||||
|
weather: weatherCode,
|
||||||
|
degree: temperature,
|
||||||
|
feels: feels,
|
||||||
|
time: time,
|
||||||
|
timeDay: sunrise,
|
||||||
|
timeNight: sunset,
|
||||||
|
tempMax: tempMax,
|
||||||
|
tempMin: tempMin,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyList(
|
||||||
|
BuildContext context,
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int startIndex,
|
||||||
|
String sunrise,
|
||||||
|
String sunset,
|
||||||
|
) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 135,
|
||||||
|
child: ScrollablePositionedList.separated(
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return const VerticalDivider(
|
||||||
|
width: 10,
|
||||||
|
indent: 40,
|
||||||
|
endIndent: 40,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: 24,
|
||||||
|
itemBuilder: (ctx, i) {
|
||||||
|
return _buildHourlyItem(
|
||||||
|
context,
|
||||||
|
weatherData,
|
||||||
|
startIndex,
|
||||||
|
i,
|
||||||
|
sunrise,
|
||||||
|
sunset,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyItem(
|
||||||
|
BuildContext context,
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int startIndex,
|
||||||
|
int i,
|
||||||
|
String sunrise,
|
||||||
|
String sunset,
|
||||||
|
) {
|
||||||
|
int hourlyIndex = startIndex + i;
|
||||||
|
bool isSelected = i == hourOfDay;
|
||||||
|
|
||||||
|
final time = weatherData.time?[hourlyIndex];
|
||||||
|
final weatherCode = weatherData.weathercode?[hourlyIndex];
|
||||||
|
final temperature = weatherData.temperature2M?[hourlyIndex];
|
||||||
|
|
||||||
|
if (time == null || weatherCode == null || temperature == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
hourOfDay = i;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 5),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? context.theme.colorScheme.secondaryContainer
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Hourly(
|
||||||
|
time: time,
|
||||||
|
weather: weatherCode,
|
||||||
|
degree: temperature,
|
||||||
|
timeDay: sunrise,
|
||||||
|
timeNight: sunset,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSunsetSunriseWidget(String sunrise, String sunset) {
|
||||||
|
return SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHourlyDescContainer(
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int startIndex,
|
||||||
|
int hourOfDay,
|
||||||
|
) {
|
||||||
|
final hourlyIndex = startIndex + hourOfDay;
|
||||||
|
|
||||||
|
return DescContainer(
|
||||||
|
humidity: weatherData.relativehumidity2M?[hourlyIndex],
|
||||||
|
wind: weatherData.windspeed10M?[hourlyIndex],
|
||||||
|
visibility: weatherData.visibility?[hourlyIndex],
|
||||||
|
feels: weatherData.apparentTemperature?[hourlyIndex],
|
||||||
|
evaporation: weatherData.evapotranspiration?[hourlyIndex],
|
||||||
|
precipitation: weatherData.precipitation?[hourlyIndex],
|
||||||
|
direction: weatherData.winddirection10M?[hourlyIndex],
|
||||||
|
pressure: weatherData.surfacePressure?[hourlyIndex],
|
||||||
|
rain: weatherData.rain?[hourlyIndex],
|
||||||
|
cloudcover: weatherData.cloudcover?[hourlyIndex],
|
||||||
|
windgusts: weatherData.windgusts10M?[hourlyIndex],
|
||||||
|
uvIndex: weatherData.uvIndex?[hourlyIndex],
|
||||||
|
dewpoint2M: weatherData.dewpoint2M?[hourlyIndex],
|
||||||
|
precipitationProbability:
|
||||||
|
weatherData.precipitationProbability?[hourlyIndex],
|
||||||
|
shortwaveRadiation: weatherData.shortwaveRadiation?[hourlyIndex],
|
||||||
|
initiallyExpanded: true,
|
||||||
|
title: 'hourlyVariables'.tr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDailyDescContainer(
|
||||||
|
WeatherCard weatherData,
|
||||||
|
double? temperature2MMin,
|
||||||
|
double? temperature2MMax,
|
||||||
|
double? apparentTemperatureMin,
|
||||||
|
double? apparentTemperatureMax,
|
||||||
|
double? uvIndexMax,
|
||||||
|
int? windDirection10MDominant,
|
||||||
|
double? windSpeed10MMax,
|
||||||
|
double? windGusts10MMax,
|
||||||
|
int? precipitationProbabilityMax,
|
||||||
|
double? rainSum,
|
||||||
|
double? precipitationSum,
|
||||||
|
) {
|
||||||
|
return DescContainer(
|
||||||
|
apparentTemperatureMin: apparentTemperatureMin,
|
||||||
|
apparentTemperatureMax: apparentTemperatureMax,
|
||||||
|
uvIndexMax: uvIndexMax,
|
||||||
|
windDirection10MDominant: windDirection10MDominant,
|
||||||
|
windSpeed10MMax: windSpeed10MMax,
|
||||||
|
windGusts10MMax: windGusts10MMax,
|
||||||
|
precipitationProbabilityMax: precipitationProbabilityMax,
|
||||||
|
rainSum: rainSum,
|
||||||
|
precipitationSum: precipitationSum,
|
||||||
|
initiallyExpanded: true,
|
||||||
|
title: 'dailyVariables'.tr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
84
lib/app/ui/widgets/weather/daily/daily_card_list.dart
Executable file
|
@ -0,0 +1,84 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/daily/daily_card_info.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/daily/daily_card.dart';
|
||||||
|
|
||||||
|
class DailyCardList extends StatefulWidget {
|
||||||
|
const DailyCardList({super.key, required this.weatherData});
|
||||||
|
final WeatherCard weatherData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DailyCardList> createState() => _DailyCardListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DailyCardListState extends State<DailyCardList> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final weatherData = widget.weatherData;
|
||||||
|
final timeDaily = weatherData.timeDaily ?? [];
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: _buildAppBar(context),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: timeDaily.length,
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
_buildDailyCardItem(context, weatherData, index),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppBar _buildAppBar(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
centerTitle: true,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'weatherMore'.tr,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDailyCardItem(
|
||||||
|
BuildContext context,
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int index,
|
||||||
|
) {
|
||||||
|
final timeDaily = weatherData.timeDaily?[index];
|
||||||
|
final weathercodeDaily = weatherData.weathercodeDaily?[index];
|
||||||
|
final temperature2MMax = weatherData.temperature2MMax?[index];
|
||||||
|
final temperature2MMin = weatherData.temperature2MMin?[index];
|
||||||
|
|
||||||
|
if (timeDaily == null ||
|
||||||
|
weathercodeDaily == null ||
|
||||||
|
temperature2MMax == null ||
|
||||||
|
temperature2MMin == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => Get.to(
|
||||||
|
() => DailyCardInfo(weatherData: weatherData, index: index),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
),
|
||||||
|
child: DailyCard(
|
||||||
|
timeDaily: timeDaily,
|
||||||
|
weathercodeDaily: weathercodeDaily,
|
||||||
|
temperature2MMax: temperature2MMax,
|
||||||
|
temperature2MMin: temperature2MMin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
206
lib/app/ui/widgets/weather/daily/daily_container.dart
Executable file
|
@ -0,0 +1,206 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:rain/app/data/db.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/daily/daily_card_info.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';
|
||||||
|
|
||||||
|
class DailyContainer extends StatefulWidget {
|
||||||
|
const DailyContainer({
|
||||||
|
super.key,
|
||||||
|
required this.weatherData,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final WeatherCard weatherData;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DailyContainer> createState() => _DailyContainerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DailyContainerState extends State<DailyContainer> {
|
||||||
|
final statusWeather = StatusWeather();
|
||||||
|
final statusData = StatusData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final splashColor = context.theme.colorScheme.primary.withValues(
|
||||||
|
alpha: 0.4,
|
||||||
|
);
|
||||||
|
const inkWellBorderRadius = BorderRadius.all(Radius.circular(16));
|
||||||
|
|
||||||
|
final weatherData = widget.weatherData;
|
||||||
|
final weatherCodeDaily = weatherData.weathercodeDaily ?? [];
|
||||||
|
final textTheme = context.textTheme;
|
||||||
|
final labelLarge = textTheme.labelLarge;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildDailyListView(
|
||||||
|
context,
|
||||||
|
weatherData,
|
||||||
|
weatherCodeDaily,
|
||||||
|
labelLarge,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
_buildMoreInfoButton(context, splashColor, inkWellBorderRadius),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDailyListView(
|
||||||
|
BuildContext context,
|
||||||
|
WeatherCard weatherData,
|
||||||
|
List<int?> weatherCodeDaily,
|
||||||
|
TextStyle? labelLarge,
|
||||||
|
) {
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: 7,
|
||||||
|
itemBuilder: (ctx, index) {
|
||||||
|
return _buildDailyItem(
|
||||||
|
context,
|
||||||
|
weatherData,
|
||||||
|
weatherCodeDaily,
|
||||||
|
index,
|
||||||
|
labelLarge,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDailyItem(
|
||||||
|
BuildContext context,
|
||||||
|
WeatherCard weatherData,
|
||||||
|
List<int?> weatherCodeDaily,
|
||||||
|
int index,
|
||||||
|
TextStyle? labelLarge,
|
||||||
|
) {
|
||||||
|
final splashColor = context.theme.colorScheme.primary.withValues(
|
||||||
|
alpha: 0.4,
|
||||||
|
);
|
||||||
|
const inkWellBorderRadius = BorderRadius.all(Radius.circular(16));
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
splashColor: splashColor,
|
||||||
|
borderRadius: inkWellBorderRadius,
|
||||||
|
onTap: () => Get.to(
|
||||||
|
() => DailyCardInfo(weatherData: weatherData, index: index),
|
||||||
|
transition: Transition.downToUp,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
_buildDayText(weatherData, index, labelLarge),
|
||||||
|
_buildWeatherInfo(weatherCodeDaily, index, labelLarge),
|
||||||
|
_buildTemperatureRange(weatherData, index, labelLarge),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDayText(
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int index,
|
||||||
|
TextStyle? labelLarge,
|
||||||
|
) {
|
||||||
|
return Expanded(
|
||||||
|
child: Text(
|
||||||
|
DateFormat.EEEE(
|
||||||
|
locale.languageCode,
|
||||||
|
).format((weatherData.timeDaily ?? [])[index]),
|
||||||
|
style: labelLarge,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherInfo(
|
||||||
|
List<int?> weatherCodeDaily,
|
||||||
|
int index,
|
||||||
|
TextStyle? labelLarge,
|
||||||
|
) {
|
||||||
|
final weatherCode = weatherCodeDaily[index];
|
||||||
|
if (weatherCode == null) {
|
||||||
|
return const Expanded(child: SizedBox.shrink());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(statusWeather.getImage7Day(weatherCode), scale: 3),
|
||||||
|
const Gap(5),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
statusWeather.getText(weatherCode),
|
||||||
|
style: labelLarge,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTemperatureRange(
|
||||||
|
WeatherCard weatherData,
|
||||||
|
int index,
|
||||||
|
TextStyle? labelLarge,
|
||||||
|
) {
|
||||||
|
return Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
statusData.getDegree(
|
||||||
|
(weatherData.temperature2MMax ?? [])[index]?.round(),
|
||||||
|
),
|
||||||
|
style: labelLarge,
|
||||||
|
),
|
||||||
|
Text(' / ', style: labelLarge),
|
||||||
|
Text(
|
||||||
|
statusData.getDegree(
|
||||||
|
(weatherData.temperature2MMin ?? [])[index]?.round(),
|
||||||
|
),
|
||||||
|
style: labelLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMoreInfoButton(
|
||||||
|
BuildContext context,
|
||||||
|
Color splashColor,
|
||||||
|
BorderRadius inkWellBorderRadius,
|
||||||
|
) {
|
||||||
|
return InkWell(
|
||||||
|
splashColor: splashColor,
|
||||||
|
borderRadius: inkWellBorderRadius,
|
||||||
|
onTap: widget.onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: Text(
|
||||||
|
'weatherMore'.tr,
|
||||||
|
style: context.textTheme.titleMedium,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
54
lib/app/widgets/desc/desc.dart → lib/app/ui/widgets/weather/desc/desc.dart
Normal file → Executable file
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class DescWeather extends StatefulWidget {
|
class DescWeather extends StatefulWidget {
|
||||||
|
@ -9,6 +10,7 @@ class DescWeather extends StatefulWidget {
|
||||||
required this.desc,
|
required this.desc,
|
||||||
this.message = '',
|
this.message = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String imageName;
|
final String imageName;
|
||||||
final String value;
|
final String value;
|
||||||
final String desc;
|
final String desc;
|
||||||
|
@ -25,36 +27,42 @@ class _DescWeatherState extends State<DescWeather> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = context.textTheme;
|
final textTheme = context.textTheme;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => setState(() => hide = !hide),
|
onTap: _toggleDescriptionVisibility,
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 90,
|
height: 90,
|
||||||
width: 100,
|
width: 100,
|
||||||
child: Column(
|
child: _buildContent(textTheme),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Image.asset(
|
|
||||||
widget.imageName,
|
|
||||||
scale: 20,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
widget.value,
|
|
||||||
style: textTheme.labelLarge,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
widget.desc,
|
|
||||||
style: textTheme.bodySmall,
|
|
||||||
overflow: hide ? TextOverflow.ellipsis : TextOverflow.visible,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _toggleDescriptionVisibility() {
|
||||||
|
setState(() => hide = !hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(TextTheme textTheme) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(widget.imageName, scale: 20),
|
||||||
|
const Gap(5),
|
||||||
|
Text(
|
||||||
|
widget.value,
|
||||||
|
style: textTheme.labelLarge,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.desc,
|
||||||
|
style: textTheme.bodySmall,
|
||||||
|
overflow: hide ? TextOverflow.ellipsis : TextOverflow.visible,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
263
lib/app/ui/widgets/weather/desc/desc_container.dart
Executable file
|
@ -0,0 +1,263 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/desc/desc.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/desc/message.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
|
||||||
|
|
||||||
|
class DescContainer extends StatefulWidget {
|
||||||
|
const DescContainer({
|
||||||
|
super.key,
|
||||||
|
this.humidity,
|
||||||
|
this.wind,
|
||||||
|
this.visibility,
|
||||||
|
this.feels,
|
||||||
|
this.evaporation,
|
||||||
|
this.precipitation,
|
||||||
|
this.direction,
|
||||||
|
this.pressure,
|
||||||
|
this.rain,
|
||||||
|
this.cloudcover,
|
||||||
|
this.windgusts,
|
||||||
|
this.uvIndex,
|
||||||
|
this.dewpoint2M,
|
||||||
|
this.precipitationProbability,
|
||||||
|
this.shortwaveRadiation,
|
||||||
|
this.apparentTemperatureMin,
|
||||||
|
this.apparentTemperatureMax,
|
||||||
|
this.uvIndexMax,
|
||||||
|
this.windDirection10MDominant,
|
||||||
|
this.windSpeed10MMax,
|
||||||
|
this.windGusts10MMax,
|
||||||
|
this.precipitationProbabilityMax,
|
||||||
|
this.rainSum,
|
||||||
|
this.precipitationSum,
|
||||||
|
required this.initiallyExpanded,
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int? humidity;
|
||||||
|
final double? wind;
|
||||||
|
final double? visibility;
|
||||||
|
final double? feels;
|
||||||
|
final double? evaporation;
|
||||||
|
final double? precipitation;
|
||||||
|
final int? direction;
|
||||||
|
final double? pressure;
|
||||||
|
final double? rain;
|
||||||
|
final int? cloudcover;
|
||||||
|
final double? windgusts;
|
||||||
|
final double? uvIndex;
|
||||||
|
final double? dewpoint2M;
|
||||||
|
final int? precipitationProbability;
|
||||||
|
final double? shortwaveRadiation;
|
||||||
|
final double? apparentTemperatureMin;
|
||||||
|
final double? apparentTemperatureMax;
|
||||||
|
final double? uvIndexMax;
|
||||||
|
final int? windDirection10MDominant;
|
||||||
|
final double? windSpeed10MMax;
|
||||||
|
final double? windGusts10MMax;
|
||||||
|
final int? precipitationProbabilityMax;
|
||||||
|
final double? rainSum;
|
||||||
|
final double? precipitationSum;
|
||||||
|
final bool initiallyExpanded;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DescContainer> createState() => _DescContainerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DescContainerState extends State<DescContainer> {
|
||||||
|
final statusData = StatusData();
|
||||||
|
final message = Message();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: ExpansionTile(
|
||||||
|
shape: const Border(),
|
||||||
|
title: Text(widget.title, style: context.textTheme.labelLarge),
|
||||||
|
initiallyExpanded: widget.initiallyExpanded,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.spaceEvenly,
|
||||||
|
spacing: 5,
|
||||||
|
children: _buildWeatherDescriptions(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildWeatherDescriptions(BuildContext context) {
|
||||||
|
final List<Widget> descriptions = [];
|
||||||
|
|
||||||
|
void addDescriptionIfNotNull({
|
||||||
|
required dynamic value,
|
||||||
|
required String imageName,
|
||||||
|
required String desc,
|
||||||
|
String? message,
|
||||||
|
}) {
|
||||||
|
if (value != null &&
|
||||||
|
value != '' &&
|
||||||
|
value != 'null°C' &&
|
||||||
|
value != 'null°F' &&
|
||||||
|
value != 'null°' &&
|
||||||
|
value != 'null%' &&
|
||||||
|
value != 'null ${'W/m2'.tr}') {
|
||||||
|
descriptions.add(
|
||||||
|
DescWeather(
|
||||||
|
imageName: imageName,
|
||||||
|
value: value.toString(),
|
||||||
|
desc: desc,
|
||||||
|
message: message ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
descriptions.add(Container());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final weatherData = [
|
||||||
|
{
|
||||||
|
'value': statusData.getDegree(widget.apparentTemperatureMin?.round()),
|
||||||
|
'imageName': 'assets/images/cold.png',
|
||||||
|
'desc': 'apparentTemperatureMin'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getDegree(widget.apparentTemperatureMax?.round()),
|
||||||
|
'imageName': 'assets/images/hot.png',
|
||||||
|
'desc': 'apparentTemperatureMax'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': widget.uvIndexMax?.round(),
|
||||||
|
'imageName': 'assets/images/uv.png',
|
||||||
|
'desc': 'uvIndex'.tr,
|
||||||
|
'message': message.getUvIndex(widget.uvIndexMax?.round()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '${widget.windDirection10MDominant}°',
|
||||||
|
'imageName': 'assets/images/windsock.png',
|
||||||
|
'desc': 'direction'.tr,
|
||||||
|
'message': message.getDirection(widget.windDirection10MDominant),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getSpeed(widget.windSpeed10MMax?.round()),
|
||||||
|
'imageName': 'assets/images/wind.png',
|
||||||
|
'desc': 'wind'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getSpeed(widget.windGusts10MMax?.round()),
|
||||||
|
'imageName': 'assets/images/windgusts.png',
|
||||||
|
'desc': 'windgusts'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '${widget.precipitationProbabilityMax}%',
|
||||||
|
'imageName': 'assets/images/precipitation_probability.png',
|
||||||
|
'desc': 'precipitationProbability'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getPrecipitation(widget.rainSum),
|
||||||
|
'imageName': 'assets/images/water.png',
|
||||||
|
'desc': 'rain'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getPrecipitation(widget.precipitationSum),
|
||||||
|
'imageName': 'assets/images/rainfall.png',
|
||||||
|
'desc': 'precipitation'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getDegree(widget.dewpoint2M?.round()),
|
||||||
|
'imageName': 'assets/images/dew.png',
|
||||||
|
'desc': 'dewpoint'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getDegree(widget.feels?.round()),
|
||||||
|
'imageName': 'assets/images/temperature.png',
|
||||||
|
'desc': 'feels'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getVisibility(widget.visibility),
|
||||||
|
'imageName': 'assets/images/fog.png',
|
||||||
|
'desc': 'visibility'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '${widget.direction}°',
|
||||||
|
'imageName': 'assets/images/windsock.png',
|
||||||
|
'desc': 'direction'.tr,
|
||||||
|
'message': message.getDirection(widget.direction),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getSpeed(widget.wind?.round()),
|
||||||
|
'imageName': 'assets/images/wind.png',
|
||||||
|
'desc': 'wind'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getSpeed(widget.windgusts?.round()),
|
||||||
|
'imageName': 'assets/images/windgusts.png',
|
||||||
|
'desc': 'windgusts'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getPrecipitation(widget.evaporation?.abs()),
|
||||||
|
'imageName': 'assets/images/evaporation.png',
|
||||||
|
'desc': 'evaporation'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getPrecipitation(widget.precipitation),
|
||||||
|
'imageName': 'assets/images/rainfall.png',
|
||||||
|
'desc': 'precipitation'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getPrecipitation(widget.rain),
|
||||||
|
'imageName': 'assets/images/water.png',
|
||||||
|
'desc': 'rain'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '${widget.precipitationProbability}%',
|
||||||
|
'imageName': 'assets/images/precipitation_probability.png',
|
||||||
|
'desc': 'precipitationProbability'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '${widget.humidity}%',
|
||||||
|
'imageName': 'assets/images/humidity.png',
|
||||||
|
'desc': 'humidity'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '${widget.cloudcover}%',
|
||||||
|
'imageName': 'assets/images/cloudy.png',
|
||||||
|
'desc': 'cloudcover'.tr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': statusData.getPressure(widget.pressure?.round()),
|
||||||
|
'imageName': 'assets/images/atmospheric.png',
|
||||||
|
'desc': 'pressure'.tr,
|
||||||
|
'message': message.getPressure(widget.pressure?.round()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': widget.uvIndex?.round(),
|
||||||
|
'imageName': 'assets/images/uv.png',
|
||||||
|
'desc': 'uvIndex'.tr,
|
||||||
|
'message': message.getUvIndex(widget.uvIndex?.round()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': '${widget.shortwaveRadiation?.round()} ${'W/m2'.tr}',
|
||||||
|
'imageName': 'assets/images/shortwave_radiation.png',
|
||||||
|
'desc': 'shortwaveRadiation'.tr,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var data in weatherData) {
|
||||||
|
addDescriptionIfNotNull(
|
||||||
|
value: data['value'],
|
||||||
|
imageName: '${data['imageName']}',
|
||||||
|
desc: '${data['desc']}',
|
||||||
|
message: '${data['message']}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
|
}
|
65
lib/app/ui/widgets/weather/desc/message.dart
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
String getPressure(int? pressure) {
|
||||||
|
return _getPressureDescription(pressure);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUvIndex(int? uvIndex) {
|
||||||
|
return _getUvIndexDescription(uvIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDirection(int? direction) {
|
||||||
|
return _getDirectionDescription(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getPressureDescription(int? pressure) {
|
||||||
|
if (pressure == null) return '';
|
||||||
|
|
||||||
|
if (pressure < 1000) {
|
||||||
|
return 'low'.tr;
|
||||||
|
} else if (pressure > 1020) {
|
||||||
|
return 'high'.tr;
|
||||||
|
} else {
|
||||||
|
return 'normal'.tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getUvIndexDescription(int? uvIndex) {
|
||||||
|
if (uvIndex == null) return '';
|
||||||
|
|
||||||
|
if (uvIndex < 3) {
|
||||||
|
return 'uvLow'.tr;
|
||||||
|
} else if (uvIndex < 6) {
|
||||||
|
return 'uvAverage'.tr;
|
||||||
|
} else if (uvIndex < 8) {
|
||||||
|
return 'uvHigh'.tr;
|
||||||
|
} else if (uvIndex < 11) {
|
||||||
|
return 'uvVeryHigh'.tr;
|
||||||
|
} else {
|
||||||
|
return 'uvExtreme'.tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getDirectionDescription(int? direction) {
|
||||||
|
if (direction == null) return '';
|
||||||
|
|
||||||
|
if (direction >= 337.5 || direction < 22.5) {
|
||||||
|
return 'north'.tr;
|
||||||
|
} else if (direction >= 22.5 && direction < 67.5) {
|
||||||
|
return 'northeast'.tr;
|
||||||
|
} else if (direction >= 67.5 && direction < 112.5) {
|
||||||
|
return 'east'.tr;
|
||||||
|
} else if (direction >= 112.5 && direction < 157.5) {
|
||||||
|
return 'southeast'.tr;
|
||||||
|
} else if (direction >= 157.5 && direction < 202.5) {
|
||||||
|
return 'south'.tr;
|
||||||
|
} else if (direction >= 202.5 && direction < 247.5) {
|
||||||
|
return 'southwest'.tr;
|
||||||
|
} else if (direction >= 247.5 && direction < 292.5) {
|
||||||
|
return 'west'.tr;
|
||||||
|
} else {
|
||||||
|
return 'northwest'.tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
lib/app/ui/widgets/weather/hourly.dart
Executable file
|
@ -0,0 +1,77 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.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';
|
||||||
|
|
||||||
|
class Hourly extends StatefulWidget {
|
||||||
|
const Hourly({
|
||||||
|
super.key,
|
||||||
|
required this.time,
|
||||||
|
required this.weather,
|
||||||
|
required this.degree,
|
||||||
|
required this.timeDay,
|
||||||
|
required this.timeNight,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String time;
|
||||||
|
final String timeDay;
|
||||||
|
final String timeNight;
|
||||||
|
final int weather;
|
||||||
|
final double degree;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Hourly> createState() => _HourlyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HourlyState extends State<Hourly> {
|
||||||
|
final statusWeather = StatusWeather();
|
||||||
|
final statusData = StatusData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = context.textTheme;
|
||||||
|
final time = widget.time;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildTimeText(textTheme, time),
|
||||||
|
_buildWeatherImage(),
|
||||||
|
_buildTemperatureText(textTheme),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTimeText(TextTheme textTheme, String time) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text(statusData.getTimeFormat(time), style: textTheme.labelLarge),
|
||||||
|
Text(
|
||||||
|
DateFormat('E', locale.languageCode).format(DateTime.tryParse(time)!),
|
||||||
|
style: textTheme.labelLarge?.copyWith(color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherImage() {
|
||||||
|
return Image.asset(
|
||||||
|
statusWeather.getImageToday(
|
||||||
|
widget.weather,
|
||||||
|
widget.time,
|
||||||
|
widget.timeDay,
|
||||||
|
widget.timeNight,
|
||||||
|
),
|
||||||
|
scale: 3,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTemperatureText(TextTheme textTheme) {
|
||||||
|
return Text(
|
||||||
|
statusData.getDegree(widget.degree.round()),
|
||||||
|
style: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
182
lib/app/ui/widgets/weather/now.dart
Executable file
|
@ -0,0 +1,182 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.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';
|
||||||
|
|
||||||
|
class Now extends StatefulWidget {
|
||||||
|
const Now({
|
||||||
|
super.key,
|
||||||
|
required this.weather,
|
||||||
|
required this.degree,
|
||||||
|
required this.time,
|
||||||
|
required this.timeDay,
|
||||||
|
required this.timeNight,
|
||||||
|
required this.tempMax,
|
||||||
|
required this.tempMin,
|
||||||
|
required this.feels,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String time;
|
||||||
|
final String timeDay;
|
||||||
|
final String timeNight;
|
||||||
|
final int weather;
|
||||||
|
final double degree;
|
||||||
|
final double tempMax;
|
||||||
|
final double tempMin;
|
||||||
|
final double feels;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Now> createState() => _NowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NowState extends State<Now> {
|
||||||
|
final statusWeather = StatusWeather();
|
||||||
|
final statusData = StatusData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return largeElement
|
||||||
|
? _buildLargeElementLayout(context)
|
||||||
|
: _buildCompactElementLayout(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLargeElementLayout(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Gap(15),
|
||||||
|
_buildWeatherImage(200),
|
||||||
|
_buildTemperatureText(context, widget.degree, 90),
|
||||||
|
Text(
|
||||||
|
statusWeather.getText(widget.weather),
|
||||||
|
style: context.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const Gap(5),
|
||||||
|
_buildDateText(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCompactElementLayout(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 18,
|
||||||
|
bottom: 18,
|
||||||
|
left: 25,
|
||||||
|
right: 15,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildDateText(context),
|
||||||
|
const Gap(5),
|
||||||
|
Text(
|
||||||
|
statusWeather.getText(widget.weather),
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
|
||||||
|
),
|
||||||
|
_buildFeelsLikeText(context),
|
||||||
|
const Gap(30),
|
||||||
|
_buildTemperatureCompactText(context, widget.degree),
|
||||||
|
const Gap(5),
|
||||||
|
_buildMinMaxTemperatureText(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildWeatherImage(140),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWeatherImage(double height) {
|
||||||
|
return Image(
|
||||||
|
image: AssetImage(
|
||||||
|
statusWeather.getImageNow(
|
||||||
|
widget.weather,
|
||||||
|
widget.time,
|
||||||
|
widget.timeDay,
|
||||||
|
widget.timeNight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTemperatureText(
|
||||||
|
BuildContext context,
|
||||||
|
double degree,
|
||||||
|
double? fontSize,
|
||||||
|
) {
|
||||||
|
return Text(
|
||||||
|
'${roundDegree ? degree.round() : degree}',
|
||||||
|
style: context.textTheme.displayLarge?.copyWith(
|
||||||
|
fontSize: fontSize,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
shadows: const [Shadow(blurRadius: 15, offset: Offset(5, 5))],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTemperatureCompactText(BuildContext context, double degree) {
|
||||||
|
return Text(
|
||||||
|
statusData.getDegree(roundDegree ? widget.degree.round() : widget.degree),
|
||||||
|
style: context.textTheme.displayMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDateText(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
DateFormat.MMMMEEEEd(
|
||||||
|
locale.languageCode,
|
||||||
|
).format(DateTime.parse(widget.time)),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(color: Colors.grey),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFeelsLikeText(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('feels'.tr, style: context.textTheme.bodyMedium),
|
||||||
|
Text(' • ', style: context.textTheme.bodyMedium),
|
||||||
|
Text(
|
||||||
|
statusData.getDegree(widget.feels.round()),
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMinMaxTemperatureText(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
statusData.getDegree((widget.tempMin.round())),
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
Text(' / ', style: context.textTheme.labelLarge),
|
||||||
|
Text(
|
||||||
|
statusData.getDegree((widget.tempMax.round())),
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
127
lib/app/ui/widgets/weather/status/status_data.dart
Executable file
|
@ -0,0 +1,127 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:rain/main.dart';
|
||||||
|
import 'package:timezone/timezone.dart';
|
||||||
|
|
||||||
|
class StatusData {
|
||||||
|
String getDegree(dynamic degree) {
|
||||||
|
return _formatDegree(degree);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSpeed(int? speed) {
|
||||||
|
return _formatSpeed(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPressure(int? pressure) {
|
||||||
|
return _formatPressure(pressure);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getVisibility(double? length) {
|
||||||
|
return _formatVisibility(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPrecipitation(double? precipitation) {
|
||||||
|
return _formatPrecipitation(precipitation);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTimeFormat(String time) {
|
||||||
|
return _formatTime(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTimeFormatTz(TZDateTime time) {
|
||||||
|
return _formatTimeTz(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDegree(dynamic degree) {
|
||||||
|
switch (settings.degrees) {
|
||||||
|
case 'celsius':
|
||||||
|
return '$degree°C';
|
||||||
|
case 'fahrenheit':
|
||||||
|
return '$degree°F';
|
||||||
|
default:
|
||||||
|
return '$degree°C';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatSpeed(int? speed) {
|
||||||
|
if (speed == null) return '';
|
||||||
|
|
||||||
|
switch (settings.measurements) {
|
||||||
|
case 'metric':
|
||||||
|
return settings.wind == 'm/s'
|
||||||
|
? '${(speed * (5 / 18)).toPrecision(1)} ${'m/s'.tr}'
|
||||||
|
: '$speed ${'kph'.tr}';
|
||||||
|
case 'imperial':
|
||||||
|
return '$speed ${'mph'.tr}';
|
||||||
|
default:
|
||||||
|
return '$speed ${'kph'.tr}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatPressure(int? pressure) {
|
||||||
|
if (pressure == null) return '';
|
||||||
|
|
||||||
|
return settings.pressure == 'mmHg'
|
||||||
|
? '${(pressure * (3 / 4)).toPrecision(1)} ${'mmHg'.tr}'
|
||||||
|
: '$pressure ${'hPa'.tr}';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatVisibility(double? length) {
|
||||||
|
if (length == null) return '';
|
||||||
|
|
||||||
|
switch (settings.measurements) {
|
||||||
|
case 'metric':
|
||||||
|
return _formatMetricVisibility(length);
|
||||||
|
case 'imperial':
|
||||||
|
return _formatImperialVisibility(length);
|
||||||
|
default:
|
||||||
|
return _formatMetricVisibility(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatMetricVisibility(double length) {
|
||||||
|
return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatImperialVisibility(double length) {
|
||||||
|
return '${length > 5280 ? (length / 5280).round() : (length / 5280).toStringAsFixed(2)} ${'mi'.tr}';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatPrecipitation(double? precipitation) {
|
||||||
|
if (precipitation == null) return '';
|
||||||
|
|
||||||
|
switch (settings.measurements) {
|
||||||
|
case 'metric':
|
||||||
|
return '$precipitation ${'mm'.tr}';
|
||||||
|
case 'imperial':
|
||||||
|
return '$precipitation ${'inch'.tr}';
|
||||||
|
default:
|
||||||
|
return '$precipitation ${'mm'.tr}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTime(String time) {
|
||||||
|
final parsedTime = DateTime.tryParse(time);
|
||||||
|
if (parsedTime == null) return '';
|
||||||
|
|
||||||
|
switch (settings.timeformat) {
|
||||||
|
case '12':
|
||||||
|
return DateFormat.jm(locale.languageCode).format(parsedTime);
|
||||||
|
case '24':
|
||||||
|
return DateFormat.Hm(locale.languageCode).format(parsedTime);
|
||||||
|
default:
|
||||||
|
return DateFormat.Hm(locale.languageCode).format(parsedTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTimeTz(TZDateTime time) {
|
||||||
|
switch (settings.timeformat) {
|
||||||
|
case '12':
|
||||||
|
return DateFormat.jm(locale.languageCode).format(time);
|
||||||
|
case '24':
|
||||||
|
return DateFormat.Hm(locale.languageCode).format(time);
|
||||||
|
default:
|
||||||
|
return DateFormat.Hm(locale.languageCode).format(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
385
lib/app/ui/widgets/weather/status/status_weather.dart
Executable file
|
@ -0,0 +1,385 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
const assetImageRoot = 'assets/images/';
|
||||||
|
|
||||||
|
class StatusWeather {
|
||||||
|
String getImageNow(
|
||||||
|
int weather,
|
||||||
|
String time,
|
||||||
|
String timeDay,
|
||||||
|
String timeNight,
|
||||||
|
) {
|
||||||
|
return _getImageBasedOnTime(
|
||||||
|
weather,
|
||||||
|
time,
|
||||||
|
timeDay,
|
||||||
|
timeNight,
|
||||||
|
_getDayNightImagePaths,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getImageNowDaily(int? weather) {
|
||||||
|
return _getDailyImage(weather);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getImageToday(
|
||||||
|
int weather,
|
||||||
|
String time,
|
||||||
|
String timeDay,
|
||||||
|
String timeNight,
|
||||||
|
) {
|
||||||
|
return _getImageBasedOnTime(
|
||||||
|
weather,
|
||||||
|
time,
|
||||||
|
timeDay,
|
||||||
|
timeNight,
|
||||||
|
_getTodayImagePaths,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getImage7Day(int? weather) {
|
||||||
|
return _getDailyImage(weather, isDay: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getText(int? weather) {
|
||||||
|
return _getWeatherText(weather);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getImageNotification(
|
||||||
|
int weather,
|
||||||
|
String time,
|
||||||
|
String timeDay,
|
||||||
|
String timeNight,
|
||||||
|
) {
|
||||||
|
return _getImageBasedOnTime(
|
||||||
|
weather,
|
||||||
|
time,
|
||||||
|
timeDay,
|
||||||
|
timeNight,
|
||||||
|
_getNotificationImagePaths,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getImageBasedOnTime(
|
||||||
|
int weather,
|
||||||
|
String time,
|
||||||
|
String timeDay,
|
||||||
|
String timeNight,
|
||||||
|
Map<int, Map<bool, String>> imagePaths,
|
||||||
|
) {
|
||||||
|
final currentTime = DateTime.parse(time);
|
||||||
|
final day = DateTime.parse(timeDay);
|
||||||
|
final night = DateTime.parse(timeNight);
|
||||||
|
|
||||||
|
final dayTime = DateTime(
|
||||||
|
day.year,
|
||||||
|
day.month,
|
||||||
|
day.day,
|
||||||
|
day.hour,
|
||||||
|
day.minute,
|
||||||
|
);
|
||||||
|
final nightTime = DateTime(
|
||||||
|
night.year,
|
||||||
|
night.month,
|
||||||
|
night.day,
|
||||||
|
night.hour,
|
||||||
|
night.minute,
|
||||||
|
);
|
||||||
|
|
||||||
|
final isDayTime =
|
||||||
|
currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime);
|
||||||
|
|
||||||
|
return imagePaths[weather]?[isDayTime] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getDailyImage(int? weather, {bool isDay = false}) {
|
||||||
|
switch (weather) {
|
||||||
|
case 0:
|
||||||
|
return '$assetImageRoot${isDay ? 'clear_day' : 'sun'}.png';
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
return '$assetImageRoot${isDay ? 'cloudy_day' : 'cloud'}.png';
|
||||||
|
case 45:
|
||||||
|
case 48:
|
||||||
|
return '${assetImageRoot}fog${isDay ? '_day' : ''}.png';
|
||||||
|
case 51:
|
||||||
|
case 53:
|
||||||
|
case 55:
|
||||||
|
case 56:
|
||||||
|
case 57:
|
||||||
|
case 61:
|
||||||
|
case 63:
|
||||||
|
case 65:
|
||||||
|
case 66:
|
||||||
|
case 67:
|
||||||
|
case 80:
|
||||||
|
case 81:
|
||||||
|
case 82:
|
||||||
|
return '${assetImageRoot}rain${isDay ? '_day' : ''}.png';
|
||||||
|
case 71:
|
||||||
|
case 73:
|
||||||
|
case 75:
|
||||||
|
case 77:
|
||||||
|
case 85:
|
||||||
|
case 86:
|
||||||
|
return '${assetImageRoot}snow${isDay ? '_day' : ''}.png';
|
||||||
|
case 95:
|
||||||
|
case 96:
|
||||||
|
case 99:
|
||||||
|
return '${assetImageRoot}thunder${isDay ? '_day' : ''}.png';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getWeatherText(int? weather) {
|
||||||
|
switch (weather) {
|
||||||
|
case 0:
|
||||||
|
return 'clear_sky'.tr;
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
return 'cloudy'.tr;
|
||||||
|
case 3:
|
||||||
|
return 'overcast'.tr;
|
||||||
|
case 45:
|
||||||
|
case 48:
|
||||||
|
return 'fog'.tr;
|
||||||
|
case 51:
|
||||||
|
case 53:
|
||||||
|
case 55:
|
||||||
|
return 'drizzle'.tr;
|
||||||
|
case 56:
|
||||||
|
case 57:
|
||||||
|
return 'drizzling_rain'.tr;
|
||||||
|
case 61:
|
||||||
|
case 63:
|
||||||
|
case 65:
|
||||||
|
return 'rain'.tr;
|
||||||
|
case 66:
|
||||||
|
case 67:
|
||||||
|
return 'freezing_rain'.tr;
|
||||||
|
case 80:
|
||||||
|
case 81:
|
||||||
|
case 82:
|
||||||
|
return 'heavy_rains'.tr;
|
||||||
|
case 71:
|
||||||
|
case 73:
|
||||||
|
case 75:
|
||||||
|
case 77:
|
||||||
|
case 85:
|
||||||
|
case 86:
|
||||||
|
return 'snow'.tr;
|
||||||
|
case 95:
|
||||||
|
case 96:
|
||||||
|
case 99:
|
||||||
|
return 'thunderstorm'.tr;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<int, Map<bool, String>> _getDayNightImagePaths = {
|
||||||
|
0: {
|
||||||
|
true: '${assetImageRoot}sun.png',
|
||||||
|
false: '${assetImageRoot}full-moon.png',
|
||||||
|
},
|
||||||
|
1: {true: '${assetImageRoot}cloud.png', false: '${assetImageRoot}moon.png'},
|
||||||
|
2: {true: '${assetImageRoot}cloud.png', false: '${assetImageRoot}moon.png'},
|
||||||
|
3: {true: '${assetImageRoot}cloud.png', false: '${assetImageRoot}moon.png'},
|
||||||
|
45: {
|
||||||
|
true: '${assetImageRoot}fog.png',
|
||||||
|
false: '${assetImageRoot}fog_moon.png',
|
||||||
|
},
|
||||||
|
48: {
|
||||||
|
true: '${assetImageRoot}fog.png',
|
||||||
|
false: '${assetImageRoot}fog_moon.png',
|
||||||
|
},
|
||||||
|
51: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
53: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
55: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
56: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
57: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
61: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
63: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
65: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
66: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
67: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||||
|
80: {
|
||||||
|
true: '${assetImageRoot}rain-fall.png',
|
||||||
|
false: '${assetImageRoot}rain-fall.png',
|
||||||
|
},
|
||||||
|
81: {
|
||||||
|
true: '${assetImageRoot}rain-fall.png',
|
||||||
|
false: '${assetImageRoot}rain-fall.png',
|
||||||
|
},
|
||||||
|
82: {
|
||||||
|
true: '${assetImageRoot}rain-fall.png',
|
||||||
|
false: '${assetImageRoot}rain-fall.png',
|
||||||
|
},
|
||||||
|
71: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||||
|
73: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||||
|
75: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||||
|
77: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||||
|
85: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||||
|
86: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||||
|
95: {
|
||||||
|
true: '${assetImageRoot}thunder.png',
|
||||||
|
false: '${assetImageRoot}thunder.png',
|
||||||
|
},
|
||||||
|
96: {
|
||||||
|
true: '${assetImageRoot}storm.png',
|
||||||
|
false: '${assetImageRoot}storm.png',
|
||||||
|
},
|
||||||
|
99: {
|
||||||
|
true: '${assetImageRoot}storm.png',
|
||||||
|
false: '${assetImageRoot}storm.png',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
final Map<int, Map<bool, String>> _getTodayImagePaths = {
|
||||||
|
0: {
|
||||||
|
true: '${assetImageRoot}clear_day.png',
|
||||||
|
false: '${assetImageRoot}clear_night.png',
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
true: '${assetImageRoot}cloudy_day.png',
|
||||||
|
false: '${assetImageRoot}cloudy_night.png',
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
true: '${assetImageRoot}cloudy_day.png',
|
||||||
|
false: '${assetImageRoot}cloudy_night.png',
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
true: '${assetImageRoot}cloudy_day.png',
|
||||||
|
false: '${assetImageRoot}cloudy_night.png',
|
||||||
|
},
|
||||||
|
45: {
|
||||||
|
true: '${assetImageRoot}fog_day.png',
|
||||||
|
false: '${assetImageRoot}fog_night.png',
|
||||||
|
},
|
||||||
|
48: {
|
||||||
|
true: '${assetImageRoot}fog_day.png',
|
||||||
|
false: '${assetImageRoot}fog_night.png',
|
||||||
|
},
|
||||||
|
51: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
53: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
55: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
56: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
57: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
61: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
63: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
65: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
66: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
67: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
80: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
81: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
82: {
|
||||||
|
true: '${assetImageRoot}rain_day.png',
|
||||||
|
false: '${assetImageRoot}rain_night.png',
|
||||||
|
},
|
||||||
|
71: {
|
||||||
|
true: '${assetImageRoot}snow_day.png',
|
||||||
|
false: '${assetImageRoot}snow_night.png',
|
||||||
|
},
|
||||||
|
73: {
|
||||||
|
true: '${assetImageRoot}snow_day.png',
|
||||||
|
false: '${assetImageRoot}snow_night.png',
|
||||||
|
},
|
||||||
|
75: {
|
||||||
|
true: '${assetImageRoot}snow_day.png',
|
||||||
|
false: '${assetImageRoot}snow_night.png',
|
||||||
|
},
|
||||||
|
77: {
|
||||||
|
true: '${assetImageRoot}snow_day.png',
|
||||||
|
false: '${assetImageRoot}snow_night.png',
|
||||||
|
},
|
||||||
|
85: {
|
||||||
|
true: '${assetImageRoot}snow_day.png',
|
||||||
|
false: '${assetImageRoot}snow_night.png',
|
||||||
|
},
|
||||||
|
86: {
|
||||||
|
true: '${assetImageRoot}snow_day.png',
|
||||||
|
false: '${assetImageRoot}snow_night.png',
|
||||||
|
},
|
||||||
|
95: {
|
||||||
|
true: '${assetImageRoot}thunder_day.png',
|
||||||
|
false: '${assetImageRoot}thunder_night.png',
|
||||||
|
},
|
||||||
|
96: {
|
||||||
|
true: '${assetImageRoot}thunder_day.png',
|
||||||
|
false: '${assetImageRoot}thunder_night.png',
|
||||||
|
},
|
||||||
|
99: {
|
||||||
|
true: '${assetImageRoot}thunder_day.png',
|
||||||
|
false: '${assetImageRoot}thunder_night.png',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
final Map<int, Map<bool, String>> _getNotificationImagePaths = {
|
||||||
|
0: {true: 'sun.png', false: 'full-moon.png'},
|
||||||
|
1: {true: 'cloud.png', false: 'moon.png'},
|
||||||
|
2: {true: 'cloud.png', false: 'moon.png'},
|
||||||
|
3: {true: 'cloud.png', false: 'moon.png'},
|
||||||
|
45: {true: 'fog.png', false: 'fog_moon.png'},
|
||||||
|
48: {true: 'fog.png', false: 'fog_moon.png'},
|
||||||
|
51: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
53: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
55: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
56: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
57: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
61: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
63: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
65: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
66: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
67: {true: 'rain.png', false: 'rain.png'},
|
||||||
|
80: {true: 'rain-fall.png', false: 'rain-fall.png'},
|
||||||
|
81: {true: 'rain-fall.png', false: 'rain-fall.png'},
|
||||||
|
82: {true: 'rain-fall.png', false: 'rain-fall.png'},
|
||||||
|
71: {true: 'snow.png', false: 'snow.png'},
|
||||||
|
73: {true: 'snow.png', false: 'snow.png'},
|
||||||
|
75: {true: 'snow.png', false: 'snow.png'},
|
||||||
|
77: {true: 'snow.png', false: 'snow.png'},
|
||||||
|
85: {true: 'snow.png', false: 'snow.png'},
|
||||||
|
86: {true: 'snow.png', false: 'snow.png'},
|
||||||
|
95: {true: 'thunder.png', false: 'thunder.png'},
|
||||||
|
96: {true: 'storm.png', false: 'storm.png'},
|
||||||
|
99: {true: 'storm.png', false: 'storm.png'},
|
||||||
|
};
|
||||||
|
}
|
85
lib/app/ui/widgets/weather/sunset_sunrise.dart
Executable file
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
|
||||||
|
|
||||||
|
class SunsetSunrise extends StatefulWidget {
|
||||||
|
const SunsetSunrise({
|
||||||
|
super.key,
|
||||||
|
required this.timeSunrise,
|
||||||
|
required this.timeSunset,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String timeSunrise;
|
||||||
|
final String timeSunset;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SunsetSunrise> createState() => _SunsetSunriseState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SunsetSunriseState extends State<SunsetSunrise> {
|
||||||
|
final statusData = StatusData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = context.textTheme;
|
||||||
|
final titleSmall = textTheme.titleSmall;
|
||||||
|
final titleLarge = textTheme.titleLarge;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildSunTimeColumn(
|
||||||
|
context,
|
||||||
|
'sunrise'.tr,
|
||||||
|
statusData.getTimeFormat(widget.timeSunrise),
|
||||||
|
'assets/images/sunrise.png',
|
||||||
|
titleSmall,
|
||||||
|
titleLarge,
|
||||||
|
),
|
||||||
|
_buildSunTimeColumn(
|
||||||
|
context,
|
||||||
|
'sunset'.tr,
|
||||||
|
statusData.getTimeFormat(widget.timeSunset),
|
||||||
|
'assets/images/sunset.png',
|
||||||
|
titleSmall,
|
||||||
|
titleLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSunTimeColumn(
|
||||||
|
BuildContext context,
|
||||||
|
String label,
|
||||||
|
String time,
|
||||||
|
String imagePath,
|
||||||
|
TextStyle? labelStyle,
|
||||||
|
TextStyle? timeStyle,
|
||||||
|
) {
|
||||||
|
return Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(label, style: labelStyle, overflow: TextOverflow.ellipsis),
|
||||||
|
const Gap(2),
|
||||||
|
Text(time, style: timeStyle),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(5),
|
||||||
|
Flexible(child: Image.asset(imagePath, scale: 10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
26
lib/app/utils/color_converter.dart
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
extension HexColor on Color {
|
||||||
|
/// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#".
|
||||||
|
static Color fromHex(String hexString) {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
|
||||||
|
buffer.write(hexString.replaceFirst('#', ''));
|
||||||
|
return Color(int.parse(buffer.toString(), radix: 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`).
|
||||||
|
String toHex({bool leadingHashSign = true}) {
|
||||||
|
final argb = toARGB32(); // Get 32-bit integer representation
|
||||||
|
final a = (argb >> 24) & 0xFF;
|
||||||
|
final r = (argb >> 16) & 0xFF;
|
||||||
|
final g = (argb >> 8) & 0xFF;
|
||||||
|
final b = argb & 0xFF;
|
||||||
|
|
||||||
|
return '${leadingHashSign ? '#' : ''}'
|
||||||
|
'${a.toRadixString(16).padLeft(2, '0')}'
|
||||||
|
'${r.toRadixString(16).padLeft(2, '0')}'
|
||||||
|
'${g.toRadixString(16).padLeft(2, '0')}'
|
||||||
|
'${b.toRadixString(16).padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
}
|
31
lib/app/utils/device_info.dart
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class DeviceFeature {
|
||||||
|
DeviceFeature._internal();
|
||||||
|
|
||||||
|
static final DeviceFeature _singleton = DeviceFeature._internal();
|
||||||
|
|
||||||
|
factory DeviceFeature() {
|
||||||
|
return _singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
final _deviceInfoPlugin = DeviceInfoPlugin();
|
||||||
|
|
||||||
|
AndroidDeviceInfo? _androidDeviceInfo;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
try {
|
||||||
|
_androidDeviceInfo = await _deviceInfoPlugin.androidInfo;
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('Error initializing device info: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEdgeToEdgeAvailable() {
|
||||||
|
return _androidDeviceInfo != null &&
|
||||||
|
_androidDeviceInfo!.version.sdkInt > 28;
|
||||||
|
}
|
||||||
|
}
|
58
lib/app/utils/notification.dart
Executable file
|
@ -0,0 +1,58 @@
|
||||||
|
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 {
|
||||||
|
static const String _channelId = 'Rain';
|
||||||
|
static const String _channelName = 'DARK NIGHT';
|
||||||
|
|
||||||
|
Future<void> showNotification(
|
||||||
|
int id,
|
||||||
|
String title,
|
||||||
|
String body,
|
||||||
|
DateTime date,
|
||||||
|
String icon,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final imagePath = await _getLocalImagePath(icon);
|
||||||
|
final notificationDetails = await _buildNotificationDetails(imagePath);
|
||||||
|
final scheduledTime = _getScheduledTime(date);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
scheduledTime,
|
||||||
|
notificationDetails,
|
||||||
|
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||||
|
payload: imagePath,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error showing notification: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _getLocalImagePath(String icon) async {
|
||||||
|
return await WeatherController().getLocalImagePath(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<NotificationDetails> _buildNotificationDetails(
|
||||||
|
String imagePath,
|
||||||
|
) async {
|
||||||
|
final androidNotificationDetails = AndroidNotificationDetails(
|
||||||
|
_channelId,
|
||||||
|
_channelName,
|
||||||
|
priority: Priority.high,
|
||||||
|
importance: Importance.max,
|
||||||
|
playSound: false,
|
||||||
|
enableVibration: false,
|
||||||
|
largeIcon: FilePathAndroidBitmap(imagePath),
|
||||||
|
);
|
||||||
|
return NotificationDetails(android: androidNotificationDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
tz.TZDateTime _getScheduledTime(DateTime date) {
|
||||||
|
return tz.TZDateTime.from(date, tz.local);
|
||||||
|
}
|
||||||
|
}
|
17
lib/app/utils/show_snack_bar.dart
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldMessengerState> globalKey =
|
||||||
|
GlobalKey<ScaffoldMessengerState>();
|
||||||
|
|
||||||
|
void showSnackBar({required String content, VoidCallback? onPressed}) {
|
||||||
|
globalKey.currentState?.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(content),
|
||||||
|
action:
|
||||||
|
onPressed != null
|
||||||
|
? SnackBarAction(label: 'settings'.tr, onPressed: onPressed)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class MyTextButton extends StatelessWidget {
|
|
||||||
const MyTextButton({
|
|
||||||
super.key,
|
|
||||||
required this.buttonName,
|
|
||||||
required this.onPressed,
|
|
||||||
});
|
|
||||||
final String buttonName;
|
|
||||||
final VoidCallback onPressed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 50,
|
|
||||||
width: double.infinity,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: onPressed,
|
|
||||||
child: Text(
|
|
||||||
buttonName,
|
|
||||||
style: context.textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,250 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_glow/flutter_glow.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:iconsax/iconsax.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:rain/app/data/weather.dart';
|
|
||||||
import 'package:rain/app/widgets/desc/desc.dart';
|
|
||||||
import 'package:rain/app/widgets/desc/message.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_data.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_weather.dart';
|
|
||||||
import 'package:rain/app/widgets/sun_moon/sunset_sunrise.dart';
|
|
||||||
import 'package:rain/main.dart';
|
|
||||||
|
|
||||||
class InfoDailyCard extends StatefulWidget {
|
|
||||||
const InfoDailyCard({
|
|
||||||
super.key,
|
|
||||||
required this.weatherData,
|
|
||||||
required this.index,
|
|
||||||
});
|
|
||||||
|
|
||||||
final WeatherCard weatherData;
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<InfoDailyCard> createState() => _InfoDailyCardState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InfoDailyCardState extends State<InfoDailyCard> {
|
|
||||||
final statusWeather = StatusWeather();
|
|
||||||
final statusData = StatusData();
|
|
||||||
final message = Message();
|
|
||||||
late PageController pageController;
|
|
||||||
int pageIndex = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
pageController = PageController(initialPage: widget.index);
|
|
||||||
pageIndex = widget.index;
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
pageController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final weatherData = widget.weatherData;
|
|
||||||
final timeDaily = weatherData.timeDaily ?? [];
|
|
||||||
final weatherCodeDaily = weatherData.weathercodeDaily ?? [];
|
|
||||||
|
|
||||||
final textTheme = context.textTheme;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
centerTitle: true,
|
|
||||||
leading: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Iconsax.arrow_left_1,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
splashColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
DateFormat.MMMMEEEEd(locale.languageCode)
|
|
||||||
.format(timeDaily[pageIndex]),
|
|
||||||
style: textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: PageView.builder(
|
|
||||||
controller: pageController,
|
|
||||||
onPageChanged: (index) {
|
|
||||||
setState(() {
|
|
||||||
pageIndex = index;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
itemCount: timeDaily.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final indexedWeatherCodeDaily = weatherCodeDaily[index];
|
|
||||||
final apparentTemperatureMin =
|
|
||||||
weatherData.apparentTemperatureMin?[index];
|
|
||||||
final apparentTemperatureMax =
|
|
||||||
weatherData.apparentTemperatureMax?[index];
|
|
||||||
final uvIndexMax = weatherData.uvIndexMax?[index];
|
|
||||||
final windDirection10MDominant =
|
|
||||||
weatherData.winddirection10MDominant?[index];
|
|
||||||
final windSpeed10MMax = weatherData.windspeed10MMax?[index];
|
|
||||||
final windGusts10MMax = weatherData.windgusts10MMax?[index];
|
|
||||||
final precipitationProbabilityMax =
|
|
||||||
weatherData.precipitationProbabilityMax?[index];
|
|
||||||
final rainSum = weatherData.rainSum?[index];
|
|
||||||
final precipitationSum = weatherData.precipitationSum?[index];
|
|
||||||
|
|
||||||
return indexedWeatherCodeDaily == null
|
|
||||||
? null
|
|
||||||
: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
Image(
|
|
||||||
image: AssetImage(
|
|
||||||
statusWeather
|
|
||||||
.getImageNowDaily(indexedWeatherCodeDaily),
|
|
||||||
),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
height: 200,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
GlowText(
|
|
||||||
'${weatherData.temperature2MMin![index]?.round()} / ${weatherData.temperature2MMax![index]?.round()}',
|
|
||||||
style: textTheme.titleLarge?.copyWith(
|
|
||||||
fontSize: 35,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
blurRadius: 4,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
statusWeather.getText(indexedWeatherCodeDaily),
|
|
||||||
style: textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
DateFormat.MMMMEEEEd(locale.languageCode)
|
|
||||||
.format(timeDaily[index]),
|
|
||||||
style: textTheme.labelLarge?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 15),
|
|
||||||
child: SunsetSunrise(
|
|
||||||
timeSunrise: weatherData.sunrise![index],
|
|
||||||
timeSunset: weatherData.sunset![index],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.spaceEvenly,
|
|
||||||
spacing: 5,
|
|
||||||
children: [
|
|
||||||
apparentTemperatureMin == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/cold.png',
|
|
||||||
value: statusData.getDegree(
|
|
||||||
apparentTemperatureMin.round()),
|
|
||||||
desc: 'apparentTemperatureMin'.tr,
|
|
||||||
),
|
|
||||||
apparentTemperatureMax == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/hot.png',
|
|
||||||
value: statusData.getDegree(
|
|
||||||
apparentTemperatureMax.round()),
|
|
||||||
desc: 'apparentTemperatureMax'.tr,
|
|
||||||
),
|
|
||||||
uvIndexMax == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/uv.png',
|
|
||||||
value: '${uvIndexMax.round()}',
|
|
||||||
desc: 'uvIndex'.tr,
|
|
||||||
message: message
|
|
||||||
.getUvIndex(uvIndexMax.round()),
|
|
||||||
),
|
|
||||||
windDirection10MDominant == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/windsock.png',
|
|
||||||
value: '$windDirection10MDominant°',
|
|
||||||
desc: 'direction'.tr,
|
|
||||||
message: message.getDirection(
|
|
||||||
windDirection10MDominant),
|
|
||||||
),
|
|
||||||
windSpeed10MMax == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/wind.png',
|
|
||||||
value: statusData
|
|
||||||
.getSpeed(windSpeed10MMax.round()),
|
|
||||||
desc: 'wind'.tr,
|
|
||||||
),
|
|
||||||
windGusts10MMax == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName:
|
|
||||||
'assets/images/windgusts.png',
|
|
||||||
value: statusData
|
|
||||||
.getSpeed(windGusts10MMax.round()),
|
|
||||||
desc: 'windgusts'.tr,
|
|
||||||
),
|
|
||||||
precipitationProbabilityMax == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName:
|
|
||||||
'assets/images/precipitation_probability.png',
|
|
||||||
value: '$precipitationProbabilityMax%',
|
|
||||||
desc: 'precipitationProbability'.tr,
|
|
||||||
),
|
|
||||||
rainSum == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/water.png',
|
|
||||||
value: statusData
|
|
||||||
.getPrecipitation(rainSum),
|
|
||||||
desc: 'rain'.tr,
|
|
||||||
),
|
|
||||||
precipitationSum == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/rainfall.png',
|
|
||||||
value: statusData
|
|
||||||
.getPrecipitation(precipitationSum),
|
|
||||||
desc: 'precipitation'.tr,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_weather.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_data.dart';
|
|
||||||
import 'package:rain/main.dart';
|
|
||||||
|
|
||||||
class ListDailyCard extends StatefulWidget {
|
|
||||||
const ListDailyCard({
|
|
||||||
super.key,
|
|
||||||
required this.timeDaily,
|
|
||||||
required this.weathercodeDaily,
|
|
||||||
required this.temperature2MMax,
|
|
||||||
required this.temperature2MMin,
|
|
||||||
});
|
|
||||||
final DateTime timeDaily;
|
|
||||||
final int? weathercodeDaily;
|
|
||||||
final double? temperature2MMax;
|
|
||||||
final double? temperature2MMin;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ListDailyCard> createState() => _ListDailyCardState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ListDailyCardState extends State<ListDailyCard> {
|
|
||||||
final statusWeather = StatusWeather();
|
|
||||||
final statusData = StatusData();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return widget.weathercodeDaily == null
|
|
||||||
? Container()
|
|
||||||
: 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: [
|
|
||||||
Text(
|
|
||||||
'${statusData.getDegree(widget.temperature2MMin?.round())} / ${statusData.getDegree(widget.temperature2MMax?.round())}',
|
|
||||||
style: context.textTheme.titleLarge?.copyWith(
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
DateFormat.MMMMEEEEd(locale.languageCode)
|
|
||||||
.format(widget.timeDaily),
|
|
||||||
style: context.textTheme.titleMedium?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
statusWeather.getText(widget.weathercodeDaily),
|
|
||||||
style: context.textTheme.titleMedium?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Image.asset(
|
|
||||||
statusWeather.getImageNowDaily(widget.weathercodeDaily),
|
|
||||||
scale: 6.5,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,146 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:rain/app/data/weather.dart';
|
|
||||||
import 'package:rain/app/widgets/daily/info_daily_card.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_data.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_weather.dart';
|
|
||||||
import 'package:rain/main.dart';
|
|
||||||
|
|
||||||
class WeatherDaily extends StatefulWidget {
|
|
||||||
const WeatherDaily({
|
|
||||||
super.key,
|
|
||||||
required this.weatherData,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
final WeatherCard weatherData;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<WeatherDaily> createState() => _WeatherDailyState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WeatherDailyState extends State<WeatherDaily> {
|
|
||||||
final statusWeather = StatusWeather();
|
|
||||||
final statusData = StatusData();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final splashColor = context.theme.colorScheme.primary.withOpacity(0.4);
|
|
||||||
const inkWellBorderRadius = BorderRadius.all(
|
|
||||||
Radius.circular(16),
|
|
||||||
);
|
|
||||||
|
|
||||||
final weatherData = widget.weatherData;
|
|
||||||
final weatherCodeDaily = weatherData.weathercodeDaily ?? [];
|
|
||||||
final textTheme = context.textTheme;
|
|
||||||
final labelLarge = textTheme.labelLarge;
|
|
||||||
final bodyMediumGrey = textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: 7,
|
|
||||||
itemBuilder: (ctx, index) {
|
|
||||||
return InkWell(
|
|
||||||
splashColor: splashColor,
|
|
||||||
borderRadius: inkWellBorderRadius,
|
|
||||||
onTap: () => Get.to(
|
|
||||||
() => InfoDailyCard(
|
|
||||||
weatherData: weatherData,
|
|
||||||
index: index,
|
|
||||||
),
|
|
||||||
transition: Transition.downToUp,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
DateFormat.EEEE(locale.languageCode)
|
|
||||||
.format((weatherData.timeDaily ?? [])[index]),
|
|
||||||
style: labelLarge,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Image.asset(
|
|
||||||
statusWeather
|
|
||||||
.getImage7Day(weatherCodeDaily[index]),
|
|
||||||
scale: 3,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
statusWeather
|
|
||||||
.getText(weatherCodeDaily[index]),
|
|
||||||
style: labelLarge,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
statusData.getDegree(
|
|
||||||
(weatherData.temperature2MMin ?? [])[index]
|
|
||||||
?.round()),
|
|
||||||
style: labelLarge,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
' / ',
|
|
||||||
style: bodyMediumGrey,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
statusData.getDegree(
|
|
||||||
(weatherData.temperature2MMax ?? [])[index]
|
|
||||||
?.round()),
|
|
||||||
style: bodyMediumGrey,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
InkWell(
|
|
||||||
splashColor: splashColor,
|
|
||||||
borderRadius: inkWellBorderRadius,
|
|
||||||
onTap: widget.onTap,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
||||||
child: Text(
|
|
||||||
'weatherMore'.tr,
|
|
||||||
style: textTheme.titleMedium,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:iconsax/iconsax.dart';
|
|
||||||
import 'package:rain/app/data/weather.dart';
|
|
||||||
import 'package:rain/app/widgets/daily/info_daily_card.dart';
|
|
||||||
import 'package:rain/app/widgets/daily/list_daily_card.dart';
|
|
||||||
|
|
||||||
class WeatherMore extends StatefulWidget {
|
|
||||||
const WeatherMore({
|
|
||||||
super.key,
|
|
||||||
required this.weatherData,
|
|
||||||
});
|
|
||||||
final WeatherCard weatherData;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<WeatherMore> createState() => _WeatherMoreState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WeatherMoreState extends State<WeatherMore> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
const transparent = Colors.transparent;
|
|
||||||
final weatherData = widget.weatherData;
|
|
||||||
final timeDaily = weatherData.timeDaily ?? [];
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
centerTitle: true,
|
|
||||||
leading: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Iconsax.arrow_left_1,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
splashColor: transparent,
|
|
||||||
highlightColor: transparent,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
'weatherMore'.tr,
|
|
||||||
style: context.textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: timeDaily.length,
|
|
||||||
itemBuilder: (context, index) => GestureDetector(
|
|
||||||
onTap: () => Get.to(
|
|
||||||
() => InfoDailyCard(
|
|
||||||
weatherData: weatherData,
|
|
||||||
index: index,
|
|
||||||
),
|
|
||||||
transition: Transition.downToUp,
|
|
||||||
),
|
|
||||||
child: ListDailyCard(
|
|
||||||
timeDaily: timeDaily[index],
|
|
||||||
weathercodeDaily: weatherData.weathercodeDaily![index],
|
|
||||||
temperature2MMax: weatherData.temperature2MMax![index],
|
|
||||||
temperature2MMin: weatherData.temperature2MMin![index],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,189 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:rain/app/widgets/desc/desc.dart';
|
|
||||||
import 'package:rain/app/widgets/desc/message.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_data.dart';
|
|
||||||
|
|
||||||
class DescContainer extends StatefulWidget {
|
|
||||||
const DescContainer({
|
|
||||||
super.key,
|
|
||||||
required this.humidity,
|
|
||||||
required this.wind,
|
|
||||||
required this.visibility,
|
|
||||||
required this.feels,
|
|
||||||
required this.evaporation,
|
|
||||||
required this.precipitation,
|
|
||||||
required this.direction,
|
|
||||||
required this.pressure,
|
|
||||||
required this.rain,
|
|
||||||
required this.cloudcover,
|
|
||||||
required this.windgusts,
|
|
||||||
required this.uvIndex,
|
|
||||||
required this.dewpoint2M,
|
|
||||||
required this.precipitationProbability,
|
|
||||||
required this.shortwaveRadiation,
|
|
||||||
});
|
|
||||||
final int? humidity;
|
|
||||||
final double? wind;
|
|
||||||
final double? visibility;
|
|
||||||
final double? feels;
|
|
||||||
final double? evaporation;
|
|
||||||
final double? precipitation;
|
|
||||||
final int? direction;
|
|
||||||
final double? pressure;
|
|
||||||
final double? rain;
|
|
||||||
final int? cloudcover;
|
|
||||||
final double? windgusts;
|
|
||||||
final double? uvIndex;
|
|
||||||
final double? dewpoint2M;
|
|
||||||
final int? precipitationProbability;
|
|
||||||
final double? shortwaveRadiation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<DescContainer> createState() => _DescContainerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DescContainerState extends State<DescContainer> {
|
|
||||||
final statusData = StatusData();
|
|
||||||
final message = Message();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final dewpoint2M = widget.dewpoint2M?.round();
|
|
||||||
final feels = widget.feels;
|
|
||||||
final visibility = widget.visibility;
|
|
||||||
final direction = widget.direction;
|
|
||||||
final wind = widget.wind;
|
|
||||||
final windgusts = widget.windgusts;
|
|
||||||
final evaporation = widget.evaporation;
|
|
||||||
final precipitation = widget.precipitation;
|
|
||||||
final rain = widget.rain;
|
|
||||||
final precipitationProbability = widget.precipitationProbability;
|
|
||||||
final humidity = widget.humidity;
|
|
||||||
final cloudcover = widget.cloudcover;
|
|
||||||
final pressure = widget.pressure;
|
|
||||||
final uvIndex = widget.uvIndex;
|
|
||||||
final shortwaveRadiation = widget.shortwaveRadiation;
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 22, bottom: 5),
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.spaceEvenly,
|
|
||||||
spacing: 5,
|
|
||||||
children: [
|
|
||||||
dewpoint2M == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/dew.png',
|
|
||||||
value: statusData.getDegree(dewpoint2M.round()),
|
|
||||||
desc: 'dewpoint'.tr,
|
|
||||||
),
|
|
||||||
feels == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/temperature.png',
|
|
||||||
value: statusData.getDegree(feels.round()),
|
|
||||||
desc: 'feels'.tr,
|
|
||||||
),
|
|
||||||
visibility == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/fog.png',
|
|
||||||
value: statusData.getVisibility(visibility),
|
|
||||||
desc: 'visibility'.tr,
|
|
||||||
),
|
|
||||||
direction == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/windsock.png',
|
|
||||||
value: '$direction°',
|
|
||||||
desc: 'direction'.tr,
|
|
||||||
message: message.getDirection(direction),
|
|
||||||
),
|
|
||||||
wind == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/wind.png',
|
|
||||||
value: statusData.getSpeed(wind.round()),
|
|
||||||
desc: 'wind'.tr,
|
|
||||||
),
|
|
||||||
windgusts == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/windgusts.png',
|
|
||||||
value: statusData.getSpeed(windgusts.round()),
|
|
||||||
desc: 'windgusts'.tr,
|
|
||||||
),
|
|
||||||
evaporation == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/evaporation.png',
|
|
||||||
value: statusData.getPrecipitation(evaporation.abs()),
|
|
||||||
desc: 'evaporation'.tr,
|
|
||||||
),
|
|
||||||
precipitation == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/rainfall.png',
|
|
||||||
value: statusData.getPrecipitation(precipitation),
|
|
||||||
desc: 'precipitation'.tr,
|
|
||||||
),
|
|
||||||
rain == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/water.png',
|
|
||||||
value: statusData.getPrecipitation(rain),
|
|
||||||
desc: 'rain'.tr,
|
|
||||||
),
|
|
||||||
precipitationProbability == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/precipitation_probability.png',
|
|
||||||
value: '$precipitationProbability%',
|
|
||||||
desc: 'precipitationProbability'.tr,
|
|
||||||
),
|
|
||||||
humidity == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/humidity.png',
|
|
||||||
value: '$humidity%',
|
|
||||||
desc: 'humidity'.tr,
|
|
||||||
),
|
|
||||||
cloudcover == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/cloudy.png',
|
|
||||||
value: '$cloudcover%',
|
|
||||||
desc: 'cloudcover'.tr,
|
|
||||||
),
|
|
||||||
pressure == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/atmospheric.png',
|
|
||||||
value: '${pressure.round()} ${'hPa'.tr}',
|
|
||||||
desc: 'pressure'.tr,
|
|
||||||
message: message.getPressure(pressure.round()),
|
|
||||||
),
|
|
||||||
uvIndex == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/uv.png',
|
|
||||||
value: '${uvIndex.round()}',
|
|
||||||
desc: 'uvIndex'.tr,
|
|
||||||
message: message.getUvIndex(uvIndex.round()),
|
|
||||||
),
|
|
||||||
shortwaveRadiation == null
|
|
||||||
? const Offstage()
|
|
||||||
: DescWeather(
|
|
||||||
imageName: 'assets/images/shortwave_radiation.png',
|
|
||||||
value: '${shortwaveRadiation.round()} ${'W/m2'.tr}',
|
|
||||||
desc: 'shortwaveRadiation'.tr,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class Message {
|
|
||||||
String getPressure(int? pressure) {
|
|
||||||
if (pressure != null) {
|
|
||||||
if (pressure < 1000) {
|
|
||||||
return 'low'.tr;
|
|
||||||
} else if (pressure > 1020) {
|
|
||||||
return 'high'.tr;
|
|
||||||
} else {
|
|
||||||
return 'normal'.tr;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getUvIndex(int? uvIndex) {
|
|
||||||
if (uvIndex != null) {
|
|
||||||
if (uvIndex < 3) {
|
|
||||||
return 'uvLow'.tr;
|
|
||||||
} else if (uvIndex < 6) {
|
|
||||||
return 'uvAverage'.tr;
|
|
||||||
} else if (uvIndex < 8) {
|
|
||||||
return 'uvHigh'.tr;
|
|
||||||
} else if (uvIndex < 11) {
|
|
||||||
return 'uvVeryHigh'.tr;
|
|
||||||
} else {
|
|
||||||
return 'uvExtreme'.tr;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getDirection(int? direction) {
|
|
||||||
if (direction != null) {
|
|
||||||
if (direction >= 337.5 || direction < 22.5) {
|
|
||||||
return 'north'.tr;
|
|
||||||
} else if (direction >= 22.5 && direction < 67.5) {
|
|
||||||
return 'northeast'.tr;
|
|
||||||
} else if (direction >= 67.5 && direction < 112.5) {
|
|
||||||
return 'east'.tr;
|
|
||||||
} else if (direction >= 112.5 && direction < 157.5) {
|
|
||||||
return 'southeast'.tr;
|
|
||||||
} else if (direction >= 157.5 && direction < 202.5) {
|
|
||||||
return 'south'.tr;
|
|
||||||
} else if (direction >= 202.5 && direction < 247.5) {
|
|
||||||
return 'southwest'.tr;
|
|
||||||
} else if (direction >= 247.5 && direction < 292.5) {
|
|
||||||
return 'west'.tr;
|
|
||||||
} else {
|
|
||||||
return 'northwest'.tr;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_data.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_weather.dart';
|
|
||||||
import 'package:rain/main.dart';
|
|
||||||
|
|
||||||
class WeatherHourly extends StatefulWidget {
|
|
||||||
const WeatherHourly({
|
|
||||||
super.key,
|
|
||||||
required this.time,
|
|
||||||
required this.weather,
|
|
||||||
required this.degree,
|
|
||||||
required this.timeDay,
|
|
||||||
required this.timeNight,
|
|
||||||
});
|
|
||||||
final String time;
|
|
||||||
final String timeDay;
|
|
||||||
final String timeNight;
|
|
||||||
final int weather;
|
|
||||||
final double degree;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<WeatherHourly> createState() => _WeatherHourlyState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WeatherHourlyState extends State<WeatherHourly> {
|
|
||||||
final statusWeather = StatusWeather();
|
|
||||||
final statusData = StatusData();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final textTheme = context.textTheme;
|
|
||||||
final time = widget.time;
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
statusData.getTimeFormat(time),
|
|
||||||
style: textTheme.labelLarge,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
DateFormat('E', locale.languageCode)
|
|
||||||
.format(DateTime.tryParse(time)!),
|
|
||||||
style: textTheme.labelLarge?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Image.asset(
|
|
||||||
statusWeather.getImageToday(
|
|
||||||
widget.weather,
|
|
||||||
time,
|
|
||||||
widget.timeDay,
|
|
||||||
widget.timeNight,
|
|
||||||
),
|
|
||||||
scale: 3,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
statusData.getDegree(widget.degree.round()),
|
|
||||||
style: textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_glow/flutter_glow.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_weather.dart';
|
|
||||||
import 'package:rain/main.dart';
|
|
||||||
|
|
||||||
class WeatherNow extends StatefulWidget {
|
|
||||||
const WeatherNow({
|
|
||||||
super.key,
|
|
||||||
required this.weather,
|
|
||||||
required this.degree,
|
|
||||||
required this.time,
|
|
||||||
required this.timeDay,
|
|
||||||
required this.timeNight,
|
|
||||||
});
|
|
||||||
final String time;
|
|
||||||
final String timeDay;
|
|
||||||
final String timeNight;
|
|
||||||
final int weather;
|
|
||||||
final double degree;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<WeatherNow> createState() => _WeatherNowState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WeatherNowState extends State<WeatherNow> {
|
|
||||||
final statusWeather = StatusWeather();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
Image(
|
|
||||||
image: AssetImage(statusWeather.getImageNow(
|
|
||||||
widget.weather, widget.time, widget.timeDay, widget.timeNight)),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
height: 200,
|
|
||||||
),
|
|
||||||
GlowText(
|
|
||||||
'${roundDegree ? widget.degree.round() : widget.degree}',
|
|
||||||
style: context.textTheme.displayLarge?.copyWith(
|
|
||||||
fontSize: 90,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
statusWeather.getText(widget.weather),
|
|
||||||
style: context.textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
DateFormat.MMMMEEEEd(locale.languageCode).format(
|
|
||||||
DateTime.parse(widget.time),
|
|
||||||
),
|
|
||||||
style: context.textTheme.labelLarge?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:rain/main.dart';
|
|
||||||
import 'package:timezone/timezone.dart';
|
|
||||||
|
|
||||||
class StatusData {
|
|
||||||
String getDegree(int? degree) {
|
|
||||||
switch (settings.degrees) {
|
|
||||||
case 'celsius':
|
|
||||||
return '$degree°C';
|
|
||||||
case 'fahrenheit':
|
|
||||||
return '$degree°F';
|
|
||||||
default:
|
|
||||||
return '$degree°C';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getSpeed(int? speed) {
|
|
||||||
switch (settings.measurements) {
|
|
||||||
case 'metric':
|
|
||||||
return '$speed ${'kph'.tr}';
|
|
||||||
case 'imperial':
|
|
||||||
return '$speed ${'mph'.tr}';
|
|
||||||
default:
|
|
||||||
return '$speed ${'kph'.tr}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getVisibility(double? length) {
|
|
||||||
if (length != null) {
|
|
||||||
switch (settings.measurements) {
|
|
||||||
case 'metric':
|
|
||||||
return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}';
|
|
||||||
case 'imperial':
|
|
||||||
return '${length > 5280 ? (length / 5280).round() : (length / 5280).toStringAsFixed(2)} ${'mi'.tr}';
|
|
||||||
default:
|
|
||||||
return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getPrecipitation(double? precipitation) {
|
|
||||||
switch (settings.measurements) {
|
|
||||||
case 'metric':
|
|
||||||
return '$precipitation ${'mm'.tr}';
|
|
||||||
case 'imperial':
|
|
||||||
return '$precipitation ${'inch'.tr}';
|
|
||||||
default:
|
|
||||||
return '$precipitation ${'mm'.tr}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getTimeFormat(String time) {
|
|
||||||
switch (settings.timeformat) {
|
|
||||||
case '12':
|
|
||||||
return DateFormat.jm().format(DateTime.tryParse(time)!);
|
|
||||||
case '24':
|
|
||||||
return DateFormat.Hm().format(DateTime.tryParse(time)!);
|
|
||||||
default:
|
|
||||||
return DateFormat.Hm().format(DateTime.tryParse(time)!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getTimeFormatTz(TZDateTime time) {
|
|
||||||
switch (settings.timeformat) {
|
|
||||||
case '12':
|
|
||||||
return DateFormat.jm().format(time);
|
|
||||||
case '24':
|
|
||||||
return DateFormat.Hm().format(time);
|
|
||||||
default:
|
|
||||||
return DateFormat.Hm().format(time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,340 +0,0 @@
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
const assetImageRoot = 'assets/images/';
|
|
||||||
|
|
||||||
class StatusWeather {
|
|
||||||
String getImageNow(
|
|
||||||
int weather, String time, String timeDay, String timeNight) {
|
|
||||||
final currentTime = DateTime.parse(time);
|
|
||||||
final day = DateTime.parse(timeDay);
|
|
||||||
final night = DateTime.parse(timeNight);
|
|
||||||
|
|
||||||
final dayTime =
|
|
||||||
DateTime(day.year, day.month, day.day, day.hour, day.minute);
|
|
||||||
final nightTime =
|
|
||||||
DateTime(night.year, night.month, night.day, night.hour, night.minute);
|
|
||||||
|
|
||||||
switch (weather) {
|
|
||||||
case 0:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}sun.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}full-moon.png';
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}cloud.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}moon.png';
|
|
||||||
}
|
|
||||||
case 45:
|
|
||||||
case 48:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}fog.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}fog_moon.png';
|
|
||||||
}
|
|
||||||
case 51:
|
|
||||||
case 53:
|
|
||||||
case 55:
|
|
||||||
case 56:
|
|
||||||
case 57:
|
|
||||||
case 61:
|
|
||||||
case 63:
|
|
||||||
case 65:
|
|
||||||
case 66:
|
|
||||||
case 67:
|
|
||||||
return '${assetImageRoot}rain.png';
|
|
||||||
case 80:
|
|
||||||
case 81:
|
|
||||||
case 82:
|
|
||||||
return '${assetImageRoot}rain-fall.png';
|
|
||||||
case 71:
|
|
||||||
case 73:
|
|
||||||
case 75:
|
|
||||||
case 77:
|
|
||||||
case 85:
|
|
||||||
case 86:
|
|
||||||
return '${assetImageRoot}snow.png';
|
|
||||||
case 95:
|
|
||||||
return '${assetImageRoot}thunder.png';
|
|
||||||
case 96:
|
|
||||||
case 99:
|
|
||||||
return '${assetImageRoot}storm.png';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getImageNowDaily(int? weather) {
|
|
||||||
switch (weather) {
|
|
||||||
case 0:
|
|
||||||
return '${assetImageRoot}sun.png';
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
return '${assetImageRoot}cloud.png';
|
|
||||||
case 45:
|
|
||||||
case 48:
|
|
||||||
return '${assetImageRoot}fog.png';
|
|
||||||
case 51:
|
|
||||||
case 53:
|
|
||||||
case 55:
|
|
||||||
case 56:
|
|
||||||
case 57:
|
|
||||||
case 61:
|
|
||||||
case 63:
|
|
||||||
case 65:
|
|
||||||
case 66:
|
|
||||||
case 67:
|
|
||||||
return '${assetImageRoot}rain.png';
|
|
||||||
case 80:
|
|
||||||
case 81:
|
|
||||||
case 82:
|
|
||||||
return '${assetImageRoot}rain-fall.png';
|
|
||||||
case 71:
|
|
||||||
case 73:
|
|
||||||
case 75:
|
|
||||||
case 77:
|
|
||||||
case 85:
|
|
||||||
case 86:
|
|
||||||
return '${assetImageRoot}snow.png';
|
|
||||||
case 95:
|
|
||||||
return '${assetImageRoot}thunder.png';
|
|
||||||
case 96:
|
|
||||||
case 99:
|
|
||||||
return '${assetImageRoot}storm.png';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getImageToday(
|
|
||||||
int weather, String time, String timeDay, String timeNight) {
|
|
||||||
final currentTime = DateTime.parse(time);
|
|
||||||
final day = DateTime.parse(timeDay);
|
|
||||||
final night = DateTime.parse(timeNight);
|
|
||||||
|
|
||||||
final dayTime =
|
|
||||||
DateTime(day.year, day.month, day.day, day.hour, day.minute);
|
|
||||||
final nightTime =
|
|
||||||
DateTime(night.year, night.month, night.day, night.hour, night.minute);
|
|
||||||
|
|
||||||
switch (weather) {
|
|
||||||
case 0:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}clear_day.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}clear_night.png';
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}cloudy_day.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}cloudy_night.png';
|
|
||||||
}
|
|
||||||
case 45:
|
|
||||||
case 48:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}fog_day.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}fog_night.png';
|
|
||||||
}
|
|
||||||
case 51:
|
|
||||||
case 53:
|
|
||||||
case 55:
|
|
||||||
case 56:
|
|
||||||
case 57:
|
|
||||||
case 61:
|
|
||||||
case 63:
|
|
||||||
case 65:
|
|
||||||
case 66:
|
|
||||||
case 67:
|
|
||||||
case 80:
|
|
||||||
case 81:
|
|
||||||
case 82:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}rain_day.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}rain_night.png';
|
|
||||||
}
|
|
||||||
case 71:
|
|
||||||
case 73:
|
|
||||||
case 75:
|
|
||||||
case 77:
|
|
||||||
case 85:
|
|
||||||
case 86:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}snow_day.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}snow_night.png';
|
|
||||||
}
|
|
||||||
case 95:
|
|
||||||
case 96:
|
|
||||||
case 99:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return '${assetImageRoot}thunder_day.png';
|
|
||||||
} else {
|
|
||||||
return '${assetImageRoot}thunder_night.png';
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getImage7Day(int? weather) {
|
|
||||||
switch (weather) {
|
|
||||||
case 0:
|
|
||||||
return '${assetImageRoot}clear_day.png';
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
return '${assetImageRoot}cloudy_day.png';
|
|
||||||
case 45:
|
|
||||||
case 48:
|
|
||||||
return '${assetImageRoot}fog_day.png';
|
|
||||||
case 51:
|
|
||||||
case 53:
|
|
||||||
case 55:
|
|
||||||
case 56:
|
|
||||||
case 57:
|
|
||||||
case 61:
|
|
||||||
case 63:
|
|
||||||
case 65:
|
|
||||||
case 66:
|
|
||||||
case 67:
|
|
||||||
case 80:
|
|
||||||
case 81:
|
|
||||||
case 82:
|
|
||||||
return '${assetImageRoot}rain_day.png';
|
|
||||||
case 71:
|
|
||||||
case 73:
|
|
||||||
case 75:
|
|
||||||
case 77:
|
|
||||||
case 85:
|
|
||||||
case 86:
|
|
||||||
return '${assetImageRoot}snow_day.png';
|
|
||||||
case 95:
|
|
||||||
case 96:
|
|
||||||
case 99:
|
|
||||||
return '${assetImageRoot}thunder_day.png';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getText(int? weather) {
|
|
||||||
switch (weather) {
|
|
||||||
case 0:
|
|
||||||
return 'clear_sky'.tr;
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
return 'cloudy'.tr;
|
|
||||||
case 3:
|
|
||||||
return 'overcast'.tr;
|
|
||||||
case 45:
|
|
||||||
case 48:
|
|
||||||
return 'fog'.tr;
|
|
||||||
case 51:
|
|
||||||
case 53:
|
|
||||||
case 55:
|
|
||||||
return 'drizzle'.tr;
|
|
||||||
case 56:
|
|
||||||
case 57:
|
|
||||||
return 'drizzling_rain'.tr;
|
|
||||||
case 61:
|
|
||||||
case 63:
|
|
||||||
case 65:
|
|
||||||
return 'rain'.tr;
|
|
||||||
case 66:
|
|
||||||
case 67:
|
|
||||||
return 'freezing_rain'.tr;
|
|
||||||
case 80:
|
|
||||||
case 81:
|
|
||||||
case 82:
|
|
||||||
return 'heavy_rains'.tr;
|
|
||||||
case 71:
|
|
||||||
case 73:
|
|
||||||
case 75:
|
|
||||||
case 77:
|
|
||||||
case 85:
|
|
||||||
case 86:
|
|
||||||
return 'snow'.tr;
|
|
||||||
case 95:
|
|
||||||
case 96:
|
|
||||||
case 99:
|
|
||||||
return 'thunderstorm'.tr;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getImageNotification(
|
|
||||||
int weather, String time, String timeDay, String timeNight) {
|
|
||||||
final currentTime = DateTime.parse(time);
|
|
||||||
final day = DateTime.parse(timeDay);
|
|
||||||
final night = DateTime.parse(timeNight);
|
|
||||||
|
|
||||||
final dayTime =
|
|
||||||
DateTime(day.year, day.month, day.day, day.hour, day.minute);
|
|
||||||
final nightTime =
|
|
||||||
DateTime(night.year, night.month, night.day, night.hour, night.minute);
|
|
||||||
|
|
||||||
switch (weather) {
|
|
||||||
case 0:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return 'sun.png';
|
|
||||||
} else {
|
|
||||||
return 'full-moon.png';
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return 'cloud.png';
|
|
||||||
} else {
|
|
||||||
return 'moon.png';
|
|
||||||
}
|
|
||||||
case 45:
|
|
||||||
case 48:
|
|
||||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
|
||||||
return 'fog.png';
|
|
||||||
} else {
|
|
||||||
return 'fog_moon.png';
|
|
||||||
}
|
|
||||||
case 51:
|
|
||||||
case 53:
|
|
||||||
case 55:
|
|
||||||
case 56:
|
|
||||||
case 57:
|
|
||||||
case 61:
|
|
||||||
case 63:
|
|
||||||
case 65:
|
|
||||||
case 66:
|
|
||||||
case 67:
|
|
||||||
return 'rain.png';
|
|
||||||
case 80:
|
|
||||||
case 81:
|
|
||||||
case 82:
|
|
||||||
return 'rain-fall.png';
|
|
||||||
case 71:
|
|
||||||
case 73:
|
|
||||||
case 75:
|
|
||||||
case 77:
|
|
||||||
case 85:
|
|
||||||
case 86:
|
|
||||||
return 'snow.png';
|
|
||||||
case 95:
|
|
||||||
return 'thunder.png';
|
|
||||||
case 96:
|
|
||||||
case 99:
|
|
||||||
return 'storm.png';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:rain/app/widgets/status/status_data.dart';
|
|
||||||
|
|
||||||
class SunsetSunrise extends StatefulWidget {
|
|
||||||
const SunsetSunrise({
|
|
||||||
super.key,
|
|
||||||
required this.timeSunrise,
|
|
||||||
required this.timeSunset,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String timeSunrise;
|
|
||||||
final String timeSunset;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SunsetSunrise> createState() => _SunsetSunriseState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SunsetSunriseState extends State<SunsetSunrise> {
|
|
||||||
final statusData = StatusData();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
const crossAxisCenterAlignment = CrossAxisAlignment.center;
|
|
||||||
final textTheme = context.textTheme;
|
|
||||||
final titleSmall = textTheme.titleSmall;
|
|
||||||
final titleLarge = textTheme.titleLarge;
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: crossAxisCenterAlignment,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: crossAxisCenterAlignment,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'sunrise'.tr,
|
|
||||||
style: titleSmall,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
statusData.getTimeFormat(widget.timeSunrise),
|
|
||||||
style: titleLarge,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Flexible(
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/sunrise.png',
|
|
||||||
scale: 10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: crossAxisCenterAlignment,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: crossAxisCenterAlignment,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'sunset'.tr,
|
|
||||||
style: titleSmall,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
statusData.getTimeFormat(widget.timeSunset),
|
|
||||||
style: titleLarge,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Flexible(
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/sunset.png',
|
|
||||||
scale: 10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
407
lib/main.dart
Normal file → Executable file
|
@ -10,30 +10,36 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_timezone/flutter_timezone.dart';
|
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:home_widget/home_widget.dart';
|
import 'package:home_widget/home_widget.dart';
|
||||||
|
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:rain/app/controller/controller.dart';
|
import 'package:rain/app/controller/controller.dart';
|
||||||
import 'package:rain/app/modules/home.dart';
|
import 'package:rain/app/data/db.dart';
|
||||||
import 'package:rain/app/modules/onboarding.dart';
|
import 'package:rain/app/ui/geolocation.dart';
|
||||||
|
import 'package:rain/app/ui/home.dart';
|
||||||
|
import 'package:rain/app/ui/onboarding.dart';
|
||||||
import 'package:rain/theme/theme.dart';
|
import 'package:rain/theme/theme.dart';
|
||||||
import 'package:time_machine/time_machine.dart';
|
import 'package:rain/theme/theme_controller.dart';
|
||||||
|
import 'package:rain/translation/translation.dart';
|
||||||
|
import 'package:rain/app/utils/device_info.dart';
|
||||||
import 'package:timezone/data/latest_all.dart' as tz;
|
import 'package:timezone/data/latest_all.dart' as tz;
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'app/data/weather.dart';
|
|
||||||
import 'theme/theme_controller.dart';
|
|
||||||
import 'translation/translation.dart';
|
|
||||||
|
|
||||||
late Isar isar;
|
late Isar isar;
|
||||||
late Settings settings;
|
late Settings settings;
|
||||||
bool isOnline = false;
|
late LocationCache locationCache;
|
||||||
|
final ValueNotifier<Future<bool>> isOnline = ValueNotifier(
|
||||||
|
InternetConnection().hasInternetAccess,
|
||||||
|
);
|
||||||
|
|
||||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
bool amoledTheme = false;
|
bool amoledTheme = false;
|
||||||
bool materialColor = false;
|
bool materialColor = false;
|
||||||
bool roundDegree = false;
|
bool roundDegree = false;
|
||||||
|
bool largeElement = false;
|
||||||
Locale locale = const Locale('en', 'US');
|
Locale locale = const Locale('en', 'US');
|
||||||
int timeRange = 1;
|
int timeRange = 1;
|
||||||
String timeStart = '09:00';
|
String timeStart = '09:00';
|
||||||
|
@ -41,32 +47,36 @@ String timeEnd = '21:00';
|
||||||
String widgetBackgroundColor = '';
|
String widgetBackgroundColor = '';
|
||||||
String widgetTextColor = '';
|
String widgetTextColor = '';
|
||||||
|
|
||||||
final List appLanguages = [
|
|
||||||
{'name': 'বাংলা', 'locale': const Locale('bn', 'IN')},
|
|
||||||
{'name': 'Čeština', 'locale': const Locale('cs', 'CZ')},
|
|
||||||
{'name': 'Deutsch', 'locale': const Locale('de', 'DE')},
|
|
||||||
{'name': 'English', 'locale': const Locale('en', 'US')},
|
|
||||||
{'name': 'Español', 'locale': const Locale('es', 'ES')},
|
|
||||||
{'name': 'Français', 'locale': const Locale('fr', 'FR')},
|
|
||||||
// {'name': 'Gaeilge', 'locale': const Locale('ga', 'IE')},
|
|
||||||
{'name': 'हिन्दी', 'locale': const Locale('hi', 'IN')},
|
|
||||||
{'name': 'Magyar', 'locale': const Locale('hu', 'HU')},
|
|
||||||
{'name': 'Italiano', 'locale': const Locale('it', 'IT')},
|
|
||||||
{'name': 'ქართული', 'locale': const Locale('ka', 'GE')},
|
|
||||||
{'name': 'Nederlands', 'locale': const Locale('nl', 'NL')},
|
|
||||||
{'name': 'Polski', 'locale': const Locale('pl', 'PL')},
|
|
||||||
{'name': 'Português (Brasil)', 'locale': const Locale('pt', 'BR')},
|
|
||||||
{'name': 'Română', 'locale': const Locale('ro', 'RO')},
|
|
||||||
{'name': 'Русский', 'locale': const Locale('ru', 'RU')},
|
|
||||||
{'name': 'Slovenčina', 'locale': const Locale('sk', 'SK')},
|
|
||||||
{'name': 'Türkçe', 'locale': const Locale('tr', 'TR')},
|
|
||||||
{'name': 'اردو', 'locale': const Locale('ur', 'PK')},
|
|
||||||
{'name': '中文', 'locale': const Locale('zh', 'CN')},
|
|
||||||
];
|
|
||||||
|
|
||||||
const String appGroupId = 'DARK NIGHT';
|
const String appGroupId = 'DARK NIGHT';
|
||||||
const String androidWidgetName = 'OreoWidget';
|
const String androidWidgetName = 'OreoWidget';
|
||||||
|
|
||||||
|
const List<Map<String, dynamic>> appLanguages = [
|
||||||
|
{'name': 'বাংলা', 'locale': Locale('bn', 'IN')},
|
||||||
|
{'name': 'Čeština', 'locale': Locale('cs', 'CZ')},
|
||||||
|
{'name': 'Dansk', 'locale': Locale('da', 'DK')},
|
||||||
|
{'name': 'Deutsch', 'locale': Locale('de', 'DE')},
|
||||||
|
{'name': 'English', 'locale': Locale('en', 'US')},
|
||||||
|
{'name': 'Español', 'locale': Locale('es', 'ES')},
|
||||||
|
{'name': 'Français', 'locale': Locale('fr', 'FR')},
|
||||||
|
// {'name': 'Gaeilge', 'locale': Locale('ga', 'IE')},
|
||||||
|
{'name': 'हिन्दी', 'locale': Locale('hi', 'IN')},
|
||||||
|
{'name': 'Magyar', 'locale': Locale('hu', 'HU')},
|
||||||
|
{'name': 'Italiano', 'locale': Locale('it', 'IT')},
|
||||||
|
{'name': '한국어', 'locale': Locale('ko', 'KR')},
|
||||||
|
{'name': 'فارسی', 'locale': Locale('fa', 'IR')},
|
||||||
|
{'name': 'ქართული', 'locale': Locale('ka', 'GE')},
|
||||||
|
{'name': 'Nederlands', 'locale': Locale('nl', 'NL')},
|
||||||
|
{'name': 'Polski', 'locale': Locale('pl', 'PL')},
|
||||||
|
{'name': 'Português (Brasil)', 'locale': Locale('pt', 'BR')},
|
||||||
|
{'name': 'Română', 'locale': Locale('ro', 'RO')},
|
||||||
|
{'name': 'Русский', 'locale': Locale('ru', 'RU')},
|
||||||
|
{'name': 'Slovenčina', 'locale': Locale('sk', 'SK')},
|
||||||
|
{'name': 'Türkçe', 'locale': Locale('tr', 'TR')},
|
||||||
|
{'name': 'اردو', 'locale': Locale('ur', 'PK')},
|
||||||
|
{'name': '中文(简体)', 'locale': Locale('zh', 'CN')},
|
||||||
|
{'name': '中文(繁體)', 'locale': Locale('zh', 'TW')},
|
||||||
|
];
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void callbackDispatcher() {
|
void callbackDispatcher() {
|
||||||
Workmanager().executeTask((task, inputData) {
|
Workmanager().executeTask((task, inputData) {
|
||||||
|
@ -75,55 +85,40 @@ void callbackDispatcher() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
final String timeZoneName;
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
|
await initializeApp();
|
||||||
result == ConnectivityResult.none ? isOnline = false : isOnline = true;
|
|
||||||
});
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
|
||||||
const SystemUiOverlayStyle(systemNavigationBarColor: Colors.black));
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode);
|
|
||||||
await setOptimalDisplayMode();
|
|
||||||
}
|
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
|
||||||
timeZoneName = await FlutterTimezone.getLocalTimezone();
|
|
||||||
} else {
|
|
||||||
timeZoneName = '${DateTimeZone.local}';
|
|
||||||
}
|
|
||||||
tz.initializeTimeZones();
|
|
||||||
tz.setLocalLocation(tz.getLocation(timeZoneName));
|
|
||||||
await isarInit();
|
|
||||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
|
||||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
||||||
const DarwinInitializationSettings initializationSettingsDarwin =
|
|
||||||
DarwinInitializationSettings();
|
|
||||||
const LinuxInitializationSettings initializationSettingsLinux =
|
|
||||||
LinuxInitializationSettings(defaultActionName: 'Rain');
|
|
||||||
const InitializationSettings initializationSettings = InitializationSettings(
|
|
||||||
android: initializationSettingsAndroid,
|
|
||||||
iOS: initializationSettingsDarwin,
|
|
||||||
linux: initializationSettingsLinux,
|
|
||||||
);
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setOptimalDisplayMode() async {
|
Future<void> initializeApp() async {
|
||||||
final List<DisplayMode> supported = await FlutterDisplayMode.supported;
|
setupConnectivityListener();
|
||||||
final DisplayMode active = await FlutterDisplayMode.active;
|
await initializeTimeZone();
|
||||||
final List<DisplayMode> sameResolution = supported
|
await initializeIsar();
|
||||||
.where((DisplayMode m) =>
|
await initializeNotifications();
|
||||||
m.width == active.width && m.height == active.height)
|
if (Platform.isAndroid) {
|
||||||
.toList()
|
await setOptimalDisplayMode();
|
||||||
..sort((DisplayMode a, DisplayMode b) =>
|
Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode);
|
||||||
b.refreshRate.compareTo(a.refreshRate));
|
HomeWidget.setAppGroupId(appGroupId);
|
||||||
final DisplayMode mostOptimalMode =
|
}
|
||||||
sameResolution.isNotEmpty ? sameResolution.first : active;
|
DeviceFeature().init();
|
||||||
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> isarInit() async {
|
void setupConnectivityListener() {
|
||||||
|
Connectivity().onConnectivityChanged.listen((result) {
|
||||||
|
isOnline.value =
|
||||||
|
result.contains(ConnectivityResult.none)
|
||||||
|
? Future.value(false)
|
||||||
|
: InternetConnection().hasInternetAccess;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initializeTimeZone() async {
|
||||||
|
final timeZoneName = await FlutterTimezone.getLocalTimezone();
|
||||||
|
tz.initializeTimeZones();
|
||||||
|
tz.setLocalLocation(tz.getLocation(timeZoneName));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initializeIsar() async {
|
||||||
isar = await Isar.open([
|
isar = await Isar.open([
|
||||||
SettingsSchema,
|
SettingsSchema,
|
||||||
MainWeatherCacheSchema,
|
MainWeatherCacheSchema,
|
||||||
|
@ -131,6 +126,8 @@ Future<void> isarInit() async {
|
||||||
WeatherCardSchema,
|
WeatherCardSchema,
|
||||||
], directory: (await getApplicationSupportDirectory()).path);
|
], directory: (await getApplicationSupportDirectory()).path);
|
||||||
settings = isar.settings.where().findFirstSync() ?? Settings();
|
settings = isar.settings.where().findFirstSync() ?? Settings();
|
||||||
|
locationCache =
|
||||||
|
isar.locationCaches.where().findFirstSync() ?? LocationCache();
|
||||||
|
|
||||||
if (settings.language == null) {
|
if (settings.language == null) {
|
||||||
settings.language = '${Get.deviceLocale}';
|
settings.language = '${Get.deviceLocale}';
|
||||||
|
@ -143,6 +140,28 @@ Future<void> isarInit() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initializeNotifications() async {
|
||||||
|
const initializationSettings = InitializationSettings(
|
||||||
|
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
|
||||||
|
iOS: DarwinInitializationSettings(),
|
||||||
|
linux: LinuxInitializationSettings(defaultActionName: 'Rain'),
|
||||||
|
);
|
||||||
|
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setOptimalDisplayMode() async {
|
||||||
|
final supported = await FlutterDisplayMode.supported;
|
||||||
|
final active = await FlutterDisplayMode.active;
|
||||||
|
final sameResolution =
|
||||||
|
supported
|
||||||
|
.where((m) => m.width == active.width && m.height == active.height)
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
|
||||||
|
final mostOptimalMode =
|
||||||
|
sameResolution.isNotEmpty ? sameResolution.first : active;
|
||||||
|
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
||||||
|
}
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@ -151,6 +170,7 @@ class MyApp extends StatefulWidget {
|
||||||
bool? newAmoledTheme,
|
bool? newAmoledTheme,
|
||||||
bool? newMaterialColor,
|
bool? newMaterialColor,
|
||||||
bool? newRoundDegree,
|
bool? newRoundDegree,
|
||||||
|
bool? newLargeElement,
|
||||||
Locale? newLocale,
|
Locale? newLocale,
|
||||||
int? newTimeRange,
|
int? newTimeRange,
|
||||||
String? newTimeStart,
|
String? newTimeStart,
|
||||||
|
@ -160,27 +180,14 @@ class MyApp extends StatefulWidget {
|
||||||
}) async {
|
}) async {
|
||||||
final state = context.findAncestorStateOfType<_MyAppState>()!;
|
final state = context.findAncestorStateOfType<_MyAppState>()!;
|
||||||
|
|
||||||
if (newAmoledTheme != null) {
|
if (newAmoledTheme != null) state.changeAmoledTheme(newAmoledTheme);
|
||||||
state.changeAmoledTheme(newAmoledTheme);
|
if (newMaterialColor != null) state.changeMarerialTheme(newMaterialColor);
|
||||||
}
|
if (newRoundDegree != null) state.changeRoundDegree(newRoundDegree);
|
||||||
if (newMaterialColor != null) {
|
if (newLargeElement != null) state.changeLargeElement(newLargeElement);
|
||||||
state.changeMarerialTheme(newMaterialColor);
|
if (newLocale != null) state.changeLocale(newLocale);
|
||||||
}
|
if (newTimeRange != null) state.changeTimeRange(newTimeRange);
|
||||||
if (newRoundDegree != null) {
|
if (newTimeStart != null) state.changeTimeStart(newTimeStart);
|
||||||
state.changeRoundDegree(newRoundDegree);
|
if (newTimeEnd != null) state.changeTimeEnd(newTimeEnd);
|
||||||
}
|
|
||||||
if (newLocale != null) {
|
|
||||||
state.changeLocale(newLocale);
|
|
||||||
}
|
|
||||||
if (newTimeRange != null) {
|
|
||||||
state.changeTimeRange(newTimeRange);
|
|
||||||
}
|
|
||||||
if (newTimeStart != null) {
|
|
||||||
state.changeTimeStart(newTimeStart);
|
|
||||||
}
|
|
||||||
if (newTimeEnd != null) {
|
|
||||||
state.changeTimeEnd(newTimeEnd);
|
|
||||||
}
|
|
||||||
if (newWidgetBackgroundColor != null) {
|
if (newWidgetBackgroundColor != null) {
|
||||||
state.changeWidgetBackgroundColor(newWidgetBackgroundColor);
|
state.changeWidgetBackgroundColor(newWidgetBackgroundColor);
|
||||||
}
|
}
|
||||||
|
@ -196,120 +203,136 @@ class MyApp extends StatefulWidget {
|
||||||
class _MyAppState extends State<MyApp> {
|
class _MyAppState extends State<MyApp> {
|
||||||
final themeController = Get.put(ThemeController());
|
final themeController = Get.put(ThemeController());
|
||||||
|
|
||||||
void changeAmoledTheme(bool newAmoledTheme) {
|
void changeAmoledTheme(bool newAmoledTheme) =>
|
||||||
setState(() {
|
setState(() => amoledTheme = newAmoledTheme);
|
||||||
amoledTheme = newAmoledTheme;
|
void changeMarerialTheme(bool newMaterialColor) =>
|
||||||
});
|
setState(() => materialColor = newMaterialColor);
|
||||||
}
|
void changeRoundDegree(bool newRoundDegree) =>
|
||||||
|
setState(() => roundDegree = newRoundDegree);
|
||||||
void changeMarerialTheme(bool newMaterialColor) {
|
void changeLargeElement(bool newLargeElement) =>
|
||||||
setState(() {
|
setState(() => largeElement = newLargeElement);
|
||||||
materialColor = newMaterialColor;
|
void changeTimeRange(int newTimeRange) =>
|
||||||
});
|
setState(() => timeRange = newTimeRange);
|
||||||
}
|
void changeTimeStart(String newTimeStart) =>
|
||||||
|
setState(() => timeStart = newTimeStart);
|
||||||
void changeRoundDegree(bool newRoundDegree) {
|
void changeTimeEnd(String newTimeEnd) => setState(() => timeEnd = newTimeEnd);
|
||||||
setState(() {
|
void changeLocale(Locale newLocale) => setState(() => locale = newLocale);
|
||||||
roundDegree = newRoundDegree;
|
void changeWidgetBackgroundColor(String newWidgetBackgroundColor) =>
|
||||||
});
|
setState(() => widgetBackgroundColor = newWidgetBackgroundColor);
|
||||||
}
|
void changeWidgetTextColor(String newWidgetTextColor) =>
|
||||||
|
setState(() => widgetTextColor = newWidgetTextColor);
|
||||||
void changeTimeRange(int newTimeRange) {
|
|
||||||
setState(() {
|
|
||||||
timeRange = newTimeRange;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeTimeStart(String newTimeStart) {
|
|
||||||
setState(() {
|
|
||||||
timeStart = newTimeStart;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeTimeEnd(String newTimeEnd) {
|
|
||||||
setState(() {
|
|
||||||
timeEnd = newTimeEnd;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeLocale(Locale newLocale) {
|
|
||||||
setState(() {
|
|
||||||
locale = newLocale;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeWidgetBackgroundColor(String newWidgetBackgroundColor) {
|
|
||||||
setState(() {
|
|
||||||
widgetBackgroundColor = newWidgetBackgroundColor;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeWidgetTextColor(String newWidgetTextColor) {
|
|
||||||
setState(() {
|
|
||||||
widgetTextColor = newWidgetTextColor;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
amoledTheme = settings.amoledTheme;
|
amoledTheme = settings.amoledTheme;
|
||||||
materialColor = settings.materialColor;
|
materialColor = settings.materialColor;
|
||||||
roundDegree = settings.roundDegree;
|
roundDegree = settings.roundDegree;
|
||||||
|
largeElement = settings.largeElement;
|
||||||
locale = Locale(
|
locale = Locale(
|
||||||
settings.language!.substring(0, 2), settings.language!.substring(3));
|
settings.language!.substring(0, 2),
|
||||||
|
settings.language!.substring(3),
|
||||||
|
);
|
||||||
timeRange = settings.timeRange ?? 1;
|
timeRange = settings.timeRange ?? 1;
|
||||||
timeStart = settings.timeStart ?? '09:00';
|
timeStart = settings.timeStart ?? '09:00';
|
||||||
timeEnd = settings.timeEnd ?? '21:00';
|
timeEnd = settings.timeEnd ?? '21:00';
|
||||||
widgetBackgroundColor = settings.widgetBackgroundColor ?? '';
|
widgetBackgroundColor = settings.widgetBackgroundColor ?? '';
|
||||||
widgetTextColor = settings.widgetTextColor ?? '';
|
widgetTextColor = settings.widgetTextColor ?? '';
|
||||||
if (Platform.isAndroid) {
|
|
||||||
HomeWidget.setAppGroupId(appGroupId);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DynamicColorBuilder(
|
final edgeToEdgeAvailable = DeviceFeature().isEdgeToEdgeAvailable();
|
||||||
builder: (lightColorScheme, darkColorScheme) {
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
final lightMaterialTheme =
|
|
||||||
lightTheme(lightColorScheme?.surface, lightColorScheme);
|
|
||||||
final darkMaterialTheme =
|
|
||||||
darkTheme(darkColorScheme?.surface, darkColorScheme);
|
|
||||||
final darkMaterialThemeOled = darkTheme(oledColor, darkColorScheme);
|
|
||||||
|
|
||||||
return GetMaterialApp(
|
return GestureDetector(
|
||||||
themeMode: themeController.theme,
|
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
theme: materialColor
|
child: DynamicColorBuilder(
|
||||||
? lightColorScheme != null
|
builder: (lightColorScheme, darkColorScheme) {
|
||||||
? lightMaterialTheme
|
final lightMaterialTheme = lightTheme(
|
||||||
: lightTheme(lightColor, colorSchemeLight)
|
lightColorScheme?.surface,
|
||||||
: lightTheme(lightColor, colorSchemeLight),
|
lightColorScheme,
|
||||||
darkTheme: amoledTheme
|
edgeToEdgeAvailable,
|
||||||
? materialColor
|
);
|
||||||
? darkColorScheme != null
|
final darkMaterialTheme = darkTheme(
|
||||||
? darkMaterialThemeOled
|
darkColorScheme?.surface,
|
||||||
: darkTheme(oledColor, colorSchemeDark)
|
darkColorScheme,
|
||||||
: darkTheme(oledColor, colorSchemeDark)
|
edgeToEdgeAvailable,
|
||||||
: materialColor
|
);
|
||||||
? darkColorScheme != null
|
final darkMaterialThemeOled = darkTheme(
|
||||||
? darkMaterialTheme
|
oledColor,
|
||||||
: darkTheme(darkColor, colorSchemeDark)
|
darkColorScheme,
|
||||||
: darkTheme(darkColor, colorSchemeDark),
|
edgeToEdgeAvailable,
|
||||||
localizationsDelegates: const [
|
);
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
return GetMaterialApp(
|
||||||
GlobalCupertinoLocalizations.delegate,
|
themeMode: themeController.theme,
|
||||||
],
|
theme:
|
||||||
translations: Translation(),
|
materialColor
|
||||||
locale: locale,
|
? lightColorScheme != null
|
||||||
fallbackLocale: const Locale('en', 'US'),
|
? lightMaterialTheme
|
||||||
supportedLocales:
|
: lightTheme(
|
||||||
appLanguages.map((e) => e['locale'] as Locale).toList(),
|
lightColor,
|
||||||
debugShowCheckedModeBanner: false,
|
colorSchemeLight,
|
||||||
home: settings.onboard ? const HomePage() : const OnBording(),
|
edgeToEdgeAvailable,
|
||||||
);
|
)
|
||||||
},
|
: lightTheme(
|
||||||
|
lightColor,
|
||||||
|
colorSchemeLight,
|
||||||
|
edgeToEdgeAvailable,
|
||||||
|
),
|
||||||
|
darkTheme:
|
||||||
|
amoledTheme
|
||||||
|
? materialColor
|
||||||
|
? darkColorScheme != null
|
||||||
|
? darkMaterialThemeOled
|
||||||
|
: darkTheme(
|
||||||
|
oledColor,
|
||||||
|
colorSchemeDark,
|
||||||
|
edgeToEdgeAvailable,
|
||||||
|
)
|
||||||
|
: darkTheme(
|
||||||
|
oledColor,
|
||||||
|
colorSchemeDark,
|
||||||
|
edgeToEdgeAvailable,
|
||||||
|
)
|
||||||
|
: materialColor
|
||||||
|
? darkColorScheme != null
|
||||||
|
? darkMaterialTheme
|
||||||
|
: darkTheme(
|
||||||
|
darkColor,
|
||||||
|
colorSchemeDark,
|
||||||
|
edgeToEdgeAvailable,
|
||||||
|
)
|
||||||
|
: darkTheme(
|
||||||
|
darkColor,
|
||||||
|
colorSchemeDark,
|
||||||
|
edgeToEdgeAvailable,
|
||||||
|
),
|
||||||
|
localizationsDelegates: const [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
translations: Translation(),
|
||||||
|
locale: locale,
|
||||||
|
fallbackLocale: const Locale('en', 'US'),
|
||||||
|
supportedLocales:
|
||||||
|
appLanguages.map((e) => e['locale'] as Locale).toList(),
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
home:
|
||||||
|
settings.onboard
|
||||||
|
? (locationCache.city == null ||
|
||||||
|
locationCache.district == null ||
|
||||||
|
locationCache.lat == null ||
|
||||||
|
locationCache.lon == null)
|
||||||
|
? const SelectGeolocation(isStart: true)
|
||||||
|
: const HomePage()
|
||||||
|
: const OnBording(),
|
||||||
|
title: 'Rain',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
215
lib/theme/theme.dart
Normal file → Executable file
|
@ -1,8 +1,9 @@
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
|
|
||||||
final ThemeData baseLigth = ThemeData.light(useMaterial3: true);
|
final ThemeData baseLight = ThemeData.light(useMaterial3: true);
|
||||||
final ThemeData baseDark = ThemeData.dark(useMaterial3: true);
|
final ThemeData baseDark = ThemeData.dark(useMaterial3: true);
|
||||||
|
|
||||||
const Color lightColor = Colors.white;
|
const Color lightColor = Colors.white;
|
||||||
|
@ -13,108 +14,146 @@ ColorScheme colorSchemeLight = ColorScheme.fromSeed(
|
||||||
seedColor: Colors.deepPurple,
|
seedColor: Colors.deepPurple,
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
);
|
);
|
||||||
|
|
||||||
ColorScheme colorSchemeDark = ColorScheme.fromSeed(
|
ColorScheme colorSchemeDark = ColorScheme.fromSeed(
|
||||||
seedColor: Colors.deepPurple,
|
seedColor: Colors.deepPurple,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
);
|
);
|
||||||
|
|
||||||
ThemeData lightTheme(Color? color, ColorScheme? colorScheme) {
|
ThemeData lightTheme(
|
||||||
return baseLigth.copyWith(
|
Color? color,
|
||||||
|
ColorScheme? colorScheme,
|
||||||
|
bool edgeToEdgeAvailable,
|
||||||
|
) {
|
||||||
|
return _buildTheme(
|
||||||
|
baseTheme: baseLight,
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
colorScheme: colorScheme
|
color: color,
|
||||||
?.copyWith(
|
colorScheme: colorScheme,
|
||||||
brightness: Brightness.light,
|
edgeToEdgeAvailable: edgeToEdgeAvailable,
|
||||||
background: color,
|
|
||||||
surface: baseLigth.colorScheme.background,
|
|
||||||
)
|
|
||||||
.harmonized(),
|
|
||||||
textTheme: GoogleFonts.ubuntuTextTheme(baseLigth.textTheme),
|
|
||||||
appBarTheme: AppBarTheme(
|
|
||||||
backgroundColor: color,
|
|
||||||
foregroundColor: baseLigth.colorScheme.onSurface,
|
|
||||||
shadowColor: Colors.transparent,
|
|
||||||
surfaceTintColor: Colors.transparent,
|
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
primaryColor: color,
|
|
||||||
canvasColor: color,
|
|
||||||
scaffoldBackgroundColor: color,
|
|
||||||
cardTheme: baseLigth.cardTheme.copyWith(
|
|
||||||
color: color,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
shadowColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
bottomSheetTheme: baseLigth.bottomSheetTheme.copyWith(
|
|
||||||
backgroundColor: color,
|
|
||||||
),
|
|
||||||
navigationRailTheme: baseLigth.navigationRailTheme.copyWith(
|
|
||||||
backgroundColor: color,
|
|
||||||
),
|
|
||||||
navigationBarTheme: baseLigth.navigationBarTheme.copyWith(
|
|
||||||
backgroundColor: color,
|
|
||||||
),
|
|
||||||
inputDecorationTheme: baseLigth.inputDecorationTheme.copyWith(
|
|
||||||
labelStyle: MaterialStateTextStyle.resolveWith(
|
|
||||||
(Set<MaterialState> states) {
|
|
||||||
return const TextStyle(fontSize: 14);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
focusedBorder: InputBorder.none,
|
|
||||||
enabledBorder: InputBorder.none,
|
|
||||||
),
|
|
||||||
indicatorColor: Colors.black,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData darkTheme(Color? color, ColorScheme? colorScheme) {
|
ThemeData darkTheme(
|
||||||
return baseDark.copyWith(
|
Color? color,
|
||||||
|
ColorScheme? colorScheme,
|
||||||
|
bool edgeToEdgeAvailable,
|
||||||
|
) {
|
||||||
|
return _buildTheme(
|
||||||
|
baseTheme: baseDark,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
colorScheme: colorScheme
|
color: color,
|
||||||
?.copyWith(
|
colorScheme: colorScheme,
|
||||||
brightness: Brightness.dark,
|
edgeToEdgeAvailable: edgeToEdgeAvailable,
|
||||||
background: color,
|
);
|
||||||
surface: baseDark.colorScheme.background,
|
}
|
||||||
)
|
|
||||||
.harmonized(),
|
ThemeData _buildTheme({
|
||||||
textTheme: GoogleFonts.ubuntuTextTheme(baseDark.textTheme),
|
required ThemeData baseTheme,
|
||||||
appBarTheme: AppBarTheme(
|
required Brightness brightness,
|
||||||
backgroundColor: color,
|
required Color? color,
|
||||||
foregroundColor: baseDark.colorScheme.onSurface,
|
required ColorScheme? colorScheme,
|
||||||
shadowColor: Colors.transparent,
|
required bool edgeToEdgeAvailable,
|
||||||
surfaceTintColor: Colors.transparent,
|
}) {
|
||||||
elevation: 0,
|
final harmonizedColorScheme =
|
||||||
|
colorScheme
|
||||||
|
?.copyWith(
|
||||||
|
brightness: brightness,
|
||||||
|
surface: baseTheme.colorScheme.surface,
|
||||||
|
)
|
||||||
|
.harmonized();
|
||||||
|
|
||||||
|
return baseTheme.copyWith(
|
||||||
|
brightness: brightness,
|
||||||
|
colorScheme: harmonizedColorScheme,
|
||||||
|
textTheme: GoogleFonts.ubuntuTextTheme(baseTheme.textTheme),
|
||||||
|
appBarTheme: _buildAppBarTheme(
|
||||||
|
color,
|
||||||
|
baseTheme.colorScheme.onSurface,
|
||||||
|
edgeToEdgeAvailable,
|
||||||
|
brightness,
|
||||||
|
harmonizedColorScheme,
|
||||||
),
|
),
|
||||||
primaryColor: color,
|
primaryColor: color,
|
||||||
canvasColor: color,
|
canvasColor: color,
|
||||||
scaffoldBackgroundColor: color,
|
scaffoldBackgroundColor: color,
|
||||||
cardTheme: baseDark.cardTheme.copyWith(
|
cardTheme: _buildCardTheme(color, harmonizedColorScheme),
|
||||||
color: color,
|
bottomSheetTheme: _buildBottomSheetTheme(color, harmonizedColorScheme),
|
||||||
shape: RoundedRectangleBorder(
|
navigationRailTheme: baseTheme.navigationRailTheme.copyWith(
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
shadowColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
bottomSheetTheme: baseDark.bottomSheetTheme.copyWith(
|
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
),
|
),
|
||||||
navigationRailTheme: baseDark.navigationRailTheme.copyWith(
|
navigationBarTheme: _buildNavigationBarTheme(color, harmonizedColorScheme),
|
||||||
backgroundColor: color,
|
inputDecorationTheme: _buildInputDecorationTheme(),
|
||||||
),
|
);
|
||||||
navigationBarTheme: baseDark.navigationBarTheme.copyWith(
|
}
|
||||||
backgroundColor: color,
|
|
||||||
),
|
AppBarTheme _buildAppBarTheme(
|
||||||
inputDecorationTheme: baseDark.inputDecorationTheme.copyWith(
|
Color? color,
|
||||||
labelStyle: MaterialStateTextStyle.resolveWith(
|
Color? onSurfaceColor,
|
||||||
(Set<MaterialState> states) {
|
bool edgeToEdgeAvailable,
|
||||||
return const TextStyle(fontSize: 14);
|
Brightness brightness,
|
||||||
},
|
ColorScheme? colorScheme,
|
||||||
),
|
) {
|
||||||
border: InputBorder.none,
|
return AppBarTheme(
|
||||||
focusedBorder: InputBorder.none,
|
backgroundColor: color,
|
||||||
enabledBorder: InputBorder.none,
|
foregroundColor: onSurfaceColor,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness:
|
||||||
|
brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
systemStatusBarContrastEnforced: false,
|
||||||
|
systemNavigationBarContrastEnforced: false,
|
||||||
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
|
systemNavigationBarIconBrightness:
|
||||||
|
brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||||
|
systemNavigationBarColor:
|
||||||
|
edgeToEdgeAvailable ? Colors.transparent : colorScheme?.surface,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CardThemeData _buildCardTheme(Color? color, ColorScheme? colorScheme) {
|
||||||
|
return CardThemeData(
|
||||||
|
color: color,
|
||||||
|
surfaceTintColor:
|
||||||
|
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BottomSheetThemeData _buildBottomSheetTheme(
|
||||||
|
Color? color,
|
||||||
|
ColorScheme? colorScheme,
|
||||||
|
) {
|
||||||
|
return BottomSheetThemeData(
|
||||||
|
backgroundColor: color,
|
||||||
|
surfaceTintColor:
|
||||||
|
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationBarThemeData _buildNavigationBarTheme(
|
||||||
|
Color? color,
|
||||||
|
ColorScheme? colorScheme,
|
||||||
|
) {
|
||||||
|
return NavigationBarThemeData(
|
||||||
|
backgroundColor: color,
|
||||||
|
surfaceTintColor:
|
||||||
|
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputDecorationTheme _buildInputDecorationTheme() {
|
||||||
|
return InputDecorationTheme(
|
||||||
|
labelStyle: WidgetStateTextStyle.resolveWith((Set<WidgetState> states) {
|
||||||
|
return const TextStyle(fontSize: 14);
|
||||||
|
}),
|
||||||
|
border: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
33
lib/theme/theme_controller.dart
Normal file → Executable file
|
@ -1,31 +1,40 @@
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:rain/app/data/weather.dart';
|
import 'package:rain/app/data/db.dart';
|
||||||
import 'package:rain/main.dart';
|
import 'package:rain/main.dart';
|
||||||
|
|
||||||
class ThemeController extends GetxController {
|
class ThemeController extends GetxController {
|
||||||
ThemeMode get theme => settings.theme == 'system'
|
ThemeMode get theme => _getThemeMode();
|
||||||
? ThemeMode.system
|
|
||||||
: settings.theme == 'dark'
|
|
||||||
? ThemeMode.dark
|
|
||||||
: ThemeMode.light;
|
|
||||||
|
|
||||||
void saveOledTheme(bool isOled) {
|
void saveOledTheme(bool isOled) {
|
||||||
settings.amoledTheme = isOled;
|
_updateSetting((settings) => settings.amoledTheme = isOled);
|
||||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveMaterialTheme(bool isMaterial) {
|
void saveMaterialTheme(bool isMaterial) {
|
||||||
settings.materialColor = isMaterial;
|
_updateSetting((settings) => settings.materialColor = isMaterial);
|
||||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveTheme(String themeMode) {
|
void saveTheme(String themeMode) {
|
||||||
settings.theme = themeMode;
|
_updateSetting((settings) => settings.theme = themeMode);
|
||||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeTheme(ThemeData theme) => Get.changeTheme(theme);
|
void changeTheme(ThemeData theme) => Get.changeTheme(theme);
|
||||||
|
|
||||||
void changeThemeMode(ThemeMode themeMode) => Get.changeThemeMode(themeMode);
|
void changeThemeMode(ThemeMode themeMode) => Get.changeThemeMode(themeMode);
|
||||||
|
|
||||||
|
ThemeMode _getThemeMode() {
|
||||||
|
switch (settings.theme) {
|
||||||
|
case 'system':
|
||||||
|
return ThemeMode.system;
|
||||||
|
case 'dark':
|
||||||
|
return ThemeMode.dark;
|
||||||
|
default:
|
||||||
|
return ThemeMode.light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSetting(void Function(Settings) update) {
|
||||||
|
update(settings);
|
||||||
|
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
261
lib/translation/bn_in.dart
Normal file → Executable file
|
@ -1,126 +1,141 @@
|
||||||
class BnIn {
|
class BnIn {
|
||||||
Map<String, String> get messages => {
|
Map<String, String> get messages => {
|
||||||
'start': 'শুরু করুন',
|
'start': 'শুরু করুন',
|
||||||
'description':
|
'description':
|
||||||
'প্রতি ঘণ্টায়, দিনে এবং সপ্তাহের জন্য প্রতিষ্ঠানের জন্য সক্রিয় পূর্বাভাস সহ আবহাওয়া অ্যাপ্লিকেশন।',
|
'প্রতি ঘণ্টায়, দিনে এবং সপ্তাহের জন্য প্রতিষ্ঠানের জন্য সক্রিয় পূর্বাভাস সহ আবহাওয়া অ্যাপ্লিকেশন।',
|
||||||
'name': 'আবহাওয়া',
|
'name': 'আবহাওয়া',
|
||||||
'name2': 'সুবিধাজনক ডিজাইন',
|
'name2': 'সুবিধাজনক ডিজাইন',
|
||||||
'name3': 'আমাদের সাথে যোগাযোগ করুন',
|
'name3': 'আমাদের সাথে যোগাযোগ করুন',
|
||||||
'description2':
|
'description2':
|
||||||
'সমস্ত নেভিগেশনটি এমনভাবে তৈরি করা হয়েছে যাতে আপনি অ্যাপ্লিকেশনে সর্বোত্তম সুবিধায় এবং দ্রুত ইন্টারঅ্যাক্ট করতে পারেন।',
|
'সমস্ত নেভিগেশনটি এমনভাবে তৈরি করা হয়েছে যাতে আপনি অ্যাপ্লিকেশনে সর্বোত্তম সুবিধায় এবং দ্রুত ইন্টারঅ্যাক্ট করতে পারেন।',
|
||||||
'description3':
|
'description3':
|
||||||
'আপনার যদি কোনও সমস্যা হয়, অনুগ্রহ করে ইমেল বা অ্যাপ্লিকেশন পর্যালোচনার মাধ্যমে আমাদের সাথে যোগাযোগ করুন।',
|
'আপনার যদি কোনও সমস্যা হয়, অনুগ্রহ করে ইমেল বা অ্যাপ্লিকেশন পর্যালোচনার মাধ্যমে আমাদের সাথে যোগাযোগ করুন।',
|
||||||
'next': 'পরবর্তী',
|
'next': 'পরবর্তী',
|
||||||
'search': 'অনুসন্ধান...',
|
'search': 'অনুসন্ধান...',
|
||||||
'loading': 'লোড হচ্ছে...',
|
'loading': 'লোড হচ্ছে...',
|
||||||
'searchCity': 'আপনার শহর খুঁজুন',
|
'searchCity': 'আপনার শহর খুঁজুন',
|
||||||
'humidity': 'আর্দ্ধমন্দ',
|
'humidity': 'আর্দ্ধমন্দ',
|
||||||
'wind': 'বায়ু',
|
'wind': 'বায়ু',
|
||||||
'visibility': 'দৃশ্যতা',
|
'visibility': 'দৃশ্যতা',
|
||||||
'feels': 'অনুভব',
|
'feels': 'অনুভব',
|
||||||
'evaporation': 'অবপাত ও প্রবাহ',
|
'evaporation': 'অবপাত ও প্রবাহ',
|
||||||
'precipitation': 'বৃষ্টিপাত',
|
'precipitation': 'বৃষ্টিপাত',
|
||||||
'direction': 'দিশা',
|
'direction': 'দিশা',
|
||||||
'pressure': 'চাপ',
|
'pressure': 'চাপ',
|
||||||
'rain': 'বৃষ্টি',
|
'rain': 'বৃষ্টি',
|
||||||
'clear_sky': 'পরিষ্কার আকাশ',
|
'clear_sky': 'পরিষ্কার আকাশ',
|
||||||
'cloudy': 'মেঘলা',
|
'cloudy': 'মেঘলা',
|
||||||
'overcast': 'মেঘাচ্ছন্ন',
|
'overcast': 'মেঘাচ্ছন্ন',
|
||||||
'fog': 'কুয়াশা',
|
'fog': 'কুয়াশা',
|
||||||
'drizzle': 'বৃষ্টি বৃষ্টি',
|
'drizzle': 'বৃষ্টি বৃষ্টি',
|
||||||
'freezing_drizzle': 'শীতল বৃষ্টি',
|
'freezing_drizzle': 'শীতল বৃষ্টি',
|
||||||
'freezing_rain': 'শীতল বৃষ্টি',
|
'freezing_rain': 'শীতল বৃষ্টি',
|
||||||
'rain_showers': 'বৃষ্টির বৃষ্টি',
|
'rain_showers': 'বৃষ্টির বৃষ্টি',
|
||||||
'snow': 'তুষার',
|
'snow': 'তুষার',
|
||||||
'thunderstorm': 'বজ্রপাত',
|
'thunderstorm': 'বজ্রপাত',
|
||||||
'kph': 'কিমি/ঘণ্টা',
|
'kph': 'কিমি/ঘণ্টা',
|
||||||
'mph': 'মাইল/ঘণ্টা',
|
'mph': 'মাইল/ঘণ্টা',
|
||||||
'mi': 'মাইল',
|
'm/s': 'মি/সে',
|
||||||
'km': 'কিমি',
|
'mmHg': 'মিমি Hg',
|
||||||
'inch': 'ইঞ্চ',
|
'mi': 'মাইল',
|
||||||
'mm': 'মিমি',
|
'km': 'কিমি',
|
||||||
'hPa': 'হেক্টোপাস্কল',
|
'inch': 'ইঞ্চ',
|
||||||
'settings': 'সেটিংস',
|
'mm': 'মিমি',
|
||||||
'no_inter': 'ইন্টারনেট নেই',
|
'hPa': 'হেক্টোপাস্কল',
|
||||||
'on_inter': 'মেটিয়োরোলজিক তথ্য পেতে ইন্টারনেট চালু করুন।',
|
'settings': 'সেটিংস',
|
||||||
'location': 'অবস্থান',
|
'no_inter': 'ইন্টারনেট নেই',
|
||||||
'no_location':
|
'on_inter': 'মেটিয়োরোলজিক তথ্য পেতে ইন্টারনেট চালু করুন।',
|
||||||
'বর্তমান অবস্থানের জন্য আবহাওয়া ডেটা পেতে অবস্থান সেবা সক্রিয় করুন।',
|
'location': 'অবস্থান',
|
||||||
'theme': 'থিম',
|
'no_location':
|
||||||
'low': 'নিম্ন',
|
'বর্তমান অবস্থানের জন্য আবহাওয়া ডেটা পেতে অবস্থান সেবা সক্রিয় করুন।',
|
||||||
'high': 'উচ্চ',
|
'theme': 'থিম',
|
||||||
'normal': 'সাধারণ',
|
'low': 'নিম্ন',
|
||||||
'lat': 'অক্ষাংশ',
|
'high': 'উচ্চ',
|
||||||
'lon': 'দ্রাঘিমাংশ',
|
'normal': 'সাধারণ',
|
||||||
'create': 'তৈরি করুন',
|
'lat': 'অক্ষাংশ',
|
||||||
'city': 'শহর',
|
'lon': 'দ্রাঘিমাংশ',
|
||||||
'district': 'জেলা',
|
'create': 'তৈরি করুন',
|
||||||
'noWeatherCard': 'একটি শহর যোগ করুন',
|
'city': 'শহর',
|
||||||
'deletedCardWeather': 'একটি শহর মুছে ফেলা হচ্ছে',
|
'district': 'জেলা',
|
||||||
'deletedCardWeatherQuery': 'আপনি কি নিশ্চিত যে আপনি শহরটি মুছতে চান?',
|
'noWeatherCard': 'একটি শহর যোগ করুন',
|
||||||
'delete': 'মুছে ফেলুন',
|
'deletedCardWeather': 'একটি শহর মুছে ফেলা হচ্ছে',
|
||||||
'cancel': 'বাতিল করুন',
|
'deletedCardWeatherQuery': 'আপনি কি নিশ্চিত যে আপনি শহরটি মুছতে চান?',
|
||||||
'time': 'শহরে সময়',
|
'delete': 'মুছে ফেলুন',
|
||||||
'validateName': 'দয়া করে নাম লিখুন',
|
'cancel': 'বাতিল করুন',
|
||||||
'measurements': 'মাপনের সিস্টেম',
|
'time': 'শহরে সময়',
|
||||||
'degrees': 'ডিগ্রি',
|
'validateName': 'দয়া করে নাম লিখুন',
|
||||||
'celsius': 'সেলসিয়াস',
|
'measurements': 'মাপনের সিস্টেম',
|
||||||
'fahrenheit': 'ফারেনহাইট',
|
'degrees': 'ডিগ্রি',
|
||||||
'imperial': 'ইমপেরিয়াল',
|
'celsius': 'সেলসিয়াস',
|
||||||
'metric': 'মেট্রিক',
|
'fahrenheit': 'ফারেনহাইট',
|
||||||
'validateValue': 'দয়া করে একটি মান লিখুন',
|
'imperial': 'ইমপেরিয়াল',
|
||||||
'validateNumber': 'দয়া করে একটি বৈধ সংখ্যা লিখুন',
|
'metric': 'মেট্রিক',
|
||||||
'validate90': 'মান -৯০ থেকে ৯০ মধ্যে হতে হবে',
|
'validateValue': 'দয়া করে একটি মান লিখুন',
|
||||||
'validate180': 'মান -১৮০ থেকে ১৮০ মধ্যে হতে হবে',
|
'validateNumber': 'দয়া করে একটি বৈধ সংখ্যা লিখুন',
|
||||||
'notifications': 'বিজ্ঞপ্তি',
|
'validate90': 'মান -৯০ থেকে ৯০ মধ্যে হতে হবে',
|
||||||
'sunrise': 'সূর্যোদয়',
|
'validate180': 'মান -১৮০ থেকে ১৮০ মধ্যে হতে হবে',
|
||||||
'sunset': 'সূর্যাস্ত',
|
'notifications': 'বিজ্ঞপ্তি',
|
||||||
'timeformat': 'সময় বিন্যাস',
|
'sunrise': 'সূর্যোদয়',
|
||||||
'12': '১২-ঘণ্টা',
|
'sunset': 'সূর্যাস্ত',
|
||||||
'24': '২৪-ঘণ্টা',
|
'timeformat': 'সময় বিন্যাস',
|
||||||
'cloudcover': 'মেঘপর্দা',
|
'12': '১২-ঘণ্টা',
|
||||||
'uvIndex': 'আল্ট্রাভায়োলেট-সূচী',
|
'24': '২৪-ঘণ্টা',
|
||||||
'materialColor': 'গতিবিধির রঙ',
|
'cloudcover': 'মেঘপর্দা',
|
||||||
'uvLow': 'নিম্ন',
|
'uvIndex': 'আল্ট্রাভায়োলেট-সূচী',
|
||||||
'uvAverage': 'মধ্যম',
|
'materialColor': 'গতিবিধির রঙ',
|
||||||
'uvHigh': 'উচ্চ',
|
'uvLow': 'নিম্ন',
|
||||||
'uvVeryHigh': 'অত্যন্ত উচ্চ',
|
'uvAverage': 'মধ্যম',
|
||||||
'uvExtreme': 'একাধিক',
|
'uvHigh': 'উচ্চ',
|
||||||
'weatherMore': '১২-দিনের আবহাওয়া পূর্বানুমান',
|
'uvVeryHigh': 'অত্যন্ত উচ্চ',
|
||||||
'windgusts': 'ঝংকার',
|
'uvExtreme': 'একাধিক',
|
||||||
'north': 'উত্তর',
|
'weatherMore': '১২-দিনের আবহাওয়া পূর্বানুমান',
|
||||||
'northeast': 'উত্তরপূর্ব',
|
'windgusts': 'ঝংকার',
|
||||||
'east': 'পূর্ব',
|
'north': 'উত্তর',
|
||||||
'southeast': 'দক্ষিণপূর্ব',
|
'northeast': 'উত্তরপূর্ব',
|
||||||
'south': 'দক্ষিণ',
|
'east': 'পূর্ব',
|
||||||
'southwest': 'দক্ষিণপশ্চিম',
|
'southeast': 'দক্ষিণপূর্ব',
|
||||||
'west': 'পশ্চিম',
|
'south': 'দক্ষিণ',
|
||||||
'northwest': 'উত্তরপশ্চিম',
|
'southwest': 'দক্ষিণপশ্চিম',
|
||||||
'project': 'প্রকল্প',
|
'west': 'পশ্চিম',
|
||||||
'version': 'অ্যাপ্লিকেশন সংস্করণ',
|
'northwest': 'উত্তরপশ্চিম',
|
||||||
'precipitationProbability': 'বৃষ্টিপাতের সম্ভাবনা',
|
'project': 'প্রকল্প',
|
||||||
'apparentTemperatureMin':
|
'version': 'অ্যাপ্লিকেশন সংস্করণ',
|
||||||
'ন্যায্য ন্যায্য তাপমাত্রা ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য',
|
'precipitationProbability': 'বৃষ্টিপাতের সম্ভাবনা',
|
||||||
'apparentTemperatureMax': 'সর্বাধিক ন্যায্য তাপমাত্রা',
|
'apparentTemperatureMin':
|
||||||
'amoledTheme': 'এমোলেড-থিম',
|
'ন্যায্য ন্যায্য তাপমাত্রা ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য',
|
||||||
'appearance': 'উপস্থিতি',
|
'apparentTemperatureMax': 'সর্বাধিক ন্যায্য তাপমাত্রা',
|
||||||
'functions': 'কার্য',
|
'amoledTheme': 'এমোলেড-থিম',
|
||||||
'data': 'ডেটা',
|
'appearance': 'উপস্থিতি',
|
||||||
'language': 'ভাষা',
|
'functions': 'কার্য',
|
||||||
'timeRange': 'সময় পরিস্থিতি (ঘণ্টায়)',
|
'data': 'ডেটা',
|
||||||
'timeStart': 'শুরুর সময়',
|
'language': 'ভাষা',
|
||||||
'timeEnd': 'শেষ সময়',
|
'timeRange': 'সময় পরিস্থিতি (ঘণ্টায়)',
|
||||||
'support': 'সাহায্য',
|
'timeStart': 'শুরুর সময়',
|
||||||
'system': 'সিস্টেম',
|
'timeEnd': 'শেষ সময়',
|
||||||
'dark': 'ডার্ক',
|
'support': 'সাহায্য',
|
||||||
'light': 'আলো',
|
'system': 'সিস্টেম',
|
||||||
'license': 'লাইসেন্স',
|
'dark': 'ডার্ক',
|
||||||
'widget': 'উইজেট',
|
'light': 'আলো',
|
||||||
'widgetBackground': 'উইজেট পেশা',
|
'license': 'লাইসেন্স',
|
||||||
'widgetText': 'উইজেট টেক্সট',
|
'widget': 'উইজেট',
|
||||||
'dewpoint': 'তুষার বিন্দু',
|
'widgetBackground': 'উইজেট পেশা',
|
||||||
'shortwaveRadiation': 'সংক্ষেপণ তরঙ্গ প্রকৃতি',
|
'widgetText': 'উইজেট টেক্সট',
|
||||||
'W/m2': 'ডব্লিউ/মিটার বর্গ',
|
'dewpoint': 'তুষার বিন্দু',
|
||||||
'roundDegree': 'ডিগ্রি রাউন্ড করুন',
|
'shortwaveRadiation': 'সংক্ষেপণ তরঙ্গ প্রকৃতি',
|
||||||
};
|
'W/m2': 'ডব্লিউ/মিটার বর্গ',
|
||||||
|
'roundDegree': 'ডিগ্রি রাউন্ড করুন',
|
||||||
|
'settings_full': 'সেটিংস',
|
||||||
|
'cities': 'শহর',
|
||||||
|
'groups': 'আমাদের দলগুলি',
|
||||||
|
'openMeteo': 'Open-Meteo থেকে ডেটা (CC-BY 4.0)',
|
||||||
|
'hourlyVariables': 'ঘণ্টায় আবহাওয়ার পরিবর্তনশীল',
|
||||||
|
'dailyVariables': 'দৈনিক আবহাওয়ার পরিবর্তনশীল',
|
||||||
|
'largeElement': 'বড় আবহাওয়া ডিসপ্লে',
|
||||||
|
'map': 'মানচিত্র',
|
||||||
|
'clearCacheStore': 'ক্যাশ পরিষ্কার করুন',
|
||||||
|
'deletedCacheStore': 'ক্যাশ পরিষ্কার করা হচ্ছে',
|
||||||
|
'deletedCacheStoreQuery': 'আপনি কি সত্যিই ক্যাশ পরিষ্কার করতে চান?',
|
||||||
|
'addWidget': 'উইজেট যোগ করুন',
|
||||||
|
'hideMap': 'মানচিত্র লুকান',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
259
lib/translation/cs_cz.dart
Normal file → Executable file
|
@ -1,124 +1,141 @@
|
||||||
class CsCz {
|
class CsCz {
|
||||||
Map<String, String> get messages => {
|
Map<String, String> get messages => {
|
||||||
'start': 'Začít',
|
'start': 'Začít',
|
||||||
'description':
|
'description':
|
||||||
'Aplikace počasí s aktuálním předpovědí na každou hodinu, den a týden pro libovolné místo.',
|
'Aplikace počasí s aktuálním předpovědí na každou hodinu, den a týden pro libovolné místo.',
|
||||||
'name': 'Počasí',
|
'name': 'Počasí',
|
||||||
'name2': 'Pohodlný design',
|
'name2': 'Pohodlný design',
|
||||||
'name3': 'Kontaktujte nás',
|
'name3': 'Kontaktujte nás',
|
||||||
'description2':
|
'description2':
|
||||||
'Celá navigace je navržena tak, aby bylo možné s aplikací co nejpohodlněji a nejrychleji interagovat.',
|
'Celá navigace je navržena tak, aby bylo možné s aplikací co nejpohodlněji a nejrychleji interagovat.',
|
||||||
'description3':
|
'description3':
|
||||||
'Pokud narazíte na nějaké potíže, kontaktujte nás prosím e-mailem nebo v recenzích aplikace.',
|
'Pokud narazíte na nějaké potíže, kontaktujte nás prosím e-mailem nebo v recenzích aplikace.',
|
||||||
'next': 'Další',
|
'next': 'Další',
|
||||||
'search': 'Hledat...',
|
'search': 'Hledat...',
|
||||||
'loading': 'Načítá se...',
|
'loading': 'Načítá se...',
|
||||||
'searchCity': 'Najděte své místo',
|
'searchCity': 'Najděte své místo',
|
||||||
'humidity': 'Vlhkost',
|
'humidity': 'Vlhkost',
|
||||||
'wind': 'Vítr',
|
'wind': 'Vítr',
|
||||||
'visibility': 'Viditelnost',
|
'visibility': 'Viditelnost',
|
||||||
'feels': 'Pocitová teplota',
|
'feels': 'Pocitová teplota',
|
||||||
'evaporation': 'Evapotranspirace',
|
'evaporation': 'Evapotranspirace',
|
||||||
'precipitation': 'Srážky',
|
'precipitation': 'Srážky',
|
||||||
'direction': 'Směr',
|
'direction': 'Směr',
|
||||||
'pressure': 'Tlak',
|
'pressure': 'Tlak',
|
||||||
'rain': 'Déšť',
|
'rain': 'Déšť',
|
||||||
'clear_sky': 'Jasno',
|
'clear_sky': 'Jasno',
|
||||||
'cloudy': 'Oblačno',
|
'cloudy': 'Oblačno',
|
||||||
'overcast': 'Zataženo',
|
'overcast': 'Zataženo',
|
||||||
'fog': 'Mlha',
|
'fog': 'Mlha',
|
||||||
'drizzle': 'Mrholení',
|
'drizzle': 'Mrholení',
|
||||||
'drizzling_rain': 'Mrznúce mrholení',
|
'drizzling_rain': 'Mrznúce mrholení',
|
||||||
'freezing_rain': 'Mrazivý déšť',
|
'freezing_rain': 'Mrazivý déšť',
|
||||||
'heavy_rains': 'Přeháňky',
|
'heavy_rains': 'Přeháňky',
|
||||||
'snow': 'Sníh',
|
'snow': 'Sníh',
|
||||||
'thunderstorm': 'Bouřka',
|
'thunderstorm': 'Bouřka',
|
||||||
'kph': 'km/h',
|
'kph': 'km/h',
|
||||||
'mph': 'mph',
|
'mph': 'mph',
|
||||||
'mi': 'mi',
|
'm/s': 'm/s',
|
||||||
'km': 'km',
|
'mmHg': 'mmHg',
|
||||||
'inch': 'inch',
|
'mi': 'mi',
|
||||||
'mm': 'mm',
|
'km': 'km',
|
||||||
'hPa': 'hPa',
|
'inch': 'inch',
|
||||||
'settings': 'Nast.',
|
'mm': 'mm',
|
||||||
'no_inter': 'Žádný internet',
|
'hPa': 'hPa',
|
||||||
'on_inter': 'Připojte se k internetu a získejte meteorologické údaje.',
|
'settings': 'Nast.',
|
||||||
'location': 'Poloha',
|
'no_inter': 'Žádný internet',
|
||||||
'no_location':
|
'on_inter': 'Připojte se k internetu a získejte meteorologické údaje.',
|
||||||
'Chcete-li získat údaje o počasí pro aktuální polohu, povolte službu určování polohy.',
|
'location': 'Poloha',
|
||||||
'theme': 'Téma',
|
'no_location':
|
||||||
'low': 'Nízký',
|
'Chcete-li získat údaje o počasí pro aktuální polohu, povolte službu určování polohy.',
|
||||||
'high': 'Vysoký',
|
'theme': 'Téma',
|
||||||
'normal': 'Normální',
|
'low': 'Nízký',
|
||||||
'lat': 'Zeměpisná šířka',
|
'high': 'Vysoký',
|
||||||
'lon': 'Zemepisná délka',
|
'normal': 'Normální',
|
||||||
'create': 'Vytvořit',
|
'lat': 'Zeměpisná šířka',
|
||||||
'city': 'Místo',
|
'lon': 'Zemepisná délka',
|
||||||
'district': 'Okres',
|
'create': 'Vytvořit',
|
||||||
'noWeatherCard': 'Přidat město',
|
'city': 'Místo',
|
||||||
'deletedCardWeather': 'Vymazat město',
|
'district': 'Okres',
|
||||||
'deletedCardWeatherQuery': 'Opravdu chcete odstranit město?',
|
'noWeatherCard': 'Přidat město',
|
||||||
'delete': 'Odstranit',
|
'deletedCardWeather': 'Vymazat město',
|
||||||
'cancel': 'Zrušit',
|
'deletedCardWeatherQuery': 'Opravdu chcete odstranit město?',
|
||||||
'time': 'Čas ve městě',
|
'delete': 'Odstranit',
|
||||||
'validateName': 'Prosím zadejte název',
|
'cancel': 'Zrušit',
|
||||||
'measurements': 'Jednotky měření',
|
'time': 'Čas ve městě',
|
||||||
'degrees': 'Stupně',
|
'validateName': 'Prosím zadejte název',
|
||||||
'celsius': 'Celzius',
|
'measurements': 'Jednotky měření',
|
||||||
'fahrenheit': 'Fahrenheit',
|
'degrees': 'Stupně',
|
||||||
'imperial': 'Imperiální',
|
'celsius': 'Celzius',
|
||||||
'metric': 'Metrické',
|
'fahrenheit': 'Fahrenheit',
|
||||||
'validateValue': 'Zadejte hodnotu',
|
'imperial': 'Imperiální',
|
||||||
'validateNumber': 'Zadejte platné číslo',
|
'metric': 'Metrické',
|
||||||
'validate90': 'Hodnota musí být mezi -90 a 90',
|
'validateValue': 'Zadejte hodnotu',
|
||||||
'validate180': 'Hodnota musí být mezi -180 a 180',
|
'validateNumber': 'Zadejte platné číslo',
|
||||||
'notifications': 'Notifikace',
|
'validate90': 'Hodnota musí být mezi -90 a 90',
|
||||||
'sunrise': 'Východ slunce',
|
'validate180': 'Hodnota musí být mezi -180 a 180',
|
||||||
'sunset': 'Západ slunce',
|
'notifications': 'Notifikace',
|
||||||
'timeformat': 'Formát času',
|
'sunrise': 'Východ slunce',
|
||||||
'12': '12-hodinový',
|
'sunset': 'Západ slunce',
|
||||||
'24': '24-hodinový',
|
'timeformat': 'Formát času',
|
||||||
'cloudcover': 'Oblačnost',
|
'12': '12-hodinový',
|
||||||
'uvIndex': 'UV-index',
|
'24': '24-hodinový',
|
||||||
'materialColor': 'Dynamické Barvy',
|
'cloudcover': 'Oblačnost',
|
||||||
'uvLow': 'Nízký',
|
'uvIndex': 'UV-index',
|
||||||
'uvAverage': 'Mírný',
|
'materialColor': 'Dynamické Barvy',
|
||||||
'uvHigh': 'Vysoký',
|
'uvLow': 'Nízký',
|
||||||
'uvVeryHigh': 'Velmi vysoký',
|
'uvAverage': 'Mírný',
|
||||||
'uvExtreme': 'Extrémní',
|
'uvHigh': 'Vysoký',
|
||||||
'weatherMore': 'Předpověď počasí na 12 dní',
|
'uvVeryHigh': 'Velmi vysoký',
|
||||||
'windgusts': 'Nárazy větru',
|
'uvExtreme': 'Extrémní',
|
||||||
'north': 'Sever',
|
'weatherMore': 'Předpověď počasí na 12 dní',
|
||||||
'northeast': 'Severo-Východ',
|
'windgusts': 'Nárazy větru',
|
||||||
'east': 'Východ',
|
'north': 'Sever',
|
||||||
'southeast': 'Juhovýchod',
|
'northeast': 'Severo-Východ',
|
||||||
'south': 'Juž',
|
'east': 'Východ',
|
||||||
'southwest': 'Juhozápad',
|
'southeast': 'Juhovýchod',
|
||||||
'west': 'Západ',
|
'south': 'Juž',
|
||||||
'northwest': 'Severo-Západ',
|
'southwest': 'Juhozápad',
|
||||||
'project': 'Projekt na',
|
'west': 'Západ',
|
||||||
'version': 'Verzia aplikace',
|
'northwest': 'Severo-Západ',
|
||||||
'precipitationProbability': 'Pravděpodobnost srážek',
|
'project': 'Projekt na',
|
||||||
'apparentTemperatureMin': 'Minimální pocitová teplota',
|
'version': 'Verzia aplikace',
|
||||||
'apparentTemperatureMax': 'Maximální pocitová teplota',
|
'precipitationProbability': 'Pravděpodobnost srážek',
|
||||||
'amoledTheme': 'AMOLED-téma',
|
'apparentTemperatureMin': 'Minimální pocitová teplota',
|
||||||
'appearance': 'Vzhled',
|
'apparentTemperatureMax': 'Maximální pocitová teplota',
|
||||||
'functions': 'Funkce',
|
'amoledTheme': 'AMOLED-téma',
|
||||||
'data': 'Data',
|
'appearance': 'Vzhled',
|
||||||
'language': 'Jazyk',
|
'functions': 'Funkce',
|
||||||
'timeRange': 'Frekvence (v hodinách)',
|
'data': 'Data',
|
||||||
'timeStart': 'Čas začátku',
|
'language': 'Jazyk',
|
||||||
'timeEnd': 'Čas ukončení',
|
'timeRange': 'Frekvence (v hodinách)',
|
||||||
'support': 'Podpora',
|
'timeStart': 'Čas začátku',
|
||||||
'system': 'Systém',
|
'timeEnd': 'Čas ukončení',
|
||||||
'dark': 'Tmavá',
|
'support': 'Podpora',
|
||||||
'light': 'Světlá',
|
'system': 'Systém',
|
||||||
'license': 'Licence',
|
'dark': 'Tmavá',
|
||||||
'widget': 'Widget',
|
'light': 'Světlá',
|
||||||
'widgetBackground': 'Pozadí widgetu',
|
'license': 'Licence',
|
||||||
'widgetText': 'Text widgetu',
|
'widget': 'Widget',
|
||||||
'dewpoint': 'Rosný bod',
|
'widgetBackground': 'Pozadí widgetu',
|
||||||
'shortwaveRadiation': 'Krátká vlnová radiace',
|
'widgetText': 'Text widgetu',
|
||||||
'roundDegree': 'Zaokrouhlit stupně',
|
'dewpoint': 'Rosný bod',
|
||||||
};
|
'shortwaveRadiation': 'Krátká vlnová radiace',
|
||||||
|
'roundDegree': 'Zaokrouhlit stupně',
|
||||||
|
'settings_full': 'Nastavení',
|
||||||
|
'cities': 'Města',
|
||||||
|
'searchMethod': 'Použijte hledání nebo geolokaci',
|
||||||
|
'done': 'Hotovo',
|
||||||
|
'groups': 'Naše skupiny',
|
||||||
|
'openMeteo': 'Data z Open-Meteo (CC-BY 4.0)',
|
||||||
|
'hourlyVariables': 'Hodinové meteorologické proměnné',
|
||||||
|
'dailyVariables': 'Denní meteorologické proměnné',
|
||||||
|
'largeElement': 'Velké zobrazení počasí',
|
||||||
|
'map': 'Mapa',
|
||||||
|
'clearCacheStore': 'Vymazat mezipaměť',
|
||||||
|
'deletedCacheStore': 'Čištění mezipaměti',
|
||||||
|
'deletedCacheStoreQuery': 'Opravdu chcete vymazat mezipaměť?',
|
||||||
|
'addWidget': 'Přidat widget',
|
||||||
|
'hideMap': 'Skrýt mapu',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
142
lib/translation/da_dk.dart
Executable file
|
@ -0,0 +1,142 @@
|
||||||
|
class DaDk {
|
||||||
|
Map<String, String> get messages => {
|
||||||
|
'start': 'Kom i gang',
|
||||||
|
'description':
|
||||||
|
'Vejr app med en opdateret vejrudsigt for hver time, dag og uge for ethvert sted.',
|
||||||
|
'name': 'Vejr',
|
||||||
|
'name2': 'Praktisk design',
|
||||||
|
'name3': 'Kontakt os',
|
||||||
|
'description2':
|
||||||
|
'Al navigation er designet til at interagere med appen så bekvemt og hurtigt som muligt.',
|
||||||
|
'description3':
|
||||||
|
'Hvis du støder på problemer, må du meget gerne kontakte os via e-mail eller i app anmeldelserne.',
|
||||||
|
'next': 'Næste',
|
||||||
|
'search': 'Søg...',
|
||||||
|
'loading': 'Henter...',
|
||||||
|
'searchCity': 'Find din by',
|
||||||
|
'humidity': 'Luftfugtighed',
|
||||||
|
'wind': 'Vind',
|
||||||
|
'visibility': 'Sigtbarhed',
|
||||||
|
'feels': 'Føles som',
|
||||||
|
'evaporation': 'Fordampning',
|
||||||
|
'precipitation': 'Nedbør',
|
||||||
|
'direction': 'Retning',
|
||||||
|
'pressure': 'Tryk',
|
||||||
|
'rain': 'Regn',
|
||||||
|
'clear_sky': 'Skyfri himmel',
|
||||||
|
'cloudy': 'Skyet',
|
||||||
|
'overcast': 'Overskyet',
|
||||||
|
'fog': 'Tåge',
|
||||||
|
'drizzle': 'Støv regn',
|
||||||
|
'drizzling_rain': 'Frysende støvregn',
|
||||||
|
'freezing_rain': 'Frostregn',
|
||||||
|
'heavy_rains': 'Regnskyl',
|
||||||
|
'snow': 'Sne',
|
||||||
|
'thunderstorm': 'Tordenvejr',
|
||||||
|
'kph': 'km/h',
|
||||||
|
'mph': 'mph',
|
||||||
|
'm/s': 'm/s',
|
||||||
|
'mmHg': 'mmHg',
|
||||||
|
'mi': 'mi',
|
||||||
|
'km': 'km',
|
||||||
|
'inch': 'tommer',
|
||||||
|
'mm': 'mm',
|
||||||
|
'hPa': 'hPa',
|
||||||
|
'settings': 'Inds.',
|
||||||
|
'no_inter': 'Ingen Internet',
|
||||||
|
'on_inter': 'Tænd for internettet for at få meteorologisk data.',
|
||||||
|
'location': 'Placering',
|
||||||
|
'no_location':
|
||||||
|
'Aktiver placeringer for at få vejrdata for den aktuelle placering.',
|
||||||
|
'theme': 'Tema',
|
||||||
|
'low': 'Lav',
|
||||||
|
'high': 'Høj',
|
||||||
|
'normal': 'Normal',
|
||||||
|
'lat': 'Breddegrad',
|
||||||
|
'lon': 'Længdegrad',
|
||||||
|
'create': 'Opret',
|
||||||
|
'city': 'By',
|
||||||
|
'district': 'Distrikt',
|
||||||
|
'noWeatherCard': 'Tilføj en by',
|
||||||
|
'deletedCardWeather': 'Slet en by',
|
||||||
|
'deletedCardWeatherQuery': 'Er du sikker på at du vil slette denne by?',
|
||||||
|
'delete': 'Slet',
|
||||||
|
'cancel': 'Annullere',
|
||||||
|
'time': 'Tid i byen',
|
||||||
|
'validateName': 'Indtast venligst navnet',
|
||||||
|
'measurements': 'Foranstaltningssystemet',
|
||||||
|
'degrees': 'Grader',
|
||||||
|
'celsius': 'Celsius',
|
||||||
|
'fahrenheit': 'Fahrenheit',
|
||||||
|
'imperial': 'Imperialistisk',
|
||||||
|
'metric': 'Metrisk',
|
||||||
|
'validateValue': 'Indtast en værdi',
|
||||||
|
'validateNumber': 'Indtast et gyldigt nummer',
|
||||||
|
'validate90': 'Værdien skal være mellem -90 og 90',
|
||||||
|
'validate180': 'Værdien skal være mellem -180 og 180',
|
||||||
|
'notifications': 'Notifikationer',
|
||||||
|
'sunrise': 'Solopgang',
|
||||||
|
'sunset': 'Solnedgang',
|
||||||
|
'timeformat': 'Tids format',
|
||||||
|
'12': '12-timer',
|
||||||
|
'24': '24-timer',
|
||||||
|
'cloudcover': 'skydække',
|
||||||
|
'uvIndex': 'UV-index',
|
||||||
|
'materialColor': 'Dynamiske farver',
|
||||||
|
'uvLow': 'Lav',
|
||||||
|
'uvAverage': 'Moderat',
|
||||||
|
'uvHigh': 'Høj',
|
||||||
|
'uvVeryHigh': 'Meget højt',
|
||||||
|
'uvExtreme': 'Ekstrem',
|
||||||
|
'weatherMore': '12 dages vejrudsigt',
|
||||||
|
'windgusts': 'Vindstød',
|
||||||
|
'north': 'Nord',
|
||||||
|
'northeast': 'Nordøst',
|
||||||
|
'east': 'Øst',
|
||||||
|
'southeast': 'Sydøst',
|
||||||
|
'south': 'Syd',
|
||||||
|
'southwest': 'Sydvest',
|
||||||
|
'west': 'Vest',
|
||||||
|
'northwest': 'Nordvest',
|
||||||
|
'project': 'Projektet findes på',
|
||||||
|
'version': 'App version',
|
||||||
|
'precipitationProbability': 'Sandsynlighed for nedbør',
|
||||||
|
'apparentTemperatureMin': 'Minimum temperature',
|
||||||
|
'apparentTemperatureMax': 'Maksimal temperatur',
|
||||||
|
'amoledTheme': 'AMOLED-tema',
|
||||||
|
'appearance': 'Udseende',
|
||||||
|
'functions': 'Funktioner',
|
||||||
|
'data': 'Data',
|
||||||
|
'language': 'Sprog',
|
||||||
|
'timeRange': 'Hyppighed (i timer)',
|
||||||
|
'timeStart': 'Start tid',
|
||||||
|
'timeEnd': 'Slut tid',
|
||||||
|
'support': 'Support',
|
||||||
|
'system': 'System',
|
||||||
|
'dark': 'Mørk',
|
||||||
|
'light': 'Lys',
|
||||||
|
'license': 'Licenser',
|
||||||
|
'widget': 'Widget',
|
||||||
|
'widgetBackground': 'Widget baggrund',
|
||||||
|
'widgetText': 'Widget tekst',
|
||||||
|
'dewpoint': 'Dugpunktet',
|
||||||
|
'shortwaveRadiation': 'Kortbølgestråling',
|
||||||
|
'W/m2': 'W/m2',
|
||||||
|
'roundDegree': 'Afrundede grader',
|
||||||
|
'settings_full': 'Indstillinger',
|
||||||
|
'cities': 'Byer',
|
||||||
|
'searchMethod': 'Brug søgning eller geolokation',
|
||||||
|
'done': 'Færdig',
|
||||||
|
'groups': 'Vores grupper',
|
||||||
|
'openMeteo': 'Data fra Open-Meteo (CC-BY 4.0)',
|
||||||
|
'hourlyVariables': 'Timevise vejrfaktorer',
|
||||||
|
'dailyVariables': 'Daglige vejrfaktorer',
|
||||||
|
'largeElement': 'Stort vejrdisplay',
|
||||||
|
'map': 'Kort',
|
||||||
|
'clearCacheStore': 'Ryd cache',
|
||||||
|
'deletedCacheStore': 'Rydder cache',
|
||||||
|
'deletedCacheStoreQuery': 'Er du sikker på, at du vil rydde cachen?',
|
||||||
|
'addWidget': 'Tilføj widget',
|
||||||
|
'hideMap': 'Skjul kort',
|
||||||
|
};
|
||||||
|
}
|
264
lib/translation/de_de.dart
Normal file → Executable file
|
@ -1,126 +1,144 @@
|
||||||
class DeDe {
|
class DeDe {
|
||||||
Map<String, String> get messages => {
|
Map<String, String> get messages => {
|
||||||
'start': 'Los gehts',
|
'start': 'Los gehts',
|
||||||
'description':
|
'description':
|
||||||
'Wetteranwendung mit einer aktuellen Prognose für jede Stunde, Tag und Woche für jeden Ort.',
|
'Wetteranwendung mit einer aktuellen Prognose für jede Stunde, Tag und Woche für jeden Ort.',
|
||||||
'name': 'Wetter',
|
'name': 'Wetter',
|
||||||
'name2': 'Bequemes Design',
|
'name2': 'Bequemes Design',
|
||||||
'name3': 'Kontaktiere uns',
|
'name3': 'Kontaktiere uns',
|
||||||
'description2':
|
'description2':
|
||||||
'Die gesamte Navigation ist so gestaltet, dass die Interaktion mit der Anwendung so bequem und schnell wie möglich erfolgt.',
|
'Die gesamte Navigation ist so gestaltet, dass die Interaktion mit der Anwendung so bequem und schnell wie möglich erfolgt.',
|
||||||
'description3':
|
'description3':
|
||||||
'Wenn Sie auf Probleme stoßen, kontaktieren Sie uns bitte per E-Mail oder in den Bewertungen der Anwendung.',
|
'Wenn Sie auf Probleme stoßen, kontaktieren Sie uns bitte per E-Mail oder in den Bewertungen der Anwendung.',
|
||||||
'next': 'Weiter',
|
'next': 'Weiter',
|
||||||
'search': 'Suchen...',
|
'search': 'Suchen...',
|
||||||
'loading': 'Lädt...',
|
'loading': 'Lädt...',
|
||||||
'searchCity': 'Finde deine Stadt',
|
'searchCity': 'Finde deine Stadt',
|
||||||
'humidity': 'Luftfeuchtigkeit',
|
'humidity': 'Luftfeuchtigkeit',
|
||||||
'wind': 'Wind',
|
'wind': 'Wind',
|
||||||
'visibility': 'Sichtweite',
|
'visibility': 'Sichtweite',
|
||||||
'feels': 'Gefühlt',
|
'feels': 'Gefühlt',
|
||||||
'evaporation': 'Verdunstung',
|
'evaporation': 'Verdunstung',
|
||||||
'precipitation': 'Niederschlag',
|
'precipitation': 'Niederschlag',
|
||||||
'direction': 'Richtung',
|
'direction': 'Richtung',
|
||||||
'pressure': 'Druck',
|
'pressure': 'Druck',
|
||||||
'rain': 'Regen',
|
'rain': 'Regen',
|
||||||
'clear_sky': 'Klarer Himmel',
|
'clear_sky': 'Klarer Himmel',
|
||||||
'cloudy': 'Bewölkt',
|
'cloudy': 'Bewölkt',
|
||||||
'overcast': 'Bedeckt',
|
'overcast': 'Bedeckt',
|
||||||
'fog': 'Nebel',
|
'fog': 'Nebel',
|
||||||
'drizzle': 'Nieselregen',
|
'drizzle': 'Nieselregen',
|
||||||
'drizzling_rain': 'Gefrierender Nieselregen',
|
'drizzling_rain': 'Gefrierender Nieselregen',
|
||||||
'freezing_rain': 'Gefrierender Regen',
|
'freezing_rain': 'Gefrierender Regen',
|
||||||
'heavy_rains': 'Regenschauer',
|
'heavy_rains': 'Regenschauer',
|
||||||
'snow': 'Schnee',
|
'snow': 'Schnee',
|
||||||
'thunderstorm': 'Gewitter',
|
'thunderstorm': 'Gewitter',
|
||||||
'kph': 'km/h',
|
'kph': 'km/h',
|
||||||
'mph': 'mph',
|
'mph': 'mph',
|
||||||
'mi': 'mi',
|
'm/s': 'm/s',
|
||||||
'km': 'km',
|
'mmHg': 'mmHg',
|
||||||
'inch': 'inch',
|
'mi': 'mi',
|
||||||
'mm': 'mm',
|
'km': 'km',
|
||||||
'hPa': 'hPa',
|
'inch': 'inch',
|
||||||
'settings': 'Einstellungen',
|
'mm': 'mm',
|
||||||
'no_inter': 'Keine Internetverbindung',
|
'hPa': 'hPa',
|
||||||
'on_inter':
|
'settings': 'Einstellungen',
|
||||||
'Schalte das Internet ein, um meteorologische Daten zu erhalten.',
|
'no_inter': 'Keine Internetverbindung',
|
||||||
'location': 'Standort',
|
'on_inter':
|
||||||
'no_location':
|
'Schalte das Internet ein, um meteorologische Daten zu erhalten.',
|
||||||
'Aktiviere den Standortdienst, um Wetterdaten für den aktuellen Standort zu erhalten.',
|
'location': 'Standort',
|
||||||
'theme': 'Thema',
|
'no_location':
|
||||||
'low': 'Niedrig',
|
'Aktiviere den Standortdienst, um Wetterdaten für den aktuellen Standort zu erhalten.',
|
||||||
'high': 'Hoch',
|
'theme': 'Thema',
|
||||||
'normal': 'Normal',
|
'low': 'Niedrig',
|
||||||
'lat': 'Breitengrad',
|
'high': 'Hoch',
|
||||||
'lon': 'Längengrad',
|
'normal': 'Normal',
|
||||||
'create': 'Erstellen',
|
'lat': 'Breitengrad',
|
||||||
'city': 'Stadt',
|
'lon': 'Längengrad',
|
||||||
'district': 'Bezirk',
|
'create': 'Erstellen',
|
||||||
'noWeatherCard': 'Füge eine Stadt hinzu',
|
'city': 'Stadt',
|
||||||
'deletedCardWeather': 'Stadt löschen',
|
'district': 'Bezirk',
|
||||||
'deletedCardWeatherQuery':
|
'noWeatherCard': 'Füge eine Stadt hinzu',
|
||||||
'Sind Sie sicher, dass Sie die Stadt löschen möchten?',
|
'deletedCardWeather': 'Stadt löschen',
|
||||||
'delete': 'Löschen',
|
'deletedCardWeatherQuery':
|
||||||
'cancel': 'Abbrechen',
|
'Sind Sie sicher, dass Sie die Stadt löschen möchten?',
|
||||||
'time': 'Ortszeit',
|
'delete': 'Löschen',
|
||||||
'validateName': 'Bitte geben Sie den Namen ein',
|
'cancel': 'Abbrechen',
|
||||||
'measurements': 'Einheitensystem',
|
'time': 'Ortszeit',
|
||||||
'degrees': 'Grade',
|
'validateName': 'Bitte geben Sie den Namen ein',
|
||||||
'celsius': 'Celsius',
|
'measurements': 'Einheitensystem',
|
||||||
'fahrenheit': 'Fahrenheit',
|
'degrees': 'Grade',
|
||||||
'imperial': 'Imperial',
|
'celsius': 'Celsius',
|
||||||
'metric': 'Metrisch',
|
'fahrenheit': 'Fahrenheit',
|
||||||
'validateValue': 'Bitte geben Sie einen Wert ein',
|
'imperial': 'Imperial',
|
||||||
'validateNumber': 'Bitte geben Sie eine Nummer ein',
|
'metric': 'Metrisch',
|
||||||
'validate90': 'Der Wert muss zwischen -90 und 90 liegen',
|
'validateValue': 'Bitte geben Sie einen Wert ein',
|
||||||
'validate180': 'Der Wert muss zwischen -180 und 180 liegen',
|
'validateNumber': 'Bitte geben Sie eine Nummer ein',
|
||||||
'notifications': 'Benachrichtigungen',
|
'validate90': 'Der Wert muss zwischen -90 und 90 liegen',
|
||||||
'sunrise': 'Sonnenaufgang',
|
'validate180': 'Der Wert muss zwischen -180 und 180 liegen',
|
||||||
'sunset': 'Sonnenuntergang',
|
'notifications': 'Benachrichtigungen',
|
||||||
'timeformat': 'Zeitformat',
|
'sunrise': 'Sonnenaufgang',
|
||||||
'12': '12-stunden',
|
'sunset': 'Sonnenuntergang',
|
||||||
'24': '24-stunden',
|
'timeformat': 'Zeitformat',
|
||||||
'cloudcover': 'Wolkenbedeckung',
|
'12': '12-stunden',
|
||||||
'uvIndex': 'UV-index',
|
'24': '24-stunden',
|
||||||
'materialColor': 'Dynamische Farben',
|
'cloudcover': 'Wolkenbedeckung',
|
||||||
'uvLow': 'Niedrig',
|
'uvIndex': 'UV-index',
|
||||||
'uvAverage': 'Mäßig',
|
'materialColor': 'Dynamische Farben',
|
||||||
'uvHigh': 'Hoch',
|
'uvLow': 'Niedrig',
|
||||||
'uvVeryHigh': 'Sehr hoch',
|
'uvAverage': 'Mäßig',
|
||||||
'uvExtreme': 'Extrem',
|
'uvHigh': 'Hoch',
|
||||||
'weatherMore': '12-Tage-Wettervorhersage',
|
'uvVeryHigh': 'Sehr hoch',
|
||||||
'windgusts': 'Böe',
|
'uvExtreme': 'Extrem',
|
||||||
'north': 'Norden',
|
'weatherMore': '12-Tage-Wettervorhersage',
|
||||||
'northeast': 'Nordosten',
|
'windgusts': 'Böe',
|
||||||
'east': 'Osten',
|
'north': 'Norden',
|
||||||
'southeast': 'Südosten',
|
'northeast': 'Nordosten',
|
||||||
'south': 'Süden',
|
'east': 'Osten',
|
||||||
'southwest': 'Südwesten',
|
'southeast': 'Südosten',
|
||||||
'west': 'Westen',
|
'south': 'Süden',
|
||||||
'northwest': 'Nordwesten',
|
'southwest': 'Südwesten',
|
||||||
'project': 'Projekt auf',
|
'west': 'Westen',
|
||||||
'version': 'Anwendungsversion',
|
'northwest': 'Nordwesten',
|
||||||
'precipitationProbability': 'Niederschlagswahrscheinlichkeit',
|
'project': 'Projekt auf',
|
||||||
'apparentTemperatureMin': 'Minimale gefühlte Temperatur',
|
'version': 'Anwendungsversion',
|
||||||
'apparentTemperatureMax': 'Maximale gefühlte Temperatur',
|
'precipitationProbability': 'Niederschlagswahrscheinlichkeit',
|
||||||
'amoledTheme': 'AMOLED-thema',
|
'apparentTemperatureMin': 'Minimale gefühlte Temperatur',
|
||||||
'appearance': 'Erscheinungsbild',
|
'apparentTemperatureMax': 'Maximale gefühlte Temperatur',
|
||||||
'functions': 'Funktionen',
|
'amoledTheme': 'AMOLED-thema',
|
||||||
'data': 'Daten',
|
'appearance': 'Erscheinungsbild',
|
||||||
'language': 'Sprache',
|
'functions': 'Funktionen',
|
||||||
'timeRange': 'Häufigkeit (in Stunden)',
|
'data': 'Daten',
|
||||||
'timeStart': 'Startzeit',
|
'language': 'Sprache',
|
||||||
'timeEnd': 'Endzeit',
|
'timeRange': 'Häufigkeit (in Stunden)',
|
||||||
'support': 'Unterstützung',
|
'timeStart': 'Startzeit',
|
||||||
'system': 'System',
|
'timeEnd': 'Endzeit',
|
||||||
'dark': 'Dunkel',
|
'support': 'Unterstützung',
|
||||||
'light': 'Hell',
|
'system': 'System',
|
||||||
'license': 'Lizenzen',
|
'dark': 'Dunkel',
|
||||||
'widget': 'Widget',
|
'light': 'Hell',
|
||||||
'widgetBackground': 'Widget-Hintergrund',
|
'license': 'Lizenzen',
|
||||||
'widgetText': 'Widget-Text',
|
'widget': 'Widget',
|
||||||
'dewpoint': 'Taupunkt',
|
'widgetBackground': 'Widget-Hintergrund',
|
||||||
'shortwaveRadiation': 'Kurzwellenstrahlung',
|
'widgetText': 'Widget-Text',
|
||||||
'roundDegree': 'Grad runden',
|
'dewpoint': 'Taupunkt',
|
||||||
};
|
'shortwaveRadiation': 'Kurzwellenstrahlung',
|
||||||
|
'roundDegree': 'Grad runden',
|
||||||
|
'settings_full': 'Einstellungen',
|
||||||
|
'cities': 'Städte',
|
||||||
|
'searchMethod': 'Verwenden Sie die Suche oder die Geolokalisierung',
|
||||||
|
'done': 'Fertig',
|
||||||
|
'groups': 'Unsere gruppen',
|
||||||
|
'openMeteo': 'Daten von Open-Meteo (CC-BY 4.0)',
|
||||||
|
'hourlyVariables': 'Stündliche Wettervariablen',
|
||||||
|
'dailyVariables': 'Tägliche Wettervariablen',
|
||||||
|
'largeElement': 'Große Wetteranzeige',
|
||||||
|
'map': 'Karte',
|
||||||
|
'clearCacheStore': 'Cache leeren',
|
||||||
|
'deletedCacheStore': 'Cache wird geleert',
|
||||||
|
'deletedCacheStoreQuery':
|
||||||
|
'Sind Sie sicher, dass Sie den Cache leeren möchten?',
|
||||||
|
'addWidget': 'Widget hinzufügen',
|
||||||
|
'hideMap': 'Karte ausblenden',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
261
lib/translation/en_us.dart
Normal file → Executable file
|
@ -1,125 +1,142 @@
|
||||||
class EnUs {
|
class EnUs {
|
||||||
Map<String, String> get messages => {
|
Map<String, String> get messages => {
|
||||||
'start': 'Get Started',
|
'start': 'Get Started',
|
||||||
'description':
|
'description':
|
||||||
'Weather application with an up-to-date forecast for each hour, day, and week for any location.',
|
'Weather application with an up-to-date forecast for each hour, day, and week for any location.',
|
||||||
'name': 'Weather',
|
'name': 'Weather',
|
||||||
'name2': 'Convenient Design',
|
'name2': 'Convenient Design',
|
||||||
'name3': 'Contact Us',
|
'name3': 'Contact Us',
|
||||||
'description2':
|
'description2':
|
||||||
'All navigation is designed to interact with the application as conveniently and quickly as possible.',
|
'All navigation is designed to interact with the application as conveniently and quickly as possible.',
|
||||||
'description3':
|
'description3':
|
||||||
'If you encounter any issues, please contact us via email or in the application reviews.',
|
'If you encounter any issues, please contact us via email or in the application reviews.',
|
||||||
'next': 'Next',
|
'next': 'Next',
|
||||||
'search': 'Search...',
|
'search': 'Search...',
|
||||||
'loading': 'Loading...',
|
'loading': 'Loading...',
|
||||||
'searchCity': 'Find your city',
|
'searchCity': 'Find your city',
|
||||||
'humidity': 'Humidity',
|
'humidity': 'Humidity',
|
||||||
'wind': 'Wind',
|
'wind': 'Wind',
|
||||||
'visibility': 'Visibility',
|
'visibility': 'Visibility',
|
||||||
'feels': 'Feels',
|
'feels': 'Feels',
|
||||||
'evaporation': 'Evapotranspiration',
|
'evaporation': 'Evapotranspiration',
|
||||||
'precipitation': 'Precipitation',
|
'precipitation': 'Precipitation',
|
||||||
'direction': 'Direction',
|
'direction': 'Direction',
|
||||||
'pressure': 'Pressure',
|
'pressure': 'Pressure',
|
||||||
'rain': 'Rain',
|
'rain': 'Rain',
|
||||||
'clear_sky': 'Clear sky',
|
'clear_sky': 'Clear sky',
|
||||||
'cloudy': 'Cloudy',
|
'cloudy': 'Cloudy',
|
||||||
'overcast': 'Overcast',
|
'overcast': 'Overcast',
|
||||||
'fog': 'Fog',
|
'fog': 'Fog',
|
||||||
'drizzle': 'Drizzle',
|
'drizzle': 'Drizzle',
|
||||||
'drizzling_rain': 'Freezing Drizzle',
|
'drizzling_rain': 'Freezing Drizzle',
|
||||||
'freezing_rain': 'Freezing Rain',
|
'freezing_rain': 'Freezing Rain',
|
||||||
'heavy_rains': 'Rain showers',
|
'heavy_rains': 'Rain showers',
|
||||||
'snow': 'Snow',
|
'snow': 'Snow',
|
||||||
'thunderstorm': 'Thunderstorm',
|
'thunderstorm': 'Thunderstorm',
|
||||||
'kph': 'km/h',
|
'kph': 'km/h',
|
||||||
'mph': 'mph',
|
'mph': 'mph',
|
||||||
'mi': 'mi',
|
'm/s': 'm/s',
|
||||||
'km': 'km',
|
'mmHg': 'mmHg',
|
||||||
'inch': 'inch',
|
'mi': 'mi',
|
||||||
'mm': 'mm',
|
'km': 'km',
|
||||||
'hPa': 'hPa',
|
'inch': 'inch',
|
||||||
'settings': 'Set.',
|
'mm': 'mm',
|
||||||
'no_inter': 'No Internet',
|
'hPa': 'hPa',
|
||||||
'on_inter': 'Turn on the Internet to get meteorological data.',
|
'settings': 'Set.',
|
||||||
'location': 'Location',
|
'no_inter': 'No Internet',
|
||||||
'no_location':
|
'on_inter': 'Turn on the Internet to get meteorological data.',
|
||||||
'Enable the location service to get weather data for the current location.',
|
'location': 'Location',
|
||||||
'theme': 'Theme',
|
'no_location':
|
||||||
'low': 'Low',
|
'Enable the location service to get weather data for the current location.',
|
||||||
'high': 'High',
|
'theme': 'Theme',
|
||||||
'normal': 'Normal',
|
'low': 'Low',
|
||||||
'lat': 'Latitude',
|
'high': 'High',
|
||||||
'lon': 'Longitude',
|
'normal': 'Normal',
|
||||||
'create': 'Create',
|
'lat': 'Latitude',
|
||||||
'city': 'City',
|
'lon': 'Longitude',
|
||||||
'district': 'District',
|
'create': 'Create',
|
||||||
'noWeatherCard': 'Add a city',
|
'city': 'City',
|
||||||
'deletedCardWeather': 'Deleting a city',
|
'district': 'District',
|
||||||
'deletedCardWeatherQuery': 'Are you sure you want to delete the city?',
|
'noWeatherCard': 'Add a city',
|
||||||
'delete': 'Delete',
|
'deletedCardWeather': 'Deleting a city',
|
||||||
'cancel': 'Cancel',
|
'deletedCardWeatherQuery': 'Are you sure you want to delete the city?',
|
||||||
'time': 'Time in the city',
|
'delete': 'Delete',
|
||||||
'validateName': 'Please enter the name',
|
'cancel': 'Cancel',
|
||||||
'measurements': 'System of measures',
|
'time': 'Time in the city',
|
||||||
'degrees': 'Degrees',
|
'validateName': 'Please enter the name',
|
||||||
'celsius': 'Celsius',
|
'measurements': 'System of measures',
|
||||||
'fahrenheit': 'Fahrenheit',
|
'degrees': 'Degrees',
|
||||||
'imperial': 'Imperial',
|
'celsius': 'Celsius',
|
||||||
'metric': 'Metric',
|
'fahrenheit': 'Fahrenheit',
|
||||||
'validateValue': 'Please enter a value',
|
'imperial': 'Imperial',
|
||||||
'validateNumber': 'Please enter a valid number',
|
'metric': 'Metric',
|
||||||
'validate90': 'Value must be between -90 and 90',
|
'validateValue': 'Please enter a value',
|
||||||
'validate180': 'Value must be between -180 and 180',
|
'validateNumber': 'Please enter a valid number',
|
||||||
'notifications': 'Notifications',
|
'validate90': 'Value must be between -90 and 90',
|
||||||
'sunrise': 'Sunrise',
|
'validate180': 'Value must be between -180 and 180',
|
||||||
'sunset': 'Sunset',
|
'notifications': 'Notifications',
|
||||||
'timeformat': 'Time format',
|
'sunrise': 'Sunrise',
|
||||||
'12': '12-hour',
|
'sunset': 'Sunset',
|
||||||
'24': '24-hour',
|
'timeformat': 'Time format',
|
||||||
'cloudcover': 'Сloudcover',
|
'12': '12-hour',
|
||||||
'uvIndex': 'UV-index',
|
'24': '24-hour',
|
||||||
'materialColor': 'Dynamic colors',
|
'cloudcover': 'Cloudcover',
|
||||||
'uvLow': 'Low',
|
'uvIndex': 'UV-index',
|
||||||
'uvAverage': 'Moderate',
|
'materialColor': 'Dynamic colors',
|
||||||
'uvHigh': 'High',
|
'uvLow': 'Low',
|
||||||
'uvVeryHigh': 'Very high',
|
'uvAverage': 'Moderate',
|
||||||
'uvExtreme': 'Extreme',
|
'uvHigh': 'High',
|
||||||
'weatherMore': '12-day weather forecast',
|
'uvVeryHigh': 'Very high',
|
||||||
'windgusts': 'Gust',
|
'uvExtreme': 'Extreme',
|
||||||
'north': 'North',
|
'weatherMore': '12-day weather forecast',
|
||||||
'northeast': 'Northeast',
|
'windgusts': 'Gust',
|
||||||
'east': 'East',
|
'north': 'North',
|
||||||
'southeast': 'Southeast',
|
'northeast': 'Northeast',
|
||||||
'south': 'South',
|
'east': 'East',
|
||||||
'southwest': 'Southwest',
|
'southeast': 'Southeast',
|
||||||
'west': 'West',
|
'south': 'South',
|
||||||
'northwest': 'Northwest',
|
'southwest': 'Southwest',
|
||||||
'project': 'Project on',
|
'west': 'West',
|
||||||
'version': 'Application version',
|
'northwest': 'Northwest',
|
||||||
'precipitationProbability': 'Precipitation probability',
|
'project': 'Project on',
|
||||||
'apparentTemperatureMin': 'Minimum apparent temperature',
|
'version': 'Application version',
|
||||||
'apparentTemperatureMax': 'Maximum apparent temperature',
|
'precipitationProbability': 'Precipitation probability',
|
||||||
'amoledTheme': 'AMOLED-theme',
|
'apparentTemperatureMin': 'Minimum apparent temperature',
|
||||||
'appearance': 'Appearance',
|
'apparentTemperatureMax': 'Maximum apparent temperature',
|
||||||
'functions': 'Functions',
|
'amoledTheme': 'AMOLED-theme',
|
||||||
'data': 'Data',
|
'appearance': 'Appearance',
|
||||||
'language': 'Language',
|
'functions': 'Functions',
|
||||||
'timeRange': 'Frequency (in hours)',
|
'data': 'Data',
|
||||||
'timeStart': 'Start time',
|
'language': 'Language',
|
||||||
'timeEnd': 'End time',
|
'timeRange': 'Frequency (in hours)',
|
||||||
'support': 'Support',
|
'timeStart': 'Start time',
|
||||||
'system': 'System',
|
'timeEnd': 'End time',
|
||||||
'dark': 'Dark',
|
'support': 'Donate',
|
||||||
'light': 'Light',
|
'system': 'System',
|
||||||
'license': 'Licenses',
|
'dark': 'Dark',
|
||||||
'widget': 'Widget',
|
'light': 'Light',
|
||||||
'widgetBackground': 'Widget background',
|
'license': 'Licenses',
|
||||||
'widgetText': 'Widget text',
|
'widget': 'Widget',
|
||||||
'dewpoint': 'Dewpoint',
|
'widgetBackground': 'Widget background',
|
||||||
'shortwaveRadiation': 'Shortwave radiation',
|
'widgetText': 'Widget text',
|
||||||
'W/m2': 'W/m2',
|
'dewpoint': 'Dewpoint',
|
||||||
'roundDegree': 'Round degrees',
|
'shortwaveRadiation': 'Shortwave radiation',
|
||||||
};
|
'W/m2': 'W/m2',
|
||||||
|
'roundDegree': 'Round degrees',
|
||||||
|
'settings_full': 'Settings',
|
||||||
|
'cities': 'Cities',
|
||||||
|
'searchMethod': 'Use search or geolocation',
|
||||||
|
'done': 'Done',
|
||||||
|
'groups': 'Our groups',
|
||||||
|
'openMeteo': 'Data by Open-Meteo (CC-BY 4.0)',
|
||||||
|
'hourlyVariables': 'Hourly weather variables',
|
||||||
|
'dailyVariables': 'Daily weather variables',
|
||||||
|
'largeElement': 'Large weather display',
|
||||||
|
'map': 'Map',
|
||||||
|
'clearCacheStore': 'Clear cache',
|
||||||
|
'deletedCacheStore': 'Clearing the cache',
|
||||||
|
'deletedCacheStoreQuery': 'Are you sure you want to clear the cache?',
|
||||||
|
'addWidget': 'Add widget',
|
||||||
|
'hideMap': 'Hide map',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|