From 6936eab68542a339840ce032e89c694a82dac9ae Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 28 Mar 2023 10:30:49 -0400 Subject: [PATCH] initial commit --- .gitignore | 10 + app/.gitignore | 1 + app/build.gradle.kts | 96 +++++++++ app/proguard-rules.pro | 21 ++ app/src/main/AndroidManifest.xml | 27 +++ .../java/com/henryhiles/qweather/QWeather.kt | 21 ++ .../data/location/DefaultLocationTracker.kt | 56 ++++++ .../qweather/data/mappers/WeatherMappers.kt | 35 ++++ .../qweather/data/remote/WeatherApi.kt | 12 ++ .../qweather/data/remote/WeatherDataDto.kt | 17 ++ .../qweather/data/remote/WeatherDto.kt | 8 + .../data/repository/WeatherRepositoryImpl.kt | 18 ++ .../com/henryhiles/qweather/di/AppModule.kt | 40 ++++ .../henryhiles/qweather/di/LocationModule.kt | 20 ++ .../qweather/di/RepositoryModule.kt | 19 ++ .../domain/location/LocationTracker.kt | 7 + .../domain/repository/WeatherRepository.kt | 8 + .../qweather/domain/util/Resource.kt | 6 + .../qweather/domain/weather/WeatherData.kt | 12 ++ .../qweather/domain/weather/WeatherInfo.kt | 6 + .../qweather/domain/weather/WeatherType.kt | 154 +++++++++++++++ .../presentation/activity/MainActivity.kt | 49 +++++ .../presentation/components/WeatherCard.kt | 81 ++++++++ .../components/WeatherDataDisplay.kt | 33 ++++ .../presentation/components/WeatherDay.kt | 2 + .../components/WeatherForecast.kt | 40 ++++ .../presentation/components/WeatherHour.kt | 39 ++++ .../presentation/screen/TodayScreen.kt | 61 ++++++ .../qweather/presentation/ui/theme/Color.kt | 11 ++ .../qweather/presentation/ui/theme/Theme.kt | 59 ++++++ .../viewmodel/WeatherViewModel.kt | 58 ++++++ .../drawable-v24/ic_launcher_foreground.xml | 30 +++ app/src/main/res/drawable/ic_cloudy.xml | 17 ++ app/src/main/res/drawable/ic_drop.xml | 9 + app/src/main/res/drawable/ic_heavysnow.xml | 44 +++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ app/src/main/res/drawable/ic_pressure.xml | 33 ++++ app/src/main/res/drawable/ic_rainshower.xml | 89 +++++++++ app/src/main/res/drawable/ic_rainy.xml | 49 +++++ app/src/main/res/drawable/ic_rainythunder.xml | 38 ++++ app/src/main/res/drawable/ic_snowy.xml | 92 +++++++++ app/src/main/res/drawable/ic_snowyrainy.xml | 81 ++++++++ app/src/main/res/drawable/ic_sunny.xml | 87 ++++++++ app/src/main/res/drawable/ic_sunnycloudy.xml | 73 +++++++ app/src/main/res/drawable/ic_sunnyrainy.xml | 118 +++++++++++ app/src/main/res/drawable/ic_thunder.xml | 26 +++ app/src/main/res/drawable/ic_very_cloudy.xml | 23 +++ app/src/main/res/drawable/ic_wind.xml | 9 + app/src/main/res/drawable/ic_windy.xml | 41 ++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 5 + build.gradle.kts | 5 + gradle.properties | 23 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 ++++++++++++++++++ gradlew.bat | 89 +++++++++ settings.gradle | 16 ++ 70 files changed, 2398 insertions(+) create mode 100644 .gitignore create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/henryhiles/qweather/QWeather.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/data/remote/WeatherApi.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDataDto.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDto.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/data/repository/WeatherRepositoryImpl.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/di/AppModule.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/di/LocationModule.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/di/RepositoryModule.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/domain/location/LocationTracker.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/domain/util/Resource.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherInfo.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherType.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/activity/MainActivity.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherCard.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDataDisplay.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDay.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherForecast.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherHour.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/screen/TodayScreen.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Color.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Theme.kt create mode 100644 app/src/main/java/com/henryhiles/qweather/presentation/viewmodel/WeatherViewModel.kt create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_cloudy.xml create mode 100644 app/src/main/res/drawable/ic_drop.xml create mode 100644 app/src/main/res/drawable/ic_heavysnow.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_pressure.xml create mode 100644 app/src/main/res/drawable/ic_rainshower.xml create mode 100644 app/src/main/res/drawable/ic_rainy.xml create mode 100644 app/src/main/res/drawable/ic_rainythunder.xml create mode 100644 app/src/main/res/drawable/ic_snowy.xml create mode 100644 app/src/main/res/drawable/ic_snowyrainy.xml create mode 100644 app/src/main/res/drawable/ic_sunny.xml create mode 100644 app/src/main/res/drawable/ic_sunnycloudy.xml create mode 100644 app/src/main/res/drawable/ic_sunnyrainy.xml create mode 100644 app/src/main/res/drawable/ic_thunder.xml create mode 100644 app/src/main/res/drawable/ic_very_cloudy.xml create mode 100644 app/src/main/res/drawable/ic_wind.xml create mode 100644 app/src/main/res/drawable/ic_windy.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec7d729 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.iml +/.idea +.gradle +/local.properties +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..cfa6faa --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,96 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("kotlin-kapt") +} + +android { + namespace = "com.henryhiles.qweather" + compileSdk = 33 + + defaultConfig { + applicationId = "com.henryhiles.qweather" + minSdk = 21 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + named("release") { + isMinifyEnabled = true + setProguardFiles( + listOf( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + ) + } + named("debug") { + applicationIdSuffix = ".debug" + } + } + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.3" + } + packagingOptions { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.compose.ui:ui:1.4.0") + implementation("androidx.compose.material3:material3:1.1.0-beta01") + implementation("androidx.compose.ui:ui-tooling-preview:1.4.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") + implementation("androidx.activity:activity-compose:1.7.0") + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0") + debugImplementation("androidx.compose.ui:ui-tooling:1.4.0") + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2") + + // Voyager + val voyagerVersion = "1.0.0-rc04" + + implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion") + implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion") + implementation("cafe.adriel.voyager:voyager-androidx:$voyagerVersion") + implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion") + + // Koin + val koinVersion = "3.2.0" + + implementation("io.insert-koin:koin-core:$koinVersion") + implementation("io.insert-koin:koin-android:$koinVersion") + implementation("io.insert-koin:koin-androidx-compose:$koinVersion") + + // Retrofit + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-moshi:2.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3") + + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ff59496 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fff1a58 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/QWeather.kt b/app/src/main/java/com/henryhiles/qweather/QWeather.kt new file mode 100644 index 0000000..34e1a5e --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/QWeather.kt @@ -0,0 +1,21 @@ +package com.henryhiles.qweather + +import android.app.Application +import com.henryhiles.qweather.di.appModule +import com.henryhiles.qweather.di.locationModule +import com.henryhiles.qweather.di.repositoryModule +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin + +class QWeather : Application() { + override fun onCreate() { + super.onCreate() + startKoin { + androidContext(this@QWeather) + modules( + appModule, locationModule, repositoryModule + ) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt b/app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt new file mode 100644 index 0000000..02f0184 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt @@ -0,0 +1,56 @@ +package com.henryhiles.qweather.data.location + +import android.Manifest +import android.app.Application +import android.content.Context +import android.content.pm.PackageManager +import android.location.Location +import android.location.LocationManager +import androidx.core.content.ContextCompat +import com.henryhiles.qweather.domain.location.LocationTracker + +class DefaultLocationTracker constructor( + private val application: Application +) : LocationTracker { + override suspend fun getCurrentLocation(): Location? { + val hasAccessFineLocationPermission = ContextCompat.checkSelfPermission( + application, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + + val hasAccessCoarseLocationPermission = ContextCompat.checkSelfPermission( + application, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + + val locationManager = + application.getSystemService(Context.LOCATION_SERVICE) as LocationManager + val isGpsEnabled = locationManager.isProviderEnabled( + LocationManager.GPS_PROVIDER + ) + if (!hasAccessFineLocationPermission || !isGpsEnabled) return null + + return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) +// return suspendCancellableCoroutine { cont -> +// locationManager.getLastKnownLocation.apply { +// if (isComplete) { +// if (isSuccessful) { +// cont.resume(result) +// } else { +// cont.resume(null) +// } +// return@suspendCancellableCoroutine +// } +// addOnSuccessListener { +// cont.resume(it) +// } +// addOnFailureListener { +// cont.resume(null) +// } +// addOnCanceledListener { +// cont.cancel() +// } +// } +// } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt b/app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt new file mode 100644 index 0000000..62b05de --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt @@ -0,0 +1,35 @@ +package com.henryhiles.qweather.data.mappers + +import com.henryhiles.qweather.data.remote.WeatherDataDto +import com.henryhiles.qweather.data.remote.WeatherDto +import com.henryhiles.qweather.domain.weather.WeatherData +import com.henryhiles.qweather.domain.weather.WeatherInfo +import com.henryhiles.qweather.domain.weather.WeatherType +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +private data class IndexedWeatherData(val index: Int, val data: WeatherData) + +fun WeatherDataDto.toWeatherDataMap(): Map> { + return time.mapIndexed { index, time -> + IndexedWeatherData( + index = index, data = WeatherData( + time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME), + temperatureCelsius = temperatures[index], + pressure = pressures[index], + windSpeed = windSpeeds[index], + humidity = humidities[index], + weatherType = WeatherType.fromWMO(weatherCodes[index]) + ) + ) + }.groupBy { it.index / 24 }.mapValues { entry -> entry.value.map { it.data } } +} + +fun WeatherDto.toWeatherInfo(): WeatherInfo { + val weatherDataMap = weatherData.toWeatherDataMap() + val now = LocalDateTime.now() + val currentWeatherData = weatherDataMap[0]?.find { + it.time.hour == now.hour + } + return WeatherInfo(weatherDataPerDay = weatherDataMap, currentWeatherData = currentWeatherData) +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherApi.kt b/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherApi.kt new file mode 100644 index 0000000..112c766 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherApi.kt @@ -0,0 +1,12 @@ +package com.henryhiles.qweather.data.remote + +import retrofit2.http.GET +import retrofit2.http.Query + +interface WeatherApi { + @GET("v1/forecast?hourly=temperature_2m,weathercode,relativehumidity_2m,windspeed_10m,pressure_msl") + suspend fun getWeatherData( + @Query("latitude") lat: Double, + @Query("longitude") long: Double + ): WeatherDto +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDataDto.kt b/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDataDto.kt new file mode 100644 index 0000000..82982ba --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDataDto.kt @@ -0,0 +1,17 @@ +package com.henryhiles.qweather.data.remote + +import com.squareup.moshi.Json + +data class WeatherDataDto( + val time: List, + @field:Json(name = "temperature_2m") + val temperatures: List, + @field:Json(name = "weathercode") + val weatherCodes: List, + @field:Json(name = "pressure_msl") + val pressures: List, + @field:Json(name = "windspeed_10m") + val windSpeeds: List, + @field:Json(name = "relativehumidity_2m") + val humidities: List +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDto.kt b/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDto.kt new file mode 100644 index 0000000..cc977ae --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/data/remote/WeatherDto.kt @@ -0,0 +1,8 @@ +package com.henryhiles.qweather.data.remote + +import com.squareup.moshi.Json + +data class WeatherDto( + @field:Json(name = "hourly") + val weatherData: WeatherDataDto +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/data/repository/WeatherRepositoryImpl.kt b/app/src/main/java/com/henryhiles/qweather/data/repository/WeatherRepositoryImpl.kt new file mode 100644 index 0000000..ee36300 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/data/repository/WeatherRepositoryImpl.kt @@ -0,0 +1,18 @@ +package com.henryhiles.qweather.data.repository + +import com.henryhiles.qweather.data.mappers.toWeatherInfo +import com.henryhiles.qweather.data.remote.WeatherApi +import com.henryhiles.qweather.domain.repository.WeatherRepository +import com.henryhiles.qweather.domain.util.Resource +import com.henryhiles.qweather.domain.weather.WeatherInfo + +class WeatherRepositoryImpl constructor(private val api: WeatherApi) : WeatherRepository { + override suspend fun getWeatherData(lat: Double, long: Double): Resource { + return try { + Resource.Success(data = api.getWeatherData(lat = lat, long = long).toWeatherInfo()) + } catch (e: Exception) { + e.printStackTrace() + Resource.Error(e.message ?: "An unknown error occurred.") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt b/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt new file mode 100644 index 0000000..cefda7b --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt @@ -0,0 +1,40 @@ +package com.henryhiles.qweather.di + +import com.henryhiles.qweather.data.remote.WeatherApi +import com.henryhiles.qweather.presentation.viewmodel.WeatherViewModel +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.create + +//@Module +//@InstallIn(SingletonComponent::class) +//object AppModule { +// @Provides +// @Singleton +// fun provideWeatherApi(): WeatherApi { +// return Retrofit.Builder().baseUrl("https://api.open-meteo.com") +// .addConverterFactory(MoshiConverterFactory.create()).build().create() +// } +// +// @Provides +// @Singleton +// fun provideFusedLocationProviderClient(app: Application): FusedLocationProviderClient { +// return LocationServices.getFusedLocationProviderClient(app) +// } +//} + +val appModule = module { + fun provideWeatherApi(): WeatherApi { + return Retrofit.Builder().baseUrl("https://api.open-meteo.com") + .addConverterFactory(MoshiConverterFactory.create()).build().create() + } + + singleOf(::provideWeatherApi) +// single { +// LocationServices.getFusedLocationProviderClient(get()) +// } + viewModelOf(::WeatherViewModel) +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/di/LocationModule.kt b/app/src/main/java/com/henryhiles/qweather/di/LocationModule.kt new file mode 100644 index 0000000..eaafa10 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/di/LocationModule.kt @@ -0,0 +1,20 @@ +package com.henryhiles.qweather.di + +import com.henryhiles.qweather.data.location.DefaultLocationTracker +import com.henryhiles.qweather.domain.location.LocationTracker +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +//@Module +//@InstallIn(SingletonComponent::class) +//abstract class LocationModule { +// @Binds +// @Singleton +// abstract fun bindLocationTracker(defaultLocationTracker: DefaultLocationTracker): LocationTracker +//} + + +val locationModule = module { + singleOf(::DefaultLocationTracker) bind LocationTracker::class +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/di/RepositoryModule.kt b/app/src/main/java/com/henryhiles/qweather/di/RepositoryModule.kt new file mode 100644 index 0000000..ee5e862 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/di/RepositoryModule.kt @@ -0,0 +1,19 @@ +package com.henryhiles.qweather.di + +import com.henryhiles.qweather.data.repository.WeatherRepositoryImpl +import com.henryhiles.qweather.domain.repository.WeatherRepository +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module + +//@Module +//@InstallIn(SingletonComponent::class) +//abstract class RepositoryModule { +// @Binds +// @Singleton +// abstract fun bindWeatherRepository(weatherRepository: WeatherRepository): WeatherRepository +//} + +val repositoryModule = module { + singleOf(::WeatherRepositoryImpl) bind WeatherRepository::class +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/location/LocationTracker.kt b/app/src/main/java/com/henryhiles/qweather/domain/location/LocationTracker.kt new file mode 100644 index 0000000..5f98184 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/location/LocationTracker.kt @@ -0,0 +1,7 @@ +package com.henryhiles.qweather.domain.location + +import android.location.Location + +interface LocationTracker { + suspend fun getCurrentLocation(): Location? +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt b/app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt new file mode 100644 index 0000000..30cc457 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt @@ -0,0 +1,8 @@ +package com.henryhiles.qweather.domain.repository + +import com.henryhiles.qweather.domain.util.Resource +import com.henryhiles.qweather.domain.weather.WeatherInfo + +interface WeatherRepository { + suspend fun getWeatherData(lat: Double, long: Double): Resource +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/util/Resource.kt b/app/src/main/java/com/henryhiles/qweather/domain/util/Resource.kt new file mode 100644 index 0000000..adea1b8 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/util/Resource.kt @@ -0,0 +1,6 @@ +package com.henryhiles.qweather.domain.util + +sealed class Resource(val data: T? = null, val message: String? = null) { + class Success(data: T?): Resource(data) + class Error(message: String, data: T? = null): Resource(data, message) +} diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt new file mode 100644 index 0000000..6471ec6 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt @@ -0,0 +1,12 @@ +package com.henryhiles.qweather.domain.weather + +import java.time.LocalDateTime + +data class WeatherData( + val time: LocalDateTime, + val temperatureCelsius: Double, + val pressure: Double, + val windSpeed: Double, + val humidity: Double, + val weatherType: WeatherType +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherInfo.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherInfo.kt new file mode 100644 index 0000000..9d6a991 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherInfo.kt @@ -0,0 +1,6 @@ +package com.henryhiles.qweather.domain.weather + +data class WeatherInfo( + val weatherDataPerDay: Map>, + val currentWeatherData: WeatherData? +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherType.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherType.kt new file mode 100644 index 0000000..0e700e2 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherType.kt @@ -0,0 +1,154 @@ +package com.henryhiles.qweather.domain.weather + +import androidx.annotation.DrawableRes +import com.henryhiles.qweather.R + +sealed class WeatherType( + val weatherDesc: String, + @DrawableRes val iconRes: Int +) { + object ClearSky : WeatherType( + weatherDesc = "Clear sky", + iconRes = R.drawable.ic_sunny + ) + object MainlyClear : WeatherType( + weatherDesc = "Mainly clear", + iconRes = R.drawable.ic_cloudy + ) + object PartlyCloudy : WeatherType( + weatherDesc = "Partly cloudy", + iconRes = R.drawable.ic_cloudy + ) + object Overcast : WeatherType( + weatherDesc = "Overcast", + iconRes = R.drawable.ic_cloudy + ) + object Foggy : WeatherType( + weatherDesc = "Foggy", + iconRes = R.drawable.ic_very_cloudy + ) + object DepositingRimeFog : WeatherType( + weatherDesc = "Depositing rime fog", + iconRes = R.drawable.ic_very_cloudy + ) + object LightDrizzle : WeatherType( + weatherDesc = "Light drizzle", + iconRes = R.drawable.ic_rainshower + ) + object ModerateDrizzle : WeatherType( + weatherDesc = "Moderate drizzle", + iconRes = R.drawable.ic_rainshower + ) + object DenseDrizzle : WeatherType( + weatherDesc = "Dense drizzle", + iconRes = R.drawable.ic_rainshower + ) + object LightFreezingDrizzle : WeatherType( + weatherDesc = "Slight freezing drizzle", + iconRes = R.drawable.ic_snowyrainy + ) + object DenseFreezingDrizzle : WeatherType( + weatherDesc = "Dense freezing drizzle", + iconRes = R.drawable.ic_snowyrainy + ) + object SlightRain : WeatherType( + weatherDesc = "Slight rain", + iconRes = R.drawable.ic_rainy + ) + object ModerateRain : WeatherType( + weatherDesc = "Rainy", + iconRes = R.drawable.ic_rainy + ) + object HeavyRain : WeatherType( + weatherDesc = "Heavy rain", + iconRes = R.drawable.ic_rainy + ) + object HeavyFreezingRain: WeatherType( + weatherDesc = "Heavy freezing rain", + iconRes = R.drawable.ic_snowyrainy + ) + object SlightSnowFall: WeatherType( + weatherDesc = "Slight snow fall", + iconRes = R.drawable.ic_snowy + ) + object ModerateSnowFall: WeatherType( + weatherDesc = "Moderate snow fall", + iconRes = R.drawable.ic_heavysnow + ) + object HeavySnowFall: WeatherType( + weatherDesc = "Heavy snow fall", + iconRes = R.drawable.ic_heavysnow + ) + object SnowGrains: WeatherType( + weatherDesc = "Snow grains", + iconRes = R.drawable.ic_heavysnow + ) + object SlightRainShowers: WeatherType( + weatherDesc = "Slight rain showers", + iconRes = R.drawable.ic_rainshower + ) + object ModerateRainShowers: WeatherType( + weatherDesc = "Moderate rain showers", + iconRes = R.drawable.ic_rainshower + ) + object ViolentRainShowers: WeatherType( + weatherDesc = "Violent rain showers", + iconRes = R.drawable.ic_rainshower + ) + object SlightSnowShowers: WeatherType( + weatherDesc = "Light snow showers", + iconRes = R.drawable.ic_snowy + ) + object HeavySnowShowers: WeatherType( + weatherDesc = "Heavy snow showers", + iconRes = R.drawable.ic_snowy + ) + object ModerateThunderstorm: WeatherType( + weatherDesc = "Moderate thunderstorm", + iconRes = R.drawable.ic_thunder + ) + object SlightHailThunderstorm: WeatherType( + weatherDesc = "Thunderstorm with slight hail", + iconRes = R.drawable.ic_rainythunder + ) + object HeavyHailThunderstorm: WeatherType( + weatherDesc = "Thunderstorm with heavy hail", + iconRes = R.drawable.ic_rainythunder + ) + + companion object { + fun fromWMO(code: Int): WeatherType { + return when(code) { + 0 -> ClearSky + 1 -> MainlyClear + 2 -> PartlyCloudy + 3 -> Overcast + 45 -> Foggy + 48 -> DepositingRimeFog + 51 -> LightDrizzle + 53 -> ModerateDrizzle + 55 -> DenseDrizzle + 56 -> LightFreezingDrizzle + 57 -> DenseFreezingDrizzle + 61 -> SlightRain + 63 -> ModerateRain + 65 -> HeavyRain + 66 -> LightFreezingDrizzle + 67 -> HeavyFreezingRain + 71 -> SlightSnowFall + 73 -> ModerateSnowFall + 75 -> HeavySnowFall + 77 -> SnowGrains + 80 -> SlightRainShowers + 81 -> ModerateRainShowers + 82 -> ViolentRainShowers + 85 -> SlightSnowShowers + 86 -> HeavySnowShowers + 95 -> ModerateThunderstorm + 96 -> SlightHailThunderstorm + 99 -> HeavyHailThunderstorm + else -> ClearSky + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/activity/MainActivity.kt b/app/src/main/java/com/henryhiles/qweather/presentation/activity/MainActivity.kt new file mode 100644 index 0000000..cb208c9 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/activity/MainActivity.kt @@ -0,0 +1,49 @@ +package com.henryhiles.qweather.presentation.activity + +import android.Manifest +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.ui.Modifier +import cafe.adriel.voyager.navigator.CurrentScreen +import cafe.adriel.voyager.navigator.Navigator +import com.henryhiles.qweather.presentation.screen.TodayScreen +import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme +import com.henryhiles.qweather.presentation.viewmodel.WeatherViewModel +import org.koin.androidx.viewmodel.ext.android.getViewModel + +class MainActivity : ComponentActivity() { + private lateinit var permissionLauncher: ActivityResultLauncher> + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val weatherViewModel = getViewModel() + + permissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { weatherViewModel.loadWeatherInfo() } + permissionLauncher.launch( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + ) + ) + setContent { + WeatherAppTheme { + Surface { + Navigator(TodayScreen()) { navigator -> + Scaffold( + topBar = { /* ... */ }, + content = { padding -> Box(modifier = Modifier.padding(padding)) { CurrentScreen() } }, + bottomBar = { /* ... */ } + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherCard.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherCard.kt new file mode 100644 index 0000000..7a7e9e2 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherCard.kt @@ -0,0 +1,81 @@ +package com.henryhiles.qweather.presentation.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.henryhiles.qweather.R +import com.henryhiles.qweather.presentation.viewmodel.WeatherState +import java.time.format.DateTimeFormatter +import kotlin.math.roundToInt + +@Composable +fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) { + state.weatherInfo?.currentWeatherData?.let { + val formattedTime = remember(it) { + it.time.format(DateTimeFormatter.ofPattern("HH:mm")) + } + + Card( + shape = RoundedCornerShape(8.dp), + modifier = modifier.padding(16.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Today $formattedTime", + modifier = Modifier.align(Alignment.End), color = Color.White + ) + Spacer(modifier = Modifier.height(16.dp)) + Image( + painter = painterResource(id = it.weatherType.iconRes), + contentDescription = "Image of ${it.weatherType.weatherDesc}", + modifier = Modifier.width(200.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text(text = "${it.temperatureCelsius}°C", fontSize = 50.sp) + Spacer(modifier = Modifier.height(16.dp)) + Text(text = it.weatherType.weatherDesc, fontSize = 20.sp) + Spacer(modifier = Modifier.height(32.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceAround + ) { + WeatherDataDisplay( + value = it.pressure.roundToInt(), + unit = "hpa", + icon = ImageVector.vectorResource(id = R.drawable.ic_pressure), + description = "Pressure", + ) + WeatherDataDisplay( + value = it.humidity.roundToInt(), + unit = "%", + icon = ImageVector.vectorResource(id = R.drawable.ic_drop), + description = "Humidity" + ) + WeatherDataDisplay( + value = it.windSpeed.roundToInt(), + unit = "km/h", + icon = ImageVector.vectorResource(id = R.drawable.ic_wind), + description = "Wind Speed", + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDataDisplay.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDataDisplay.kt new file mode 100644 index 0000000..d808b2f --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDataDisplay.kt @@ -0,0 +1,33 @@ +package com.henryhiles.qweather.presentation.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +@Composable +fun WeatherDataDisplay( + value: Int, + unit: String, + icon: ImageVector, + description: String, + modifier: Modifier = Modifier, +) { + Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = icon, + contentDescription = description, +// tint = MaterialTheme.colorScheme.onSecondary, + modifier = Modifier.size(25.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text(text = "$value$unit") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDay.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDay.kt new file mode 100644 index 0000000..4704f72 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDay.kt @@ -0,0 +1,2 @@ +package com.henryhiles.qweather.presentation.components + diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherForecast.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherForecast.kt new file mode 100644 index 0000000..7df9fbf --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherForecast.kt @@ -0,0 +1,40 @@ +package com.henryhiles.qweather.presentation.components + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.henryhiles.qweather.presentation.viewmodel.WeatherState +import java.time.LocalDateTime + +@Composable +fun WeatherForecast(state: WeatherState, modifier: Modifier = Modifier) { + state.weatherInfo?.weatherDataPerDay?.get(0)?.let { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + Text(text = "Today", fontSize = 20.sp, color = Color.White) + Spacer(modifier = Modifier.height(16.dp)) + val rowState = rememberLazyListState(LocalDateTime.now().hour) + + LazyRow(state = rowState) { + items(it) { + WeatherHour( + data = it, + modifier = Modifier + .height(100.dp) + .padding(horizontal = 16.dp) + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherHour.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherHour.kt new file mode 100644 index 0000000..aa4655b --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherHour.kt @@ -0,0 +1,39 @@ +package com.henryhiles.qweather.presentation.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.henryhiles.qweather.domain.weather.WeatherData +import java.time.format.DateTimeFormatter + +@Composable +fun WeatherHour(data: WeatherData, modifier: Modifier = Modifier) { + data.let { + val formattedTime = remember(it) { + it.time.format(DateTimeFormatter.ofPattern("HH:mm")) + } + + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + Text(text = formattedTime) + Image( + painter = painterResource(id = it.weatherType.iconRes), + contentDescription = "Image of ${data.weatherType.weatherDesc}", + modifier = Modifier.width(40.dp) + ) + Text(text = "${it.temperatureCelsius}°C") + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screen/TodayScreen.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screen/TodayScreen.kt new file mode 100644 index 0000000..119a634 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/screen/TodayScreen.kt @@ -0,0 +1,61 @@ +package com.henryhiles.qweather.presentation.screen + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.androidx.AndroidScreen +import com.henryhiles.qweather.presentation.components.WeatherCard +import com.henryhiles.qweather.presentation.components.WeatherForecast +import com.henryhiles.qweather.presentation.viewmodel.WeatherViewModel +import org.koin.androidx.compose.getViewModel + +class TodayScreen : AndroidScreen() { + @OptIn(ExperimentalMaterial3Api::class) + @Composable + override fun Content() { + val weatherViewModel = getViewModel() + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + ) { + WeatherCard(state = weatherViewModel.state) + Spacer(modifier = Modifier.height(16.dp)) + WeatherForecast(state = weatherViewModel.state) + } + if (weatherViewModel.state.isLoading) CircularProgressIndicator( + modifier = Modifier.align( + Alignment.Center + ) + ) + weatherViewModel.state.error?.let { + AlertDialog(onDismissRequest = {}) { + Surface( + shape = MaterialTheme.shapes.large + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = "An error occurred", + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + SelectionContainer { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(16.dp) + ) + } + + } + } + } + + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Color.kt b/app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Color.kt new file mode 100644 index 0000000..1e55a68 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.henryhiles.qweather.presentation.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Theme.kt b/app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Theme.kt new file mode 100644 index 0000000..03da777 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/ui/theme/Theme.kt @@ -0,0 +1,59 @@ +package com.henryhiles.qweather.presentation.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun WeatherAppTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colorScheme = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb() + } + } + + MaterialTheme( + colorScheme = colorScheme, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/viewmodel/WeatherViewModel.kt b/app/src/main/java/com/henryhiles/qweather/presentation/viewmodel/WeatherViewModel.kt new file mode 100644 index 0000000..1600ff3 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/viewmodel/WeatherViewModel.kt @@ -0,0 +1,58 @@ +package com.henryhiles.qweather.presentation.viewmodel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.henryhiles.qweather.domain.location.LocationTracker +import com.henryhiles.qweather.domain.repository.WeatherRepository +import com.henryhiles.qweather.domain.util.Resource +import com.henryhiles.qweather.domain.weather.WeatherInfo +import kotlinx.coroutines.launch + +data class WeatherState( + val weatherInfo: WeatherInfo? = null, + val isLoading: Boolean = false, + val error: String? = null +) + +class WeatherViewModel constructor( + private val repository: WeatherRepository, + private val locationTracker: LocationTracker, +) : ViewModel() { + var state by mutableStateOf(WeatherState()) + private set + + fun loadWeatherInfo() { + viewModelScope.launch { + state = state.copy(isLoading = true, error = null) + val currentLocation = locationTracker.getCurrentLocation() + currentLocation?.let { location -> + state = when (val result = + repository.getWeatherData(location.latitude, location.longitude)) { + is Resource.Success -> { + state.copy( + weatherInfo = result.data, + isLoading = false, + error = null + ) + } + + is Resource.Error -> { + state.copy( + weatherInfo = null, + isLoading = false, + error = result.message + ) + } + } + } ?: kotlin.run { + state = state.copy( + isLoading = false, + error = "Couldn't retrieve location. Make sure to grant permission and enable GPS." + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cloudy.xml b/app/src/main/res/drawable/ic_cloudy.xml new file mode 100644 index 0000000..f2fefb9 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloudy.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_drop.xml b/app/src/main/res/drawable/ic_drop.xml new file mode 100644 index 0000000..7ef4816 --- /dev/null +++ b/app/src/main/res/drawable/ic_drop.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_heavysnow.xml b/app/src/main/res/drawable/ic_heavysnow.xml new file mode 100644 index 0000000..4386600 --- /dev/null +++ b/app/src/main/res/drawable/ic_heavysnow.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_pressure.xml b/app/src/main/res/drawable/ic_pressure.xml new file mode 100644 index 0000000..80aa4b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_pressure.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_rainshower.xml b/app/src/main/res/drawable/ic_rainshower.xml new file mode 100644 index 0000000..55c87bf --- /dev/null +++ b/app/src/main/res/drawable/ic_rainshower.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_rainy.xml b/app/src/main/res/drawable/ic_rainy.xml new file mode 100644 index 0000000..3dd83f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_rainy.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_rainythunder.xml b/app/src/main/res/drawable/ic_rainythunder.xml new file mode 100644 index 0000000..7f54070 --- /dev/null +++ b/app/src/main/res/drawable/ic_rainythunder.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_snowy.xml b/app/src/main/res/drawable/ic_snowy.xml new file mode 100644 index 0000000..3f35566 --- /dev/null +++ b/app/src/main/res/drawable/ic_snowy.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_snowyrainy.xml b/app/src/main/res/drawable/ic_snowyrainy.xml new file mode 100644 index 0000000..d349018 --- /dev/null +++ b/app/src/main/res/drawable/ic_snowyrainy.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_sunny.xml b/app/src/main/res/drawable/ic_sunny.xml new file mode 100644 index 0000000..1b78b01 --- /dev/null +++ b/app/src/main/res/drawable/ic_sunny.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_sunnycloudy.xml b/app/src/main/res/drawable/ic_sunnycloudy.xml new file mode 100644 index 0000000..3845b06 --- /dev/null +++ b/app/src/main/res/drawable/ic_sunnycloudy.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_sunnyrainy.xml b/app/src/main/res/drawable/ic_sunnyrainy.xml new file mode 100644 index 0000000..64b41bf --- /dev/null +++ b/app/src/main/res/drawable/ic_sunnyrainy.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_thunder.xml b/app/src/main/res/drawable/ic_thunder.xml new file mode 100644 index 0000000..217eb8f --- /dev/null +++ b/app/src/main/res/drawable/ic_thunder.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_very_cloudy.xml b/app/src/main/res/drawable/ic_very_cloudy.xml new file mode 100644 index 0000000..6d2645e --- /dev/null +++ b/app/src/main/res/drawable/ic_very_cloudy.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_wind.xml b/app/src/main/res/drawable/ic_wind.xml new file mode 100644 index 0000000..f9028cc --- /dev/null +++ b/app/src/main/res/drawable/ic_wind.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_windy.xml b/app/src/main/res/drawable/ic_windy.xml new file mode 100644 index 0000000..a9f62cf --- /dev/null +++ b/app/src/main/res/drawable/ic_windy.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmWIYbaN|VWnc(*bqWXzu!!JdU|xt`5!v_jjvkEam13D%HG@k-IMc zNLB6Ts|;5bzSwJe>ULbl1cvf&ZkhaCoSq2>@AZ5-D>jj1lIT2H&dag$gn7N+e4L{u z^V=qU9`n4<|KGj+Jg1Vc;+*Ym8}@ms^PajGxhz`fUcGqB^ot%&0-(G6 ztJqa}H}n3HU3Yip`SGVeoN_0jHpk{$^4G>a6%8p%x7&Z)J5^*02Bzg(emT zh6~~ha~ZWf7(5v43`IJe7AY!6)06s|5LTRZ~a|9(#3-?A(9e&YZ4?>X2s?GW>o|33SDelz`B z`+wn|Q;!w?=CKR)$n9qK&JB=|i?B8=Za(_yby{8ahEC_FQc?|fX6jq-S#{;T*P>@x z_ZT?env#-TMm+56J zYxbSv3`wunSg8L0QM8}=O*rp?-VOhM|NEg<)7%*xHvh-siA*kcCjMS>GB?P(>_@}3 z)Ft&xoDRNt&3eFh)jsaGt6V1Y-utsGX3D{BPrGiMe#bFC=cA;8JMEoLi`oHXxI)DE^oHMbkaEEw% z-9tKtyZ;ro&wXg4x@O8`m1O~q z?MexGg12&nX4y|S@pnFV^V}u9giQ-GT$3$KnU6R18RkeC3%K&!v-YvBIofAmw>PFs zIdS)#VNole5AMDC;{Wfgucb?`mgwZOH*=)K)xFxh zvAbsbOYcV^yis0yb^(2L-iLG^EU>@ZcYlA->$iWR*2l$qNu~2Y6n&DlA@j?-eeQ>s z$tnK&Z05FhmCbjTD+dy9O%PYyrL7Vlbht#oNZ2QS;ASZ@?QSi@KIA`)Q{Plar-EBMn zOue{8S#RsoI9{I8-1%wjYL3ra-WXN>myuB|k`TLaRw0(}Q{v*)neU%|Uf=%kE$@XZ zhG9pKE&f~)a@RGze*OGS1~2BsylnCL*)X3i|99J|eC4QK#}-~?>BH&Aafd zWjZOh{NVc@|Mx8WeQ&M$PqIskKh9jN|JQv<&;0)q`yZ~E&-DGzd;15MW#|73p4FDw z7Oym+LiWGhsn`4Zdv7t_;1XiVJF+=y-{rn5k2YR9d}BwU-%Zx7^dl3uQO%=KtB6@_8OJ`?s99PuHc(Wv{Uvn57cQnzH^! z4YT&W4>v_49d0g>;5}1QsqLw*bguIK-eRRko_V#EY72aobT&L-pQO|$YAra)UWXy) z?H0HH>?%h$pNjLSyTHFTTZD1@o5kYirK&#Ospl5Bs^WBcdditu$Df4zc4P@8o+&!k z$5~=Hz27y&v^)8mahg-|JA1KhUV`UTT02EQ$+(1e7MN)IY?yFdc~#TV8Gl98qIT3X z<+w`qp4Rl)QQ*p5k{WN6_9(e-vDU^NGYy%0D%Eo$*=nm_Iu+cp7Fo9EyM*zH8S<-( zbe>%_4mh8j;GJ3H&*}PO^CeZDWfSd0{LeLOrhQauJ;-&xL3dH%i?m$@GTTcW#Uo<; zjyK5WH|QF?3ik3iw0R4K4powOB8I2&<4clQ^(Uj_3W$DXcxV zjR)uF>MkgGAGK?O%ys_DTn)LunuI2O_?_0>^yO@cqUF8WeOw_XMar=jeY4eFRxzx+ zX0Gv&!#=}z;R?_B9=`%?D${O>ImiC?pAyhg|KwG`rL=QOwG#98Xb4Yrsy&|GbGYMY zsk`kdlbM#*pzy1VI5w^M*Ee@fMUMHI`3^U=Cjaj%{FL<1WJ2K6gWG!I4_RayPWQ{} z49K*qKIG@Msb#m{$z_3+$=9FuO;TC7d)e;5P)F16cb`b8t1sKVcD?JuS-Vv0pYk2^ z?A-ZzR?TX+($eVvC%s?B2+etX>GH4JCacy)S1+&gKejK-POW~zQ&92b*Z1)zdh>m?RW6x4`?Q$9`o-?6Ybw5O=a)6J znxAdrvF2pT?)Lt<)la$Q|86dLJ5APl+nU%P=iT*VEX(hFn3r=*;o`1!s<%xIlFl4i zS=?{;ZpY&1elso7jyQyGTk5C$(`ss_?45b9U%h<$_T{Oq{CQ`Wm^6jnax4A%wRGZD zE>kv!umB5%GX|4H8{Z@_=K|4QA)x&-@h`_S6Qf2Dsh)QA3idd>WW^{=-t z8-KfgX#H*dd;SOcKdXQF|C@c;|3t~WUrT=WpZoB8>;4~njC{d|%8TTG+k4fYt629} z`$G7y{eSuc-+!+;Su6Tm{R8ix`M=ix@qbr4*FLkh&)(F)Ap{?{NBv@`t@kSAS1B$~ybiwY(%|)}H4jSI%v=Qr@Kb`cGWAVur)+zV()i z4?A6W@j#!K@m+50(S5dCwQWzj|JbP4>YvFS6mr$y>y5yEP4n*)KXi85vDBuw8+)!4 zIjeehEpxeZftfr@;e;aal}OP<_kN? z8H{ULme&3EZhY!uu;D>YX!zaP=dUD)?5tnSEBwE5$LCktf9xI-v&s*Ozst5$IQ ztz6E$SS?<0^Oel!de8Uk-&y&8nqNTNN&Tt+Zu}Bi&*pc0mW1hw3qg@P8K2jzKfCzw zHwEtaOZf}R?W9-jP1LO2u-f?4frw)m@uOwudWAd#cu zuQEC>WPX%+vnGjgrr~jxtOx%pBYz+J^#AaG;q$9E&IwJm|GvUqUxGzG_5e4xXEvv6 zUG??f*A4cxJX`Ph{MWt5;i?784E{zroM3h>eb|uf@u>gLnbODXYbvw2O_xZse|c(u z{@?5UpX+y+b=}`LwM!vR@9)Xk%}PSkq$=wz3t2gzT#1vK$a^#WIQv~K*X{jr5`vA4 zk}IoYntV?^VNM;4_Wtk=Dw1> zU>nNDXITF309S16?5P2*0cZIdFI}(TnK${}flpU|+W*b$I?TVL`}AF|KU=RT$2+{M z@x5zt%k8c5RqGdKzp4zL-c{E;J2SyAWbSN#%N)P`n}0tJxXw6%tL0fnyWefC^;4sF z=${nIn6pMYeVf3BCvCqk&AGa7L*L4RBX?!D-25h+IA>X7_2<=3{N)~Ls;5j@V)#yj z`|9^pJ;^pHU-6p9OXHSlZp_p7o7i^ZvgKXR34(E222)?wGG6+d{`Y|L)!s>5SvPJq z9*V6fURC|MuVF>KzT|!p=Gexx`XzH*t}NXqZtE~9F6@)v(j|NkXLH-zX}46JOMT#* zlol>^bI1PcT{3F25#f7R%wXAfO>A0)vBJF8O9lRu*6&J{Sl5;>B;LL<&f|~5f$NKe zy8WNXDcD6eO~KY~l{Xv%)=37U*G z9@e=QE!yR(DL-y_ZvWj^|G(Z?>DZ%9&#cqV_P<#iGf%`!AZcZQ_~U-r8|lRfslT&% ztGiT|PYZfe)F5*2<;?VuC+=%)f^IuKDr&ydqI)rT+M4}e-Rsil9K0Ho5%Nm@pTH&A zDfbM@=gFL!TbX}-(VL%(gU;7n5|PbnUbihsL;pX2v4Ct&tU9ly((2#k_7AF;7oL2w zNrK(x&6L;gcDZI&SEy_G?N2s^h4ZJGuJJYgE!XzPEm_U+_5;h^ ovnzgd{?9o4BIu2n{`8h0i$C!PvP=E;mwU3J$(bU=hK^z`!8Dz`&pnMvguK9;+A_7#JI(Z(rN=;?q9<^78W4 zOhLCk^T{mJia8V*b8oxYO%_2z#)|1qo4Sv8EKs<}%(5dn@YxLM1=cLV#rqQHoBJ-P ze!F;*?O9#M^PhhFJpHdShE;K$%BJ0q-%tGeeTTl|y;HfOn)Vxlmwf8K7&h_K-a~#9 zU+FfU_ytybXGvZo&)>z9l9eA$(>TAa{cmGw{f)Yiqp}kWBeOSLpX_$1_R`GUH)*mD z6IZ5Aub3VdV4?7jiGkq)Kf_!`EdvGv20LA$Hm8LP`AoMn@GvGtGw6Ca@MSlLe+-`; ze#LtKr2nUDCw-f7JN{AcugUL?&of?Ye8Kp%@=fJ$`4W==$ltZ?P1l7gjK3~&UA6o}nD~qHWos?In)WT!`}J%WM{B$gv&7cr z9t{6}uUyYksmO6E;}~}hpZb%x*-NJElJdTPV@q6u%I+_+UM1SU(>5(EUT@*rD7ER9 z!i-9Jp$n_!U%S@F1=rvCkXB^V`}>Fg+d9q<^Y^*jtQX#p|3Bc?>VF{}hf0%v7@hoQ z7jWJ1lTgsqcM{d=yw%ZOQkUW{trmUN$(mIuyN508vZCzSJ@*uK&i|axyTxhpl!EE< zU!KgkwY>7vLA9lgJNhhEdyC!_JY_C&lJ`oU_M-E@-Y&Q@XZMqxM`iOKJnj!)6aT2} zAE=0ka6FprVae3f|#zmTubOVOLtZ&wu@K77-HXRq>!IX3ez%vm)R&GJ*) zB65nqE9~*Sf1lU=>FAXGvhl-=-AQk=ew>{B=b>4zH_zLO!^;zWI#Vv_&S=hFIw>YT zYWkfrsL6LkpRU-vVbkwrC70)2oYC3j)SZ-{m9Yo5yqn zH>ff&&3ON_z$0Tld#k&{wfYYMZ~xvqF~z0o|Fbz^|K6$fn`y*~)=pfiz^nW2LBQF2 z;RlQ!$I3VT-G4G(`1>c#TjoFKd%y6P+R1sja{W24fB!XDH8qaz{BLU>d-UT5c46~? zxn)r@h3o&z{I~t`TKD3s3D17#=if}2zdUBonLMqZZc(f&x0EUh7Cw8>?vR!=(XF;t z##)$tR_v$8yXKhA&YNu%8Tc!XJ6I*2TgUn0V-rFVlVx3UmpLJ$(bU=hK^z`!8Dz`&pnMvguK9x+S|42%o1Z*R-I{VKRR_O_X_ z^vvAbVQHV4bMD_b_PMm~oSu2zw)-*3-E@E^|?El(}TXJtN zGyM?1dh-P_hvly7^Pcbj^Y6F*=kxwLuUHPe+GM%l*dBKK%`fK5pIObfZ z2U@NXWrA_R2Cf+Sf=lfF*^|Zcp*zy130q&}_cR#(H z{~u3cRZS6Z$xdX<2w9zQiX%AeS`(Yjvp|WZ1)Mq}9~$iV&L5XLwczpjDHbjEA4Nx?jQY9E50B(=5AwH{IM^g z)eG|MmVNJSc=C8gTuAfQ=gGSstqIPSbdpgl=q_Tjty^+L@y|;8HBPRm7IQirE~Qf8v3vi0~2iPiN<(SvFq^U6*%# z?^G2{*GVZ)_vMuqbETGgPL_+d|Iz6B|HHv+a=t?CMcUz83!fWW=j}OB|L?5%{O9p^ zZWu1}e!R+0**Q0M?Xlcl-=xfHer$+~y=}7Y=^D3Vr=At9%lbOYbai;CWxA*rTUdaF zLJ|uD!vt}Lxr|x{3@iF-{w*N3)rWHzi~e}|LyY)>~F&V|2`$Z_lwQ1w@URY zKVomi|GM{+UH+l|Hs&jJ|My+}yz^i4<>PZeDyUx1xb;~UI40Ekc=EzRTi2rzh;?hed#*(i;e?0N%CC}4Tma95H)sm;qe&@*} zD!$3}+5(1u|Edjp--bH%*G4?OS>B#lEOCxiZQ5TBlklv!dyE5$YggNz^mz4t(_wyo z#*aMxFB7iJH#}A!KH(L^;(4>4Zrb|%@K^Omf0pYs@BRNPbjOc*<(lT3ZvLNtZt>#_ zub2OK4WIpbYVU=Fd;d_d@dXc8-pPonn6G~m%;FhlXNTA%$6a5TIyKQ)@`nWf(tT?l zFZeg-|D*N)6t?egI(ciKrI@miq+>?eYq7wCY_^ltndD6`GG31le%>X%CZuii7R^6O zvnNWv_`?6dX7T%qub%@%P2OF39QRBvYlRkzJIl(s{}%1M{q~L2vaj84e9bFY9;o@T z=WXDQck??9p0`i?P`qc>?AkPz>n4YGrVA}DTe|qk*?HTmUsbz^vZ-ZOY@aT#UhlQ_ z)1^<^mo~NJ^F39pacW+**GyA{>$k%cwrAmU{v0Tn=)a>vc!}`qLtm#e1aJGDn37?- z=IJ5RLW5N-9ZhlDf|sqZ-S|G;a?yrllfzzXMU9_5JJqo@`Ga^JhoF~;_1z{}#+Vyn zr|x-edok^Y`~SoKH}W5;*G+X&h!T*CD_iqv<>bxs@0R(mx7_x;!X`UDcJDotYN14*kn_7jwr}zihOj{bNGVOcxxf%|EtzTbQJuki$dZvwg z)ozC9>*v1LZdYB+oBj37)5Y6Z>_yvzxZBhIGTo~d3tFj{QJu%;yiv1dvh#i)rRp=*JjrimCi=5XTmE|Hjnx}7p7$-#5c+FYu^^z!B0Vn-KqJ`V;40Lb8F AkN^Mx literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 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

( 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..9287f5083623b375139afb391af71cc533a7dd37 GIT binary patch literal 5914 zcmWIYbaN9DXJ80-bqWXzu!!JdU|J<7_Jf5|s~ za~9p8x_okrvVg~}_?nguk6ibyxwXXoxs;ALRhQRLvv^kzb^pZ&Xr*bDEjezyDhy*}Mq;6Z4*SmVm{ zn5uIVYZ)H!H6;i;3Y=j`sJInh|M|SG!v?O!iVH2Cx1Hu=ncy-hW4($Y!;J5*=l|uM z*eEI*bL0ED+x<*aGLBT77UihrQF5ln9)Y16jhk#PS$KrH_iBkhz-h4lJH$dycT%AP) zN0Wamwq+%B)ciclxTHZ(PiDtAF{U*Q6AF%9_jBQKDUUvKVC{0{sOvMmYCf1PlH4^< ztng53L+SK~{J-n}%N${vn!#}HuKYPYqdyD2o;@hhv5H6V_`Ieg>kJvD@+38KsTx!; z9n>>C5ZZJiV66z>OUB)Y8X_3&*%oE#CVpR;AkGnF*}*94lepV)#b<$&2?yEL7GLHS zU>Dolp5V@zcW=Xo-n9YjoN3BB|CXH%IL?um7I9EFdLf@+4bO%y?NqSN?IiqxQt>(>#w8p4xpsJRwv+&Q2rpcgJ?Y z>Q7Af-`Q@;U1;+(gC!+w$6FcwTdLK(8>dOTRA1fWt2l4_-mDXO3s38XUzEG~x!^Rv z%=uZ{RgX+AZ&p0q)_pGUBHstcB}(o-6&+d4M{UouOiX=rirHq1<94O1pHebE=-rwj z(H%YUqjZ(h@gytJ4Kb2^cKwMt*Of$fCLJn$A`#(d$?vkQ^E110wDDY)hdZysU!YyG<^BMRp7yDbbE);E@tniHI(8*`I z7Y<)M@lUYz#;F5&3HLhwmggMZQa*G4T>g{{w>SKe&q8Ee%$j8x4D$u}j7-$O&Ej}- zQtZyRLo5Z~ADlgK_rM)NYxV*YXTt{02UGT?9a*9&+~^scu()yZFLkEc$(?DodlTMu z%!}I+p>4pZQ+4BrM2?a`kC~K%4@>JaCokc~Cl@jtdIH|HNL);cJkGM=Vul(Izc0(H z!xu6RY+Ebbm^*uR=PeV#8$vgEqFA5$Ma;f%XN!#Rfvb60O1iAY?mFU8SDHEx{0WQc zZdp|*WRUgcSLHD2L!v6$w zu}!Kf)9M|r{oi<2C~9ib&)sdB;hR2fPn{CgD3y84{e<|k|Mpi~r%9@Pzgssedftk= zY!{t-E>?BVo$t8psPpu0iQtR(|9{ClS>!T*PxW54zSe8e)_?S#oaBq-PkIyf`hE2t zbwm3v8$RD+=K#vy)LdWdeDF4FRPWZ) z4=f8~Y|c);b$rqKvJ0LYY9#vXoE+aWuYIw4?#=hpxIgdPoUO?D_&|Wc9G<667KR%{ zj!j*7osHkwjQ`HbUoleh+Ev|$%h-z@-c*HLjOKXk-L{CseMUs>6j?bZ!DHMDSa0kr z2$PllE!DJ9c#T528(TTYnwf%++1qBdGOoDQ(s-!Lp@hL<^MQXWSNu5nmnY37eNx&R z?Nw`EBpV3L^%8R73=<7(QtCX%=xMTzX_XRV{53}*S(XNscC&*awnjNpN}eSmjsl-b z7p!tSvP(t4<@xEQM>(g?p0*?Ag;%A~*$BR>Qj3j6Y-ik8JZACaoMp-WEc9X9&dYNH zg*wiuFPIo@|3~QH>I2OC{$xycTjZ^JGI+_qkDMCs&BQ<6ynobaCrc?igC^_igVSy~ zGCxZZ_hdX>zyH8h4^N?8H_tk8MC^O~Z|}4NdLKnLJUw4ua75zn#ivSVZ!{Mj{q|e* zS%UZut}Fk4T|T?+;fzUFN*T_c$-1>C>(xBd=E<_E?gzHn|1C&NQa<+NUM+*;g@o2? zx(E80O*r2OaI)$AKDvFmrS#WLJ$fN(j*?qnUe^D;ef`9uhQ}(MY&o;PHLpMKe@|kS zc~Vg*o8hEQ3oNqFJ-e0QxLAhiwvwX>!{N>U|4xsL7CyDmZE=rY$QlOD#(nP|TV|iS zcXGkRq6S^g1&L2ySeTF95CjOWixy`TBa z-g)h5$Kl&;uX>GkwP;N{{ZvzOVQ#nmpU?L8e`@aeS@Za;`pVM0+$}ld^RKPi({v+4 zr?VWbik$YfN_l6?s%cM7+PVpT+L3wEAHn^3R@7%58cw&XZ&gBxGLPtCtl@%QYjC35C7#kQ^7?>1FOc}xgEEG=g zF)(bfXPC>VHGyFQgPoyRr_&-uMX^_{OB9${HZ!FCd9gq3Ps1CZXaAHxxqs4r#&+eO z_;2$U>yPWzI?uiN{^h@yc7^|!eD!|D_^a|?_R9A&>|fMB`k!KZul~xfyI&aoPX7P@ zt^cc~fA{}=zJdMI`*;8M|GQEDqyA!z>c7kXPW|Efx99)M*9;F!{~i3Y`sw~3{j2vq zzVAKX{I`Cb{Dtzz+tj{pxA}MV{=(J$HT%!}TKRA1zsVEr(&X>dU;KNrX6FBc-;>{Y zzx@BV?xFnI`j!9J{dxTE>hJP5{=atI+9ZCy_sW+I>{5}pKFS}Omt*nyjnuUdxl2A8 z>^XfxK;fv^&&9<}X|a2*d0r6OV7JZi)XDU<`l+C9Xa%?aTu3WX;T}$MX?8ZRWM1GEm zPF=0CR+IL-GDOZP>FVFRBf@LOe9c)eeZMVMFL-9@aHymD{fSf8+E18IofF;c$MRd+ ztdiwHX+w>N^{h{=n>~vJ7pb#*UhPVD&ddv&ZMk?=%0tPZ>~j^~;#8o}(Md zX*8E5p#toTBes@Q{BKiT{{5OgPlDm!|NcFK@h_Oz8vULa&b+!R zQ(P*r)U9vJzF*At5eMHFN;0rTw3SI5`ExJe>)S+yDcTip#YHL>J^odH`rNJXtFM-I zuwSzM?D;3aIpti1Tk6{rY0JEpmj$wVnIy+{uQT7z`~HEK_vM{t#V;3@ZaBQ_$Bd7A ztSauO_J>QS{qNWw`7}VzVB?L&zUhivpN04TvRVA&ZQbwxcRJ1Mgd-P7cPK2=P-iU!%FgzYBi03)B5p`{{q5zzgHK|Jm8EFAWzo`B#0n?{Ve%KQnFB{#7h7u3r^? zJiqr}KJ#f+m!^H<+Y1a2eNi{PR>{72%d*C87u1V(1(&fo?JPYsA#qP+&Lkyik-Ooa z^g?e|wmtqIswjWdFl68Trm)`?v-f!a-K=$C=K1MAlWLlBH5fGQ7jSOgy8rH;MO6Vu zpDi!^WdA9oNjCYm&JI=o_IUOp!SdMamBMp>O`0*|@!c{9waC1_?8Fvft;UzX;}4bl zrkM*I@Z{r)ds+14`@gu4$B%7(^Lx?F&m5bsZ@tfV{#OjASb(ie-i~RTwyb&Z?B@mF zjMD!FQHNyP{x1z+WR=+(5WC!deTxFe!{dLsmi11vdoR2A;np*gcx_+#>(3Wi6IUz0 z%jDJCDZ7_wI|d}S^X-lJC^6;Jq^TEHe=srmu`?jlUh>k91wF!ZQ&SD2HYP1#-Mz}> zuYhyV8c9{pl4MKwhfEz&Z#|iA%9bA4_9DMtB-&8z)1DPCpTA~$Q>A)G?Ea+*_x`Q1 z>F=4Em}fLEM|A4>9TABlRg-w%P2?(=l9KXu`U7ijEpLG+v0p~t`57;o|1F;(rfz@n zt>FKRHammW*ZgW?ye~>-{nR`>t+l`K;Il_o2``zWU${8`5&38#^;KJL@$5eNvsS5R zj!MY?KJvI^-X9x-{o)1hKR!99yS(_w5#DQeW`3CdQ}DvoPiI9Ef9^~CwxqU0$-6nk z+N(PFxV6QGWLK&1Rg-kj^YHeX1;@nP<>BTGdB)T`;ncPa$*?2xUQH8H?HN~0xi0sm zq})9z{MnXf@sOL|bz45~2{oM_(&2PDCN+EWMeiw`E0t6m*3D?T`y(gYm+PjbWi{vI zoFAXJ`1f|4b6Xlv^FG2e;V|n}3--D-hYeqw9{+Hr>FKirKTo>EH4t6^%@r8ub~Q#bBNw=4bcB_5Vlt{;@VHS##va^Z9R0V%Ayje-~I|x$W-i67&Dg(`;|be^UKd7brH} zS(dl*$+WVa$s1DKMRO;A`}2C`)5^U^Ykmq%*d%i7o7T}D?bqAiRv9jRa7ujIEtln& zyly*}9QbqL>}*qGZP%*8Yj0CoFa6v4O4HT#_Py0FBY&q1WVTOEGmmUdgK<~cHT)x)T(e{m>EKYRI(?2q$UzEl8 zK(0K)*4M8M>T-mo*&JW`3wC`8o*An7ZibS@2k*p;jR}4BcjW|Nrd*qvMooZ??Og*wf$z037WywZ! z79FmYraI022i{FT5&7RLte#!tooZ+65A9E@*S@t~_i_07%)=S;0F^=90=!(6e*xcj`SwR*_^yzn(gKV0c_@9u9q z)H;3lEC!L|OZ&P`d7nL#{&B`+Wr=sOS_$hr;@6sMUy!&k`&UOX*X=ToskgQV)MsYw zDJqXFV7L}gadh_XI4ym#vgeEa84Z_jn{eV;19L$7l;(#w^5Z_wIhgx=j=t#>-@6MJ z&9U2f{QDOlK|MqBa zCDXd42R3)l$p1Tem(9ToDOpqBNGh)o(_g#odIVE}MDA+ueS+O3zpkhLGC#%4#;Rwi zmy+(TqRi{}%DH#v7Uem-m6nYTCj!-@XT6<&xV>SG#=4qy4{9>F^phA58Z+u{e#f%j z}R)L@cpN?2WSIdK10lLwN% zf&cFAS^DQ!-26>5V@_&cUA6bkKCiXkdP~G2eqRxMVyb?A`ll89zrWIb6~|!6t)ka) z<%R3Ki4V@q`X`aF>)E7b++j=X_XQNouPb4TmMpsEzC0ixq3Z6}2W+qPjSqJ!{&Vb( z5aTRNF1I{&Phtr(rOLG%_OCymqWk^4-W-DqskOLgZXycyAOnXxbR=%^UU2kIXo{4Ui$I8JJF+g^ShKthOk;(*V|iu z8xLN}e!Pn3H1qB+IY-0K-Mg%Ac`24X<(Hz$^E0PEE^XR2abA4&9K-mfWpfXlEjh(e zDg1o-w1}eR)|pBss}?PkzqNc}z>#|w1edm0w)1^^{D0@H*UJ|38TuaRzxQVL46T*6 z6B{c|;#O}IlX(_Fl6EIM`IieZw)mO8W7Z>lz5o1(t$1+QIO&(`wa{L^V0b)`dW=YhzE&MhWg7Ya^QMt=D`vs29G z>8<#eH&uQwbMfe%bm!OO8qM@;A_`Y#=O)+{vvwRl(|hq_D4Tjnc)>;OXQgVUFW2ZU zEL~HmuHAfJJ>Xz-w$`MZMbXVqQuc~OM(VOGw>Ty8=h*cp0*j|5OxZEff35PXS@zM# zzIH`NU+{Q)QK$C7B1Kcff|#gs!>I**U1@XkwqM+Ly8E6%%RjSwtovRzonbZ!j1my% z=6fLFl;-mC#&VD4limsaeR-_%>B|>OW%~5lv&|FF*WcvWb*jUWwTaIrxn|nS8Qlso z(Pj0&WXrF_daVxNn`h*p*IeQG_2Zdv>zgsk1v8Fz3B|k0q&x&qPO<=YQ^Z8mE9{3ZsBR!P<}5hHh7k&N~5z+p>X2UVw%sqz!{@)L9U%$Dbsw%*F(GMH%i zW)oA;cB_P3HQ7@oF`?NjQ;vlNUyXR76&z5v)br!?w#(-K6J;OdvjtQ<5aSPHIQQ+F zTf)IPm(DK@J8|rHbwY9O^ozI$<2jBas9DB&+eE0pV>h*1R*u^_$d=0!Q_C!V6i0|m}`F`I|9$mWN z+~c~SuO4%!<(^pQ8R)80_9Jx11?Dwj$?|)5&nsAcTResFkkC=dRA*Pts`qUPC(p22 zWE^DwX6$^od&aABS+?Id55#Y`&1zMDtH~0RqFcu|;|I5OpGxa`_oVw@lJzQgITl~J zskZxAbt8{Um}X2Q+uTUSLmfXkLp;+C9h|*l_s{2@Uh#h2ihs^mcoeYg`doeg#r`;F zhk{98Egc%KrKGyE^xmIzCQRg)%(aWFr*SImn$^T~_3hms%{(fPY|?wnjd#dy(Ajs= zE9~&Xrwe%1C(c?kVWY6bIgvcoqbI-iS$F^aDto^AglJ2NJ$(bU=hK^z`!8Dz`(E{j2wLeJldHU7(>b%bUDJ3u)$aAX7!}(6$UFx zSAJ(;QR8K7Q)4t%i+=x8TwYyK?P*%phS&RMFPd4^=k>ODyUiUb+w~U`j$i(`{EcNx zX0h4Z?B#Y{6Nv6&-#EViZ?%=zb_mtm-8m%igMtLg>q%5EcXXQvfLM45im!z zY(`Of&gO=bPjM`(1aJ!@ERx-wv2=J6*7Qs!s4Ev)(YIAf+&bNI{FtF51YU75I5 z(fL)Qt&a{LtBt``nY-P&6-8H`nTfee$JbPQb-m&X%F(l5Riw=$-{<|jZ%wbHL+;lb z*6bJ7YS=tzGc11H^&)OU!{_jKC)GAdT;V&;d|qdfM;ZHx%Nw3;+4?*DLlTFex$W*f zzf`5BHmc;iEi2w{QPL{P9Je#;%&H&3sVYSc-e);%O8(A?74wskzEb=pe}C2Qi{;nX zA3ZOcc_OGNT)O=_zx}U=Cpf;z*!(pqe*Ej<{w)*J&bYpt#LxfZ=i>FkRdI!f&Isnz z8(!);arN#~p_~~L-&pT_<#MS==!vJ$422cXIf4Z|*pxSkK6y7OpiAX!&xwep)3TT3 zZzTU><}?G|1tc; zUa6ju_Wer#t9@_(mDV5rs{7CR3iDUzKkuK0XPJAOwzZ~e{s8}&E7CjUQJ&HY<SKKvfO^VCzn)BnFqJ08klIC19l-HvVN zpM?F^sku~cr)z1v!81)nS()_(pW}`*=9!a1QorARTqkZA-X8j9YwJq6LvLeM*B2l8 zykVxX&>EE%F^?B-ULQ6Uu83EZe=g@{;{8R<(Z%$afR1d{!g5b|H(~LryO|DtHt$i*Jp!Yu7CejS27u&h}|Nnnt^m()?X0BW3$>8FpLR0E4`fr*xYyYh2 zo~|;Ai+`2e&R67=uj5uU*Im24W6zVRMy$D#6B-!)eR#>hz#x48{}uUoJ*{XFroj83 zuIL3Fc`otwut8|9=6lY1v11vdv`KF~?!yPSv*m+|PD>zHx&2)!qFcWL2^cglSw@ zIluXVfpX}s!1#GOi{k!DEPe5P@g|k%$KT{XDSIAq?by9Qz_f1PJ>Q;rK^;}!+xF

0vl^(ubK57i3$j`RXVdItc2Zu zTjP%3T{qqDyHu$1wf>en`}f$lI?l)hfy+`aCTxu8`Mp4Wt8~lzsk7fIKUzdbU&D@u>@#|8JoKt2mbv8|~*&2E%ct_*EIJ>)7o&PKE5_$1H zYTdgu^~m!0A4{%YZ`c&qZ)$yr|AXm!sqXjcKR?_Pn|Ao^jNS9)1$Q|wns@3F%fsvg zwSO(SHf}Wd&gY@5dZETG=E3!B4m$1-}j)^|n6tq;mOd3k8c0DH_GHdj+bC#}?wC!#j7mQS#vf_fXiT$x+$0-Ud(~5MTubmb>WqqsT;k{xpsIQhc5wmzv^leMXZ>rUpkvod!E z%4X!{CT@DfUb}1l9;OF%&cZj$=~`?izExW$`!W#MmrU&bU9x zY4?Y|ihvD`aVe?!Jd*bn>a_Ljw%A+cxQM63#5tgD>7pvIfk zXZB`A(+B?|xK;mhgAxy6j8m5a|tYYQtz-&)`wciH>w!z4Jl6EbdLNgW&t4n&Z^`r` zg&vWF3I>a?I;mM9pQdx?h+g`4^#9q@#m5&cUv`wUW67(N(f8cqnde#^*{|z4`#8&% z$(*MTuC#KwaPVof`>D`&U$yg@H74@YWB+XNzOk9HzHI)?S*96(9jadGnl+RhV?HYU zTPymOU!2^Y9Y;SYsn1QEpStG70rM^O4*D71J+(bX{#n9KLDg&XW+cq#d%E33PUWv_ zkfOTshO5hBt#1C8*W^yL{vnXQVRqiLErQMrj6U1<_5@j|Em*A4E4-te^ZzpYDI0B` zXg;x>w9{g(u&mK%{^Fz8n{xBlOZuI;!E!86S+BwIO2Xl&Fh%#ZoBUQU_O0g4%ANk> z0Jmq=?VG;}VuQcWs7ieBJAdXY(fhi`7ADvReRQ>AUivA=PwM}&`iLJJJJZ{_&k3Y2 zUvhnu->Tx7bEg!!9%6moQp;o7lU20XJ=QDEK<4}Dx`6F-nzAlROmUdnwpX%JBgyHE&EJd_^1F8lZE|Tb`4Z9o=_5<>!KzQ} zGZoDj*B>l-_{cMi-TF`Qmi1X-=QLK8d3<{luV|^Id8CphQgh85osBxngzXY9@84_h z%f`4XHqy%Sk8jJOIRCXp`#HYZ9tjb8r7sFp_47S~Y8LMZ0|ACD*T9*?vNtsq>c#FZ;4AF83!N-}7Zh7R+l= zw!4tMaFcX!P3Els8SK9LLhRyJK z+&t%0aQM50`k&8S%~Uwp?rm21|LFNWlV+G(ACydR-+6qUWz*kv3iZ{C%If{&>euW) z@@W5$w|~FeitEW*v$L+4y5z_58%Bq(G3{FX-GAnci|2Se?q;?>GnLW&F(+=#|9Z#w z-#_o;-&VrrqOwwO%B-^WHLF&}EjZj>w|%nOHqFIdHlY~LM|@kt^cB?v?aRN!JSJaHjLeW zb$GNz$NM?U8z(oNo3`r8e%lS3CEre$zVUyZ&{wym*PqV*QMTjIf~G4kJUQ0C+9_cC zLSfq{(=7&@u0G04(^)DtZ};(To{cq(j|-+htN*jEpuxFk0?Ru8XP3^z-&SNkURt^2 zTSfZcN5x^>ML|ky1rH}oViJ7+@cetDleV!hBksSwEn~4q@Xz`GmCrjHOK)GEd0p-B z(Y6SS8poDMuE(=E|4rGmcvp9MsGHU9V8)Ek>HE*irk%F9rK7RU-2N}yiz$uUO57Qx zXRAv!tdNVQd!7^~xNg;>=ojmFGC8tun)m$oP3PKhc*onS@K5?rr^}yu zUmUrRUESJJ=>F0}m;TMP>i;SjwwR~)_nQ*mc_z*-WwSXs6&fs_bqPz9s|2|0xg0$2 t@u$Pda=rYAtrOikX7%&GRsG&;Z1Lh{&yL;Ao6ROYUN$pxVgmyL003>zdZqvX literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..9126ae37cbc3587421d6889eadd1d91fbf1994d4 GIT binary patch literal 7778 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..8d12d2e --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + QWeather + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..29b8bc1 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +