├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── ic_launcher-playstore.png
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── values
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── arrays.xml
│ │ │ │ ├── preloaded_fonts.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── themes.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── font_certs.xml
│ │ │ ├── drawable
│ │ │ │ ├── indicator_dot_style.xml
│ │ │ │ ├── current_hourly_weather_background.xml
│ │ │ │ ├── hourly_weather_box_background.xml
│ │ │ │ ├── tomorrow_weather_box_background.xml
│ │ │ │ ├── search_location_input_style.xml
│ │ │ │ ├── rounded_blurry_box.xml
│ │ │ │ ├── home_background.xml
│ │ │ │ ├── icon_music.xml
│ │ │ │ ├── icon_add.xml
│ │ │ │ ├── icon_menu.xml
│ │ │ │ ├── icon_right_arrow.xml
│ │ │ │ ├── icon_back.xml
│ │ │ │ ├── icon_search.xml
│ │ │ │ ├── icon_dropdown.xml
│ │ │ │ ├── icon_humidity.xml
│ │ │ │ ├── icon_weather_sun.xml
│ │ │ │ ├── icon_weather_cloud.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ ├── icon_weather_cloud_fog.xml
│ │ │ │ ├── icon_weather_rain_cloud.xml
│ │ │ │ ├── icon_weather_sun_cloud.xml
│ │ │ │ ├── icon_wind.xml
│ │ │ │ ├── icon_sunrise.xml
│ │ │ │ ├── icon_sunset.xml
│ │ │ │ ├── icon_weather_sun_cloud_rain.xml
│ │ │ │ ├── icon_weather_sun_rain_cloud.xml
│ │ │ │ ├── icon_pressure.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── icon_weather_thunderstorm_cloud.xml
│ │ │ │ └── icon_weather_snow_cloud.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── menu
│ │ │ │ ├── atmospheric_pressure_unit_menu.xml
│ │ │ │ ├── temperature_unit_menu.xml
│ │ │ │ └── windsped_unit_menu.xml
│ │ │ ├── font
│ │ │ │ ├── inter.xml
│ │ │ │ ├── inter_bold.xml
│ │ │ │ ├── inter_light.xml
│ │ │ │ ├── inter_medium.xml
│ │ │ │ └── inter_semibold.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── layout_hourly_weather_item.xml
│ │ │ │ ├── layout_next_weather_item.xml
│ │ │ │ ├── cities_item_layout.xml
│ │ │ │ ├── fragment_search.xml
│ │ │ │ └── fragment_next_7_days.xml
│ │ │ └── navigation
│ │ │ │ └── app_nav_graph.xml
│ │ ├── java
│ │ │ └── me
│ │ │ │ └── tangobee
│ │ │ │ └── weathernaut
│ │ │ │ ├── models
│ │ │ │ ├── NewLocationModel.kt
│ │ │ │ ├── DailyWeatherRVModel.kt
│ │ │ │ ├── HourlyWeatherRVModel.kt
│ │ │ │ ├── WeatherData
│ │ │ │ │ ├── DailyWeather
│ │ │ │ │ │ ├── DailyWeather.kt
│ │ │ │ │ │ ├── DailyUnits.kt
│ │ │ │ │ │ └── Daily.kt
│ │ │ │ │ ├── HourlyWeather
│ │ │ │ │ │ ├── HourlyWeather.kt
│ │ │ │ │ │ ├── HourlyUnits.kt
│ │ │ │ │ │ └── Hourly.kt
│ │ │ │ │ ├── CurrentWeather
│ │ │ │ │ │ ├── CurrentWeather.kt
│ │ │ │ │ │ ├── Current.kt
│ │ │ │ │ │ └── CurrentUnits.kt
│ │ │ │ │ └── WeatherData.kt
│ │ │ │ ├── GeocodingData
│ │ │ │ │ ├── GeocodingModel.kt
│ │ │ │ │ └── GeocodingResult.kt
│ │ │ │ ├── GeoWeatherModel.kt
│ │ │ │ └── SettingsModel.kt
│ │ │ │ ├── data
│ │ │ │ ├── api
│ │ │ │ │ ├── GeocodingService.kt
│ │ │ │ │ └── WeatherService.kt
│ │ │ │ ├── RetrofitHelper.kt
│ │ │ │ └── repository
│ │ │ │ │ ├── GeocodingRepository.kt
│ │ │ │ │ └── WeatherRepository.kt
│ │ │ │ ├── viewmodels
│ │ │ │ ├── WeatherViewModelFactory.kt
│ │ │ │ ├── GeocodingViewModelFactory.kt
│ │ │ │ ├── GeocodingViewModel.kt
│ │ │ │ └── WeatherViewModel.kt
│ │ │ │ ├── constants
│ │ │ │ ├── UnitsMapper.kt
│ │ │ │ ├── WeatherImageMapper.kt
│ │ │ │ └── WeatherConstants.kt
│ │ │ │ ├── utils
│ │ │ │ ├── UnitsCalculator.kt
│ │ │ │ ├── GeocodingHelper.kt
│ │ │ │ ├── SharedPreferencesHelper.kt
│ │ │ │ └── WeatherHelper.kt
│ │ │ │ ├── ui
│ │ │ │ ├── adapter
│ │ │ │ │ ├── DailyWeatherRVAdapter.kt
│ │ │ │ │ ├── HourlyWeatherRVAdapter.kt
│ │ │ │ │ └── LocationRVAdapter.kt
│ │ │ │ ├── Next7DaysFragment.kt
│ │ │ │ ├── SearchFragment.kt
│ │ │ │ ├── HomeFragment.kt
│ │ │ │ └── SettingsFragment.kt
│ │ │ │ ├── services
│ │ │ │ └── WeatherMusicService.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── me
│ │ │ └── tangobee
│ │ │ └── weathernaut
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── tangobee
│ │ └── weathernaut
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── assets
├── 1.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
├── Flat-cover.png
└── direct-apk-download.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── vcs.xml
├── compiler.xml
├── kotlinc.xml
├── discord.xml
├── misc.xml
└── gradle.xml
├── .gitignore
├── settings.gradle.kts
├── LICENSE
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/assets/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/assets/1.jpg
--------------------------------------------------------------------------------
/assets/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/assets/2.jpg
--------------------------------------------------------------------------------
/assets/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/assets/3.jpg
--------------------------------------------------------------------------------
/assets/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/assets/4.jpg
--------------------------------------------------------------------------------
/assets/Flat-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/assets/Flat-cover.png
--------------------------------------------------------------------------------
/assets/direct-apk-download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/assets/direct-apk-download.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/NewLocationModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models
2 |
3 | data class NewLocationModel(
4 | val query: String
5 | )
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangoBeee/Weathernaut/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #39FDD6
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/DailyWeatherRVModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models
2 |
3 | data class DailyWeatherRVModel(val day: String, val weatherTemp: String, val weatherIcon: Int)
4 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/HourlyWeatherRVModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models
2 |
3 | data class HourlyWeatherRVModel(val time: String, val weatherIcon: Int, val weatherTemp: String)
4 |
--------------------------------------------------------------------------------
/.idea/discord.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/DailyWeather/DailyWeather.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.DailyWeather
2 |
3 | data class DailyWeather(
4 | val daily: Daily,
5 | val daily_units: DailyUnits
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/GeocodingData/GeocodingModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.GeocodingData
2 |
3 | data class GeocodingModel(
4 | val generationtime_ms: Double,
5 | val results: ArrayList?
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/HourlyWeather/HourlyWeather.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.HourlyWeather
2 |
3 | data class HourlyWeather(
4 | val hourly: Hourly,
5 | val hourly_units: HourlyUnits
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/GeoWeatherModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models
2 |
3 | data class GeoWeatherModel(
4 | val ll: List,
5 | val city: String,
6 | val country: String,
7 | val timezone: String
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/CurrentWeather/CurrentWeather.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.CurrentWeather
2 |
3 | data class CurrentWeather(
4 | val current: Current,
5 | val current_units: CurrentUnits
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/HourlyWeather/HourlyUnits.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.HourlyWeather
2 |
3 | data class HourlyUnits(
4 | var temperature_2m: String,
5 | val time: String,
6 | val weather_code: String
7 | )
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/HourlyWeather/Hourly.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.HourlyWeather
2 |
3 | data class Hourly(
4 | var temperature_2m: List,
5 | val time: List,
6 | val weather_code: List
7 | )
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 09 17:54:46 IST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/indicator_dot_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/SettingsModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models
2 |
3 | data class SettingsModel(
4 | var tempUnit: String = "celsius",
5 | var windSpeedUnit: String = "kmh",
6 | var pressureUnit: String = "hpa",
7 | var isMusicOn: Boolean = true
8 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/current_hourly_weather_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/hourly_weather_box_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/tomorrow_weather_box_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/search_location_input_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Reply
5 | - Reply to all
6 |
7 |
8 |
9 | - reply
10 | - reply_all
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/atmospheric_pressure_unit_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/temperature_unit_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/DailyWeather/DailyUnits.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.DailyWeather
2 |
3 | data class DailyUnits(
4 | val sunrise: String,
5 | val sunset: String,
6 | var temperature_2m_max: String,
7 | var temperature_2m_min: String,
8 | val time: String,
9 | val weather_code: String
10 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rounded_blurry_box.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/font/inter.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/DailyWeather/Daily.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.DailyWeather
2 |
3 | data class Daily(
4 | val sunrise: List,
5 | val sunset: List,
6 | var temperature_2m_max: List,
7 | var temperature_2m_min: List,
8 | val time: List,
9 | val weather_code: List
10 | )
--------------------------------------------------------------------------------
/app/src/main/res/values/preloaded_fonts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @font/inter
5 | - @font/inter_bold
6 | - @font/inter_light
7 | - @font/inter_medium
8 | - @font/inter_semibold
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/CurrentWeather/Current.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.CurrentWeather
2 |
3 | data class Current(
4 | val interval: Int,
5 | var pressure_msl: Double,
6 | val relative_humidity_2m: Int,
7 | var temperature_2m: Double,
8 | val time: String,
9 | val weather_code: Int,
10 | var wind_speed_10m: Double
11 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_bold.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "Weathernaut"
17 | include(":app")
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_music.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_medium.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_semibold.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/CurrentWeather/CurrentUnits.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData.CurrentWeather
2 |
3 | data class CurrentUnits(
4 | val interval: String,
5 | var pressure_msl: String,
6 | val relative_humidity_2m: String,
7 | var temperature_2m: String,
8 | val time: String,
9 | val weather_code: String,
10 | var wind_speed_10m: String
11 | )
--------------------------------------------------------------------------------
/app/src/test/java/me/tangobee/weathernaut/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 |
6 | #313341
7 | #9C9EAA
8 | #808080
9 | #D6996B
10 | #E2A272
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_add.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/windsped_unit_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_menu.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/data/api/GeocodingService.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.data.api
2 |
3 | import me.tangobee.weathernaut.models.GeocodingData.GeocodingModel
4 | import me.tangobee.weathernaut.models.NewLocationModel
5 | import retrofit2.Response
6 | import retrofit2.http.Body
7 | import retrofit2.http.POST
8 |
9 | interface GeocodingService {
10 |
11 | @POST("/api/geocoding")
12 | suspend fun getLocations(@Body newLocationDataModel: NewLocationModel) : Response
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_right_arrow.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/viewmodels/WeatherViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import me.tangobee.weathernaut.data.repository.WeatherRepository
6 |
7 | class WeatherViewModelFactory(private val weatherRepository: WeatherRepository) : ViewModelProvider.Factory {
8 |
9 | @Suppress("UNCHECKED_CAST")
10 | override fun create(modelClass: Class): T {
11 | return WeatherViewModel(weatherRepository) as T
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/data/api/WeatherService.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.data.api
2 |
3 | import me.tangobee.weathernaut.models.GeoWeatherModel
4 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
5 | import retrofit2.Response
6 | import retrofit2.http.Body
7 | import retrofit2.http.POST
8 |
9 | interface WeatherService {
10 |
11 | @POST("/api/weather")
12 | suspend fun getWeather() : Response
13 |
14 | @POST("/api/weather")
15 | suspend fun getGeoWeather(@Body geoWeatherModel: GeoWeatherModel) : Response
16 |
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/viewmodels/GeocodingViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import me.tangobee.weathernaut.data.repository.GeocodingRepository
6 |
7 | class GeocodingViewModelFactory(private val geocodingRepository: GeocodingRepository) : ViewModelProvider.Factory {
8 |
9 | @Suppress("UNCHECKED_CAST")
10 | override fun create(modelClass: Class): T {
11 | return GeocodingViewModel(geocodingRepository) as T
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_back.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/WeatherData/WeatherData.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.WeatherData
2 |
3 | import me.tangobee.weathernaut.models.WeatherData.CurrentWeather.CurrentWeather
4 | import me.tangobee.weathernaut.models.WeatherData.DailyWeather.DailyWeather
5 | import me.tangobee.weathernaut.models.WeatherData.HourlyWeather.HourlyWeather
6 |
7 | data class WeatherData(
8 | val city: String,
9 | val country: String,
10 | val lat: Double,
11 | val long: Double,
12 | val current_weather: CurrentWeather,
13 | val daily_weather: DailyWeather,
14 | val hourly_weather: HourlyWeather
15 | )
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_search.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_dropdown.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/models/GeocodingData/GeocodingResult.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.models.GeocodingData
2 |
3 | data class GeocodingResult(
4 | val admin1: String,
5 | val admin1_id: Int,
6 | val admin2: String,
7 | val admin2_id: Int,
8 | val admin3: String,
9 | val admin3_id: Int,
10 | val admin4: String,
11 | val admin4_id: Int,
12 | val country: String,
13 | val country_code: String,
14 | val country_id: Int,
15 | val elevation: Int,
16 | val feature_code: String,
17 | val id: Int,
18 | val latitude: Double,
19 | val longitude: Double,
20 | val name: String,
21 | val population: Int,
22 | val timezone: String
23 | )
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/constants/UnitsMapper.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.constants
2 |
3 | object UnitsMapper {
4 | private val unitMap: Map = mapOf(
5 | "°C" to "celsius",
6 | "°F" to "fahrenheit",
7 | "km/h" to "kmh",
8 | "mph" to "mph",
9 | "m/s" to "ms",
10 | "hPa" to "hpa",
11 | "atm" to "atm"
12 | )
13 |
14 | private val reverseUnitMap: Map = unitMap.entries.associate { (key, value) -> value to key }
15 |
16 | fun getFullUnit(shorthand: String): String {
17 | return unitMap.getOrDefault(shorthand, shorthand)
18 | }
19 |
20 | fun getShorthandUnit(fullUnit: String): String {
21 | return reverseUnitMap.getOrDefault(fullUnit, fullUnit)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/tangobee/weathernaut/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("me.tangobee.weathernaut", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_humidity.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/data/RetrofitHelper.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.data
2 |
3 | import okhttp3.OkHttpClient
4 | import retrofit2.Retrofit
5 | import retrofit2.converter.gson.GsonConverterFactory
6 | import java.util.concurrent.TimeUnit
7 |
8 | object RetrofitHelper {
9 |
10 | private const val BASE_URL = "https://weathernaut-backend.onrender.com"
11 |
12 | fun getInstance() : Retrofit {
13 | val okHttpClient = OkHttpClient.Builder()
14 | .connectTimeout(1, TimeUnit.MINUTES)
15 | .readTimeout(30, TimeUnit.SECONDS)
16 | .writeTimeout(15, TimeUnit.SECONDS)
17 | .retryOnConnectionFailure(true)
18 | .build()
19 |
20 |
21 | return Retrofit.Builder()
22 | .baseUrl(BASE_URL)
23 | .client(okHttpClient)
24 | .addConverterFactory(GsonConverterFactory.create())
25 | .build()
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_sun.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/constants/WeatherImageMapper.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.constants
2 |
3 | import me.tangobee.weathernaut.R
4 |
5 | class WeatherImageMapper {
6 | companion object {
7 | fun getImageForWeatherCode(weatherCode: Int): Int {
8 | return when (weatherCode) {
9 | 0, 1 -> R.drawable.icon_weather_sun
10 | 2, 3 -> R.drawable.icon_weather_sun_cloud
11 | 45, 48 -> R.drawable.icon_weather_cloud_fog
12 | in 51..55, in 56..57 -> R.drawable.icon_weather_rain_cloud
13 | in 61..65, in 66..67 -> R.drawable.icon_weather_rain_cloud
14 | 71, 73, 75, 77 -> R.drawable.icon_weather_snow_cloud
15 | 80, 81, 82 -> R.drawable.icon_weather_sun_cloud_rain
16 | 85, 86 -> R.drawable.icon_weather_snow_cloud
17 | 95, 96, 99 -> R.drawable.icon_weather_thunderstorm_cloud
18 | else -> R.drawable.icon_weather_cloud
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/data/repository/GeocodingRepository.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.data.repository
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import me.tangobee.weathernaut.data.api.GeocodingService
6 | import me.tangobee.weathernaut.models.GeocodingData.GeocodingModel
7 | import me.tangobee.weathernaut.models.NewLocationModel
8 |
9 | class GeocodingRepository(private val geocodingService: GeocodingService) {
10 |
11 | private val locationsLiveData = MutableLiveData()
12 |
13 | val locationsData: LiveData
14 | get() = locationsLiveData
15 |
16 | suspend fun getLocations(newLocationDataModel: NewLocationModel) {
17 | val locationResult = geocodingService.getLocations(newLocationDataModel)
18 |
19 | if(locationResult.isSuccessful && locationResult.body() != null) {
20 | locationsLiveData.postValue(locationResult.body())
21 | } else {
22 | locationsLiveData.postValue(null)
23 | }
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/viewmodels/GeocodingViewModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import kotlinx.coroutines.CoroutineExceptionHandler
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.launch
9 | import me.tangobee.weathernaut.data.repository.GeocodingRepository
10 | import me.tangobee.weathernaut.models.GeocodingData.GeocodingModel
11 | import me.tangobee.weathernaut.models.NewLocationModel
12 |
13 | class GeocodingViewModel(private val geocodingRepository: GeocodingRepository) : ViewModel() {
14 |
15 | fun getLocations(newLocationDataModel: NewLocationModel, coroutineExceptionHandler: CoroutineExceptionHandler) {
16 | viewModelScope.launch(Dispatchers.IO + coroutineExceptionHandler) {
17 | geocodingRepository.getLocations(newLocationDataModel)
18 | }
19 | }
20 |
21 | val locationLiveData : LiveData
22 | get() = geocodingRepository.locationsData
23 |
24 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 TangoBee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/viewmodels/WeatherViewModel.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import kotlinx.coroutines.CoroutineExceptionHandler
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.launch
9 | import me.tangobee.weathernaut.data.repository.WeatherRepository
10 | import me.tangobee.weathernaut.models.GeoWeatherModel
11 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
12 |
13 | class WeatherViewModel(private val weatherRepository: WeatherRepository) : ViewModel() {
14 |
15 | fun getWeather(coroutineExceptionHandler: CoroutineExceptionHandler) {
16 | viewModelScope.launch(Dispatchers.IO + coroutineExceptionHandler) {
17 | weatherRepository.getWeather()
18 | }
19 | }
20 |
21 | fun getGeoWeather(coroutineExceptionHandler: CoroutineExceptionHandler, geoWeatherModel: GeoWeatherModel) {
22 | viewModelScope.launch(Dispatchers.IO + coroutineExceptionHandler) {
23 | weatherRepository.getGeoWeather(geoWeatherModel)
24 | }
25 | }
26 |
27 | fun updateWeatherData(weatherData: WeatherData) {
28 | weatherRepository.updateWeatherData(weatherData)
29 | }
30 |
31 | val weatherLiveData : LiveData
32 | get() = weatherRepository.weatherData
33 |
34 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/data/repository/WeatherRepository.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.data.repository
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import me.tangobee.weathernaut.data.api.WeatherService
6 | import me.tangobee.weathernaut.models.GeoWeatherModel
7 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
8 |
9 | class WeatherRepository(private val weatherService: WeatherService) {
10 |
11 | private val weatherLiveData = MutableLiveData()
12 |
13 | val weatherData: LiveData
14 | get() = weatherLiveData
15 |
16 | suspend fun getWeather() {
17 | val weatherResult = weatherService.getWeather()
18 |
19 | if(weatherResult.isSuccessful && weatherResult.body() != null) {
20 | weatherLiveData.postValue(weatherResult.body())
21 | } else {
22 | weatherLiveData.postValue(null)
23 | }
24 | }
25 |
26 | suspend fun getGeoWeather(geoWeatherModel: GeoWeatherModel) {
27 | val weatherResult = weatherService.getGeoWeather(geoWeatherModel)
28 |
29 | if(weatherResult.isSuccessful && weatherResult.body() != null) {
30 | weatherLiveData.postValue(weatherResult.body())
31 | } else {
32 | weatherLiveData.postValue(null)
33 | }
34 | }
35 |
36 | fun updateWeatherData(weatherData: WeatherData) {
37 | weatherLiveData.postValue(weatherData)
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/utils/UnitsCalculator.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.utils
2 |
3 | class UnitConverter {
4 |
5 | fun convertTemperature(value: Double, fromUnit: String, toUnit: String): Double {
6 | val result = when (fromUnit to toUnit) {
7 | "celsius" to "fahrenheit" -> (value * 9 / 5) + 32
8 | "fahrenheit" to "celsius" -> (value - 32) * 5 / 9
9 | else -> value
10 | }
11 |
12 | return result.formatToTwoDecimalPlaces()
13 | }
14 |
15 | fun convertWindSpeed(value: Double, fromUnit: String, toUnit: String): Double {
16 | val result = when (fromUnit to toUnit) {
17 | "kmh" to "mph" -> value / 1.60934
18 | "mph" to "kmh" -> value * 1.60934
19 | "kmh" to "ms" -> value / 3.6
20 | "ms" to "kmh" -> value * 3.6
21 | "mph" to "ms" -> value * 0.44704
22 | "ms" to "mph" -> value / 0.44704
23 | else -> value
24 | }
25 |
26 | return result.formatToTwoDecimalPlaces()
27 | }
28 |
29 | fun convertPressure(value: Double, fromUnit: String, toUnit: String): Double {
30 | val result = when (fromUnit to toUnit) {
31 | "hpa" to "atm" -> value / 1013.25
32 | "atm" to "hpa" -> value * 1013.25
33 | else -> value
34 | }
35 |
36 | return result.formatToTwoDecimalPlaces()
37 | }
38 |
39 | private fun Double.formatToTwoDecimalPlaces(): Double {
40 | return String.format("%.2f", this).toDouble()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/ui/adapter/DailyWeatherRVAdapter.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.ui.adapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.RecyclerView
9 | import me.tangobee.weathernaut.R
10 | import me.tangobee.weathernaut.models.DailyWeatherRVModel
11 |
12 | class DailyWeatherRVAdapter(private val dailyWeatherRVModelList: ArrayList) : RecyclerView.Adapter() {
13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
14 | return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_next_weather_item, parent, false))
15 | }
16 |
17 | override fun getItemCount(): Int {
18 | return dailyWeatherRVModelList.size
19 | }
20 |
21 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
22 | holder.nextWeatherDay.text = dailyWeatherRVModelList[position].day
23 | holder.nextWeatherTemp.text = dailyWeatherRVModelList[position].weatherTemp
24 | holder.nextWeatherIcon.setImageResource(dailyWeatherRVModelList[position].weatherIcon)
25 | }
26 |
27 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
28 | val nextWeatherIcon: ImageView = itemView.findViewById(R.id.nextWeatherIcon)
29 | val nextWeatherDay: TextView = itemView.findViewById(R.id.nextWeatherDay)
30 | val nextWeatherTemp: TextView = itemView.findViewById(R.id.nextWeatherTemp)
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_cloud.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_hourly_weather_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
22 |
23 |
30 |
31 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/constants/WeatherConstants.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.constants
2 |
3 | data class WeatherConstants(
4 | val code: Int,
5 | val description: String
6 | )
7 |
8 | object WeatherCodes {
9 | val weatherConstants = listOf(
10 | WeatherConstants(0, "Clear"),
11 | WeatherConstants(1, "Clear/Partly Cloudy"),
12 | WeatherConstants(2, "Partly Cloudy"),
13 | WeatherConstants(3, "Overcast"),
14 | WeatherConstants(45, "Fog"),
15 | WeatherConstants(48, "Rime Fog"),
16 | WeatherConstants(51, "Light Drizzle"),
17 | WeatherConstants(53, "Moderate Drizzle"),
18 | WeatherConstants(55, "Heavy Drizzle"),
19 | WeatherConstants(56, "Light Freezing Drizzle"),
20 | WeatherConstants(57, "Heavy Freezing Drizzle"),
21 | WeatherConstants(61, "Light Rain"),
22 | WeatherConstants(63, "Moderate Rain"),
23 | WeatherConstants(65, "Heavy Rain"),
24 | WeatherConstants(66, "Light Freezing Rain"),
25 | WeatherConstants(67, "Heavy Freezing Rain"),
26 | WeatherConstants(71, "Light Snow"),
27 | WeatherConstants(73, "Moderate Snow"),
28 | WeatherConstants(75, "Heavy Snow"),
29 | WeatherConstants(77, "Snow Grains"),
30 | WeatherConstants(80, "Light Rain Showers"),
31 | WeatherConstants(81, "Moderate Rain Showers"),
32 | WeatherConstants(82, "Heavy Rain Showers"),
33 | WeatherConstants(85, "Light Snow Showers"),
34 | WeatherConstants(86, "Heavy Snow Showers"),
35 | WeatherConstants(95, "Slight Thunderstorm"),
36 | WeatherConstants(96, "Slight Thunderstorm"),
37 | WeatherConstants(99, "Heavy Thunderstorm")
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/utils/GeocodingHelper.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.utils
2 |
3 | import me.tangobee.weathernaut.models.GeocodingData.GeocodingResult
4 | import kotlin.math.*
5 |
6 | class GeocodingHelper {
7 | companion object {
8 | fun areLocationsSame(
9 | lat1: Double,
10 | lon1: Double,
11 | lat2: Double,
12 | lon2: Double,
13 | threshold: Double = 0.01
14 | ): Boolean {
15 | return abs(lat1 - lat2) < threshold && abs(lon1 - lon2) < threshold
16 | }
17 |
18 | // Haversine formula to calculate distance between two lat/lng points in kilometers
19 | private fun calculateDistance(
20 | userLat: Double, userLng: Double,
21 | placeLat: Double, placeLng: Double
22 | ): Double {
23 | val earthRadiusKm = 6371.0
24 |
25 | val dLat = Math.toRadians(placeLat - userLat)
26 | val dLng = Math.toRadians(placeLng - userLng)
27 |
28 | val a = sin(dLat / 2) * sin(dLat / 2) +
29 | cos(Math.toRadians(userLat)) * cos(Math.toRadians(placeLat)) *
30 | sin(dLng / 2) * sin(dLng / 2)
31 |
32 | val c = 2 * atan2(sqrt(a), sqrt(1 - a))
33 |
34 | return earthRadiusKm * c
35 | }
36 |
37 | fun sortLocationsByProximity(
38 | userLat: Double, userLng: Double,
39 | locationRVModelList: MutableList
40 | ): List {
41 | return locationRVModelList.sortedBy { location ->
42 | calculateDistance(userLat, userLng, location.latitude, location.longitude)
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/app_nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
16 |
19 |
22 |
23 |
28 |
33 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_next_weather_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
32 |
33 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_cloud_fog.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/cities_item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
31 |
32 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/utils/SharedPreferencesHelper.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import com.google.gson.Gson
6 | import me.tangobee.weathernaut.models.GeoWeatherModel
7 | import me.tangobee.weathernaut.models.SettingsModel
8 |
9 | class SharedPreferencesHelper(context: Context) {
10 | companion object {
11 | private const val PREFS_NAME = "WeathernautPrefs"
12 | private const val SETTINGS_KEY = "SettingsKey"
13 | private const val GEOCODING_KEY = "GeocodingKey"
14 | }
15 |
16 | private val sharedPreferences: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
17 | private val gson = Gson()
18 |
19 | fun saveSettings(settings: SettingsModel) {
20 | val settingsJson = gson.toJson(settings)
21 | sharedPreferences.edit().putString(SETTINGS_KEY, settingsJson).apply()
22 | }
23 |
24 | fun getSettings(): SettingsModel? {
25 | val settingsJson = sharedPreferences.getString(SETTINGS_KEY, null)
26 | return if (settingsJson != null) {
27 | gson.fromJson(settingsJson, SettingsModel::class.java)
28 | } else {
29 | null
30 | }
31 | }
32 |
33 | fun updateSettings(updateFunction: (SettingsModel) -> SettingsModel) {
34 | val currentSettings = getSettings()
35 | if (currentSettings != null) {
36 | val updatedSettings = updateFunction.invoke(currentSettings)
37 | saveSettings(updatedSettings)
38 | }
39 | }
40 |
41 | fun saveGeocodingData(geoWeatherModel: GeoWeatherModel) {
42 | val geocodingJson = gson.toJson(geoWeatherModel)
43 | sharedPreferences.edit().putString(GEOCODING_KEY, geocodingJson).apply()
44 | }
45 |
46 | fun getGeocodingData(): GeoWeatherModel? {
47 | val geocodingJson = sharedPreferences.getString(GEOCODING_KEY, null)
48 | return if (geocodingJson != null) {
49 | gson.fromJson(geocodingJson, GeoWeatherModel::class.java)
50 | } else {
51 | null
52 | }
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_rain_cloud.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
30 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_sun_cloud.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_wind.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
13 |
16 |
19 |
22 |
25 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/ui/adapter/HourlyWeatherRVAdapter.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.ui.adapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import android.widget.LinearLayout
8 | import android.widget.TextView
9 | import androidx.core.content.ContextCompat
10 | import androidx.core.content.res.ResourcesCompat
11 | import androidx.recyclerview.widget.RecyclerView
12 | import me.tangobee.weathernaut.R
13 | import me.tangobee.weathernaut.models.HourlyWeatherRVModel
14 |
15 | class HourlyWeatherRVAdapter(private val hourlyWeatherRVModelList: ArrayList) : RecyclerView.Adapter() {
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
17 | return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_hourly_weather_item, parent, false))
18 | }
19 |
20 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
21 | val context = holder.hourlyWeatherContainer.context
22 | holder.weatherIcon.setImageResource(hourlyWeatherRVModelList[position].weatherIcon)
23 | holder.weatherValue.text = hourlyWeatherRVModelList[position].weatherTemp
24 |
25 | val time = hourlyWeatherRVModelList[position].time
26 | if(time == "now") {
27 | val boldTypeface = ResourcesCompat.getFont(context, R.font.inter_semibold)
28 | holder.weatherTime.setTextColor(ContextCompat.getColor(context, R.color.textColor))
29 | holder.weatherTime.typeface = boldTypeface
30 | holder.hourlyWeatherContainer.background = ContextCompat.getDrawable(context, R.drawable.current_hourly_weather_background)
31 | } else {
32 | val typeface = ResourcesCompat.getFont(context, R.font.inter)
33 | holder.weatherTime.setTextColor(ContextCompat.getColor(context, R.color.upcomingTimeColor))
34 | holder.weatherTime.typeface = typeface
35 | holder.hourlyWeatherContainer.background = ContextCompat.getDrawable(context, R.drawable.hourly_weather_box_background)
36 | }
37 |
38 | holder.weatherTime.text = time
39 | }
40 |
41 | override fun getItemCount(): Int {
42 | return hourlyWeatherRVModelList.size
43 | }
44 |
45 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
46 | val hourlyWeatherContainer: LinearLayout = itemView.findViewById(R.id.hourlyWeatherContainer)
47 | val weatherTime: TextView = itemView.findViewById(R.id.weatherTime)
48 | val weatherIcon: ImageView = itemView.findViewById(R.id.weatherIcon)
49 | val weatherValue: TextView = itemView.findViewById(R.id.weatherValue)
50 | }
51 | }
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "me.tangobee.weathernaut"
8 | compileSdk = 35
9 |
10 | defaultConfig {
11 | applicationId = "me.tangobee.weathernaut"
12 | minSdk = 24
13 | targetSdk = 35
14 | versionCode = 10
15 | versionName = "2.0"
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildFeatures {
21 | viewBinding = true
22 | }
23 |
24 | buildTypes {
25 | release {
26 | isMinifyEnabled = false
27 | proguardFiles(
28 | getDefaultProguardFile("proguard-android-optimize.txt"),
29 | "proguard-rules.pro"
30 | )
31 | }
32 | }
33 | compileOptions {
34 | sourceCompatibility = JavaVersion.VERSION_1_8
35 | targetCompatibility = JavaVersion.VERSION_1_8
36 | }
37 | kotlinOptions {
38 | jvmTarget = "1.8"
39 | }
40 | }
41 |
42 | dependencies {
43 |
44 | implementation("androidx.core:core-ktx:1.12.0")
45 | implementation("androidx.appcompat:appcompat:1.6.1")
46 | implementation("com.google.android.material:material:1.11.0")
47 | implementation("androidx.constraintlayout:constraintlayout:2.1.4")
48 | testImplementation("junit:junit:4.13.2")
49 | androidTestImplementation("androidx.test.ext:junit:1.1.5")
50 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
51 | implementation("androidx.fragment:fragment-ktx:1.6.2")
52 |
53 | // SplashScreen API
54 | implementation("androidx.core:core-splashscreen:1.0.1")
55 |
56 | // ViewModel Lib
57 | val lifecycleVersion = "2.7.0"
58 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
59 | implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
60 | implementation("androidx.lifecycle:lifecycle-common:$lifecycleVersion")
61 |
62 | // Retrofit and GSON Lib
63 | implementation("com.squareup.retrofit2:retrofit:2.11.0")
64 | implementation("com.squareup.retrofit2:converter-gson:2.11.0")
65 |
66 | // Google PlayService Location Lib
67 | implementation("com.google.android.gms:play-services-location:21.2.0")
68 |
69 | // Jetpack Navigation Lib
70 | val nav_version = "2.7.7"
71 | implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
72 | implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
73 |
74 | // Responsive Unit Lib for resizing elements
75 | implementation("com.intuit.ssp:ssp-android:1.1.1")
76 | implementation("com.intuit.sdp:sdp-android:1.1.1")
77 |
78 | // Custom Switch Lib
79 | implementation("io.github.bitvale:switcher:1.1.2")
80 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
24 |
25 |
26 |
36 |
37 |
49 |
50 |
60 |
61 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_sunrise.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
10 |
12 |
16 |
19 |
22 |
25 |
28 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_sunset.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
10 |
12 |
16 |
19 |
22 |
25 |
28 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/services/WeatherMusicService.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.services
2 |
3 | import android.app.Notification
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.app.Service
7 | import android.content.Intent
8 | import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
9 | import android.media.MediaPlayer
10 | import android.os.IBinder
11 | import android.os.Build
12 | import androidx.core.app.NotificationCompat
13 | import me.tangobee.weathernaut.R
14 |
15 | class WeatherMusicService : Service() {
16 |
17 | companion object {
18 | private var mediaPlayer: MediaPlayer? = null
19 | }
20 |
21 | private val musicUrl = "https://weathernaut-backend.onrender.com/api/music"
22 | private val SERVICE_ID = 1
23 |
24 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
25 | createNotificationChannel()
26 |
27 | // Foreground notification
28 | val notification: Notification = NotificationCompat.Builder(this, "MUSIC_SERVICE_CHANNEL")
29 | .setContentTitle("Playing Weather Music")
30 | .setContentText("Enjoy the music and your day's weather!")
31 | .setSmallIcon(R.drawable.icon_music)
32 | .build()
33 |
34 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
35 | startForeground(SERVICE_ID, notification)
36 | } else {
37 | startForeground(SERVICE_ID, notification,
38 | FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
39 | }
40 |
41 | if (mediaPlayer == null) {
42 | mediaPlayer = MediaPlayer().apply {
43 | setDataSource(musicUrl)
44 | prepareAsync()
45 | setOnPreparedListener {
46 | it.start()
47 | }
48 | setOnCompletionListener {
49 | start()
50 | }
51 | setOnErrorListener { _, _, _ ->
52 | stopSelf()
53 | false
54 | }
55 | }
56 | } else if (!mediaPlayer!!.isPlaying) {
57 | mediaPlayer!!.start()
58 | }
59 |
60 | return START_STICKY
61 | }
62 |
63 | override fun onDestroy() {
64 | super.onDestroy()
65 | if (mediaPlayer != null) {
66 | mediaPlayer?.stop()
67 | mediaPlayer?.release()
68 | mediaPlayer = null
69 | }
70 | }
71 |
72 | override fun onBind(intent: Intent?): IBinder? {
73 | return null // We don't bind this service to an activity
74 | }
75 |
76 | private fun createNotificationChannel() {
77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
78 | val serviceChannel = NotificationChannel(
79 | "MUSIC_SERVICE_CHANNEL",
80 | "Music Playback Channel",
81 | NotificationManager.IMPORTANCE_LOW
82 | )
83 | val manager = getSystemService(NotificationManager::class.java)
84 | manager?.createNotificationChannel(serviceChannel)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Weathernaut
3 |
4 | Something went wrong!\nDon\'t worry its not you, its us. Try later.
5 | No Internet Connection
6 | Search Cities Button
7 | App Settings Button
8 | Atmospheric Pressure Icon
9 | Back Button
10 | Next 7 Days
11 | Tomorrow
12 | Weather Icon
13 |
14 | Messages
15 | Sync
16 |
17 |
18 | Your signature
19 | Default reply action
20 |
21 |
22 | Sync email periodically
23 | Download incoming attachments
24 | Automatically download attachments for incoming emails
25 |
26 | Only download attachments when manually requested
27 | Hello blank fragment
28 | Pressure
29 | Wind Speed
30 | Humidity
31 | Today
32 | Sunset icon
33 | Sunrise icon
34 | Settings
35 | Units
36 | Temperature units
37 | °C
38 | °F
39 | Wind speed units
40 | hPa
41 | atm
42 | Atmospheric pressure units
43 | m/s
44 | km/h
45 | mph
46 | Music according to the weather.
47 | About Weathernaut
48 | Feedback
49 | Privacy Policy
50 | Credits
51 | Other Settings
52 | Weather music
53 | Donate Us
54 | Cancel
55 | Search Your City
56 | Add remove button
57 | Country
58 | City
59 | No locations found
60 | Loading…
61 | Something went wrong!
62 |
--------------------------------------------------------------------------------
/app/src/main/res/values/font_certs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @array/com_google_android_gms_fonts_certs_dev
5 | - @array/com_google_android_gms_fonts_certs_prod
6 |
7 |
8 | -
9 | MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
10 |
11 |
12 |
13 | -
14 | MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/ui/adapter/LocationRVAdapter.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.ui.adapter
2 |
3 | import android.app.Activity
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.ImageButton
8 | import android.widget.TextView
9 | import androidx.recyclerview.widget.RecyclerView
10 | import kotlinx.coroutines.CoroutineExceptionHandler
11 | import me.tangobee.weathernaut.R
12 | import me.tangobee.weathernaut.models.GeoWeatherModel
13 | import me.tangobee.weathernaut.models.GeocodingData.GeocodingResult
14 | import me.tangobee.weathernaut.utils.GeocodingHelper
15 | import me.tangobee.weathernaut.utils.SharedPreferencesHelper
16 | import me.tangobee.weathernaut.viewmodels.WeatherViewModel
17 |
18 | class LocationRVAdapter(private val locationRVModalList: ArrayList, private val lat: Double, private val long: Double, private val coroutineExceptionHandler: CoroutineExceptionHandler, private val weatherViewModel: WeatherViewModel) : RecyclerView.Adapter() {
19 |
20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
21 | return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.cities_item_layout, parent, false))
22 | }
23 |
24 | override fun getItemCount(): Int {
25 | return locationRVModalList.size
26 | }
27 |
28 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
29 | holder.cityName.text = locationRVModalList[position].name
30 | val cityAddress = listOf(
31 | locationRVModalList[position].admin4,
32 | locationRVModalList[position].admin3,
33 | locationRVModalList[position].admin2,
34 | locationRVModalList[position].admin1,
35 | locationRVModalList[position].country
36 | ).filterNot { it.isNullOrBlank() }.joinToString(", ")
37 |
38 | holder.cityAddress.text = cityAddress
39 |
40 | holder.addRemoveCity.setOnClickListener {
41 | holder.addRemoveCity.setImageResource(R.drawable.icon_right_arrow)
42 | val context = holder.itemView.context
43 |
44 | val geoWeatherModel = GeoWeatherModel(
45 | listOf(
46 | locationRVModalList[position].latitude,
47 | locationRVModalList[position].longitude
48 | ),
49 | locationRVModalList[position].name,
50 | locationRVModalList[position].country,
51 | locationRVModalList[position].timezone
52 | )
53 |
54 | weatherViewModel.getGeoWeather(coroutineExceptionHandler, geoWeatherModel)
55 |
56 | val sharedPreferencesHelper = SharedPreferencesHelper(context)
57 | sharedPreferencesHelper.saveGeocodingData(geoWeatherModel)
58 |
59 | if (context is Activity) {
60 | context.onBackPressed()
61 | }
62 | }
63 |
64 | val samePlace = GeocodingHelper.areLocationsSame(lat, long, locationRVModalList[position].latitude, locationRVModalList[position].longitude)
65 |
66 | if(samePlace) {
67 | holder.addRemoveCity.setImageResource(R.drawable.icon_right_arrow)
68 | } else {
69 | holder.addRemoveCity.setImageResource(R.drawable.icon_add)
70 | }
71 | }
72 |
73 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
74 | val cityName: TextView = itemView.findViewById(R.id.cityName)
75 | val cityAddress: TextView = itemView.findViewById(R.id.cityAddress)
76 | val addRemoveCity: ImageButton = itemView.findViewById(R.id.addRemoveCity)
77 | }
78 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Weathernaut
2 |
3 | [](https://github.com/tangobeee/Weathernaut/releases/latest)
4 | 
5 | 
6 |
7 | 
8 |
9 | **Requirements**
10 | - Android Studio latest version
11 | - JDK 8
12 | - Target Android SDK 35
13 | - Supports API Level +24
14 | - Material Components 1.10.0
15 |
16 | **Highlights**
17 | - Use [Open-Meteo] API
18 | - Use Material Design 2
19 | - Responsive UI
20 | - Background Lofi Music
21 | - Uses locale Database
22 |
23 | [
](https://apt.izzysoft.de/fdroid/index/apk/me.tangobee.weathernaut/)
26 | [
](https://play.google.com/store/apps/details?id=me.tangobee.weathernaut)
29 | [
](https://github.com/tangobeee/Weathernaut/releases/latest)
32 |
33 | **Screenshot**
34 |
35 |
36 |

37 |
38 |
39 |
40 |
41 | **Libraries & Dependencies**
42 | - [Support libraries]: appcompat / recyclerview / constraintlayout / splashscreen / Navigation Component
43 | - [Material Design 2]: MaterialCardView / MaterialButton
44 | - Square [Retrofit] / [GSON]
45 | - Custom Views: [Dots Indicator] / [Switcher]
46 | - Intuit: [SSP] / [SDP]
47 |
48 | **Credits**
49 |
50 | # Click here -> [credit-link]
51 |
52 |
53 | **Backend**
54 |
55 | # Click here -> [background-music]
56 |
57 | # License
58 | MIT License
59 |
60 | Copyright (c) 2023 TangoBee
61 |
62 | Permission is hereby granted, free of charge, to any person obtaining a copy
63 | of this software and associated documentation files (the "Software"), to deal
64 | in the Software without restriction, including without limitation the rights
65 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
66 | copies of the Software, and to permit persons to whom the Software is
67 | furnished to do so, subject to the following conditions:
68 |
69 | The above copyright notice and this permission notice shall be included in all
70 | copies or substantial portions of the Software.
71 |
72 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
73 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
74 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
75 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
76 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
77 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
78 | SOFTWARE.
79 |
80 | [Open-Meteo]: https://open-meteo.com/
81 | [credit-link]: https://tangobee.netlify.app/weathernaut/credits
82 | [Support libraries]: https://developer.android.com/jetpack/androidx/
83 | [Material Design 2]: https://material.io/develop/android/
84 | [Retrofit]: https://github.com/square/retrofit
85 | [GSON]: https://github.com/square/retrofit/tree/master/retrofit-converters/gson
86 | [Dots Indicator]: https://github.com/tommybuonomo/dotsindicator
87 | [SDP]: https://github.com/intuit/sdp
88 | [SSP]: https://github.com/intuit/ssp
89 | [Switcher]: https://github.com/bitvale/Switcher
90 | [background-music]: https://github.com/tangobeee/Weathernaut-Backend
91 | [Release]: https://github.com/tangobeee/Weathernaut/releases
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/utils/WeatherHelper.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.utils
2 |
3 | import me.tangobee.weathernaut.constants.UnitsMapper
4 | import me.tangobee.weathernaut.models.SettingsModel
5 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
6 |
7 | class WeatherHelper(private val settings: SettingsModel, private val weatherData: WeatherData) {
8 |
9 | private val unitConverter = UnitConverter()
10 |
11 | fun convertWeatherData(): WeatherData {
12 | weatherData.current_weather.current.temperature_2m = convertTemperature(
13 | weatherData.current_weather.current.temperature_2m,
14 | weatherData.current_weather.current_units.temperature_2m,
15 | settings.tempUnit
16 | )
17 | weatherData.current_weather.current_units.temperature_2m = UnitsMapper.getShorthandUnit(settings.tempUnit)
18 |
19 | weatherData.current_weather.current.wind_speed_10m = convertWindSpeed(
20 | weatherData.current_weather.current.wind_speed_10m,
21 | weatherData.current_weather.current_units.wind_speed_10m,
22 | settings.windSpeedUnit
23 | )
24 | weatherData.current_weather.current_units.wind_speed_10m = UnitsMapper.getShorthandUnit(settings.windSpeedUnit)
25 |
26 | weatherData.current_weather.current.pressure_msl = convertPressure(
27 | weatherData.current_weather.current.pressure_msl,
28 | weatherData.current_weather.current_units.pressure_msl,
29 | settings.pressureUnit
30 | )
31 | weatherData.current_weather.current_units.pressure_msl = UnitsMapper.getShorthandUnit(settings.pressureUnit)
32 |
33 | weatherData.hourly_weather.hourly.temperature_2m = weatherData.hourly_weather.hourly.temperature_2m.map {
34 | convertTemperature(it, weatherData.hourly_weather.hourly_units.temperature_2m, settings.tempUnit)
35 | }
36 | weatherData.hourly_weather.hourly_units.temperature_2m = UnitsMapper.getShorthandUnit(settings.tempUnit)
37 |
38 | weatherData.daily_weather.daily.temperature_2m_max = weatherData.daily_weather.daily.temperature_2m_max.map {
39 | convertTemperature(it, weatherData.daily_weather.daily_units.temperature_2m_max, settings.tempUnit)
40 | }
41 | weatherData.daily_weather.daily_units.temperature_2m_max = UnitsMapper.getShorthandUnit(settings.tempUnit)
42 |
43 | weatherData.daily_weather.daily.temperature_2m_min = weatherData.daily_weather.daily.temperature_2m_min.map {
44 | convertTemperature(it, weatherData.daily_weather.daily_units.temperature_2m_min, settings.tempUnit)
45 | }
46 | weatherData.daily_weather.daily_units.temperature_2m_min = UnitsMapper.getShorthandUnit(settings.tempUnit)
47 |
48 | return weatherData
49 | }
50 |
51 | private fun convertTemperature(value: Double, fromUnit: String, toUnit: String): Double {
52 | val fromUnitName = unitName(fromUnit)
53 | val toUnitName = unitName(toUnit)
54 | return unitConverter.convertTemperature(value, fromUnitName, toUnitName)
55 | }
56 |
57 | private fun convertWindSpeed(value: Double, fromUnit: String, toUnit: String): Double {
58 | val fromUnitName = unitName(fromUnit)
59 | val toUnitName = unitName(toUnit)
60 | return unitConverter.convertWindSpeed(value, fromUnitName, toUnitName)
61 | }
62 |
63 | private fun convertPressure(value: Double, fromUnit: String, toUnit: String): Double {
64 | val fromUnitName = unitName(fromUnit)
65 | val toUnitName = unitName(toUnit)
66 | return unitConverter.convertPressure(value, fromUnitName, toUnitName)
67 | }
68 |
69 | private fun unitName(unit: String): String {
70 | return when (unit) {
71 | "°C" -> "celsius"
72 | "°F" -> "fahrenheit"
73 | "km/h" -> "kmh"
74 | "mph" -> "mph"
75 | "m/s" -> "ms"
76 | "hPa" -> "hpa"
77 | "atm" -> "atm"
78 | else -> unit
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.widget.Toast
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
9 | import androidx.lifecycle.MutableLiveData
10 | import androidx.lifecycle.ViewModelProvider
11 | import androidx.lifecycle.viewModelScope
12 | import kotlinx.coroutines.CoroutineExceptionHandler
13 | import kotlinx.coroutines.cancel
14 | import me.tangobee.weathernaut.data.RetrofitHelper
15 | import me.tangobee.weathernaut.data.api.WeatherService
16 | import me.tangobee.weathernaut.data.repository.WeatherRepository
17 | import me.tangobee.weathernaut.databinding.ActivityMainBinding
18 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
19 | import me.tangobee.weathernaut.services.WeatherMusicService
20 | import me.tangobee.weathernaut.utils.SharedPreferencesHelper
21 | import me.tangobee.weathernaut.utils.WeatherHelper
22 | import me.tangobee.weathernaut.viewmodels.WeatherViewModel
23 | import me.tangobee.weathernaut.viewmodels.WeatherViewModelFactory
24 | import java.net.UnknownHostException
25 | import kotlin.system.exitProcess
26 |
27 | class MainActivity : AppCompatActivity() {
28 |
29 | private lateinit var binding: ActivityMainBinding
30 |
31 | private lateinit var weatherViewModel: WeatherViewModel
32 | private lateinit var coroutineExceptionHandler: CoroutineExceptionHandler
33 | private lateinit var sharedPreferencesHelper: SharedPreferencesHelper
34 |
35 | private var settingsUpdated = false
36 |
37 | override fun onCreate(savedInstanceState: Bundle?) {
38 | super.onCreate(savedInstanceState)
39 | val splashScreen = installSplashScreen()
40 | binding = ActivityMainBinding.inflate(layoutInflater)
41 |
42 | sharedPreferencesHelper = SharedPreferencesHelper(this)
43 |
44 | val noInternetLiveData : MutableLiveData = MutableLiveData(false)
45 |
46 | coroutineExceptionHandler = CoroutineExceptionHandler{_, throwable ->
47 | if(throwable is UnknownHostException) {
48 | noInternetLiveData.postValue(true)
49 | }
50 | }
51 |
52 | noInternetLiveData.observe(this) {noInternet ->
53 | if(noInternet) {
54 | Toast.makeText(this, getString(R.string.no_internet), Toast.LENGTH_LONG).show()
55 | Thread.sleep(1000)
56 | exitProcess(0)
57 | }
58 | }
59 |
60 | val settingsModel = SharedPreferencesHelper(this).getSettings()
61 | if(settingsModel?.isMusicOn != false) {
62 | val startMusicIntent = Intent(this, WeatherMusicService::class.java)
63 | startService(startMusicIntent)
64 | }
65 |
66 | fetchData()
67 | splashScreen.setKeepOnScreenCondition { (weatherViewModel.weatherLiveData.value == null) }
68 |
69 | setContentView(binding.root)
70 | }
71 |
72 | private fun fetchData() {
73 | val weatherService = RetrofitHelper.getInstance().create(WeatherService::class.java)
74 | val weatherRepository = WeatherRepository(weatherService)
75 | weatherViewModel = ViewModelProvider(this, WeatherViewModelFactory(weatherRepository))[WeatherViewModel::class.java]
76 |
77 | val geocodingData = sharedPreferencesHelper.getGeocodingData()
78 |
79 | if(weatherViewModel.weatherLiveData.value == null) {
80 | if(geocodingData == null) {
81 | weatherViewModel.getWeather(coroutineExceptionHandler)
82 | } else {
83 | weatherViewModel.getGeoWeather(coroutineExceptionHandler, geocodingData)
84 | }
85 | }
86 |
87 | weatherViewModel.weatherLiveData.observe(this) { weatherData ->
88 | if(weatherData == null) {
89 | Toast.makeText(this, getString(R.string.api_fetching_error), Toast.LENGTH_SHORT).show()
90 | Thread.sleep(1000)
91 | exitProcess(0)
92 | } else {
93 | if(!settingsUpdated) {
94 | createLocalDB(weatherData)
95 | }
96 | }
97 | }
98 | }
99 |
100 | private fun createLocalDB(weatherData: WeatherData) {
101 | val currentSettings = sharedPreferencesHelper.getSettings()
102 | if (currentSettings != null) {
103 | val weatherHelper = WeatherHelper(currentSettings, weatherData)
104 | val newWeatherData = weatherHelper.convertWeatherData()
105 |
106 | if(newWeatherData != weatherData) {
107 | settingsUpdated = true
108 | weatherViewModel.updateWeatherData(newWeatherData)
109 | }
110 | }
111 | }
112 |
113 | override fun onDestroy() {
114 | super.onDestroy()
115 | weatherViewModel.viewModelScope.cancel("ActivityDestroying")
116 | }
117 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_sun_cloud_rain.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
16 |
19 |
22 |
25 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
56 |
59 |
62 |
63 |
68 |
69 |
70 |
71 |
72 |
73 |
76 |
79 |
80 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_sun_rain_cloud.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
16 |
19 |
22 |
25 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
56 |
59 |
62 |
63 |
68 |
69 |
70 |
71 |
72 |
73 |
76 |
79 |
80 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/ui/Next7DaysFragment.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.ui
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.activityViewModels
9 | import androidx.navigation.Navigation
10 | import androidx.recyclerview.widget.LinearLayoutManager
11 | import androidx.recyclerview.widget.RecyclerView
12 | import me.tangobee.weathernaut.constants.WeatherImageMapper
13 | import me.tangobee.weathernaut.databinding.FragmentNext7DaysBinding
14 | import me.tangobee.weathernaut.models.DailyWeatherRVModel
15 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
16 | import me.tangobee.weathernaut.ui.adapter.DailyWeatherRVAdapter
17 | import me.tangobee.weathernaut.viewmodels.WeatherViewModel
18 | import java.text.SimpleDateFormat
19 | import java.util.Calendar
20 | import java.util.Locale
21 | import java.util.TimeZone
22 |
23 | class Next7DaysFragment : Fragment() {
24 |
25 | private lateinit var binding: FragmentNext7DaysBinding
26 |
27 | private val weatherViewModel: WeatherViewModel by activityViewModels()
28 | private lateinit var dailyWeatherRVModelList: ArrayList
29 |
30 | override fun onCreateView(
31 | inflater: LayoutInflater, container: ViewGroup?,
32 | savedInstanceState: Bundle?
33 | ): View {
34 | // Inflate the layout for this fragment
35 | binding = FragmentNext7DaysBinding.inflate(inflater, container, false)
36 | return binding.root
37 | }
38 |
39 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
40 | super.onViewCreated(view, savedInstanceState)
41 |
42 | weatherViewModel.weatherLiveData.observe(viewLifecycleOwner) {weatherData ->
43 | if(weatherData != null) {
44 | setDailyWeatherRecyclerViewUIData(weatherData)
45 | }
46 | }
47 |
48 | binding.backButton.setOnClickListener {
49 | Navigation.findNavController(requireView()).popBackStack()
50 | }
51 | }
52 |
53 | private fun setDailyWeatherRecyclerViewUIData(weatherData: WeatherData) {
54 | val tomorrowWeatherTemp = (weatherData.daily_weather.daily.temperature_2m_min[1].toInt().toString() + "/" + weatherData.daily_weather.daily.temperature_2m_max[1].toInt().toString())
55 | binding.tomorrowWeatherTemperature.text = tomorrowWeatherTemp
56 | binding.sunriseTime.text = convertDateStringToFormattedTime(weatherData.daily_weather.daily.sunrise[1])
57 | binding.sunsetTime.text = convertDateStringToFormattedTime(weatherData.daily_weather.daily.sunset[1])
58 | binding.tomorrowWeatherIcon.setImageResource(WeatherImageMapper.getImageForWeatherCode(weatherData.daily_weather.daily.weather_code[1]))
59 |
60 | dailyWeatherRVModelList = ArrayList()
61 | for(i in 2..7) {
62 | val day = getDayOfWeek(weatherData.daily_weather.daily.time[i])
63 | val weatherTemp = (weatherData.daily_weather.daily.temperature_2m_min[i].toInt().toString() + "/" + weatherData.daily_weather.daily.temperature_2m_max[i].toInt().toString())
64 | val weatherIcon = WeatherImageMapper.getImageForWeatherCode(weatherData.daily_weather.daily.weather_code[i])
65 | dailyWeatherRVModelList.add(DailyWeatherRVModel(day, weatherTemp, weatherIcon))
66 | }
67 |
68 | val next7DaysWeatherRVLinearLayoutManager = object : LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) {
69 | override fun canScrollVertically(): Boolean {
70 | return false
71 | }
72 | }
73 | binding.next7DaysWeatherRV.setHasFixedSize(true)
74 | binding.next7DaysWeatherRV.layoutManager = next7DaysWeatherRVLinearLayoutManager
75 | val next7DaysRVAdapter = DailyWeatherRVAdapter(dailyWeatherRVModelList)
76 | binding.next7DaysWeatherRV.adapter = next7DaysRVAdapter
77 | }
78 |
79 | private fun convertDateStringToFormattedTime(dateStr: String): String {
80 | val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.getDefault())
81 | val outputFormat = SimpleDateFormat("hh:mm a", Locale.getDefault())
82 | inputFormat.timeZone = TimeZone.getDefault()
83 |
84 | val date = inputFormat.parse(dateStr)
85 | val calendar = Calendar.getInstance()
86 | if (date != null) {
87 | calendar.time = date
88 | }
89 |
90 | return outputFormat.format(calendar.time)
91 | }
92 |
93 | private fun getDayOfWeek(dateStr: String): String {
94 | val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
95 | val date = format.parse(dateStr)
96 | val calendar = Calendar.getInstance()
97 | if (date != null) {
98 | calendar.time = date
99 | }
100 |
101 | val days = arrayOf("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
102 | return days[calendar.get(Calendar.DAY_OF_WEEK) - 1]
103 | }
104 |
105 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pressure.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
13 |
14 |
16 |
19 |
22 |
25 |
28 |
31 |
34 |
37 |
40 |
43 |
46 |
49 |
52 |
55 |
58 |
61 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_next_7_days.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
32 |
33 |
41 |
42 |
53 |
54 |
65 |
66 |
74 |
75 |
85 |
86 |
96 |
97 |
107 |
108 |
118 |
119 |
120 |
128 |
129 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_thunderstorm_cloud.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
10 |
13 |
19 |
22 |
28 |
31 |
37 |
40 |
43 |
44 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/ui/SearchFragment.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.ui
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ScrollView
9 | import android.widget.Toast
10 | import androidx.appcompat.widget.SearchView.OnQueryTextListener
11 | import androidx.core.content.ContextCompat
12 | import androidx.fragment.app.activityViewModels
13 | import androidx.lifecycle.MutableLiveData
14 | import androidx.lifecycle.ViewModelProvider
15 | import androidx.recyclerview.widget.LinearLayoutManager
16 | import androidx.recyclerview.widget.RecyclerView
17 | import kotlinx.coroutines.CoroutineExceptionHandler
18 | import me.tangobee.weathernaut.R
19 | import me.tangobee.weathernaut.data.RetrofitHelper
20 | import me.tangobee.weathernaut.data.api.GeocodingService
21 | import me.tangobee.weathernaut.data.repository.GeocodingRepository
22 | import me.tangobee.weathernaut.databinding.FragmentSearchBinding
23 | import me.tangobee.weathernaut.models.GeocodingData.GeocodingModel
24 | import me.tangobee.weathernaut.models.GeocodingData.GeocodingResult
25 | import me.tangobee.weathernaut.models.NewLocationModel
26 | import me.tangobee.weathernaut.ui.adapter.LocationRVAdapter
27 | import me.tangobee.weathernaut.utils.GeocodingHelper
28 | import me.tangobee.weathernaut.viewmodels.GeocodingViewModel
29 | import me.tangobee.weathernaut.viewmodels.GeocodingViewModelFactory
30 | import me.tangobee.weathernaut.viewmodels.WeatherViewModel
31 | import java.net.UnknownHostException
32 | import kotlin.system.exitProcess
33 |
34 | class SearchFragment : Fragment() {
35 |
36 | private lateinit var binding: FragmentSearchBinding
37 |
38 | private val weatherViewModel: WeatherViewModel by activityViewModels()
39 | private var lat: Double = 0.0
40 | private var long: Double = 0.0
41 | private lateinit var geocodingViewModel: GeocodingViewModel
42 | private lateinit var coroutineExceptionHandler: CoroutineExceptionHandler
43 | private lateinit var mainContainerView: ScrollView
44 |
45 | private var locationRVModelList: ArrayList = ArrayList()
46 |
47 | override fun onCreateView(
48 | inflater: LayoutInflater, container: ViewGroup?,
49 | savedInstanceState: Bundle?
50 | ): View {
51 | binding = FragmentSearchBinding.inflate(inflater, container, false)
52 | return binding.root
53 | }
54 |
55 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 | super.onViewCreated(view, savedInstanceState)
57 | mainContainerView = requireActivity().findViewById(R.id.mainContainerView)
58 | mainContainerView.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
59 |
60 | val geocodingService = RetrofitHelper.getInstance().create(GeocodingService::class.java)
61 | val geocodingRepository = GeocodingRepository(geocodingService)
62 | geocodingViewModel = ViewModelProvider(requireActivity(), GeocodingViewModelFactory(geocodingRepository))[GeocodingViewModel::class.java]
63 |
64 | coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
65 | if (throwable is UnknownHostException) {
66 | Toast.makeText(requireContext(), getString(R.string.no_internet), Toast.LENGTH_LONG).show()
67 | exitProcess(0)
68 | }
69 | }
70 |
71 | weatherViewModel.weatherLiveData.observe(viewLifecycleOwner) { weatherData ->
72 | if(weatherData != null) {
73 | lat = weatherData.lat
74 | long = weatherData.long
75 | }
76 | }
77 |
78 | binding.cancelSearch.setOnClickListener {
79 | requireActivity().onBackPressedDispatcher.onBackPressed()
80 | }
81 |
82 | binding.searchCity.setOnQueryTextListener(object: OnQueryTextListener {
83 | override fun onQueryTextSubmit(newLocationQuery: String?): Boolean {
84 | if (newLocationQuery.isNullOrEmpty()) {
85 | showPlaceholder(getString(R.string.search_your_city))
86 | } else if (newLocationQuery.length <= 2) {
87 | showPlaceholder(getString(R.string.no_locations_found))
88 | } else {
89 | fetchLocation(newLocationQuery)
90 | }
91 | return true
92 | }
93 |
94 | override fun onQueryTextChange(locationQuery: String?): Boolean {
95 | if (locationQuery.isNullOrEmpty()) {
96 | showPlaceholder(getString(R.string.search_your_city))
97 | }
98 | return true
99 | }
100 | })
101 |
102 | observeViewModel()
103 | }
104 |
105 | private fun fetchLocation(newLocationQuery: String) {
106 | showPlaceholder(getString(R.string.loading))
107 | val newLocationDataModel = NewLocationModel(newLocationQuery)
108 | geocodingViewModel.getLocations(newLocationDataModel, coroutineExceptionHandler)
109 | }
110 |
111 | private fun observeViewModel() {
112 | geocodingViewModel.locationLiveData.observe(viewLifecycleOwner) { locationData ->
113 | if (locationData == null) {
114 | showPlaceholder(getString(R.string.something_went_wrong))
115 | Toast.makeText(requireContext(), getString(R.string.api_fetching_error), Toast.LENGTH_SHORT).show()
116 | } else if (locationData.results.isNullOrEmpty()) {
117 | showPlaceholder(getString(R.string.no_locations_found))
118 | } else {
119 | setLocationRecyclerViewUIData(locationData)
120 | }
121 | }
122 | }
123 |
124 | private fun setLocationRecyclerViewUIData(locationData: GeocodingModel) {
125 | locationRVModelList = GeocodingHelper.sortLocationsByProximity(
126 | lat,
127 | long,
128 | locationData.results ?: ArrayList()
129 | ).toCollection(ArrayList())
130 |
131 | if (locationRVModelList.isEmpty()) {
132 | showPlaceholder(getString(R.string.no_locations_found))
133 | } else {
134 | binding.citiesRecyclerView.visibility = View.VISIBLE
135 | binding.searchPlaceholderTV.visibility = View.GONE
136 |
137 | val locationRVLinearLayoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
138 | binding.citiesRecyclerView.setHasFixedSize(true)
139 | binding.citiesRecyclerView.layoutManager = locationRVLinearLayoutManager
140 | val locationAdapter = LocationRVAdapter(locationRVModelList, lat, long, coroutineExceptionHandler, weatherViewModel)
141 | binding.citiesRecyclerView.adapter = locationAdapter
142 | }
143 | }
144 |
145 | private fun showPlaceholder(message: String) {
146 | binding.searchPlaceholderTV.text = message
147 | binding.citiesRecyclerView.visibility = View.GONE
148 | binding.searchPlaceholderTV.visibility = View.VISIBLE
149 | }
150 |
151 | override fun onResume() {
152 | super.onResume()
153 | showPlaceholder(getString(R.string.search_your_city))
154 | }
155 |
156 | override fun onDestroy() {
157 | super.onDestroy()
158 | mainContainerView.background = ContextCompat.getDrawable(requireContext(), R.drawable.home_background)
159 | if(locationRVModelList.isNotEmpty()) {
160 | locationRVModelList.clear()
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/ui/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.ui
2 |
3 | import android.os.Bundle
4 | import android.text.SpannableString
5 | import android.text.style.RelativeSizeSpan
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.constraintlayout.widget.ConstraintSet
10 | import androidx.core.content.ContextCompat
11 | import androidx.core.content.res.ResourcesCompat
12 | import androidx.fragment.app.Fragment
13 | import androidx.fragment.app.activityViewModels
14 | import androidx.navigation.Navigation
15 | import androidx.recyclerview.widget.LinearLayoutManager
16 | import androidx.recyclerview.widget.RecyclerView
17 | import me.tangobee.weathernaut.R
18 | import me.tangobee.weathernaut.constants.WeatherCodes
19 | import me.tangobee.weathernaut.constants.WeatherImageMapper
20 | import me.tangobee.weathernaut.databinding.FragmentHomeBinding
21 | import me.tangobee.weathernaut.models.HourlyWeatherRVModel
22 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
23 | import me.tangobee.weathernaut.ui.adapter.HourlyWeatherRVAdapter
24 | import me.tangobee.weathernaut.viewmodels.WeatherViewModel
25 | import java.text.SimpleDateFormat
26 | import java.util.Calendar
27 | import java.util.Locale
28 | import java.util.TimeZone
29 |
30 |
31 | class HomeFragment : Fragment() {
32 |
33 | private lateinit var binding: FragmentHomeBinding
34 |
35 | private val weatherViewModel: WeatherViewModel by activityViewModels()
36 | private lateinit var todayHourlyWeatherRVAdapter: HourlyWeatherRVAdapter
37 | private lateinit var tomorrowHourlyWeatherRVAdapter: HourlyWeatherRVAdapter
38 | private val todayHourlyWeatherRVModelList: ArrayList = ArrayList()
39 | private val tomorrowHourlyWeatherRVModelList: ArrayList = ArrayList()
40 |
41 | override fun onCreateView(
42 | inflater: LayoutInflater, container: ViewGroup?,
43 | savedInstanceState: Bundle?
44 | ): View {
45 | // Inflate the layout for this fragment
46 | binding = FragmentHomeBinding.inflate(inflater, container, false)
47 | return binding.root
48 | }
49 |
50 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
51 | super.onViewCreated(view, savedInstanceState)
52 |
53 | binding.next7DaysIndicator.setOnClickListener {
54 | Navigation.findNavController(requireView()).navigate(R.id.action_homeFragment_to_next7DaysFragment)
55 | }
56 |
57 | binding.settings.setOnClickListener {
58 | Navigation.findNavController(requireView()).navigate(R.id.action_homeFragment_to_settingsFragment)
59 | }
60 |
61 | binding.searchCities.setOnClickListener {
62 | Navigation.findNavController(requireView()).navigate(R.id.action_homeFragment_to_searchFragment)
63 | }
64 |
65 | binding.todayIndicator.setOnClickListener {
66 | changeIndicatorDotPosition(R.id.todayIndicator)
67 | binding.hourlyWeatherRV.swapAdapter(todayHourlyWeatherRVAdapter, false)
68 |
69 | binding.todayIndicator.setTextColor(ContextCompat.getColor(requireContext(), R.color.textColor))
70 | binding.tomorrowIndicator.setTextColor(ContextCompat.getColor(requireContext(), R.color.unselectedIndicatorColor))
71 |
72 | val boldTypeface = ResourcesCompat.getFont(requireContext(), R.font.inter_bold)
73 | val typeface = ResourcesCompat.getFont(requireContext(), R.font.inter)
74 | binding.todayIndicator.typeface = boldTypeface
75 | binding.tomorrowIndicator.typeface = typeface
76 |
77 | binding.todayIndicator.isEnabled = false
78 | binding.tomorrowIndicator.isEnabled = true
79 | }
80 |
81 | binding.tomorrowIndicator.setOnClickListener {
82 | changeIndicatorDotPosition(R.id.tomorrowIndicator)
83 | binding.hourlyWeatherRV.swapAdapter(tomorrowHourlyWeatherRVAdapter, false)
84 |
85 | binding.tomorrowIndicator.setTextColor(ContextCompat.getColor(requireContext(), R.color.textColor))
86 | binding.todayIndicator.setTextColor(ContextCompat.getColor(requireContext(), R.color.unselectedIndicatorColor))
87 |
88 | val boldTypeface = ResourcesCompat.getFont(requireContext(), R.font.inter_bold)
89 | val typeface = ResourcesCompat.getFont(requireContext(), R.font.inter)
90 | binding.tomorrowIndicator.typeface = boldTypeface
91 | binding.todayIndicator.typeface = typeface
92 |
93 | binding.tomorrowIndicator.isEnabled = false
94 | binding.todayIndicator.isEnabled = true
95 | }
96 |
97 |
98 | weatherViewModel.weatherLiveData.observe(viewLifecycleOwner) {weatherData ->
99 | if(weatherData != null) {
100 | setCurrentWeatherUIData(weatherData)
101 | setHourlyRecyclerViewUIData(weatherData)
102 | }
103 | }
104 | }
105 |
106 | private fun setCurrentWeatherUIData(weatherData: WeatherData) {
107 | val city = "${weatherData.city}\n"
108 | val country = weatherData.country
109 | val region = SpannableString("$city$country")
110 | region.setSpan(
111 | RelativeSizeSpan(0.7f),
112 | city.length,
113 | region.length,
114 | SpannableString.SPAN_INCLUSIVE_EXCLUSIVE
115 | )
116 | binding.locationName.text = region
117 |
118 | binding.date.text = convertISO8601ToCustomDateFormat(weatherData.current_weather.current.time)
119 | binding.currentWeatherTemperature.text = weatherData.current_weather.current.temperature_2m.toInt().toString()
120 | binding.weatherUnit.text = weatherData.current_weather.current_units.temperature_2m
121 | binding.weatherIcon.setImageResource(WeatherImageMapper.getImageForWeatherCode(weatherData.current_weather.current.weather_code))
122 | binding.currentWeatherType.text = WeatherCodes.weatherConstants.first {it.code == weatherData.current_weather.current.weather_code}.description
123 |
124 | val currentPressure = weatherData.current_weather.current.pressure_msl.toString() + weatherData.current_weather.current_units.pressure_msl
125 | binding.pressureValue.text = currentPressure
126 |
127 | val windPressure = weatherData.current_weather.current.wind_speed_10m.toString() + weatherData.current_weather.current_units.wind_speed_10m
128 | binding.windValue.text = windPressure
129 |
130 | val humidityPressure = weatherData.current_weather.current.relative_humidity_2m.toString() + weatherData.current_weather.current_units.relative_humidity_2m
131 | binding.humidityValue.text = humidityPressure
132 | }
133 | private fun setHourlyRecyclerViewUIData(weatherData: WeatherData) {
134 | var currentHourlyWeatherItemPosition = 0
135 | todayHourlyWeatherRVModelList.clear()
136 | for(i in 0 .. 23) {
137 | var time = convertDateStringToFormattedTime(weatherData.hourly_weather.hourly.time[i])
138 | if(isCurrentLocalTime(time)) {
139 | time = "now"
140 | currentHourlyWeatherItemPosition = i
141 | }
142 |
143 | val weatherIcon = WeatherImageMapper.getImageForWeatherCode(weatherData.hourly_weather.hourly.weather_code[i])
144 | val weatherTemp = weatherData.hourly_weather.hourly.temperature_2m[i]
145 |
146 | todayHourlyWeatherRVModelList.add(HourlyWeatherRVModel(time, weatherIcon, "${weatherTemp.toInt()}°"))
147 | }
148 |
149 | tomorrowHourlyWeatherRVModelList.clear()
150 | for(i in 24 .. 47) {
151 | val time = convertDateStringToFormattedTime(weatherData.hourly_weather.hourly.time[i])
152 | val weatherIcon = WeatherImageMapper.getImageForWeatherCode(weatherData.hourly_weather.hourly.weather_code[i])
153 | val weatherTemp = weatherData.hourly_weather.hourly.temperature_2m[i]
154 |
155 | tomorrowHourlyWeatherRVModelList.add(HourlyWeatherRVModel(time, weatherIcon, "${weatherTemp.toInt()}°"))
156 | }
157 |
158 | binding.hourlyWeatherRV.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
159 | todayHourlyWeatherRVAdapter = HourlyWeatherRVAdapter(todayHourlyWeatherRVModelList)
160 | tomorrowHourlyWeatherRVAdapter = HourlyWeatherRVAdapter(tomorrowHourlyWeatherRVModelList)
161 | binding.hourlyWeatherRV.adapter = todayHourlyWeatherRVAdapter
162 |
163 | binding.hourlyWeatherRV.scrollToPosition(currentHourlyWeatherItemPosition)
164 |
165 | binding.tomorrowIndicator.isEnabled = true
166 | binding.todayIndicator.isEnabled = true
167 | }
168 |
169 | private fun convertISO8601ToCustomDateFormat(iso8601Date: String): String? {
170 | val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.getDefault())
171 | val outputFormat = SimpleDateFormat("EEE, dd MMM yyyy", Locale.getDefault())
172 |
173 | val date = inputFormat.parse(iso8601Date)
174 | return date?.let { outputFormat.format(it) }
175 | }
176 | private fun convertDateStringToFormattedTime(dateStr: String): String {
177 | val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.getDefault())
178 | val outputFormat = SimpleDateFormat("hh:mm a", Locale.getDefault())
179 | inputFormat.timeZone = TimeZone.getDefault()
180 |
181 | val date = inputFormat.parse(dateStr)
182 | val calendar = Calendar.getInstance()
183 | if (date != null) {
184 | calendar.time = date
185 | }
186 |
187 | return outputFormat.format(calendar.time)
188 | }
189 |
190 | private fun changeIndicatorDotPosition(viewId: Int) {
191 | val constraintSet = ConstraintSet()
192 | constraintSet.clone(binding.homeFragmentLayout)
193 | constraintSet.connect(R.id.indicator, ConstraintSet.START, viewId, ConstraintSet.START, 0)
194 | constraintSet.connect(R.id.indicator, ConstraintSet.END, viewId, ConstraintSet.END, 0)
195 | constraintSet.applyTo(binding.homeFragmentLayout)
196 | }
197 |
198 | private fun isCurrentLocalTime(timeString: String): Boolean {
199 | val sdf = SimpleDateFormat("hh:mm a", Locale.getDefault())
200 | val currentTime = Calendar.getInstance()
201 | val parsedTime = sdf.parse(timeString)
202 |
203 | if (parsedTime != null) {
204 | val parsedCalendar = Calendar.getInstance()
205 | parsedCalendar.time = parsedTime
206 |
207 | return currentTime.get(Calendar.HOUR_OF_DAY) == parsedCalendar.get(Calendar.HOUR_OF_DAY)
208 | }
209 |
210 | return false
211 | }
212 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/tangobee/weathernaut/ui/SettingsFragment.kt:
--------------------------------------------------------------------------------
1 | package me.tangobee.weathernaut.ui
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import android.os.Bundle
6 | import androidx.fragment.app.Fragment
7 | import android.view.LayoutInflater
8 | import android.view.MenuItem
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.widget.ScrollView
12 | import android.widget.Toast
13 | import androidx.appcompat.widget.PopupMenu
14 | import androidx.core.content.ContextCompat
15 | import androidx.fragment.app.activityViewModels
16 | import me.tangobee.weathernaut.R
17 | import me.tangobee.weathernaut.constants.UnitsMapper
18 | import me.tangobee.weathernaut.databinding.FragmentSettingsBinding
19 | import me.tangobee.weathernaut.models.SettingsModel
20 | import me.tangobee.weathernaut.models.WeatherData.WeatherData
21 | import me.tangobee.weathernaut.services.WeatherMusicService
22 | import me.tangobee.weathernaut.utils.SharedPreferencesHelper
23 | import me.tangobee.weathernaut.utils.WeatherHelper
24 | import me.tangobee.weathernaut.viewmodels.WeatherViewModel
25 |
26 | class SettingsFragment : Fragment() {
27 |
28 | private lateinit var binding: FragmentSettingsBinding
29 |
30 | private val weatherViewModel: WeatherViewModel by activityViewModels()
31 | private lateinit var sharedPreferencesHelper: SharedPreferencesHelper
32 | private lateinit var settingsModel: SettingsModel
33 |
34 | private lateinit var weatherData: WeatherData
35 |
36 | private lateinit var mainContainerView: ScrollView
37 |
38 | override fun onCreateView(
39 | inflater: LayoutInflater, container: ViewGroup?,
40 | savedInstanceState: Bundle?
41 | ): View {
42 | // Inflate the layout for this fragment
43 | binding = FragmentSettingsBinding.inflate(inflater, container, false)
44 | return binding.root
45 | }
46 |
47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 | super.onViewCreated(view, savedInstanceState)
49 | mainContainerView = requireActivity().findViewById(R.id.mainContainerView)
50 | mainContainerView.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
51 |
52 | weatherViewModel.weatherLiveData.observe(viewLifecycleOwner) { weatherData ->
53 | if(weatherData != null) {
54 | this.weatherData = weatherData
55 | }
56 | }
57 |
58 | sharedPreferencesHelper = SharedPreferencesHelper(requireContext())
59 | settingsModel = sharedPreferencesHelper.getSettings() ?: SettingsModel()
60 | sharedPreferencesHelper.saveSettings(settingsModel)
61 | setSettingsUIDate()
62 |
63 | binding.feedback.setOnClickListener { openURL(Uri.parse("mailto:hello.weathernaut@outlook.com?subject=Feedback on Weathernaut app.")) }
64 | binding.privacyPolicy.setOnClickListener { openURL(Uri.parse("https://tangobee.netlify.app/Weathernaut/privacy-policy")) }
65 | binding.donation.setOnClickListener { openURL(Uri.parse("https://tangobee.netlify.app/Weathernaut/donation")) }
66 | binding.credits.setOnClickListener { openURL(Uri.parse("https://tangobee.netlify.app/Weathernaut/credits")) }
67 |
68 | binding.temperatureUnit.setOnClickListener {showTemperatureUnitPopup(binding.temperatureSpinner)}
69 | binding.windSpeedUnit.setOnClickListener {showWindSpeedUnitPopup(binding.windSpeedSpinner)}
70 | binding.atmosphericPressureUnit.setOnClickListener {showAtmosphericPressureUnitPopup(binding.atmosphericPressureSpinner)}
71 |
72 | binding.backButton.setOnClickListener {
73 | requireActivity().onBackPressedDispatcher.onBackPressed()
74 | }
75 |
76 | binding.weatherMusicSwitch.setOnClickListener {
77 | val flag = binding.weatherMusicSwitch.isChecked
78 | binding.weatherMusicSwitch.setChecked(!flag)
79 |
80 |
81 | sharedPreferencesHelper.updateSettings {
82 | it.isMusicOn = !flag
83 | it
84 | }
85 |
86 | val currentSettings = sharedPreferencesHelper.getSettings()
87 | updateWeatherData(currentSettings)
88 |
89 | weatherMusicToggle(!flag)
90 | }
91 |
92 | binding.weatherMusicWrapper.setOnClickListener {
93 | val flag = binding.weatherMusicSwitch.isChecked
94 | binding.weatherMusicSwitch.setChecked(!flag)
95 |
96 | sharedPreferencesHelper.updateSettings {
97 | it.isMusicOn = !flag
98 | it
99 | }
100 |
101 | val currentSettings = sharedPreferencesHelper.getSettings()
102 | updateWeatherData(currentSettings)
103 |
104 | weatherMusicToggle(!flag)
105 | }
106 | }
107 |
108 | private fun setSettingsUIDate() {
109 | binding.temperatureSpinner.text = UnitsMapper.getShorthandUnit(settingsModel.tempUnit)
110 | binding.windSpeedSpinner.text = UnitsMapper.getShorthandUnit(settingsModel.windSpeedUnit)
111 | binding.atmosphericPressureSpinner.text = UnitsMapper.getShorthandUnit(settingsModel.pressureUnit)
112 |
113 | binding.weatherMusicSwitch.setChecked(settingsModel.isMusicOn)
114 | }
115 |
116 | private fun weatherMusicToggle(musicFlag: Boolean) {
117 | if(musicFlag) {
118 | Toast.makeText(requireContext(), "The music will start in a few seconds!", Toast.LENGTH_SHORT).show()
119 | val startMusicIntent = Intent(requireContext(), WeatherMusicService::class.java)
120 | requireActivity().startService(startMusicIntent)
121 | }
122 | else {
123 | val stopMusicIntent = Intent(requireContext(), WeatherMusicService::class.java)
124 | requireActivity().stopService(stopMusicIntent)
125 | }
126 | }
127 |
128 | private fun openURL(uri: Uri) {
129 | val intent = Intent(Intent.ACTION_VIEW, uri)
130 | startActivity(intent)
131 | }
132 |
133 | private fun showTemperatureUnitPopup(view: View) {
134 | val popup = PopupMenu(requireContext(), view)
135 | popup.inflate(R.menu.temperature_unit_menu)
136 | popup.setOnMenuItemClickListener { item: MenuItem? ->
137 | when (item!!.itemId) {
138 | R.id.celsius -> {
139 | binding.temperatureSpinner.text = item.title
140 |
141 | sharedPreferencesHelper.updateSettings {
142 | it.tempUnit = UnitsMapper.getFullUnit(binding.temperatureSpinner.text.toString())
143 | it
144 | }
145 |
146 | val currentSettings = sharedPreferencesHelper.getSettings()
147 | updateWeatherData(currentSettings)
148 | }
149 |
150 | R.id.fahrenheit -> {
151 | binding.temperatureSpinner.text = item.title
152 |
153 | sharedPreferencesHelper.updateSettings {
154 | it.tempUnit = UnitsMapper.getFullUnit(binding.temperatureSpinner.text.toString())
155 | it
156 | }
157 |
158 | val currentSettings = sharedPreferencesHelper.getSettings()
159 | updateWeatherData(currentSettings)
160 | }
161 | }
162 |
163 | true
164 | }
165 |
166 | popup.show()
167 | }
168 | private fun showWindSpeedUnitPopup(view: View) {
169 | val popup = PopupMenu(requireContext(), view)
170 | popup.inflate(R.menu.windsped_unit_menu)
171 | popup.setOnMenuItemClickListener { item: MenuItem? ->
172 | when (item!!.itemId) {
173 | R.id.kilometers -> {
174 | binding.windSpeedSpinner.text = item.title
175 |
176 | sharedPreferencesHelper.updateSettings {
177 | it.windSpeedUnit = UnitsMapper.getFullUnit(binding.windSpeedSpinner.text.toString())
178 | it
179 | }
180 |
181 | val currentSettings = sharedPreferencesHelper.getSettings()
182 | updateWeatherData(currentSettings)
183 | }
184 |
185 | R.id.meters -> {
186 | binding.windSpeedSpinner.text = item.title
187 |
188 | sharedPreferencesHelper.updateSettings {
189 | it.windSpeedUnit = UnitsMapper.getFullUnit(binding.windSpeedSpinner.text.toString())
190 | it
191 | }
192 |
193 | val currentSettings = sharedPreferencesHelper.getSettings()
194 | updateWeatherData(currentSettings)
195 | }
196 |
197 | R.id.miles -> {
198 | binding.windSpeedSpinner.text = item.title
199 |
200 | sharedPreferencesHelper.updateSettings {
201 | it.windSpeedUnit = UnitsMapper.getFullUnit(binding.windSpeedSpinner.text.toString())
202 | it
203 | }
204 |
205 | val currentSettings = sharedPreferencesHelper.getSettings()
206 | updateWeatherData(currentSettings)
207 | }
208 | }
209 |
210 | true
211 | }
212 |
213 | popup.show()
214 | }
215 | private fun showAtmosphericPressureUnitPopup(view: View) {
216 | val popup = PopupMenu(requireContext(), view)
217 | popup.inflate(R.menu.atmospheric_pressure_unit_menu)
218 | popup.setOnMenuItemClickListener { item: MenuItem? ->
219 | when (item!!.itemId) {
220 | R.id.hpa -> {
221 | binding.atmosphericPressureSpinner.text = item.title
222 |
223 | sharedPreferencesHelper.updateSettings {
224 | it.pressureUnit = UnitsMapper.getFullUnit(binding.atmosphericPressureSpinner.text.toString())
225 | it
226 | }
227 |
228 | val currentSettings = sharedPreferencesHelper.getSettings()
229 | updateWeatherData(currentSettings)
230 | }
231 |
232 | R.id.atm -> {
233 | binding.atmosphericPressureSpinner.text = item.title
234 |
235 | sharedPreferencesHelper.updateSettings {
236 | it.pressureUnit = UnitsMapper.getFullUnit(binding.atmosphericPressureSpinner.text.toString())
237 | it
238 | }
239 |
240 | val currentSettings = sharedPreferencesHelper.getSettings()
241 | updateWeatherData(currentSettings)
242 | }
243 | }
244 |
245 | true
246 | }
247 |
248 | popup.show()
249 | }
250 |
251 | private fun updateWeatherData(currentSettings: SettingsModel?) {
252 | if(currentSettings != null) {
253 | val weatherHelper = WeatherHelper(currentSettings, weatherData)
254 | val newWeatherData = weatherHelper.convertWeatherData()
255 |
256 | weatherViewModel.updateWeatherData(newWeatherData)
257 | }
258 | }
259 |
260 | override fun onDestroy() {
261 | super.onDestroy()
262 | mainContainerView.background = ContextCompat.getDrawable(requireContext(), R.drawable.home_background)
263 | }
264 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_weather_snow_cloud.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
10 |
13 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
33 |
36 |
37 |
--------------------------------------------------------------------------------