Compare commits

..

No commits in common. "main" and "v1.3.0" have entirely different histories.
main ... v1.3.0

149 changed files with 8159 additions and 11770 deletions

3
.gitignore vendored
View file

@ -15,7 +15,6 @@ 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
@ -42,4 +41,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

View file

@ -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 and should not be manually edited. # This file should be version controlled.
version: version:
revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" revision: 135454af32477f815a7525073027a3ff9eff1bfd
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: 2663184aa79047d0a33a14a3b607954f8fdd8730 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: android - platform: android
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: ios - platform: ios
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: linux - platform: linux
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: macos - platform: macos
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: web - platform: web
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: windows - platform: windows
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
# User provided section # User provided section

View file

@ -1,15 +1,15 @@
<div align='center'> <div align='center'>
<img src='/readme/icon.png' width='150'/> <img src='/assets/icons/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/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'/> <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'/>
### 💰 Support Us ### 💰 Support Us
@ -52,6 +52,7 @@ If you find Rain valuable and worthy for future innovation, consider supporting
### 📥 Get Rain Now ### 📥 Get Rain Now
[![Play Store](https://img.shields.io/badge/Google_Play-414141?style=for-the-badge&logo=google-play&logoColor=white)](https://play.google.com/store/apps/details?id=com.yoshi.rain) [![Play Store](https://img.shields.io/badge/Google_Play-414141?style=for-the-badge&logo=google-play&logoColor=white)](https://play.google.com/store/apps/details?id=com.yoshi.rain)
[![RuStore](https://img.shields.io/badge/RuStore-blue?style=for-the-badge&logo=vk&logoColor=white)](https://apps.rustore.ru/app/com.yoshi.rain)
Or get the latest APK from the [Releases Section](https://github.com/DarkMooNight/Rain/releases/latest). You can also find the app on IzzyOnDroid via a F-Droid client [here](https://apt.izzysoft.de/fdroid/index/apk/com.yoshi.rain). 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).
@ -62,5 +63,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>

View file

@ -4,6 +4,24 @@ 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()) {
@ -11,73 +29,62 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
namespace = 'com.yoshi.rain' namespace 'com.yoshi.rain'
compileSdk = 35 compileSdkVersion 34
ndkVersion = '29.0.13113456' ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled = true
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_17 jvmTarget = '1.8'
} }
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"
minSdk = 23 minSdkVersion 23
targetSdk = flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode flutterVersionCode.toInteger()
versionName = flutter.versionName versionName flutterVersionName
} }
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.1.0") implementation "androidx.core:core-remoteviews:1.0.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

View file

@ -11,6 +11,16 @@
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"
@ -18,7 +28,6 @@
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"
@ -27,32 +36,7 @@
<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" />
@ -68,4 +52,4 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
</application> </application>
</manifest> </manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before After
Before After

View file

@ -3,7 +3,6 @@
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">
@ -28,7 +27,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"

View file

@ -3,7 +3,6 @@
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">
@ -28,7 +27,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"

View file

@ -3,7 +3,6 @@
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">
@ -33,8 +32,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:ignore="ObsoleteLayoutParam" tools:text="21°"
tools:text="21°" /> tools:ignore="ObsoleteLayoutParam" />
</LinearLayout> </LinearLayout>

View file

@ -3,7 +3,6 @@
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">

View file

@ -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="@style/Theme.Material3.DynamicColors.Dark.NoActionBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<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="@style/Theme.Material3.DynamicColors.Dark.NoActionBar"> <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
</resources> </resources>

View file

@ -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="@style/Theme.Material3.DynamicColors.DayNight"> <style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.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>

View file

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<!-- 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="@style/Theme.Material3.DynamicColors.Dark.NoActionBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- 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:targetApi="q">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>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">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="@style/Theme.Material3.DynamicColors.Dark.NoActionBar"> <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
</resources> </resources>

View file

@ -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="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<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,15 +14,17 @@
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="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar"> <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<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>

View file

@ -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="@style/Theme.Material3.DynamicColors.DayNight"> parent="@android:style/Theme.DeviceDefault.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>

View file

@ -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">Rain Widget</string> <string name="app_widget_description">Oreo 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>

View file

@ -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="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- 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:targetApi="q">false</item> <item name="android:forceDarkAllowed" tools:ignore="NewApi">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:targetApi="o_mr1">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode" tools:ignore="NewApi">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,15 +17,18 @@
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="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar"> <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<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">?attr/colorSurface</item> <item name="android:background">?android:attr/colorBackground</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">?attr/colorSurface</item> <item name="android:background">?android:attr/colorBackground</item>
<item name="android:textColor">?attr/itemTextColor</item> <item name="android:textColor">?android:attr/textColorPrimary</item>
</style> </style>
</resources>
</resources>

View file

@ -1,5 +1,5 @@
<resources> <resources>
<style name="Theme.Android.AppWidgetContainerParent" parent="@style/Theme.Material3.DynamicColors.DayNight"> <style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
<!-- 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>
<!-- <!--

View file

@ -1,3 +1,15 @@
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()
@ -8,8 +20,6 @@ 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')
} }

View file

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

View file

@ -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-8.11.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

View file

@ -5,21 +5,25 @@ 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("$flutterSdkPath/packages/flutter_tools/gradle") includeBuild("${settings.ext.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 "8.9.0" apply false id "com.android.application" version '7.4.2' apply false
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
} }
include ":app" include ":app"

BIN
assets/icons/Search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
assets/images/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before After
Before After

View file

@ -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&apos;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&apos;s precise location.</string>
</dict> </dict>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>

View file

@ -1,12 +0,0 @@
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 Executable file → Normal file
View file

@ -1,38 +1,69 @@
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_api.dart'; import 'package:rain/app/api/city.dart';
import 'package:rain/app/api/weather_api.dart'; import 'package:rain/app/api/weather.dart';
import 'package:rain/app/data/db.dart'; import 'package:rain/app/data/weather.dart';
import 'package:rain/main.dart'; import 'package:rain/main.dart';
class WeatherAPI { class WeatherAPI {
final Dio dio = final Dio dio = Dio()
Dio()..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?'; ..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?';
final Dio dioLocation = Dio(); final Dio dioLocation = Dio();
static const String _weatherParams = Future<MainWeatherCache> getWeatherData(double? lat, double? lon) async {
'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' String url =
'&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' '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';
'&forecast_days=12&timezone=auto'; String urlWeather;
settings.measurements == 'imperial' && settings.degrees == 'fahrenheit'
String _buildWeatherUrl(double lat, double lon) { ? urlWeather =
String url = 'latitude=$lat&longitude=$lon&$_weatherParams'; '$url&temperature_unit=fahrenheit&windspeed_unit=mph&precipitation_unit=inch'
if (settings.measurements == 'imperial') { : settings.measurements == 'imperial'
url += '&windspeed_unit=mph&precipitation_unit=inch'; ? urlWeather = '$url&windspeed_unit=mph&precipitation_unit=inch'
} : settings.degrees == 'fahrenheit'
if (settings.degrees == 'fahrenheit') { ? urlWeather = '$url&temperature_unit=fahrenheit'
url += '&temperature_unit=fahrenheit'; : urlWeather = url;
}
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 _mapWeatherDataToCache(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(),
);
} on DioException catch (e) { } on DioException catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -41,24 +72,63 @@ class WeatherAPI {
} }
} }
Future<WeatherCard> getWeatherCard( Future<WeatherCard> getWeatherCard(double? lat, double? lon, String city,
double lat, String district, String timezone) async {
double lon, String url =
String city, '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 district, String urlWeather;
String timezone, settings.measurements == 'imperial' && settings.degrees == 'fahrenheit'
) async { ? urlWeather =
final String urlWeather = _buildWeatherUrl(lat, lon); '$url&temperature_unit=fahrenheit&windspeed_unit=mph&precipitation_unit=inch'
: settings.measurements == 'imperial'
? urlWeather = '$url&windspeed_unit=mph&precipitation_unit=inch'
: settings.degrees == 'fahrenheit'
? urlWeather = '$url&temperature_unit=fahrenheit'
: urlWeather = url;
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 _mapWeatherDataToCard( return WeatherCard(
weatherData, time: weatherData.hourly.time,
lat, temperature2M: weatherData.hourly.temperature2M,
lon, relativehumidity2M: weatherData.hourly.relativeHumidity2M,
city, apparentTemperature: weatherData.hourly.apparentTemperature,
district, precipitation: weatherData.hourly.precipitation,
timezone, 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(),
); );
} on DioException catch (e) { } on DioException catch (e) {
if (kDebugMode) { if (kDebugMode) {
@ -69,7 +139,7 @@ class WeatherAPI {
} }
Future<Iterable<Result>> getCity(String query, Locale? locale) async { Future<Iterable<Result>> getCity(String query, Locale? locale) async {
final String url = final 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);
@ -93,97 +163,4 @@ 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_api.dart → lib/app/api/city.dart Executable file → Normal file
View file

@ -1,14 +1,15 @@
class CityApi { class CityApi {
CityApi({required this.results}); CityApi({
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: results: json['results'] == null
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 {
@ -25,9 +26,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'],
); );
} }

View 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_api.freezed.dart'; part 'weather.freezed.dart';
part 'weather_api.g.dart'; part 'weather.g.dart';
@freezed @freezed
class WeatherDataApi with _$WeatherDataApi { class WeatherDataApi with _$WeatherDataApi {

View 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_api.dart'; part of 'weather.dart';
// ************************************************************************** // **************************************************************************
// FreezedGenerator // FreezedGenerator
@ -12,7 +12,7 @@ part of 'weather_api.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#adding-getters-and-methods-to-our-models'); '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');
WeatherDataApi _$WeatherDataApiFromJson(Map<String, dynamic> json) { WeatherDataApi _$WeatherDataApiFromJson(Map<String, dynamic> json) {
return _WeatherDataApi.fromJson(json); return _WeatherDataApi.fromJson(json);
@ -24,12 +24,8 @@ 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;
} }
@ -56,8 +52,6 @@ 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({
@ -81,8 +75,6 @@ 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 {
@ -91,8 +83,6 @@ 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 {
@ -126,8 +116,6 @@ 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({
@ -184,13 +172,11 @@ class _$WeatherDataApiImpl implements _WeatherDataApi {
other.timezone == timezone)); other.timezone == timezone));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, hourly, daily, timezone); int get hashCode => Object.hash(runtimeType, hourly, daily, timezone);
/// Create a copy of WeatherDataApi @JsonKey(ignore: true)
/// 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 =>
@ -220,11 +206,8 @@ 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(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith => _$$WeatherDataApiImplCopyWith<_$WeatherDataApiImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -268,12 +251,8 @@ 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;
} }
@ -314,8 +293,6 @@ 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({
@ -452,8 +429,6 @@ 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({
@ -845,7 +820,7 @@ class _$HourlyImpl implements _Hourly {
.equals(other._shortwaveRadiation, _shortwaveRadiation)); .equals(other._shortwaveRadiation, _shortwaveRadiation));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType,
@ -868,9 +843,7 @@ class _$HourlyImpl implements _Hourly {
const DeepCollectionEquality().hash(_precipitationProbability), const DeepCollectionEquality().hash(_precipitationProbability),
const DeepCollectionEquality().hash(_shortwaveRadiation)); const DeepCollectionEquality().hash(_shortwaveRadiation));
/// Create a copy of Hourly @JsonKey(ignore: true)
/// 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 =>
@ -960,11 +933,8 @@ 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(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$HourlyImplCopyWith<_$HourlyImpl> get copyWith => _$$HourlyImplCopyWith<_$HourlyImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -1008,12 +978,8 @@ 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;
} }
@ -1054,8 +1020,6 @@ 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({
@ -1177,8 +1141,6 @@ 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({
@ -1516,7 +1478,7 @@ class _$DailyImpl implements _Daily {
other._windDirection10MDominant, _windDirection10MDominant)); other._windDirection10MDominant, _windDirection10MDominant));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType,
@ -1536,9 +1498,7 @@ class _$DailyImpl implements _Daily {
const DeepCollectionEquality().hash(_rainSum), const DeepCollectionEquality().hash(_rainSum),
const DeepCollectionEquality().hash(_windDirection10MDominant)); const DeepCollectionEquality().hash(_windDirection10MDominant));
/// Create a copy of Daily @JsonKey(ignore: true)
/// 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 =>
@ -1621,11 +1581,8 @@ 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(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$DailyImplCopyWith<_$DailyImpl> get copyWith => _$$DailyImplCopyWith<_$DailyImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'weather_api.dart'; part of 'weather.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 num).toInt()) ?.map((e) => e as int)
.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 num?)?.toInt()) ?.map((e) => e as int?)
.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 num?)?.toInt()) ?.map((e) => e as int?)
.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 num?)?.toInt()) ?.map((e) => e as int?)
.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 num?)?.toInt()) ?.map((e) => e as int?)
.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 num?)?.toInt()) ?.map((e) => e as int?)
.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 num?)?.toInt()) ?.map((e) => e as int?)
.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 num?)?.toInt()) ?.map((e) => e as int?)
.toList(), .toList(),
); );

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

@ -1,6 +1,7 @@
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';
@ -10,17 +11,16 @@ 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/db.dart'; import 'package:rain/app/data/weather.dart';
import 'package:rain/app/utils/notification.dart'; import 'package:rain/app/services/notification.dart';
import 'package:rain/app/utils/show_snack_bar.dart'; import 'package:rain/app/services/utils.dart';
import 'package:rain/app/ui/widgets/weather/status/status_data.dart'; import 'package:rain/app/widgets/status/status_data.dart';
import 'package:rain/app/ui/widgets/weather/status/status_weather.dart'; import 'package:rain/app/widgets/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,14 +52,15 @@ class WeatherController extends GetxController {
@override @override
void onInit() { void onInit() {
weatherCards.assignAll( weatherCards
isar.weatherCards.where().sortByIndex().findAllSync(), .assignAll(isar.weatherCards.where().sortByIndex().findAllSync());
);
super.onInit(); super.onInit();
} }
Future<Position> _determinePosition() async { Future<Position> determinePosition() async {
LocationPermission permission = await Geolocator.checkPermission(); LocationPermission permission;
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) {
@ -69,36 +70,35 @@ 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 {
final locationCity = isar.locationCaches.where().findFirstSync(); if ((isar.locationCaches.where().findAllSync()).isNotEmpty) {
if (locationCity != null) { LocationCache locationCity =
await getLocation( (isar.locationCaches.where().findFirstSync())!;
locationCity.lat!, await getLocation(locationCity.lat!, locationCity.lon!,
locationCity.lon!, locationCity.district!, locationCity.city!);
locationCity.district!,
locationCity.city!,
);
} }
} }
} }
Future<void> getCurrentLocation() async { Future<void> getCurrentLocation() async {
if (!(await isOnline.value)) { bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!isOnline) {
showSnackBar(content: 'no_inter'.tr); showSnackBar(content: 'no_inter'.tr);
await readCache(); await readCache();
return; return;
} }
if (!await Geolocator.isLocationServiceEnabled()) { if (!serviceEnabled) {
showSnackBar( showSnackBar(
content: 'no_location'.tr, content: 'no_location'.tr,
onPressed: () => Geolocator.openLocationSettings(), onPressed: () => Geolocator.openLocationSettings(),
@ -107,73 +107,70 @@ class WeatherController extends GetxController {
return; return;
} }
if (isar.mainWeatherCaches.where().findAllSync().isNotEmpty) { if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) {
await readCache(); await readCache();
return; return;
} }
final position = await _determinePosition(); Position position = await determinePosition();
final placemarks = await placemarkFromCoordinates( List<Placemark> placemarks =
position.latitude, await placemarkFromCoordinates(position.latitude, position.longitude);
position.longitude, Placemark place = placemarks[0];
);
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 = await WeatherAPI().getWeatherData( _mainWeather.value =
_latitude.value, await WeatherAPI().getWeatherData(_latitude.value, _longitude.value);
_longitude.value,
);
notificationCheck(); notificationCheck();
await writeCache(); await writeCache();
await readCache(); await readCache();
} }
Future<Map<String, dynamic>> getCurrentLocationSearch() async { Future<Map> getCurrentLocationSearch() async {
if (!(await isOnline.value)) { bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
double lat, lon;
String city, district;
if (!isOnline) {
showSnackBar(content: 'no_inter'.tr); showSnackBar(content: 'no_inter'.tr);
} }
if (!await Geolocator.isLocationServiceEnabled()) { if (!serviceEnabled) {
showSnackBar( showSnackBar(
content: 'no_location'.tr, content: 'no_location'.tr,
onPressed: () => Geolocator.openLocationSettings(), onPressed: () => Geolocator.openLocationSettings(),
); );
} }
final position = await _determinePosition(); Position position = await determinePosition();
final placemarks = await placemarkFromCoordinates( List<Placemark> placemarks =
position.latitude, await placemarkFromCoordinates(position.latitude, position.longitude);
position.longitude, Placemark place = placemarks[0];
);
final place = placemarks[0];
return { lat = position.latitude;
'lat': position.latitude, lon = position.longitude;
'lon': position.longitude, city = '${place.administrativeArea}';
'city': place.administrativeArea ?? '', district = '${place.locality}';
'district': place.locality ?? '',
}; Map location = {'lat': lat, 'lon': lon, 'city': city, 'district': district};
return location;
} }
Future<void> getLocation( Future<void> getLocation(double latitude, double longitude, String district,
double latitude, String locality) async {
double longitude, if (!isOnline) {
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;
} }
@ -183,12 +180,11 @@ class WeatherController extends GetxController {
_district.value = district; _district.value = district;
_city.value = locality; _city.value = locality;
_mainWeather.value = await WeatherAPI().getWeatherData( _mainWeather.value =
_latitude.value, await WeatherAPI().getWeatherData(_latitude.value, _longitude.value);
_longitude.value,
);
notificationCheck(); notificationCheck();
await writeCache(); await writeCache();
await readCache(); await readCache();
} }
@ -205,14 +201,10 @@ class WeatherController extends GetxController {
_mainWeather.value = mainWeatherCache; _mainWeather.value = mainWeatherCache;
_location.value = locationCache; _location.value = locationCache;
hourOfDay.value = getTime( hourOfDay.value =
_mainWeather.value.time!, getTime(_mainWeather.value.time!, _mainWeather.value.timezone!);
_mainWeather.value.timezone!, dayOfNow.value =
); 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(
@ -243,17 +235,23 @@ class WeatherController extends GetxController {
); );
isar.writeTxnSync(() { isar.writeTxnSync(() {
if (isar.mainWeatherCaches.where().findAllSync().isEmpty) { final mainWeatherCachesIsEmpty =
(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 (!(await isOnline.value)) { if (!isOnline) {
return; return;
} }
@ -263,49 +261,41 @@ 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 (!(await isOnline.value)) { if (!isOnline) {
return; return;
} }
final serviceEnabled = await Geolocator.isLocationServiceEnabled(); bool 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 latitude, double longitude, String city, String district) async {
double longitude, if (!isOnline) {
String city,
String district,
) async {
if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr); showSnackBar(content: 'no_inter'.tr);
return; return;
} }
final tz = tzmap.latLngToTimezoneString(latitude, longitude); String tz = tzmap.latLngToTimezoneString(latitude, longitude);
_weatherCard.value = await WeatherAPI().getWeatherCard( _weatherCard.value = await WeatherAPI()
latitude, .getWeatherCard(latitude, longitude, city, district, tz);
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);
@ -313,88 +303,120 @@ class WeatherController extends GetxController {
} }
Future<void> updateCacheCard(bool refresh) async { Future<void> updateCacheCard(bool refresh) async {
final weatherCard = refresh List<WeatherCard> 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 (!(await isOnline.value) || weatherCard.isEmpty) { if (!isOnline || weatherCard.isEmpty) {
return; return;
} }
for (var oldCard in weatherCard) { for (var oldCard in weatherCard) {
final updatedCard = await WeatherAPI().getWeatherCard( var updatedCard = await WeatherAPI().getWeatherCard(oldCard.lat,
oldCard.lat!, oldCard.lon, oldCard.city!, oldCard.district!, oldCard.timezone!);
oldCard.lon!,
oldCard.city!,
oldCard.district!,
oldCard.timezone!,
);
isar.writeTxnSync(() { isar.writeTxnSync(() {
_updateWeatherCard(oldCard, 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);
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 (!(await isOnline.value)) { if (!isOnline) {
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(() {
_updateWeatherCard(weatherCard, updatedCard); weatherCard
..time = updatedCard.time
..weathercode = updatedCard.weathercode
..temperature2M = updatedCard.temperature2M
..apparentTemperature = updatedCard.apparentTemperature
..relativehumidity2M = updatedCard.relativehumidity2M
..precipitation = updatedCard.precipitation
..rain = updatedCard.rain
..surfacePressure = updatedCard.surfacePressure
..visibility = updatedCard.visibility
..evapotranspiration = updatedCard.evapotranspiration
..windspeed10M = updatedCard.windspeed10M
..winddirection10M = updatedCard.winddirection10M
..windgusts10M = updatedCard.windgusts10M
..cloudcover = updatedCard.cloudcover
..uvIndex = updatedCard.uvIndex
..dewpoint2M = updatedCard.dewpoint2M
..precipitationProbability = updatedCard.precipitationProbability
..shortwaveRadiation = updatedCard.shortwaveRadiation
..timeDaily = updatedCard.timeDaily
..weathercodeDaily = updatedCard.weathercodeDaily
..temperature2MMax = updatedCard.temperature2MMax
..temperature2MMin = updatedCard.temperature2MMin
..apparentTemperatureMax = updatedCard.apparentTemperatureMax
..apparentTemperatureMin = updatedCard.apparentTemperatureMin
..sunrise = updatedCard.sunrise
..sunset = updatedCard.sunset
..precipitationSum = updatedCard.precipitationSum
..precipitationProbabilityMax = updatedCard.precipitationProbabilityMax
..windspeed10MMax = updatedCard.windspeed10MMax
..windgusts10MMax = updatedCard.windgusts10MMax
..uvIndexMax = updatedCard.uvIndexMax
..rainSum = updatedCard.rainSum
..winddirection10MDominant = updatedCard.winddirection10MDominant
..timestamp = DateTime.now();
isar.weatherCards.putSync(weatherCard);
}); });
} }
@ -406,26 +428,35 @@ class WeatherController extends GetxController {
} }
int getTime(List<String> time, String timezone) { int getTime(List<String> time, String timezone) {
return time.indexWhere((t) { int getTime = 0;
final dateTime = DateTime.parse(t); for (var i = 0; i < time.length; i++) {
return tz.TZDateTime.now(tz.getLocation(timezone)).hour == if (tz.TZDateTime.now(tz.getLocation(timezone)).hour ==
dateTime.hour && DateTime.parse(time[i]).hour &&
tz.TZDateTime.now(tz.getLocation(timezone)).day == dateTime.day; tz.TZDateTime.now(tz.getLocation(timezone)).day ==
}); DateTime.parse(time[i]).day) {
getTime = i;
}
}
return getTime;
} }
int getDay(List<DateTime> time, String timezone) { int getDay(List<DateTime> time, String timezone) {
return time.indexWhere( int getDay = 0;
(t) => tz.TZDateTime.now(tz.getLocation(timezone)).day == t.day, for (var i = 0; i < time.length; i++) {
); if (tz.TZDateTime.now(tz.getLocation(timezone)).day == time[i].day) {
getDay = i;
}
}
return getDay;
} }
TimeOfDay timeConvert(String normTime) { TimeOfDay timeConvert(String normTime) {
final hh = normTime.endsWith('PM') ? 12 : 0; int hh = 0;
final timeParts = normTime.split(' ')[0].split(':'); if (normTime.endsWith('PM')) hh = 12;
normTime = normTime.split(' ')[0];
return TimeOfDay( return TimeOfDay(
hour: hh + int.parse(timeParts[0]) % 24, hour: hh + int.parse(normTime.split(':')[0]) % 24,
minute: int.parse(timeParts[1]) % 60, minute: int.parse(normTime.split(':')[1]) % 60,
); );
} }
@ -433,21 +464,21 @@ class WeatherController extends GetxController {
final directory = await getTemporaryDirectory(); final directory = await getTemporaryDirectory();
final imagePath = '${directory.path}/$icon'; final imagePath = '${directory.path}/$icon';
final data = await rootBundle.load('assets/images/$icon'); final ByteData data = await rootBundle.load('assets/images/$icon');
final bytes = data.buffer.asUint8List(); final List<int> bytes = data.buffer.asUint8List();
await File(imagePath).writeAsBytes(bytes); await File(imagePath).writeAsBytes(bytes);
return imagePath; return imagePath;
} }
void notification(MainWeatherCache mainWeatherCache) async { void notlification(MainWeatherCache mainWeatherCache) async {
final now = DateTime.now(); DateTime now = DateTime.now();
final startHour = timeConvert(timeStart).hour; int startHour = timeConvert(timeStart).hour;
final endHour = timeConvert(timeEnd).hour; int endHour = timeConvert(timeEnd).hour;
for (var i = 0; i < mainWeatherCache.time!.length; i += timeRange) { for (var i = 0; i < mainWeatherCache.time!.length; i += timeRange) {
final notificationTime = DateTime.parse(mainWeatherCache.time![i]); DateTime notificationTime = DateTime.parse(mainWeatherCache.time![i]);
if (notificationTime.isAfter(now) && if (notificationTime.isAfter(now) &&
notificationTime.hour >= startHour && notificationTime.hour >= startHour &&
@ -474,15 +505,15 @@ class WeatherController extends GetxController {
void notificationCheck() async { void notificationCheck() async {
if (settings.notifications) { if (settings.notifications) {
final pendingNotificationRequests = await flutterLocalNotificationsPlugin final List<PendingNotificationRequest> pendingNotificationRequests =
.pendingNotificationRequests(); await flutterLocalNotificationsPlugin.pendingNotificationRequests();
if (pendingNotificationRequests.isEmpty) { if (pendingNotificationRequests.isEmpty) {
notification(_mainWeather.value); notlification(_mainWeather.value);
} }
} }
} }
void reorder(int oldIndex, int newIndex) { void reorder(oldIndex, newIndex) {
if (newIndex > oldIndex) { if (newIndex > oldIndex) {
newIndex -= 1; newIndex -= 1;
} }
@ -502,11 +533,15 @@ class WeatherController extends GetxController {
isar.settings.putSync(settings); isar.settings.putSync(settings);
}); });
final results = await Future.wait<bool?>([ return Future.wait<bool?>([
HomeWidget.saveWidgetData('background_color', color), HomeWidget.saveWidgetData(
'background_color',
color,
),
HomeWidget.updateWidget(androidName: androidWidgetName), HomeWidget.updateWidget(androidName: androidWidgetName),
]); ]).then((value) {
return !results.contains(false); return !value.contains(false);
});
} }
Future<bool> updateWidgetTextColor(String color) async { Future<bool> updateWidgetTextColor(String color) async {
@ -515,11 +550,15 @@ class WeatherController extends GetxController {
isar.settings.putSync(settings); isar.settings.putSync(settings);
}); });
final results = await Future.wait<bool?>([ return Future.wait<bool?>([
HomeWidget.saveWidgetData('text_color', color), HomeWidget.saveWidgetData(
'text_color',
color,
),
HomeWidget.updateWidget(androidName: androidWidgetName), HomeWidget.updateWidget(androidName: androidWidgetName),
]); ]).then((value) {
return !results.contains(false); return !value.contains(false);
});
} }
Future<bool> updateWidget() async { Future<bool> updateWidget() async {
@ -534,39 +573,29 @@ class WeatherController extends GetxController {
WeatherCardSchema, WeatherCardSchema,
], directory: (await getApplicationSupportDirectory()).path); ], directory: (await getApplicationSupportDirectory()).path);
final mainWeatherCache = isarWidget.mainWeatherCaches MainWeatherCache? mainWeatherCache;
.where() mainWeatherCache = isarWidget.mainWeatherCaches.where().findFirstSync();
.findFirstSync();
if (mainWeatherCache == null) return false; if (mainWeatherCache == null) return false;
final hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!); int hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!);
final day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!); int day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!);
final results = await Future.wait<bool?>([ return Future.wait<bool?>([
HomeWidget.saveWidgetData( HomeWidget.saveWidgetData(
'weather_icon', 'weather_icon',
await getLocalImagePath( await getLocalImagePath(StatusWeather().getImageNotification(
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 !results.contains(false); return !value.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/db.dart → lib/app/data/weather.dart Executable file → Normal file
View file

@ -1,6 +1,6 @@
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
part 'db.g.dart'; part 'weather.g.dart';
@collection @collection
class Settings { class Settings {
@ -12,14 +12,10 @@ 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;
@ -105,43 +101,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
@ -152,15 +148,12 @@ class LocationCache {
String? city; String? city;
String? district; String? district;
LocationCache({this.lat, this.lon, this.city, this.district}); LocationCache({
this.lat,
Map<String, dynamic> toJson() => { this.lon,
'id': id, this.city,
'lat': lat, this.district,
'lon': lon, });
'city': city,
'district': district,
};
} }
@collection @collection
@ -251,57 +244,56 @@ 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: List<double?>.from( apparentTemperature:
json['apparentTemperature'] ?? [], List<double?>.from(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'] ?? []),
@ -314,31 +306,26 @@ 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: List<int?>.from( precipitationProbability:
json['precipitationProbability'] ?? [], List<int?>.from(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: List<double?>.from( apparentTemperatureMax:
json['apparentTemperatureMax'] ?? [], List<double?>.from(json['apparentTemperatureMax'] ?? []),
), apparentTemperatureMin:
apparentTemperatureMin: List<double?>.from( List<double?>.from(json['apparentTemperatureMin'] ?? []),
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: List<int?>.from( winddirection10MDominant:
json['winddirection10MDominant'] ?? [], List<int?>.from(json['winddirection10MDominant'] ?? []),
),
precipitationSum: List<double?>.from(json['precipitationSum'] ?? []), precipitationSum: List<double?>.from(json['precipitationSum'] ?? []),
precipitationProbabilityMax: List<int?>.from( precipitationProbabilityMax:
json['precipitationProbabilityMax'] ?? [], List<int?>.from(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/db.g.dart → lib/app/data/weather.g.dart Executable file → Normal file
View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'db.dart'; part of 'weather.dart';
// ************************************************************************** // **************************************************************************
// IsarCollectionGenerator // IsarCollectionGenerator
@ -27,95 +27,75 @@ const SettingsSchema = CollectionSchema(
name: r'degrees', name: r'degrees',
type: IsarType.string, type: IsarType.string,
), ),
r'hideMap': PropertySchema(
id: 2,
name: r'hideMap',
type: IsarType.bool,
),
r'language': PropertySchema( r'language': PropertySchema(
id: 3, id: 2,
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: 5, id: 3,
name: r'location', name: r'location',
type: IsarType.bool, type: IsarType.bool,
), ),
r'materialColor': PropertySchema( r'materialColor': PropertySchema(
id: 6, id: 4,
name: r'materialColor', name: r'materialColor',
type: IsarType.bool, type: IsarType.bool,
), ),
r'measurements': PropertySchema( r'measurements': PropertySchema(
id: 7, id: 5,
name: r'measurements', name: r'measurements',
type: IsarType.string, type: IsarType.string,
), ),
r'notifications': PropertySchema( r'notifications': PropertySchema(
id: 8, id: 6,
name: r'notifications', name: r'notifications',
type: IsarType.bool, type: IsarType.bool,
), ),
r'onboard': PropertySchema( r'onboard': PropertySchema(
id: 9, id: 7,
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: 11, id: 8,
name: r'roundDegree', name: r'roundDegree',
type: IsarType.bool, type: IsarType.bool,
), ),
r'theme': PropertySchema( r'theme': PropertySchema(
id: 12, id: 9,
name: r'theme', name: r'theme',
type: IsarType.string, type: IsarType.string,
), ),
r'timeEnd': PropertySchema( r'timeEnd': PropertySchema(
id: 13, id: 10,
name: r'timeEnd', name: r'timeEnd',
type: IsarType.string, type: IsarType.string,
), ),
r'timeRange': PropertySchema( r'timeRange': PropertySchema(
id: 14, id: 11,
name: r'timeRange', name: r'timeRange',
type: IsarType.long, type: IsarType.long,
), ),
r'timeStart': PropertySchema( r'timeStart': PropertySchema(
id: 15, id: 12,
name: r'timeStart', name: r'timeStart',
type: IsarType.string, type: IsarType.string,
), ),
r'timeformat': PropertySchema( r'timeformat': PropertySchema(
id: 16, id: 13,
name: r'timeformat', name: r'timeformat',
type: IsarType.string, type: IsarType.string,
), ),
r'widgetBackgroundColor': PropertySchema( r'widgetBackgroundColor': PropertySchema(
id: 17, id: 14,
name: r'widgetBackgroundColor', name: r'widgetBackgroundColor',
type: IsarType.string, type: IsarType.string,
), ),
r'widgetTextColor': PropertySchema( r'widgetTextColor': PropertySchema(
id: 18, id: 15,
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,
@ -129,7 +109,7 @@ const SettingsSchema = CollectionSchema(
getId: _settingsGetId, getId: _settingsGetId,
getLinks: _settingsGetLinks, getLinks: _settingsGetLinks,
attach: _settingsAttach, attach: _settingsAttach,
version: '3.1.8', version: '3.1.0+1',
); );
int _settingsEstimateSize( int _settingsEstimateSize(
@ -146,7 +126,6 @@ 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) {
@ -178,7 +157,6 @@ int _settingsEstimateSize(
bytesCount += 3 + value.length * 3; bytesCount += 3 + value.length * 3;
} }
} }
bytesCount += 3 + object.wind.length * 3;
return bytesCount; return bytesCount;
} }
@ -190,24 +168,20 @@ 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.writeBool(offsets[2], object.hideMap); writer.writeString(offsets[2], object.language);
writer.writeString(offsets[3], object.language); writer.writeBool(offsets[3], object.location);
writer.writeBool(offsets[4], object.largeElement); writer.writeBool(offsets[4], object.materialColor);
writer.writeBool(offsets[5], object.location); writer.writeString(offsets[5], object.measurements);
writer.writeBool(offsets[6], object.materialColor); writer.writeBool(offsets[6], object.notifications);
writer.writeString(offsets[7], object.measurements); writer.writeBool(offsets[7], object.onboard);
writer.writeBool(offsets[8], object.notifications); writer.writeBool(offsets[8], object.roundDegree);
writer.writeBool(offsets[9], object.onboard); writer.writeString(offsets[9], object.theme);
writer.writeString(offsets[10], object.pressure); writer.writeString(offsets[10], object.timeEnd);
writer.writeBool(offsets[11], object.roundDegree); writer.writeLong(offsets[11], object.timeRange);
writer.writeString(offsets[12], object.theme); writer.writeString(offsets[12], object.timeStart);
writer.writeString(offsets[13], object.timeEnd); writer.writeString(offsets[13], object.timeformat);
writer.writeLong(offsets[14], object.timeRange); writer.writeString(offsets[14], object.widgetBackgroundColor);
writer.writeString(offsets[15], object.timeStart); writer.writeString(offsets[15], object.widgetTextColor);
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(
@ -219,25 +193,21 @@ 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[3]); object.language = reader.readStringOrNull(offsets[2]);
object.largeElement = reader.readBool(offsets[4]); object.location = reader.readBool(offsets[3]);
object.location = reader.readBool(offsets[5]); object.materialColor = reader.readBool(offsets[4]);
object.materialColor = reader.readBool(offsets[6]); object.measurements = reader.readString(offsets[5]);
object.measurements = reader.readString(offsets[7]); object.notifications = reader.readBool(offsets[6]);
object.notifications = reader.readBool(offsets[8]); object.onboard = reader.readBool(offsets[7]);
object.onboard = reader.readBool(offsets[9]); object.roundDegree = reader.readBool(offsets[8]);
object.pressure = reader.readString(offsets[10]); object.theme = reader.readStringOrNull(offsets[9]);
object.roundDegree = reader.readBool(offsets[11]); object.timeEnd = reader.readStringOrNull(offsets[10]);
object.theme = reader.readStringOrNull(offsets[12]); object.timeRange = reader.readLongOrNull(offsets[11]);
object.timeEnd = reader.readStringOrNull(offsets[13]); object.timeStart = reader.readStringOrNull(offsets[12]);
object.timeRange = reader.readLongOrNull(offsets[14]); object.timeformat = reader.readString(offsets[13]);
object.timeStart = reader.readStringOrNull(offsets[15]); object.widgetBackgroundColor = reader.readStringOrNull(offsets[14]);
object.timeformat = reader.readString(offsets[16]); object.widgetTextColor = reader.readStringOrNull(offsets[15]);
object.widgetBackgroundColor = reader.readStringOrNull(offsets[17]);
object.widgetTextColor = reader.readStringOrNull(offsets[18]);
object.wind = reader.readString(offsets[19]);
return object; return object;
} }
@ -253,41 +223,33 @@ 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.readBool(offset)) as P;
case 3:
return (reader.readStringOrNull(offset)) as P; return (reader.readStringOrNull(offset)) as P;
case 3:
return (reader.readBool(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.readBool(offset)) as P; return (reader.readString(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.readString(offset)) as P; return (reader.readBool(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.readBool(offset)) as P; return (reader.readStringOrNull(offset)) as P;
case 10: case 10:
return (reader.readString(offset)) as P; return (reader.readStringOrNull(offset)) as P;
case 11: case 11:
return (reader.readBool(offset)) as P; return (reader.readLongOrNull(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.readStringOrNull(offset)) as P; return (reader.readString(offset)) as P;
case 14: case 14:
return (reader.readLongOrNull(offset)) as P; return (reader.readStringOrNull(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');
} }
@ -522,16 +484,6 @@ 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(
@ -730,16 +682,6 @@ 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) {
@ -914,136 +856,6 @@ 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) {
@ -2001,136 +1813,6 @@ 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
@ -2164,18 +1846,6 @@ 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);
@ -2188,18 +1858,6 @@ 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);
@ -2260,18 +1918,6 @@ 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);
@ -2368,18 +2014,6 @@ 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
@ -2408,18 +2042,6 @@ 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);
@ -2444,18 +2066,6 @@ 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);
@ -2516,18 +2126,6 @@ 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);
@ -2624,18 +2222,6 @@ 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
@ -2653,12 +2239,6 @@ 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) {
@ -2666,12 +2246,6 @@ 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');
@ -2703,13 +2277,6 @@ 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');
@ -2765,13 +2332,6 @@ 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
@ -2794,24 +2354,12 @@ 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');
@ -2842,12 +2390,6 @@ 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');
@ -2896,12 +2438,6 @@ 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
@ -3102,7 +2638,7 @@ const MainWeatherCacheSchema = CollectionSchema(
getId: _mainWeatherCacheGetId, getId: _mainWeatherCacheGetId,
getLinks: _mainWeatherCacheGetLinks, getLinks: _mainWeatherCacheGetLinks,
attach: _mainWeatherCacheAttach, attach: _mainWeatherCacheAttach,
version: '3.1.8', version: '3.1.0+1',
); );
int _mainWeatherCacheEstimateSize( int _mainWeatherCacheEstimateSize(
@ -10825,7 +10361,7 @@ const LocationCacheSchema = CollectionSchema(
getId: _locationCacheGetId, getId: _locationCacheGetId,
getLinks: _locationCacheGetLinks, getLinks: _locationCacheGetLinks,
attach: _locationCacheAttach, attach: _locationCacheAttach,
version: '3.1.8', version: '3.1.0+1',
); );
int _locationCacheEstimateSize( int _locationCacheEstimateSize(
@ -11924,7 +11460,7 @@ const WeatherCardSchema = CollectionSchema(
getId: _weatherCardGetId, getId: _weatherCardGetId,
getLinks: _weatherCardGetLinks, getLinks: _weatherCardGetLinks,
attach: _weatherCardAttach, attach: _weatherCardAttach,
version: '3.1.8', version: '3.1.0+1',
); );
int _weatherCardEstimateSize( int _weatherCardEstimateSize(

View file

@ -0,0 +1,187 @@
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,
),
),
],
),
),
),
),
);
}
}

View file

@ -0,0 +1,133 @@
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!,
),
),
),
),
],
),
),
);
}
}

View file

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

View file

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

View file

@ -0,0 +1,405 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.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/modules/home.dart';
import 'package:rain/app/widgets/button.dart';
import 'package:rain/app/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();
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(() {});
}
void fillControllerGeo(location) {
_controllerLat.text = '${location['lat']}';
_controllerLon.text = '${location['lon']}';
_controllerCity.text = location['district'];
_controllerDistrict.text = location['city'];
setState(() {});
}
@override
Widget build(BuildContext context) {
const kTextFieldElevation = 4.0;
return Form(
key: formKeySearch,
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
centerTitle: true,
leading: widget.isStart
? null
: IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(
Iconsax.arrow_left_1,
size: 20,
),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
automaticallyImplyLeading: false,
title: Text(
'searchCity'.tr,
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
),
body: SafeArea(
child: Stack(
children: [
Column(
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
children: [
Image.asset(
'assets/icons/Search.png',
scale: 7,
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 10),
child: Text(
'searchMethod'.tr,
style: context.theme.textTheme.bodyLarge
?.copyWith(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
Row(
children: [
Flexible(
child: 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,
),
),
);
},
),
),
),
);
},
),
),
Card(
elevation: kTextFieldElevation,
margin: const EdgeInsets.only(
top: 10, right: 10),
child: Container(
margin: const EdgeInsets.all(2.5),
child: IconButton(
onPressed: () async {
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;
}
setState(() => isLoading = true);
final location =
await weatherController
.getCurrentLocationSearch();
fillControllerGeo(location);
setState(() => isLoading = false);
},
icon: const Icon(
Iconsax.location,
),
),
),
),
],
),
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),
],
),
),
),
],
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
child: MyTextButton(
buttonName: 'done'.tr,
onPressed: () 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);
}
}
},
),
),
],
),
if (isLoading)
BackdropFilter(
filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
child: const Center(
child: CircularProgressIndicator(),
),
),
],
),
),
),
);
}
}

277
lib/app/modules/home.dart Normal file
View file

@ -0,0 +1,277 @@
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/geolocation.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.animation?.addListener(() {
int value = (tabController.animation!.value).round();
if (value != tabIndex) setState(() => tabIndex = value);
});
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: switch (tabIndex) {
0 => IconButton(
onPressed: () {
Get.to(() => const SelectGeolocation(isStart: false),
transition: Transition.downToUp);
},
icon: const Icon(
Iconsax.global_search,
size: 18,
),
),
int() => null,
},
title: switch (tabIndex) {
0 => 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,
),
);
},
),
1 => Text(
'cities'.tr,
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
2 => Text(
'settings_full'.tr,
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
int() => null,
},
actions: switch (tabIndex) {
0 => [
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,
),
)
],
int() => null,
},
),
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: 'cities'.tr,
),
NavigationDestination(
icon: const Icon(Iconsax.category),
selectedIcon: const Icon(Iconsax.category5),
label: 'settings'.tr,
),
],
),
floatingActionButton: tabIndex == 1
? FloatingActionButton(
onPressed: () => showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: false,
builder: (BuildContext context) =>
const CreateWeatherCard(),
),
child: const Icon(
Iconsax.add,
),
)
: null,
),
),
);
}
}

View file

@ -0,0 +1,171 @@
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,
),
)
],
);
}),
),
);
}
}

View file

@ -0,0 +1,183 @@
import 'package:rain/app/data/weather.dart';
import 'package:rain/app/modules/geolocation.dart';
import 'package:rain/app/widgets/button.dart';
import 'package:rain/main.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class OnBording extends StatefulWidget {
const OnBording({super.key});
@override
State<OnBording> createState() => _OnBordingState();
}
class _OnBordingState extends State<OnBording> {
late PageController pageController;
int pageIndex = 0;
@override
void initState() {
pageController = PageController(initialPage: 0);
super.initState();
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
void onBoardHome() {
settings.onboard = true;
isar.writeTxnSync(() => isar.settings.putSync(settings));
Get.off(() => const SelectGeolocation(isStart: true),
transition: Transition.downToUp);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
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,
),
),
),
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,
);
},
),
)
],
),
),
);
}
}
class DotIndicator extends StatelessWidget {
const DotIndicator({
super.key,
this.isActive = false,
});
final bool isActive;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 8,
width: 8,
decoration: BoxDecoration(
color: isActive
? context.theme.colorScheme.secondary
: context.theme.colorScheme.secondaryContainer,
shape: BoxShape.circle,
),
);
}
}
class Onboard {
final String image, title, description;
Onboard({
required this.image,
required this.title,
required this.description,
});
}
final List<Onboard> data = [
Onboard(
image: 'assets/icons/Rain.png',
title: 'name'.tr,
description: 'description'.tr),
Onboard(
image: 'assets/icons/Design.png',
title: 'name2'.tr,
description: 'description2'.tr),
Onboard(
image: 'assets/icons/Team.png',
title: 'name3'.tr,
description: 'description3'.tr),
];
class OnboardContent extends StatelessWidget {
const OnboardContent({
super.key,
required this.image,
required this.title,
required this.description,
});
final String image, title, description;
@override
Widget build(BuildContext context) {
return Column(
children: [
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
image,
scale: 5,
),
Text(
title,
style: context.textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.w600),
),
const SizedBox(height: 10),
SizedBox(
width: 300,
child: Text(
description,
style: context.textTheme.labelLarge?.copyWith(fontSize: 14),
textAlign: TextAlign.center,
),
),
],
),
),
],
);
}
}

View file

@ -0,0 +1,922 @@
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,
),
);
}
}

View file

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

View file

@ -0,0 +1,42 @@
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,
);
}
}

View file

@ -0,0 +1,20 @@
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,
),
);
}

View file

@ -1,479 +0,0 @@
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,
),
),
);
}
}

View file

@ -1,311 +0,0 @@
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,
),
),
);
}
}

View file

@ -1,242 +0,0 @@
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,
),
);
}
}

View file

@ -1,477 +0,0 @@
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,
);
}
}

View file

@ -1,198 +0,0 @@
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';
class OnBording extends StatefulWidget {
const OnBording({super.key});
@override
State<OnBording> createState() => _OnBordingState();
}
class _OnBordingState extends State<OnBording> {
late PageController pageController;
int pageIndex = 0;
@override
void initState() {
super.initState();
pageController = PageController(initialPage: 0);
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
void onBoardHome() {
settings.onboard = true;
isar.writeTxnSync(() => isar.settings.putSync(settings));
Get.off(
() => const SelectGeolocation(isStart: true),
transition: Transition.downToUp,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Column(
children: [
_buildPageView(),
_buildDotIndicators(),
_buildActionButton(),
],
),
),
);
}
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 {
const DotIndicator({super.key, this.isActive = false});
final bool isActive;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 8,
width: 8,
decoration: BoxDecoration(
color: isActive
? context.theme.colorScheme.secondary
: context.theme.colorScheme.secondaryContainer,
shape: BoxShape.circle,
),
);
}
}
class Onboard {
final String image, title, description;
Onboard({
required this.image,
required this.title,
required this.description,
});
}
final List<Onboard> data = [
Onboard(
image: 'assets/icons/Rain.png',
title: 'name'.tr,
description: 'description'.tr,
),
Onboard(
image: 'assets/icons/Design.png',
title: 'name2'.tr,
description: 'description2'.tr,
),
Onboard(
image: 'assets/icons/Team.png',
title: 'name3'.tr,
description: 'description3'.tr,
),
];
class OnboardContent extends StatelessWidget {
const OnboardContent({
super.key,
required this.image,
required this.title,
required this.description,
});
final String image, title, description;
@override
Widget build(BuildContext context) {
return Column(
children: [
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(image, scale: 5),
Text(
title,
style: context.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
const Gap(10),
SizedBox(
width: 300,
child: Text(
description,
style: context.textTheme.labelLarge?.copyWith(fontSize: 14),
textAlign: TextAlign.center,
),
),
],
),
),
],
);
}
}

View file

@ -1,212 +0,0 @@
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,
),
);
}
}

View file

@ -1,116 +0,0 @@
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(() {});
}
}

View file

@ -1,346 +0,0 @@
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();
}
}
}

View file

@ -1,155 +0,0 @@
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,
);
}
}

View file

@ -1,155 +0,0 @@
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!,
),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,107 +0,0 @@
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!;
}
}
}

View file

@ -1,37 +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,
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),
),
);
}
}

View file

@ -1,99 +0,0 @@
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,
);
}
}

View file

@ -1,348 +0,0 @@
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,
);
}
}

View file

@ -1,84 +0,0 @@
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,
),
);
}
}

View file

@ -1,206 +0,0 @@
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,
),
),
);
}
}

View file

@ -1,263 +0,0 @@
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;
}
}

View file

@ -1,65 +0,0 @@
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;
}
}
}

View file

@ -1,77 +0,0 @@
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),
);
}
}

View file

@ -1,182 +0,0 @@
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,
),
],
);
}
}

View file

@ -1,127 +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(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);
}
}
}

View file

@ -1,385 +0,0 @@
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'},
};
}

View file

@ -1,85 +0,0 @@
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)),
],
),
);
}
}

View file

@ -1,26 +0,0 @@
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')}';
}
}

View file

@ -1,31 +0,0 @@
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;
}
}

View file

@ -1,58 +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 {
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);
}
}

View file

@ -1,17 +0,0 @@
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,
),
);
}

View file

@ -0,0 +1,27 @@
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,
),
),
);
}
}

View file

@ -0,0 +1,250 @@
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,
),
],
),
),
),
],
),
);
},
),
),
);
}
}

View file

@ -0,0 +1,80 @@
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,
),
],
),
),
);
}
}

View file

@ -0,0 +1,146 @@
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,
),
),
),
],
),
),
);
}
}

View file

@ -0,0 +1,71 @@
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],
),
),
),
),
);
}
}

View file

@ -1,5 +1,4 @@
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 {
@ -10,7 +9,6 @@ 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;
@ -27,42 +25,36 @@ 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: _toggleDescriptionVisibility, onTap: () => setState(() => hide = !hide),
child: Tooltip( child: Tooltip(
message: widget.message, message: widget.message,
child: SizedBox( child: SizedBox(
height: 90, height: 90,
width: 100, width: 100,
child: _buildContent(textTheme), child: Column(
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,
),
),
],
);
}
} }

View file

@ -0,0 +1,189 @@
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,
),
],
),
),
);
}
}

View file

@ -0,0 +1,59 @@
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 '';
}
}
}

View file

@ -0,0 +1,71 @@
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,
),
),
],
);
}
}

View file

@ -0,0 +1,65 @@
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,
),
),
],
);
}
}

View file

@ -3,21 +3,25 @@ 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({super.key, required this.height, this.margin}); const MyShimmer({
super.key,
final double height; required this.hight,
final EdgeInsets? margin; this.edgeInsetsMargin,
});
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: _buildShimmerCard(), child: Card(
margin: edgeInsetsMargin,
child: SizedBox(
height: hight,
),
),
); );
} }
Widget _buildShimmerCard() {
return Card(margin: margin, child: SizedBox(height: height));
}
} }

View file

@ -0,0 +1,76 @@
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);
}
}
}

View file

@ -0,0 +1,340 @@
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 '';
}
}
}

View file

@ -0,0 +1,104 @@
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,
),
),
],
),
),
],
),
),
);
}
}

View file

@ -13,9 +13,7 @@ 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;
@ -25,35 +23,24 @@ 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: _buildTextFormField(context), child: TextFormField(
); focusNode: focusNode,
} controller: controller,
keyboardType: type,
Widget _buildTextFormField(BuildContext context) { style: context.textTheme.labelLarge,
return TextFormField( decoration: InputDecoration(
focusNode: focusNode, prefixIcon: icon,
controller: controller, suffixIcon: iconButton,
keyboardType: type, labelText: labelText,
style: context.textTheme.labelLarge, ),
decoration: _buildInputDecoration(), validator: validator,
validator: validator, ),
onChanged: onChanged,
);
}
InputDecoration _buildInputDecoration() {
return InputDecoration(
contentPadding: const EdgeInsets.symmetric(horizontal: 12.5, vertical: 0),
prefixIcon: icon,
suffixIcon: iconButton,
labelText: labelText,
); );
} }
} }

412
lib/main.dart Executable file → Normal file
View file

@ -10,36 +10,32 @@ 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/data/db.dart'; import 'package:rain/app/modules/geolocation.dart';
import 'package:rain/app/ui/geolocation.dart'; import 'package:rain/app/modules/home.dart';
import 'package:rain/app/ui/home.dart'; import 'package:rain/app/modules/onboarding.dart';
import 'package:rain/app/ui/onboarding.dart';
import 'package:rain/theme/theme.dart'; import 'package:rain/theme/theme.dart';
import 'package:rain/theme/theme_controller.dart'; import 'package:time_machine/time_machine.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;
late LocationCache locationCache; late LocationCache locationCache;
final ValueNotifier<Future<bool>> isOnline = ValueNotifier( bool isOnline = false;
InternetConnection().hasInternetAccess,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = 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';
@ -47,36 +43,32 @@ 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) {
@ -85,40 +77,55 @@ void callbackDispatcher() {
} }
void main() async { void main() async {
final String timeZoneName;
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await initializeApp(); Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
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> initializeApp() async { Future<void> setOptimalDisplayMode() async {
setupConnectivityListener(); final List<DisplayMode> supported = await FlutterDisplayMode.supported;
await initializeTimeZone(); final DisplayMode active = await FlutterDisplayMode.active;
await initializeIsar(); final List<DisplayMode> sameResolution = supported
await initializeNotifications(); .where((DisplayMode m) =>
if (Platform.isAndroid) { m.width == active.width && m.height == active.height)
await setOptimalDisplayMode(); .toList()
Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode); ..sort((DisplayMode a, DisplayMode b) =>
HomeWidget.setAppGroupId(appGroupId); b.refreshRate.compareTo(a.refreshRate));
} final DisplayMode mostOptimalMode =
DeviceFeature().init(); sameResolution.isNotEmpty ? sameResolution.first : active;
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
} }
void setupConnectivityListener() { Future<void> isarInit() async {
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,
@ -140,28 +147,6 @@ Future<void> initializeIsar() 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});
@ -170,7 +155,6 @@ 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,
@ -180,14 +164,27 @@ class MyApp extends StatefulWidget {
}) async { }) async {
final state = context.findAncestorStateOfType<_MyAppState>()!; final state = context.findAncestorStateOfType<_MyAppState>()!;
if (newAmoledTheme != null) state.changeAmoledTheme(newAmoledTheme); if (newAmoledTheme != null) {
if (newMaterialColor != null) state.changeMarerialTheme(newMaterialColor); state.changeAmoledTheme(newAmoledTheme);
if (newRoundDegree != null) state.changeRoundDegree(newRoundDegree); }
if (newLargeElement != null) state.changeLargeElement(newLargeElement); if (newMaterialColor != null) {
if (newLocale != null) state.changeLocale(newLocale); state.changeMarerialTheme(newMaterialColor);
if (newTimeRange != null) state.changeTimeRange(newTimeRange); }
if (newTimeStart != null) state.changeTimeStart(newTimeStart); if (newRoundDegree != null) {
if (newTimeEnd != null) state.changeTimeEnd(newTimeEnd); state.changeRoundDegree(newRoundDegree);
}
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);
} }
@ -203,136 +200,127 @@ 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(() => amoledTheme = newAmoledTheme); setState(() {
void changeMarerialTheme(bool newMaterialColor) => amoledTheme = newAmoledTheme;
setState(() => materialColor = newMaterialColor); });
void changeRoundDegree(bool newRoundDegree) => }
setState(() => roundDegree = newRoundDegree);
void changeLargeElement(bool newLargeElement) => void changeMarerialTheme(bool newMaterialColor) {
setState(() => largeElement = newLargeElement); setState(() {
void changeTimeRange(int newTimeRange) => materialColor = newMaterialColor;
setState(() => timeRange = newTimeRange); });
void changeTimeStart(String newTimeStart) => }
setState(() => timeStart = newTimeStart);
void changeTimeEnd(String newTimeEnd) => setState(() => timeEnd = newTimeEnd); void changeRoundDegree(bool newRoundDegree) {
void changeLocale(Locale newLocale) => setState(() => locale = newLocale); setState(() {
void changeWidgetBackgroundColor(String newWidgetBackgroundColor) => roundDegree = newRoundDegree;
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(0, 2), settings.language!.substring(3));
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) {
final edgeToEdgeAvailable = DeviceFeature().isEdgeToEdgeAvailable(); return DynamicColorBuilder(
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); builder: (lightColorScheme, darkColorScheme) {
final lightMaterialTheme =
lightTheme(lightColorScheme?.surface, lightColorScheme);
final darkMaterialTheme =
darkTheme(darkColorScheme?.surface, darkColorScheme);
final darkMaterialThemeOled = darkTheme(oledColor, darkColorScheme);
return GestureDetector( return GetMaterialApp(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(), themeMode: themeController.theme,
child: DynamicColorBuilder( theme: materialColor
builder: (lightColorScheme, darkColorScheme) { ? lightColorScheme != null
final lightMaterialTheme = lightTheme( ? lightMaterialTheme
lightColorScheme?.surface, : lightTheme(lightColor, colorSchemeLight)
lightColorScheme, : lightTheme(lightColor, colorSchemeLight),
edgeToEdgeAvailable, darkTheme: amoledTheme
); ? materialColor
final darkMaterialTheme = darkTheme( ? darkColorScheme != null
darkColorScheme?.surface, ? darkMaterialThemeOled
darkColorScheme, : darkTheme(oledColor, colorSchemeDark)
edgeToEdgeAvailable, : darkTheme(oledColor, colorSchemeDark)
); : materialColor
final darkMaterialThemeOled = darkTheme( ? darkColorScheme != null
oledColor, ? darkMaterialTheme
darkColorScheme, : darkTheme(darkColor, colorSchemeDark)
edgeToEdgeAvailable, : darkTheme(darkColor, colorSchemeDark),
); localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
return GetMaterialApp( GlobalWidgetsLocalizations.delegate,
themeMode: themeController.theme, GlobalCupertinoLocalizations.delegate,
theme: ],
materialColor translations: Translation(),
? lightColorScheme != null locale: locale,
? lightMaterialTheme fallbackLocale: const Locale('en', 'US'),
: lightTheme( supportedLocales:
lightColor, appLanguages.map((e) => e['locale'] as Locale).toList(),
colorSchemeLight, debugShowCheckedModeBanner: false,
edgeToEdgeAvailable, home: settings.onboard
) ? (locationCache.city == null) ||
: lightTheme( (locationCache.district == null) ||
lightColor, (locationCache.lat == null) ||
colorSchemeLight, (locationCache.lon == null)
edgeToEdgeAvailable, ? const SelectGeolocation(isStart: true)
), : const HomePage()
darkTheme: : const OnBording(),
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',
);
},
),
); );
} }
} }

219
lib/theme/theme.dart Executable file → Normal file
View file

@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
final ThemeData baseLight = ThemeData.light(useMaterial3: true); final ThemeData baseLigth = 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;
@ -14,146 +13,108 @@ 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( ThemeData lightTheme(Color? color, ColorScheme? colorScheme) {
Color? color, return baseLigth.copyWith(
ColorScheme? colorScheme,
bool edgeToEdgeAvailable,
) {
return _buildTheme(
baseTheme: baseLight,
brightness: Brightness.light, brightness: Brightness.light,
color: color, colorScheme: colorScheme
colorScheme: colorScheme, ?.copyWith(
edgeToEdgeAvailable: edgeToEdgeAvailable, brightness: Brightness.light,
); background: color,
} surface: baseLigth.colorScheme.background,
)
ThemeData darkTheme( .harmonized(),
Color? color, textTheme: GoogleFonts.ubuntuTextTheme(baseLigth.textTheme),
ColorScheme? colorScheme, appBarTheme: AppBarTheme(
bool edgeToEdgeAvailable, backgroundColor: color,
) { foregroundColor: baseLigth.colorScheme.onSurface,
return _buildTheme( shadowColor: Colors.transparent,
baseTheme: baseDark, surfaceTintColor: Colors.transparent,
brightness: Brightness.dark, elevation: 0,
color: color,
colorScheme: colorScheme,
edgeToEdgeAvailable: edgeToEdgeAvailable,
);
}
ThemeData _buildTheme({
required ThemeData baseTheme,
required Brightness brightness,
required Color? color,
required ColorScheme? colorScheme,
required bool edgeToEdgeAvailable,
}) {
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: _buildCardTheme(color, harmonizedColorScheme), cardTheme: baseLigth.cardTheme.copyWith(
bottomSheetTheme: _buildBottomSheetTheme(color, harmonizedColorScheme), color: color,
navigationRailTheme: baseTheme.navigationRailTheme.copyWith( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
shadowColor: Colors.transparent,
),
bottomSheetTheme: baseLigth.bottomSheetTheme.copyWith(
backgroundColor: color, backgroundColor: color,
), ),
navigationBarTheme: _buildNavigationBarTheme(color, harmonizedColorScheme), navigationRailTheme: baseLigth.navigationRailTheme.copyWith(
inputDecorationTheme: _buildInputDecorationTheme(), 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,
); );
} }
AppBarTheme _buildAppBarTheme( ThemeData darkTheme(Color? color, ColorScheme? colorScheme) {
Color? color, return baseDark.copyWith(
Color? onSurfaceColor, brightness: Brightness.dark,
bool edgeToEdgeAvailable, colorScheme: colorScheme
Brightness brightness, ?.copyWith(
ColorScheme? colorScheme, brightness: Brightness.dark,
) { background: color,
return AppBarTheme( surface: baseDark.colorScheme.background,
backgroundColor: color, )
foregroundColor: onSurfaceColor, .harmonized(),
shadowColor: Colors.transparent, textTheme: GoogleFonts.ubuntuTextTheme(baseDark.textTheme),
surfaceTintColor: Colors.transparent, appBarTheme: AppBarTheme(
elevation: 0, backgroundColor: color,
systemOverlayStyle: SystemUiOverlayStyle( foregroundColor: baseDark.colorScheme.onSurface,
statusBarIconBrightness: shadowColor: Colors.transparent,
brightness == Brightness.light ? Brightness.dark : Brightness.light, surfaceTintColor: Colors.transparent,
statusBarColor: Colors.transparent, elevation: 0,
systemStatusBarContrastEnforced: false, ),
systemNavigationBarContrastEnforced: false, primaryColor: color,
systemNavigationBarDividerColor: Colors.transparent, canvasColor: color,
systemNavigationBarIconBrightness: scaffoldBackgroundColor: color,
brightness == Brightness.light ? Brightness.dark : Brightness.light, cardTheme: baseDark.cardTheme.copyWith(
systemNavigationBarColor: color: color,
edgeToEdgeAvailable ? Colors.transparent : colorScheme?.surface, shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
shadowColor: Colors.transparent,
),
bottomSheetTheme: baseDark.bottomSheetTheme.copyWith(
backgroundColor: color,
),
navigationRailTheme: baseDark.navigationRailTheme.copyWith(
backgroundColor: color,
),
navigationBarTheme: baseDark.navigationBarTheme.copyWith(
backgroundColor: color,
),
inputDecorationTheme: baseDark.inputDecorationTheme.copyWith(
labelStyle: MaterialStateTextStyle.resolveWith(
(Set<MaterialState> states) {
return const TextStyle(fontSize: 14);
},
),
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
), ),
); );
} }
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 Executable file → Normal file
View file

@ -1,40 +1,31 @@
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/db.dart'; import 'package:rain/app/data/weather.dart';
import 'package:rain/main.dart'; import 'package:rain/main.dart';
class ThemeController extends GetxController { class ThemeController extends GetxController {
ThemeMode get theme => _getThemeMode(); ThemeMode get theme => settings.theme == 'system'
? ThemeMode.system
: settings.theme == 'dark'
? ThemeMode.dark
: ThemeMode.light;
void saveOledTheme(bool isOled) { void saveOledTheme(bool isOled) {
_updateSetting((settings) => settings.amoledTheme = isOled); settings.amoledTheme = isOled;
isar.writeTxnSync(() => isar.settings.putSync(settings));
} }
void saveMaterialTheme(bool isMaterial) { void saveMaterialTheme(bool isMaterial) {
_updateSetting((settings) => settings.materialColor = isMaterial); settings.materialColor = isMaterial;
isar.writeTxnSync(() => isar.settings.putSync(settings));
} }
void saveTheme(String themeMode) { void saveTheme(String themeMode) {
_updateSetting((settings) => settings.theme = themeMode); 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));
}
} }

263
lib/translation/bn_in.dart Executable file → Normal file
View file

@ -1,141 +1,128 @@
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': 'মাইল/ঘণ্টা',
'm/s': 'মি/সে', 'mi': 'মাইল',
'mmHg': 'মিমি Hg', 'km': 'কিমি',
'mi': 'মাইল', 'inch': 'ইঞ্চ',
'km': 'কিমি', 'mm': 'মিমি',
'inch': 'ইঞ্চ', 'hPa': 'হেক্টোপাস্কল',
'mm': 'মিমি', 'settings': 'সেটিংস',
'hPa': 'হেক্টোপাস্কল', 'no_inter': 'ইন্টারনেট নেই',
'settings': 'সেটিংস', 'on_inter': 'মেটিয়োরোলজিক তথ্য পেতে ইন্টারনেট চালু করুন।',
'no_inter': 'ইন্টারনেট নেই', 'location': 'অবস্থান',
'on_inter': 'মেটিয়োরোলজিক তথ্য পেতে ইন্টারনেট চালু করুন।', 'no_location':
'location': 'অবস্থান', 'বর্তমান অবস্থানের জন্য আবহাওয়া ডেটা পেতে অবস্থান সেবা সক্রিয় করুন।',
'no_location': 'theme': 'থিম',
'বর্তমান অবস্থানের জন্য আবহাওয়া ডেটা পেতে অবস্থান সেবা সক্রিয় করুন।', 'low': 'নিম্ন',
'theme': 'থিম', 'high': 'উচ্চ',
'low': 'নিম্ন', 'normal': 'সাধারণ',
'high': 'উচ্চ', 'lat': 'অক্ষাংশ',
'normal': 'সাধারণ', 'lon': 'দ্রাঘিমাংশ',
'lat': 'অক্ষাংশ', 'create': 'তৈরি করুন',
'lon': 'দ্রাঘিমাংশ', 'city': 'শহর',
'create': 'তৈরি করুন', 'district': 'জেলা',
'city': 'শহর', 'noWeatherCard': 'একটি শহর যোগ করুন',
'district': 'জেলা', 'deletedCardWeather': 'একটি শহর মুছে ফেলা হচ্ছে',
'noWeatherCard': 'একটি শহর যোগ করুন', 'deletedCardWeatherQuery': 'আপনি কি নিশ্চিত যে আপনি শহরটি মুছতে চান?',
'deletedCardWeather': 'একটি শহর মুছে ফেলা হচ্ছে', 'delete': 'মুছে ফেলুন',
'deletedCardWeatherQuery': 'আপনি কি নিশ্চিত যে আপনি শহরটি মুছতে চান?', 'cancel': 'বাতিল করুন',
'delete': 'মুছে ফেলুন', 'time': 'শহরে সময়',
'cancel': 'বাতিল করুন', 'validateName': 'দয়া করে নাম লিখুন',
'time': 'শহরে সময়', 'measurements': 'মাপনের সিস্টেম',
'validateName': 'দয়া করে নাম লিখুন', 'degrees': 'ডিগ্রি',
'measurements': 'মাপনের সিস্টেম', 'celsius': 'সেলসিয়াস',
'degrees': 'ডিগ্রি', 'fahrenheit': 'ফারেনহাইট',
'celsius': 'সেলসিয়াস', 'imperial': 'ইমপেরিয়াল',
'fahrenheit': 'ফারেনহাইট', 'metric': 'মেট্রিক',
'imperial': 'ইমপেরিয়াল', 'validateValue': 'দয়া করে একটি মান লিখুন',
'metric': 'মেট্রিক', 'validateNumber': 'দয়া করে একটি বৈধ সংখ্যা লিখুন',
'validateValue': 'দয়া করে একটি মান লিখুন', 'validate90': 'মান -৯০ থেকে ৯০ মধ্যে হতে হবে',
'validateNumber': 'দয়া করে একটি বৈধ সংখ্যা লিখুন', 'validate180': 'মান -১৮০ থেকে ১৮০ মধ্যে হতে হবে',
'validate90': 'মান -৯০ থেকে ৯০ মধ্যে হতে হবে', 'notifications': 'বিজ্ঞপ্তি',
'validate180': 'মান -১৮০ থেকে ১৮০ মধ্যে হতে হবে', 'sunrise': 'সূর্যোদয়',
'notifications': 'বিজ্ঞপ্তি', 'sunset': 'সূর্যাস্ত',
'sunrise': 'সূর্যোদয়', 'timeformat': 'সময় বিন্যাস',
'sunset': 'সূর্যাস্ত', '12': '১২-ঘণ্টা',
'timeformat': 'সময় বিন্যাস', '24': '২৪-ঘণ্টা',
'12': '১২-ঘণ্টা', 'cloudcover': 'মেঘপর্দা',
'24': '২৪-ঘণ্টা', 'uvIndex': 'আল্ট্রাভায়োলেট-সূচী',
'cloudcover': 'মেঘপর্দা', 'materialColor': 'গতিবিধির রঙ',
'uvIndex': 'আল্ট্রাভায়োলেট-সূচী', 'uvLow': 'নিম্ন',
'materialColor': 'গতিবিধির রঙ', 'uvAverage': 'মধ্যম',
'uvLow': 'নিম্ন', 'uvHigh': 'উচ্চ',
'uvAverage': 'মধ্যম', 'uvVeryHigh': 'অত্যন্ত উচ্চ',
'uvHigh': 'উচ্চ', 'uvExtreme': 'একাধিক',
'uvVeryHigh': 'অত্যন্ত উচ্চ', 'weatherMore': '১২-দিনের আবহাওয়া পূর্বানুমান',
'uvExtreme': 'একাধিক', 'windgusts': 'ঝংকার',
'weatherMore': '১২-দিনের আবহাওয়া পূর্বানুমান', 'north': 'উত্তর',
'windgusts': 'ঝংকার', 'northeast': 'উত্তরপূর্ব',
'north': 'উত্তর', 'east': 'পূর্ব',
'northeast': 'উত্তরপূর্ব', 'southeast': 'দক্ষিণপূর্ব',
'east': 'পূর্ব', 'south': 'দক্ষিণ',
'southeast': 'দক্ষিণপূর্ব', 'southwest': 'দক্ষিণপশ্চিম',
'south': 'দক্ষিণ', 'west': 'পশ্চিম',
'southwest': 'দক্ষিণপশ্চিম', 'northwest': 'উত্তরপশ্চিম',
'west': 'পশ্চিম', 'project': 'প্রকল্প',
'northwest': 'উত্তরপশ্চিম', 'version': 'অ্যাপ্লিকেশন সংস্করণ',
'project': 'প্রকল্প', 'precipitationProbability': 'বৃষ্টিপাতের সম্ভাবনা',
'version': 'অ্যাপ্লিকেশন সংস্করণ', 'apparentTemperatureMin':
'precipitationProbability': 'বৃষ্টিপাতের সম্ভাবনা', 'ন্যায্য ন্যায্য তাপমাত্রা ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য',
'apparentTemperatureMin': 'apparentTemperatureMax': 'সর্বাধিক ন্যায্য তাপমাত্রা',
'ন্যায্য ন্যায্য তাপমাত্রা ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য ন্যায্য', 'amoledTheme': 'এমোলেড-থিম',
'apparentTemperatureMax': 'সর্বাধিক ন্যায্য তাপমাত্রা', 'appearance': 'উপস্থিতি',
'amoledTheme': 'এমোলেড-থিম', 'functions': 'কার্য',
'appearance': 'উপস্থিতি', 'data': 'ডেটা',
'functions': 'কার্য', 'language': 'ভাষা',
'data': 'ডেটা', 'timeRange': 'সময় পরিস্থিতি (ঘণ্টায়)',
'language': 'ভাষা', 'timeStart': 'শুরুর সময়',
'timeRange': 'সময় পরিস্থিতি (ঘণ্টায়)', 'timeEnd': 'শেষ সময়',
'timeStart': 'শুরুর সময়', 'support': 'সাহায্য',
'timeEnd': 'শেষ সময়', 'system': 'সিস্টেম',
'support': 'সাহায্য', 'dark': 'ডার্ক',
'system': 'সিস্টেম', 'light': 'আলো',
'dark': 'ডার্ক', 'license': 'লাইসেন্স',
'light': 'আলো', 'widget': 'উইজেট',
'license': 'লাইসেন্স', 'widgetBackground': 'উইজেট পেশা',
'widget': 'উইজেট', 'widgetText': 'উইজেট টেক্সট',
'widgetBackground': 'উইজেট পেশা', 'dewpoint': 'তুষার বিন্দু',
'widgetText': 'উইজেট টেক্সট', 'shortwaveRadiation': 'সংক্ষেপণ তরঙ্গ প্রকৃতি',
'dewpoint': 'তুষার বিন্দু', 'W/m2': 'ডব্লিউ/মিটার বর্গ',
'shortwaveRadiation': 'সংক্ষেপণ তরঙ্গ প্রকৃতি', 'roundDegree': 'ডিগ্রি রাউন্ড করুন',
'W/m2': 'ডব্লিউ/মিটার বর্গ', 'settings_full': 'সেটিংস',
'roundDegree': 'ডিগ্রি রাউন্ড করুন', 'cities': 'শহর',
'settings_full': 'সেটিংস', };
'cities': 'শহর',
'groups': 'আমাদের দলগুলি',
'openMeteo': 'Open-Meteo থেকে ডেটা (CC-BY 4.0)',
'hourlyVariables': 'ঘণ্টায় আবহাওয়ার পরিবর্তনশীল',
'dailyVariables': 'দৈনিক আবহাওয়ার পরিবর্তনশীল',
'largeElement': 'বড় আবহাওয়া ডিসপ্লে',
'map': 'মানচিত্র',
'clearCacheStore': 'ক্যাশ পরিষ্কার করুন',
'deletedCacheStore': 'ক্যাশ পরিষ্কার করা হচ্ছে',
'deletedCacheStoreQuery': 'আপনি কি সত্যিই ক্যাশ পরিষ্কার করতে চান?',
'addWidget': 'উইজেট যোগ করুন',
'hideMap': 'মানচিত্র লুকান',
};
} }

263
lib/translation/cs_cz.dart Executable file → Normal file
View file

@ -1,141 +1,128 @@
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',
'm/s': 'm/s', 'mi': 'mi',
'mmHg': 'mmHg', 'km': 'km',
'mi': 'mi', 'inch': 'inch',
'km': 'km', 'mm': 'mm',
'inch': 'inch', 'hPa': 'hPa',
'mm': 'mm', 'settings': 'Nast.',
'hPa': 'hPa', 'no_inter': 'Žádný internet',
'settings': 'Nast.', 'on_inter': 'Připojte se k internetu a získejte meteorologické údaje.',
'no_inter': 'Žádný internet', 'location': 'Poloha',
'on_inter': 'Připojte se k internetu a získejte meteorologické údaje.', 'no_location':
'location': 'Poloha', 'Chcete-li získat údaje o počasí pro aktuální polohu, povolte službu určování polohy.',
'no_location': 'theme': 'Téma',
'Chcete-li získat údaje o počasí pro aktuální polohu, povolte službu určování polohy.', 'low': 'Nízký',
'theme': 'Téma', 'high': 'Vysoký',
'low': 'Nízký', 'normal': 'Normální',
'high': 'Vysoký', 'lat': 'Zeměpisná šířka',
'normal': 'Normální', 'lon': 'Zemepisná délka',
'lat': 'Zeměpisná šířka', 'create': 'Vytvořit',
'lon': 'Zemepisná délka', 'city': 'Místo',
'create': 'Vytvořit', 'district': 'Okres',
'city': 'Místo', 'noWeatherCard': 'Přidat město',
'district': 'Okres', 'deletedCardWeather': 'Vymazat město',
'noWeatherCard': 'Přidat město', 'deletedCardWeatherQuery': 'Opravdu chcete odstranit město?',
'deletedCardWeather': 'Vymazat město', 'delete': 'Odstranit',
'deletedCardWeatherQuery': 'Opravdu chcete odstranit město?', 'cancel': 'Zrušit',
'delete': 'Odstranit', 'time': 'Čas ve městě',
'cancel': 'Zrušit', 'validateName': 'Prosím zadejte název',
'time': 'Čas ve městě', 'measurements': 'Jednotky měření',
'validateName': 'Prosím zadejte název', 'degrees': 'Stupně',
'measurements': 'Jednotky měření', 'celsius': 'Celzius',
'degrees': 'Stupně', 'fahrenheit': 'Fahrenheit',
'celsius': 'Celzius', 'imperial': 'Imperiální',
'fahrenheit': 'Fahrenheit', 'metric': 'Metrické',
'imperial': 'Imperiální', 'validateValue': 'Zadejte hodnotu',
'metric': 'Metrické', 'validateNumber': 'Zadejte platné číslo',
'validateValue': 'Zadejte hodnotu', 'validate90': 'Hodnota musí být mezi -90 a 90',
'validateNumber': 'Zadejte platné číslo', 'validate180': 'Hodnota musí být mezi -180 a 180',
'validate90': 'Hodnota musí být mezi -90 a 90', 'notifications': 'Notifikace',
'validate180': 'Hodnota musí být mezi -180 a 180', 'sunrise': 'Východ slunce',
'notifications': 'Notifikace', 'sunset': 'Západ slunce',
'sunrise': 'Východ slunce', 'timeformat': 'Formát času',
'sunset': 'Západ slunce', '12': '12-hodinový',
'timeformat': 'Formát času', '24': '24-hodinový',
'12': '12-hodinový', 'cloudcover': 'Oblačnost',
'24': '24-hodinový', 'uvIndex': 'UV-index',
'cloudcover': 'Oblačnost', 'materialColor': 'Dynamické Barvy',
'uvIndex': 'UV-index', 'uvLow': 'Nízký',
'materialColor': 'Dynamické Barvy', 'uvAverage': 'Mírný',
'uvLow': 'Nízký', 'uvHigh': 'Vysoký',
'uvAverage': 'Mírný', 'uvVeryHigh': 'Velmi vysoký',
'uvHigh': 'Vysoký', 'uvExtreme': 'Extrémní',
'uvVeryHigh': 'Velmi vysoký', 'weatherMore': 'Předpověď počasí na 12 dní',
'uvExtreme': 'Extrémní', 'windgusts': 'Nárazy větru',
'weatherMore': 'Předpověď počasí na 12 dní', 'north': 'Sever',
'windgusts': 'Nárazy větru', 'northeast': 'Severo-Východ',
'north': 'Sever', 'east': 'Východ',
'northeast': 'Severo-Východ', 'southeast': 'Juhovýchod',
'east': 'Východ', 'south': 'Juž',
'southeast': 'Juhovýchod', 'southwest': 'Juhozápad',
'south': 'Juž', 'west': 'Západ',
'southwest': 'Juhozápad', 'northwest': 'Severo-Západ',
'west': 'Západ', 'project': 'Projekt na',
'northwest': 'Severo-Západ', 'version': 'Verzia aplikace',
'project': 'Projekt na', 'precipitationProbability': 'Pravděpodobnost srážek',
'version': 'Verzia aplikace', 'apparentTemperatureMin': 'Minimální pocitová teplota',
'precipitationProbability': 'Pravděpodobnost srážek', 'apparentTemperatureMax': 'Maximální pocitová teplota',
'apparentTemperatureMin': 'Minimální pocitová teplota', 'amoledTheme': 'AMOLED-téma',
'apparentTemperatureMax': 'Maximální pocitová teplota', 'appearance': 'Vzhled',
'amoledTheme': 'AMOLED-téma', 'functions': 'Funkce',
'appearance': 'Vzhled', 'data': 'Data',
'functions': 'Funkce', 'language': 'Jazyk',
'data': 'Data', 'timeRange': 'Frekvence (v hodinách)',
'language': 'Jazyk', 'timeStart': 'Čas začátku',
'timeRange': 'Frekvence (v hodinách)', 'timeEnd': 'Čas ukončení',
'timeStart': 'Čas začátku', 'support': 'Podpora',
'timeEnd': 'Čas ukončení', 'system': 'Systém',
'support': 'Podpora', 'dark': 'Tmavá',
'system': 'Systém', 'light': 'Světlá',
'dark': 'Tmavá', 'license': 'Licence',
'light': 'Světlá', 'widget': 'Widget',
'license': 'Licence', 'widgetBackground': 'Pozadí widgetu',
'widget': 'Widget', 'widgetText': 'Text widgetu',
'widgetBackground': 'Pozadí widgetu', 'dewpoint': 'Rosný bod',
'widgetText': 'Text widgetu', 'shortwaveRadiation': 'Krátká vlnová radiace',
'dewpoint': 'Rosný bod', 'roundDegree': 'Zaokrouhlit stupně',
'shortwaveRadiation': 'Krátká vlnová radiace', 'settings_full': 'Nastavení',
'roundDegree': 'Zaokrouhlit stupně', 'cities': 'Města',
'settings_full': 'Nastavení', 'searchMethod': 'Použijte hledání nebo geolokaci',
'cities': 'Města', 'done': 'Hotovo',
'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',
};
} }

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