diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..94085ad --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +WeatherApp \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..1b1b82d --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 4e3844e..ecd50f0 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,18 +1,19 @@ + diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..c224ad5 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 12b852b..1039c01 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -10,7 +10,7 @@ - + diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 1bb6d8d..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,82 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'kotlin-kapt' - id 'dagger.hilt.android.plugin' -} - -android { - compileSdk 32 - - defaultConfig { - applicationId "com.plcoding.weatherapp" - minSdk 21 - targetSdk 32 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary true - } - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion compose_version - } - packagingOptions { - resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - } - } -} - -dependencies { - - implementation 'androidx.core:core-ktx:1.7.0' - implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.compose.material:material:$compose_version" - implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' - implementation 'androidx.activity:activity-compose:1.3.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" - debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - - //Dagger - Hilt - implementation "com.google.dagger:hilt-android:2.40.5" - kapt "com.google.dagger:hilt-android-compiler:2.40.5" - implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" - kapt "androidx.hilt:hilt-compiler:1.0.0" - implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' - - // Location Services - implementation 'com.google.android.gms:play-services-location:20.0.0' - - // 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.4.1" -} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..98211cf --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,75 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + id("com.google.devtools.ksp") + id("com.google.dagger.hilt.android") +} + +android { + namespace = "com.plcoding.weatherapp" + compileSdk = 35 + + defaultConfig { + applicationId = "com.plcoding.weatherapp" + minSdk = 26 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + + // GMS Location + implementation("com.google.android.gms:play-services-location:21.3.0") + + // Hilt + implementation("com.google.dagger:hilt-android:2.51.1") + ksp("com.google.dagger:hilt-android-compiler:2.51.1") + + // Retrofit + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-moshi:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:okhttp:4.11.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.11.0") +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3f09106..a3850d3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:supportsRtl="true" android:theme="@style/Theme.WeatherApp"> diff --git a/app/src/main/java/com/plcoding/weatherapp/MainActivity.kt b/app/src/main/java/com/plcoding/weatherapp/MainActivity.kt new file mode 100644 index 0000000..804c89f --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/MainActivity.kt @@ -0,0 +1,49 @@ +package com.plcoding.weatherapp + +import android.Manifest +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.ui.Modifier +import com.plcoding.weatherapp.ui.screen.WeatherScreen +import com.plcoding.weatherapp.ui.screen.WeatherViewModel +import com.plcoding.weatherapp.ui.theme.WeatherAppTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + private val viewModel: WeatherViewModel by viewModels() + private lateinit var permissionLauncher: ActivityResultLauncher> + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + permissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { + viewModel.loadWeatherInfo() + } + permissionLauncher.launch(arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION, + )) + setContent { + WeatherAppTheme { + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + WeatherScreen( + modifier = Modifier.fillMaxSize().padding(innerPadding), + viewModel = viewModel + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt b/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt index a53d06b..3fd3e78 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/location/DefaultLocationTracker.kt @@ -9,12 +9,10 @@ import android.location.LocationManager import androidx.core.content.ContextCompat import com.google.android.gms.location.FusedLocationProviderClient import com.plcoding.weatherapp.domain.location.LocationTracker -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine import javax.inject.Inject import kotlin.coroutines.resume -@ExperimentalCoroutinesApi class DefaultLocationTracker @Inject constructor( private val locationClient: FusedLocationProviderClient, private val application: Application @@ -31,8 +29,7 @@ class DefaultLocationTracker @Inject constructor( ) == PackageManager.PERMISSION_GRANTED val locationManager = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager - val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) || - locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) || locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) if(!hasAccessCoarseLocationPermission || !hasAccessFineLocationPermission || !isGpsEnabled) { return null } diff --git a/app/src/main/java/com/plcoding/weatherapp/data/mappers/WeatherMappers.kt b/app/src/main/java/com/plcoding/weatherapp/data/mapper/WeatherMapper.kt similarity index 74% rename from app/src/main/java/com/plcoding/weatherapp/data/mappers/WeatherMappers.kt rename to app/src/main/java/com/plcoding/weatherapp/data/mapper/WeatherMapper.kt index cc18e49..fec1685 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/mappers/WeatherMappers.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/mapper/WeatherMapper.kt @@ -1,10 +1,10 @@ -package com.plcoding.weatherapp.data.mappers +package com.plcoding.weatherapp.data.mapper -import com.plcoding.weatherapp.data.remote.WeatherDataDto -import com.plcoding.weatherapp.data.remote.WeatherDto -import com.plcoding.weatherapp.domain.weather.WeatherData -import com.plcoding.weatherapp.domain.weather.WeatherInfo -import com.plcoding.weatherapp.domain.weather.WeatherType +import com.plcoding.weatherapp.data.remote.WeatherDTO +import com.plcoding.weatherapp.data.remote.WeatherDataDTO +import com.plcoding.weatherapp.domain.model.WeatherData +import com.plcoding.weatherapp.domain.model.WeatherInfo +import com.plcoding.weatherapp.domain.model.WeatherType import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -13,7 +13,7 @@ private data class IndexedWeatherData( val data: WeatherData ) -fun WeatherDataDto.toWeatherDataMap(): Map> { +fun WeatherDataDTO.toWeatherDataMap(): Map> { return time.mapIndexed { index, time -> val temperature = temperatures[index] val weatherCode = weatherCodes[index] @@ -38,7 +38,7 @@ fun WeatherDataDto.toWeatherDataMap(): Map> { } } -fun WeatherDto.toWeatherInfo(): WeatherInfo { +fun WeatherDTO.toWeatherInfo(): WeatherInfo { val weatherDataMap = weatherData.toWeatherDataMap() val now = LocalDateTime.now() val currentWeatherData = weatherDataMap[0]?.find { diff --git a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherAPIService.kt similarity index 86% rename from app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt rename to app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherAPIService.kt index 67ac577..dcbbff2 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherApi.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherAPIService.kt @@ -3,11 +3,12 @@ package com.plcoding.weatherapp.data.remote import retrofit2.http.GET import retrofit2.http.Query -interface WeatherApi { +interface WeatherAPIService { @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 + ): WeatherDTO + } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDataDto.kt b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDataDto.kt index aa25dfc..b01a7cb 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDataDto.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDataDto.kt @@ -2,7 +2,7 @@ package com.plcoding.weatherapp.data.remote import com.squareup.moshi.Json -data class WeatherDataDto( +data class WeatherDataDTO( val time: List, @field:Json(name = "temperature_2m") val temperatures: List, diff --git a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDto.kt b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDto.kt index e647e50..02755ae 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDto.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/remote/WeatherDto.kt @@ -2,7 +2,7 @@ package com.plcoding.weatherapp.data.remote import com.squareup.moshi.Json -data class WeatherDto( +data class WeatherDTO( @field:Json(name = "hourly") - val weatherData: WeatherDataDto -) + val weatherData: WeatherDataDTO +) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt b/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt index 49879dd..b514959 100644 --- a/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt +++ b/app/src/main/java/com/plcoding/weatherapp/data/repository/WeatherRepositoryImpl.kt @@ -1,16 +1,17 @@ package com.plcoding.weatherapp.data.repository -import com.plcoding.weatherapp.data.mappers.toWeatherInfo -import com.plcoding.weatherapp.data.remote.WeatherApi +import com.plcoding.weatherapp.data.mapper.toWeatherInfo +import com.plcoding.weatherapp.data.remote.WeatherAPIService +import com.plcoding.weatherapp.domain.model.WeatherInfo import com.plcoding.weatherapp.domain.repository.WeatherRepository -import com.plcoding.weatherapp.domain.util.Resource -import com.plcoding.weatherapp.domain.weather.WeatherInfo +import com.plcoding.weatherapp.util.Resource import javax.inject.Inject class WeatherRepositoryImpl @Inject constructor( - private val api: WeatherApi + private val api: WeatherAPIService ): WeatherRepository { + override suspend fun getWeatherData(lat: Double, long: Double): Resource { return try { Resource.Success( diff --git a/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/LocationProviderModule.kt similarity index 53% rename from app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt rename to app/src/main/java/com/plcoding/weatherapp/dependencyinjection/LocationProviderModule.kt index 6662377..80540de 100644 --- a/app/src/main/java/com/plcoding/weatherapp/di/AppModule.kt +++ b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/LocationProviderModule.kt @@ -1,31 +1,17 @@ -package com.plcoding.weatherapp.di +package com.plcoding.weatherapp.dependencyinjection import android.app.Application import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices -import com.plcoding.weatherapp.data.remote.WeatherApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import retrofit2.create import javax.inject.Singleton @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() - } +object LocationProviderModule { @Provides @Singleton diff --git a/app/src/main/java/com/plcoding/weatherapp/di/LocationModule.kt b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/LocationTrackerModule.kt similarity index 75% rename from app/src/main/java/com/plcoding/weatherapp/di/LocationModule.kt rename to app/src/main/java/com/plcoding/weatherapp/dependencyinjection/LocationTrackerModule.kt index 77a203a..7e9ecf3 100644 --- a/app/src/main/java/com/plcoding/weatherapp/di/LocationModule.kt +++ b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/LocationTrackerModule.kt @@ -1,4 +1,4 @@ -package com.plcoding.weatherapp.di +package com.plcoding.weatherapp.dependencyinjection import com.plcoding.weatherapp.data.location.DefaultLocationTracker import com.plcoding.weatherapp.domain.location.LocationTracker @@ -6,15 +6,14 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Singleton -@ExperimentalCoroutinesApi @Module @InstallIn(SingletonComponent::class) -abstract class LocationModule { +abstract class LocationTrackerModule { @Binds @Singleton abstract fun bindLocationTracker(defaultLocationTracker: DefaultLocationTracker): LocationTracker + } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/di/RepositoryModule.kt b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/RepositoryModule.kt similarity index 52% rename from app/src/main/java/com/plcoding/weatherapp/di/RepositoryModule.kt rename to app/src/main/java/com/plcoding/weatherapp/dependencyinjection/RepositoryModule.kt index 2dcbff7..1803887 100644 --- a/app/src/main/java/com/plcoding/weatherapp/di/RepositoryModule.kt +++ b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/RepositoryModule.kt @@ -1,24 +1,18 @@ -package com.plcoding.weatherapp.di +package com.plcoding.weatherapp.dependencyinjection -import com.plcoding.weatherapp.data.location.DefaultLocationTracker import com.plcoding.weatherapp.data.repository.WeatherRepositoryImpl -import com.plcoding.weatherapp.domain.location.LocationTracker import com.plcoding.weatherapp.domain.repository.WeatherRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Singleton -@ExperimentalCoroutinesApi @Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Binds @Singleton - abstract fun bindWeatherRepository( - weatherRepositoryImpl: WeatherRepositoryImpl - ): WeatherRepository + abstract fun bindWeatherRepository(weatherRepositoryImpl: WeatherRepositoryImpl): WeatherRepository } \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/RetrofitModule.kt b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/RetrofitModule.kt new file mode 100644 index 0000000..ccbe681 --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/dependencyinjection/RetrofitModule.kt @@ -0,0 +1,25 @@ +package com.plcoding.weatherapp.dependencyinjection + +import com.plcoding.weatherapp.data.remote.WeatherAPIService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object RetrofitModule { + + @Provides + @Singleton + fun provideWeatherApi(): WeatherAPIService { + return Retrofit.Builder() + .baseUrl("https://api.open-meteo.com/") + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(WeatherAPIService::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherData.kt b/app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherData.kt similarity index 82% rename from app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherData.kt rename to app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherData.kt index af5377c..e38e301 100644 --- a/app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherData.kt +++ b/app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherData.kt @@ -1,4 +1,4 @@ -package com.plcoding.weatherapp.domain.weather +package com.plcoding.weatherapp.domain.model import java.time.LocalDateTime @@ -9,4 +9,4 @@ data class WeatherData( val windSpeed: Double, val humidity: Double, val weatherType: WeatherType -) +) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherInfo.kt b/app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherInfo.kt similarity index 71% rename from app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherInfo.kt rename to app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherInfo.kt index ff7e9fc..71bac0d 100644 --- a/app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherInfo.kt +++ b/app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherInfo.kt @@ -1,6 +1,6 @@ -package com.plcoding.weatherapp.domain.weather +package com.plcoding.weatherapp.domain.model data class WeatherInfo( val weatherDataPerDay: Map>, val currentWeatherData: WeatherData? -) +) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherType.kt b/app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherType.kt similarity index 74% rename from app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherType.kt rename to app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherType.kt index d39edec..835685a 100644 --- a/app/src/main/java/com/plcoding/weatherapp/domain/weather/WeatherType.kt +++ b/app/src/main/java/com/plcoding/weatherapp/domain/model/WeatherType.kt @@ -1,4 +1,4 @@ -package com.plcoding.weatherapp.domain.weather +package com.plcoding.weatherapp.domain.model import androidx.annotation.DrawableRes import com.plcoding.weatherapp.R @@ -7,111 +7,111 @@ sealed class WeatherType( val weatherDesc: String, @DrawableRes val iconRes: Int ) { - object ClearSky : WeatherType( + data object ClearSky : WeatherType( weatherDesc = "Clear sky", iconRes = R.drawable.ic_sunny ) - object MainlyClear : WeatherType( + data object MainlyClear : WeatherType( weatherDesc = "Mainly clear", iconRes = R.drawable.ic_cloudy ) - object PartlyCloudy : WeatherType( + data object PartlyCloudy : WeatherType( weatherDesc = "Partly cloudy", iconRes = R.drawable.ic_cloudy ) - object Overcast : WeatherType( + data object Overcast : WeatherType( weatherDesc = "Overcast", iconRes = R.drawable.ic_cloudy ) - object Foggy : WeatherType( + data object Foggy : WeatherType( weatherDesc = "Foggy", iconRes = R.drawable.ic_very_cloudy ) - object DepositingRimeFog : WeatherType( + data object DepositingRimeFog : WeatherType( weatherDesc = "Depositing rime fog", iconRes = R.drawable.ic_very_cloudy ) - object LightDrizzle : WeatherType( + data object LightDrizzle : WeatherType( weatherDesc = "Light drizzle", iconRes = R.drawable.ic_rainshower ) - object ModerateDrizzle : WeatherType( + data object ModerateDrizzle : WeatherType( weatherDesc = "Moderate drizzle", iconRes = R.drawable.ic_rainshower ) - object DenseDrizzle : WeatherType( + data object DenseDrizzle : WeatherType( weatherDesc = "Dense drizzle", iconRes = R.drawable.ic_rainshower ) - object LightFreezingDrizzle : WeatherType( + data object LightFreezingDrizzle : WeatherType( weatherDesc = "Slight freezing drizzle", iconRes = R.drawable.ic_snowyrainy ) - object DenseFreezingDrizzle : WeatherType( + data object DenseFreezingDrizzle : WeatherType( weatherDesc = "Dense freezing drizzle", iconRes = R.drawable.ic_snowyrainy ) - object SlightRain : WeatherType( + data object SlightRain : WeatherType( weatherDesc = "Slight rain", iconRes = R.drawable.ic_rainy ) - object ModerateRain : WeatherType( + data object ModerateRain : WeatherType( weatherDesc = "Rainy", iconRes = R.drawable.ic_rainy ) - object HeavyRain : WeatherType( + data object HeavyRain : WeatherType( weatherDesc = "Heavy rain", iconRes = R.drawable.ic_rainy ) - object HeavyFreezingRain: WeatherType( + data object HeavyFreezingRain: WeatherType( weatherDesc = "Heavy freezing rain", iconRes = R.drawable.ic_snowyrainy ) - object SlightSnowFall: WeatherType( + data object SlightSnowFall: WeatherType( weatherDesc = "Slight snow fall", iconRes = R.drawable.ic_snowy ) - object ModerateSnowFall: WeatherType( + data object ModerateSnowFall: WeatherType( weatherDesc = "Moderate snow fall", iconRes = R.drawable.ic_heavysnow ) - object HeavySnowFall: WeatherType( + data object HeavySnowFall: WeatherType( weatherDesc = "Heavy snow fall", iconRes = R.drawable.ic_heavysnow ) - object SnowGrains: WeatherType( + data object SnowGrains: WeatherType( weatherDesc = "Snow grains", iconRes = R.drawable.ic_heavysnow ) - object SlightRainShowers: WeatherType( + data object SlightRainShowers: WeatherType( weatherDesc = "Slight rain showers", iconRes = R.drawable.ic_rainshower ) - object ModerateRainShowers: WeatherType( + data object ModerateRainShowers: WeatherType( weatherDesc = "Moderate rain showers", iconRes = R.drawable.ic_rainshower ) - object ViolentRainShowers: WeatherType( + data object ViolentRainShowers: WeatherType( weatherDesc = "Violent rain showers", iconRes = R.drawable.ic_rainshower ) - object SlightSnowShowers: WeatherType( + data object SlightSnowShowers: WeatherType( weatherDesc = "Light snow showers", iconRes = R.drawable.ic_snowy ) - object HeavySnowShowers: WeatherType( + data object HeavySnowShowers: WeatherType( weatherDesc = "Heavy snow showers", iconRes = R.drawable.ic_snowy ) - object ModerateThunderstorm: WeatherType( + data object ModerateThunderstorm: WeatherType( weatherDesc = "Moderate thunderstorm", iconRes = R.drawable.ic_thunder ) - object SlightHailThunderstorm: WeatherType( + data object SlightHailThunderstorm: WeatherType( weatherDesc = "Thunderstorm with slight hail", iconRes = R.drawable.ic_rainythunder ) - object HeavyHailThunderstorm: WeatherType( + data object HeavyHailThunderstorm: WeatherType( weatherDesc = "Thunderstorm with heavy hail", iconRes = R.drawable.ic_rainythunder ) diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt b/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt index ade5766..9f073ef 100644 --- a/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt +++ b/app/src/main/java/com/plcoding/weatherapp/domain/repository/WeatherRepository.kt @@ -1,7 +1,7 @@ package com.plcoding.weatherapp.domain.repository -import com.plcoding.weatherapp.domain.util.Resource -import com.plcoding.weatherapp.domain.weather.WeatherInfo +import com.plcoding.weatherapp.domain.model.WeatherInfo +import com.plcoding.weatherapp.util.Resource interface WeatherRepository { suspend fun getWeatherData(lat: Double, long: Double): Resource diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt deleted file mode 100644 index 3445490..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/MainActivity.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.plcoding.weatherapp.presentation - -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.activity.viewModels -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Text -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.plcoding.weatherapp.presentation.ui.theme.DarkBlue -import com.plcoding.weatherapp.presentation.ui.theme.DeepBlue -import com.plcoding.weatherapp.presentation.ui.theme.WeatherAppTheme -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class MainActivity : ComponentActivity() { - - private val viewModel: WeatherViewModel by viewModels() - private lateinit var permissionLauncher: ActivityResultLauncher> - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - permissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions() - ) { - viewModel.loadWeatherInfo() - } - permissionLauncher.launch(arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION, - )) - setContent { - WeatherAppTheme { - Box( - modifier = Modifier.fillMaxSize() - ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(DarkBlue) - ) { - WeatherCard( - state = viewModel.state, - backgroundColor = DeepBlue - ) - Spacer(modifier = Modifier.height(16.dp)) - WeatherForecast(state = viewModel.state) - } - if(viewModel.state.isLoading) { - CircularProgressIndicator( - modifier = Modifier.align(Alignment.Center) - ) - } - viewModel.state.error?.let { error -> - Text( - text = error, - color = Color.Red, - textAlign = TextAlign.Center, - modifier = Modifier.align(Alignment.Center) - ) - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherState.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherState.kt deleted file mode 100644 index 67bf723..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.plcoding.weatherapp.presentation - -import com.plcoding.weatherapp.domain.weather.WeatherInfo - -data class WeatherState( - val weatherInfo: WeatherInfo? = null, - val isLoading: Boolean = false, - val error: String? = null -) diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Color.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Color.kt deleted file mode 100644 index 29be551..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Color.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.plcoding.weatherapp.presentation.ui.theme - -import androidx.compose.ui.graphics.Color - -val DarkBlue = Color(0xFF1B3B5A) -val DeepBlue = Color(0xFF102840) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Shape.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Shape.kt deleted file mode 100644 index 92ffb9f..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Shape.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.plcoding.weatherapp.presentation.ui.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes -import androidx.compose.ui.unit.dp - -val Shapes = Shapes( - small = RoundedCornerShape(4.dp), - medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) -) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Theme.kt b/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Theme.kt deleted file mode 100644 index ba1d274..0000000 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Theme.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.plcoding.weatherapp.presentation.ui.theme - -import androidx.compose.material.MaterialTheme -import androidx.compose.runtime.Composable - -@Composable -fun WeatherAppTheme(content: @Composable () -> Unit) { - MaterialTheme( - typography = Typography, - shapes = Shapes, - content = content - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/HourlyWeatherDisplay.kt b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentHourlyWeather.kt similarity index 83% rename from app/src/main/java/com/plcoding/weatherapp/presentation/HourlyWeatherDisplay.kt rename to app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentHourlyWeather.kt index 4e2cd57..215fa2d 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/HourlyWeatherDisplay.kt +++ b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentHourlyWeather.kt @@ -1,10 +1,10 @@ -package com.plcoding.weatherapp.presentation +package com.plcoding.weatherapp.ui.component 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.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -13,19 +13,18 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.plcoding.weatherapp.domain.weather.WeatherData +import com.plcoding.weatherapp.domain.model.WeatherData import java.time.format.DateTimeFormatter + @Composable -fun HourlyWeatherDisplay( +fun ComponentHourlyWeather( weatherData: WeatherData, modifier: Modifier = Modifier, textColor: Color = Color.White ) { val formattedTime = remember(weatherData) { - weatherData.time.format( - DateTimeFormatter.ofPattern("HH:mm") - ) + weatherData.time.format(DateTimeFormatter.ofPattern("HH:mm")) } Column( modifier = modifier, diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherCard.kt b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeather.kt similarity index 80% rename from app/src/main/java/com/plcoding/weatherapp/presentation/WeatherCard.kt rename to app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeather.kt index 1940d36..dffd6d5 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherCard.kt +++ b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeather.kt @@ -1,13 +1,21 @@ -package com.plcoding.weatherapp.presentation +package com.plcoding.weatherapp.ui.component import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.material.Text +import androidx.compose.material3.Card +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource @@ -16,20 +24,19 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.plcoding.weatherapp.R +import com.plcoding.weatherapp.ui.screen.state.WeatherUIState import java.time.format.DateTimeFormatter import kotlin.math.roundToInt + @Composable fun WeatherCard( - state: WeatherState, - backgroundColor: Color, - modifier: Modifier = Modifier + state: WeatherUIState ) { state.weatherInfo?.currentWeatherData?.let { data -> Card( - backgroundColor = backgroundColor, shape = RoundedCornerShape(10.dp), - modifier = modifier.padding(16.dp) + modifier = Modifier.padding(16.dp).clip(RoundedCornerShape(10.dp)) ) { Column( modifier = Modifier @@ -39,9 +46,7 @@ fun WeatherCard( ) { Text( text = "Today ${ - data.time.format( - DateTimeFormatter.ofPattern("HH:mm") - ) + data.time.format(DateTimeFormatter.ofPattern("HH:mm")) }", modifier = Modifier.align(Alignment.End), color = Color.White diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherDataDisplay.kt b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeatherData.kt similarity index 86% rename from app/src/main/java/com/plcoding/weatherapp/presentation/WeatherDataDisplay.kt rename to app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeatherData.kt index fc3077a..509cc58 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherDataDisplay.kt +++ b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeatherData.kt @@ -1,17 +1,16 @@ -package com.plcoding.weatherapp.presentation +package com.plcoding.weatherapp.ui.component 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.material.Icon -import androidx.compose.material.Text +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.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeatherForecast.kt similarity index 67% rename from app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt rename to app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeatherForecast.kt index a75d391..11307ba 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherForecast.kt +++ b/app/src/main/java/com/plcoding/weatherapp/ui/component/ComponentWeatherForecast.kt @@ -1,23 +1,27 @@ -package com.plcoding.weatherapp.presentation +package com.plcoding.weatherapp.ui.component -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items -import androidx.compose.material.Text +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.plcoding.weatherapp.ui.screen.state.WeatherUIState @Composable fun WeatherForecast( - state: WeatherState, - modifier: Modifier = Modifier + state: WeatherUIState ) { state.weatherInfo?.weatherDataPerDay?.get(0)?.let { data -> Column( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) ) { @@ -29,7 +33,7 @@ fun WeatherForecast( Spacer(modifier = Modifier.height(16.dp)) LazyRow(content = { items(data) { weatherData -> - HourlyWeatherDisplay( + ComponentHourlyWeather( weatherData = weatherData, modifier = Modifier .height(100.dp) diff --git a/app/src/main/java/com/plcoding/weatherapp/ui/screen/WeatherScreen.kt b/app/src/main/java/com/plcoding/weatherapp/ui/screen/WeatherScreen.kt new file mode 100644 index 0000000..82f18fd --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/ui/screen/WeatherScreen.kt @@ -0,0 +1,66 @@ +package com.plcoding.weatherapp.ui.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +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.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.plcoding.weatherapp.ui.component.WeatherCard +import com.plcoding.weatherapp.ui.component.WeatherForecast + +@Composable +fun WeatherScreen( + modifier: Modifier = Modifier, + viewModel: WeatherViewModel +) { + Column( + modifier = modifier + ) { + WeatherCard(state = viewModel.state) + Spacer(modifier = Modifier.height(16.dp)) + WeatherForecast(state = viewModel.state) + } + if (viewModel.state.isLoading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + viewModel.state.error?.let { error -> + Column ( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = error, + color = Color.Red, + textAlign = TextAlign.Center + ) + Button( + onClick = { + viewModel.loadWeatherInfo() + } + ) { + Text( + text = "Try Again!", + color = Color.Black, + textAlign = TextAlign.Center + ) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt b/app/src/main/java/com/plcoding/weatherapp/ui/screen/WeatherViewModel.kt similarity index 78% rename from app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt rename to app/src/main/java/com/plcoding/weatherapp/ui/screen/WeatherViewModel.kt index e35faee..3bef83d 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/WeatherViewModel.kt +++ b/app/src/main/java/com/plcoding/weatherapp/ui/screen/WeatherViewModel.kt @@ -1,4 +1,4 @@ -package com.plcoding.weatherapp.presentation +package com.plcoding.weatherapp.ui.screen import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -7,7 +7,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.plcoding.weatherapp.domain.location.LocationTracker import com.plcoding.weatherapp.domain.repository.WeatherRepository -import com.plcoding.weatherapp.domain.util.Resource +import com.plcoding.weatherapp.ui.screen.state.WeatherUIState +import com.plcoding.weatherapp.util.Resource import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -18,7 +19,7 @@ class WeatherViewModel @Inject constructor( private val locationTracker: LocationTracker ): ViewModel() { - var state by mutableStateOf(WeatherState()) + var state by mutableStateOf(WeatherUIState()) private set fun loadWeatherInfo() { @@ -43,8 +44,16 @@ class WeatherViewModel @Inject constructor( error = result.message ) } + + is Resource.Loading -> { + state = state.copy( + weatherInfo = null, + isLoading = true, + error = null + ) + } } - } ?: kotlin.run { + } ?: run { state = state.copy( isLoading = false, error = "Couldn't retrieve location. Make sure to grant permission and enable GPS." diff --git a/app/src/main/java/com/plcoding/weatherapp/ui/screen/state/WeatherUIState.kt b/app/src/main/java/com/plcoding/weatherapp/ui/screen/state/WeatherUIState.kt new file mode 100644 index 0000000..9abbdcc --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/ui/screen/state/WeatherUIState.kt @@ -0,0 +1,9 @@ +package com.plcoding.weatherapp.ui.screen.state + +import com.plcoding.weatherapp.domain.model.WeatherInfo + +data class WeatherUIState( + val weatherInfo: WeatherInfo? = null, + val isLoading: Boolean = false, + val error: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/ui/theme/Color.kt b/app/src/main/java/com/plcoding/weatherapp/ui/theme/Color.kt new file mode 100644 index 0000000..34920c5 --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.plcoding.weatherapp.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/plcoding/weatherapp/ui/theme/Theme.kt b/app/src/main/java/com/plcoding/weatherapp/ui/theme/Theme.kt new file mode 100644 index 0000000..d9e704a --- /dev/null +++ b/app/src/main/java/com/plcoding/weatherapp/ui/theme/Theme.kt @@ -0,0 +1,57 @@ +package com.plcoding.weatherapp.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +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(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Type.kt b/app/src/main/java/com/plcoding/weatherapp/ui/theme/Type.kt similarity index 53% rename from app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Type.kt rename to app/src/main/java/com/plcoding/weatherapp/ui/theme/Type.kt index 2d80614..b4d3694 100644 --- a/app/src/main/java/com/plcoding/weatherapp/presentation/ui/theme/Type.kt +++ b/app/src/main/java/com/plcoding/weatherapp/ui/theme/Type.kt @@ -1,6 +1,6 @@ -package com.plcoding.weatherapp.presentation.ui.theme +package com.plcoding.weatherapp.ui.theme -import androidx.compose.material.Typography +import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -8,21 +8,27 @@ import androidx.compose.ui.unit.sp // Set of Material typography styles to start with val Typography = Typography( - body1 = TextStyle( + bodyLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, - fontSize = 16.sp + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp ) /* Other default text styles to override - button = TextStyle( + titleLarge = TextStyle( fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp ), - caption = TextStyle( + labelSmall = TextStyle( fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp ) */ ) \ No newline at end of file diff --git a/app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt b/app/src/main/java/com/plcoding/weatherapp/util/Resource.kt similarity index 74% rename from app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt rename to app/src/main/java/com/plcoding/weatherapp/util/Resource.kt index 21dae92..9ce1d00 100644 --- a/app/src/main/java/com/plcoding/weatherapp/domain/util/Resource.kt +++ b/app/src/main/java/com/plcoding/weatherapp/util/Resource.kt @@ -1,6 +1,7 @@ -package com.plcoding.weatherapp.domain.util +package com.plcoding.weatherapp.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) + class Loading: Resource() } diff --git a/build.gradle b/build.gradle deleted file mode 100644 index ba794a9..0000000 --- a/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -buildscript { - ext { - compose_version = '1.1.1' - } - dependencies { - classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5" - } -}// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - id 'com.android.application' version '7.1.0' apply false - id 'com.android.library' version '7.1.0' apply false - id 'org.jetbrains.kotlin.android' version '1.6.10' apply false -} - -task clean(type: Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..3542cad --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,10 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + // Ksp + id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false + // Hilt + id("com.google.dagger.hilt.android") version "2.51.1" apply false +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index cd0519b..20e2a01 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,11 +8,11 @@ # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK +# Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..0aa7ea2 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,32 @@ +[versions] +agp = "8.7.3" +kotlin = "2.0.21" +coreKtx = "1.15.0" +junit = "4.13.2" +junitVersion = "1.2.1" +espressoCore = "3.6.1" +lifecycleRuntimeKtx = "2.8.7" +activityCompose = "1.9.3" +composeBom = "2024.04.01" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c15336a..dce2419 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Jun 26 11:26:31 CEST 2022 +#Wed Dec 11 18:58:46 TRT 2024 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle.kts similarity index 55% rename from settings.gradle rename to settings.gradle.kts index dfe4890..5908742 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -1,8 +1,14 @@ pluginManagement { repositories { - gradlePluginPortal() - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() + gradlePluginPortal() } } dependencyResolutionManagement { @@ -12,5 +18,7 @@ dependencyResolutionManagement { mavenCentral() } } + rootProject.name = "WeatherApp" -include ':app' +include(":app") + \ No newline at end of file