commit 6936eab68542a339840ce032e89c694a82dac9ae Author: Henry-Hiles Date: Tue Mar 28 10:30:49 2023 -0400 initial commit 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 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ 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 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ 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 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ 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 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ 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 @@ + + + +