diff --git a/app/build.gradle b/app/build.gradle
index 3b7d186..b8d444f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
buildscript {
apply from: "ktlint.gradle"
+
}
plugins {
@@ -43,11 +44,15 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = '1.8'
+ jvmTarget = '17'
+ }
+ buildFeatures {
+ viewBinding = true
+ buildConfig=true
}
buildFeatures {
viewBinding = true
@@ -70,15 +75,18 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.airbnb.android:lottie:3.4.0'
// Room
+ implementation "androidx.room:room-ktx:2.6.1"
implementation "androidx.room:room-runtime:2.6.1"
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
- annotationProcessor "androidx.room:room-compiler:2.6.1"
+ kapt "androidx.room:room-compiler:2.6.1"
+
// retrofit
implementation "com.squareup.retrofit2:retrofit:2.11.0"
implementation "com.squareup.retrofit2:converter-gson:2.11.0"
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
+ implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
// ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.7.0"
@@ -86,6 +94,11 @@ dependencies {
implementation "io.insert-koin:koin-android:3.5.0"
// coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
+ // Rx
+ implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
+ implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
+ implementation 'androidx.room:room-rxjava2:2.0.0'
// WorkManager
implementation 'androidx.work:work-runtime-ktx:2.9.0'
+
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 076c1ca..128bf33 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
= (LayoutInflater) -> T
@@ -14,6 +15,7 @@ abstract class BaseActivity(private val inflate: ActivityInfla
val binding get() = _binding!!
abstract val viewModel: ViewModel
+ abstract val sharedViewModel: SharedViewModel
protected abstract fun initialize()
diff --git a/app/src/main/java/com/sun/weather/base/BaseFragment.kt b/app/src/main/java/com/sun/weather/base/BaseFragment.kt
index 7b8695a..051d305 100644
--- a/app/src/main/java/com/sun/weather/base/BaseFragment.kt
+++ b/app/src/main/java/com/sun/weather/base/BaseFragment.kt
@@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.viewbinding.ViewBinding
+import com.sun.weather.ui.SharedViewModel
typealias FragmentInflate = (LayoutInflater, ViewGroup?, Boolean) -> T
@@ -15,6 +16,7 @@ abstract class BaseFragment(private val inflate: FragmentInfla
private var _binding: VB? = null
val binding get() = _binding!!
abstract val viewModel: ViewModel
+ abstract val sharedViewModel: SharedViewModel
protected abstract fun initView()
diff --git a/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt b/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt
index 6e2950b..3f28a61 100644
--- a/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt
+++ b/app/src/main/java/com/sun/weather/data/model/CurrentWeather.kt
@@ -1,77 +1,101 @@
package com.sun.weather.data.model
+import com.example.weather.utils.ext.combineWithCountry
+import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
import com.sun.weather.data.model.entity.WeatherEntity
import com.sun.weather.utils.ext.combineWithCountry
data class CurrentWeather(
@SerializedName("main")
- var main: Main,
+ @Expose
+ var main: Main? = null,
@SerializedName("weather")
- var weathers: List,
+ @Expose
+ var weathers: List? = null,
@SerializedName("wind")
- var wind: Wind,
+ @Expose
+ var wind: Wind? = null,
@SerializedName("clouds")
- var clouds: Clouds,
+ @Expose
+ var clouds: Clouds? = null,
@SerializedName("coord")
- var coord: Coord,
+ @Expose
+ var coord: Coord? = Coord(0.0, 0.0),
@SerializedName("dt")
- var dt: Long,
+ @Expose
+ var dt: Long? = null,
@SerializedName("name")
- var nameCity: String,
+ @Expose
+ var nameCity: String? = null,
@SerializedName("sys")
- var sys: Sys,
- var day: String = "",
- var iconWeather: String = "",
+ @Expose
+ var sys: Sys? = null,
+ var day: String? = "",
+ var iconWeather: String? = "",
)
data class Main(
@SerializedName("temp")
- var currentTemperature: Double,
+ @Expose
+ var currentTemperature: Double? = null,
@SerializedName("humidity")
- var humidity: Int,
- @SerializedName("temp_min") var tempMin: Double,
- @SerializedName("temp_max") var tempMax: Double,
+ @Expose
+ var humidity: Int? = null,
+ @SerializedName("temp_min")
+ @Expose
+ var tempMin: Double? = null,
+ @SerializedName("temp_max")
+ @Expose
+ var tempMax: Double? = null,
)
data class Weather(
@SerializedName("icon")
- var iconWeather: String,
+ @Expose
+ var iconWeather: String? = null,
@SerializedName("main")
- var main: String,
+ @Expose
+ var main: String? = null,
@SerializedName("description")
- var description: String,
+ @Expose
+ var description: String? = null,
)
data class Wind(
@SerializedName("speed")
- var windSpeed: Double,
+ @Expose
+ var windSpeed: Double? = null,
)
data class Clouds(
@SerializedName("all")
- var percentCloud: Int,
+ @Expose
+ var percentCloud: Int? = null,
)
data class Coord(
@SerializedName("lon")
- var lon: Double,
+ @Expose
+ var lon: Double? = null,
@SerializedName("lat")
- var lat: Double,
+ @Expose
+ var lat: Double? = null,
)
data class Sys(
@SerializedName("country")
- var country: String,
+ @Expose
+ var country: String? = null,
)
fun CurrentWeather.toWeatherEntity(): WeatherEntity {
- val country: String? = sys.country
- val id = nameCity.combineWithCountry(country)
- val latitude = coord.lat
- val longitude = coord.lon
- val timeZone = dt
- val city = nameCity
+ val country: String = sys?.country ?: "Unknown"
+ val id = nameCity?.combineWithCountry(country) ?: "Unknown"
+ val latitude = coord?.lat ?: 0.0
+ val longitude = coord?.lon ?: 0.0
+ val timeZone = dt ?: 0
+ val city = nameCity ?: "Unknown"
val weatherCurrent: WeatherBasic? = this.toWeatherBasic()
val weatherHourlyList: List? = null
val weatherDailyList: List? = null
@@ -89,16 +113,20 @@ fun CurrentWeather.toWeatherEntity(): WeatherEntity {
)
}
-fun CurrentWeather.toWeatherBasic(): WeatherBasic {
- return WeatherBasic(
- dateTime = dt,
- currentTemperature = main.currentTemperature,
- maxTemperature = main.tempMax,
- minTemperature = main.tempMin,
- iconWeather = weathers.firstOrNull()?.iconWeather,
- weatherDescription = weathers.firstOrNull()?.description,
- humidity = main.humidity,
- percentCloud = clouds.percentCloud,
- windSpeed = wind.windSpeed,
- )
+fun CurrentWeather.toWeatherBasic(): WeatherBasic? {
+ return if (main != null && weathers?.isNotEmpty() == true && wind != null && clouds != null) {
+ WeatherBasic(
+ dateTime = dt ?: 0,
+ currentTemperature = main?.currentTemperature ?: 0.0,
+ maxTemperature = main?.tempMax ?: 0.0,
+ minTemperature = main?.tempMin ?: 0.0,
+ iconWeather = weathers?.firstOrNull()?.iconWeather,
+ weatherDescription = weathers?.firstOrNull()?.description,
+ humidity = main?.humidity ?: 0,
+ percentCloud = clouds?.percentCloud ?: 0,
+ windSpeed = wind?.windSpeed ?: 0.0,
+ )
+ } else {
+ null
+ }
}
diff --git a/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt b/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt
index 8bb92de..d0e77de 100644
--- a/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt
+++ b/app/src/main/java/com/sun/weather/data/model/HourlyForecast.kt
@@ -1,41 +1,73 @@
package com.sun.weather.data.model
+import com.example.weather.utils.ext.combineWithCountry
+import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
import com.sun.weather.data.model.entity.WeatherEntity
import com.sun.weather.utils.ext.combineWithCountry
data class HourlyForecast(
- @SerializedName("cnt") val cnt: Int,
- @SerializedName("list") val forecastList: List,
- @SerializedName("city") val city: City,
+ @SerializedName("cnt")
+ @Expose
+ val cnt: Int = 0,
+ @SerializedName("list")
+ @Expose
+ val forecastList: List? = null,
+ @SerializedName("city")
+ @Expose
+ val city: City? = null,
)
data class City(
- @SerializedName("id") val id: Int,
- @SerializedName("name") val name: String,
- @SerializedName("coord") val coord: Coord,
- @SerializedName("country") val country: String,
+ @SerializedName("id")
+ @Expose
+ val id: Int = 0,
+ @SerializedName("name")
+ @Expose
+ val name: String? = null,
+ @SerializedName("coord")
+ @Expose
+ val coord: Coord? = null,
+ @SerializedName("country")
+ @Expose
+ val country: String? = null,
)
data class HourlyForecastItem(
- @SerializedName("dt") val dt: Long,
- @SerializedName("main") val main: Main,
- @SerializedName("weather") val weather: List,
- @SerializedName("clouds") val clouds: Clouds,
- @SerializedName("wind") val wind: Wind,
- @SerializedName("dt_txt") val dtTxt: String,
- val iconWeather: String = "",
+ @SerializedName("dt")
+ @Expose
+ val dt: Long = 0,
+ @SerializedName("main")
+ @Expose
+ val main: Main? = null,
+ @SerializedName("weather")
+ @Expose
+ val weather: List? = null,
+ @SerializedName("clouds")
+ @Expose
+ val clouds: Clouds? = null,
+ @SerializedName("wind")
+ @Expose
+ val wind: Wind? = null,
+ @SerializedName("dt_txt")
+ @Expose
+ val dtTxt: String? = null,
+ val iconWeather: String? = "",
)
-fun HourlyForecast.toWeather(): WeatherEntity {
+fun HourlyForecast.toWeatherEntity(): WeatherEntity {
+ val cityName = city?.name ?: "Unknown"
+ val cityCountry = city?.country ?: "Unknown"
+ val coord = city?.coord ?: Coord(0.0, 0.0)
+
return WeatherEntity(
- id = city.name.combineWithCountry(city.country),
- latitude = city.coord.lat,
- longitude = city.coord.lon,
- city = city.name,
- country = city.country,
+ id = cityName.combineWithCountry(cityCountry),
+ latitude = coord.lat,
+ longitude = coord.lon,
+ city = cityName,
+ country = cityCountry,
weatherCurrent = null,
- weatherHourlyList = forecastList.map { it.toWeatherBasic() },
+ weatherHourlyList = forecastList?.map { it.toWeatherBasic() } ?: emptyList(),
weatherDailyList = null,
)
}
@@ -43,13 +75,13 @@ fun HourlyForecast.toWeather(): WeatherEntity {
fun HourlyForecastItem.toWeatherBasic(): WeatherBasic {
return WeatherBasic(
dateTime = dt,
- currentTemperature = main.currentTemperature,
- maxTemperature = main.tempMax,
- minTemperature = main.tempMin,
- iconWeather = weather.firstOrNull()?.iconWeather,
- weatherDescription = weather.firstOrNull()?.description,
- humidity = main.humidity,
- percentCloud = clouds.percentCloud,
- windSpeed = wind.windSpeed,
+ currentTemperature = main?.currentTemperature ?: 0.0,
+ maxTemperature = main?.tempMax ?: 0.0,
+ minTemperature = main?.tempMin ?: 0.0,
+ iconWeather = weather?.firstOrNull()?.iconWeather,
+ weatherDescription = weather?.firstOrNull()?.description,
+ humidity = main?.humidity ?: 0,
+ percentCloud = clouds?.percentCloud ?: 0,
+ windSpeed = wind?.windSpeed ?: 0.0,
)
}
diff --git a/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt b/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt
index 7ec8fe6..92c14c2 100644
--- a/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt
+++ b/app/src/main/java/com/sun/weather/data/model/WeeklyForecast.kt
@@ -1,42 +1,77 @@
package com.sun.weather.data.model
+
+import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
import com.sun.weather.data.model.entity.WeatherEntity
import com.sun.weather.utils.ext.combineWithCountry
data class WeeklyForecast(
- @SerializedName("city") val city: City,
- @SerializedName("cnt") val cnt: Int,
- @SerializedName("list") val forecastList: List,
+ @SerializedName("city")
+ @Expose
+ val city: City? = null,
+ @SerializedName("cnt")
+ @Expose
+ val cnt: Int = 0,
+ @SerializedName("list")
+ @Expose
+ val forecastList: List? = null,
)
data class WeeklyForecastItem(
- @SerializedName("dt") val dt: Long,
- @SerializedName("temp") val temp: Temp,
- @SerializedName("humidity") val humidity: Int,
- @SerializedName("weather") val weather: List,
- @SerializedName("speed") val speed: Double,
- @SerializedName("clouds") val clouds: Int,
- val day: String = "",
- val iconWeather: String = "",
+ @SerializedName("dt")
+ @Expose
+ val dt: Long = 0,
+ @SerializedName("temp")
+ @Expose
+ val temp: Temp? = null,
+ @SerializedName("humidity")
+ @Expose
+ val humidity: Int = 0,
+ @SerializedName("weather")
+ @Expose
+ val weather: List? = null,
+ @SerializedName("speed")
+ @Expose
+ val speed: Double = 0.0,
+ @SerializedName("clouds")
+ @Expose
+ val clouds: Int = 0,
+ val day: String? = "",
+ val iconWeather: String? = "",
)
data class Temp(
- @SerializedName("day") val day: Double,
- @SerializedName("min") val min: Double,
- @SerializedName("max") val max: Double,
- @SerializedName("night") val night: Double,
- @SerializedName("eve") val eve: Double,
- @SerializedName("morn") val morn: Double,
+ @SerializedName("day")
+ @Expose
+ val day: Double = 0.0,
+ @SerializedName("min")
+ @Expose
+ val min: Double = 0.0,
+ @SerializedName("max")
+ @Expose
+ val max: Double = 0.0,
+ @SerializedName("night")
+ @Expose
+ val night: Double = 0.0,
+ @SerializedName("eve")
+ @Expose
+ val eve: Double = 0.0,
+ @SerializedName("morn")
+ @Expose
+ val morn: Double = 0.0,
)
fun WeeklyForecast.toWeatherEntity(): WeatherEntity {
+ val cityName = city?.name ?: "Unknown"
+ val cityCountry = city?.country ?: "Unknown"
+ val coord = city?.coord ?: Coord(0.0, 0.0)
return WeatherEntity(
- id = city.name.combineWithCountry(city.country),
- latitude = city.coord.lat,
- longitude = city.coord.lon,
- city = city.name,
- country = city.country,
- weatherDailyList = forecastList.map { it.toWeatherBasic() },
+ id = cityName.combineWithCountry(cityCountry),
+ latitude = coord.lat,
+ longitude = coord.lon,
+ city = cityName,
+ country = cityCountry,
+ weatherDailyList = forecastList?.map { it.toWeatherBasic() } ?: emptyList(),
weatherHourlyList = null,
weatherCurrent = null,
)
@@ -45,10 +80,11 @@ fun WeeklyForecast.toWeatherEntity(): WeatherEntity {
fun WeeklyForecastItem.toWeatherBasic(): WeatherBasic {
return WeatherBasic(
dateTime = dt,
- currentTemperature = temp.day,
- maxTemperature = temp.max,
- minTemperature = temp.min,
- iconWeather = weather.firstOrNull()?.iconWeather,
+ currentTemperature = temp?.day ?: 0.0,
+ maxTemperature = temp?.max ?: 0.0,
+ minTemperature = temp?.min ?: 0.0,
+ iconWeather = weather?.firstOrNull()?.iconWeather,
+ weatherDescription = weather?.firstOrNull()?.description,
humidity = humidity,
percentCloud = clouds,
windSpeed = speed,
diff --git a/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt b/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt
index 57c46e2..4e4484a 100644
--- a/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt
+++ b/app/src/main/java/com/sun/weather/data/repository/source/WeatherDataSource.kt
@@ -2,22 +2,22 @@ package com.sun.weather.data.repository.source
import com.sun.weather.data.model.CurrentWeather
import com.sun.weather.data.model.HourlyForecast
-import com.sun.weather.data.model.Weather
import com.sun.weather.data.model.WeeklyForecast
+import com.sun.weather.data.model.entity.WeatherEntity
interface WeatherDataSource {
interface Local {
suspend fun insertCurrentWeather(
- current: Weather,
- hourly: Weather,
- daily: Weather,
+ current: WeatherEntity,
+ hourly: WeatherEntity,
+ daily: WeatherEntity,
)
- suspend fun insertCurrentWeather(weather: Weather)
+ suspend fun insertCurrentWeather(weather: WeatherEntity)
- suspend fun getAllLocalWeathers(): List
+ suspend fun getAllLocalWeathers(): List
- suspend fun getLocalWeather(id: String): Weather?
+ suspend fun getLocalWeather(id: String): WeatherEntity?
suspend fun deleteWeather(id: String)
}
diff --git a/app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt b/app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt
index 1f63b97..f5953b2 100644
--- a/app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt
+++ b/app/src/main/java/com/sun/weather/data/repository/source/local/AppDatabase.kt
@@ -5,7 +5,6 @@ import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
-import com.sun.weather.data.model.Weather
import com.sun.weather.data.repository.source.local.converter.WeatherBasicConverter
import com.sun.weather.data.repository.source.local.converter.WeatherBasicListConverter
import com.sun.weather.data.repository.source.local.dao.WeatherDao
diff --git a/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt b/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt
index aee036c..3f09c38 100644
--- a/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt
+++ b/app/src/main/java/com/sun/weather/data/repository/source/local/dao/WeatherDao.kt
@@ -5,21 +5,21 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
-import com.sun.weather.data.model.Weather
+import com.sun.weather.data.model.entity.WeatherEntity
import com.sun.weather.data.model.entity.WeatherEntry.TBL_WEATHER_NAME
@Dao
interface WeatherDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
- suspend fun insertWeather(weather: Weather)
+ suspend fun insertWeather(weather: WeatherEntity)
@Transaction
@Query("SELECT * FROM $TBL_WEATHER_NAME")
- suspend fun getAllData(): List
+ suspend fun getAllData(): List
@Transaction
@Query("SELECT * FROM $TBL_WEATHER_NAME WHERE id = :idWeather")
- suspend fun getWeather(idWeather: String): Weather?
+ suspend fun getWeather(idWeather: String): WeatherEntity?
@Query("DELETE FROM $TBL_WEATHER_NAME WHERE id = :idWeather")
suspend fun deleteWeather(idWeather: String)
diff --git a/app/src/main/java/com/sun/weather/di/AppModule.kt b/app/src/main/java/com/sun/weather/di/AppModule.kt
index ca867e3..3a2bc2c 100644
--- a/app/src/main/java/com/sun/weather/di/AppModule.kt
+++ b/app/src/main/java/com/sun/weather/di/AppModule.kt
@@ -7,18 +7,19 @@ import com.example.weather.utils.dispatchers.DispatcherProvider
import com.google.gson.FieldNamingPolicy
import com.google.gson.Gson
import com.google.gson.GsonBuilder
+import com.sun.weather.data.repository.source.local.AppDatabase
import com.sun.weather.data.repository.source.remote.api.middleware.BooleanAdapter
import com.sun.weather.data.repository.source.remote.api.middleware.DoubleAdapter
import com.sun.weather.data.repository.source.remote.api.middleware.IntegerAdapter
-import com.sun.weather.utils.DateTimeUtils
+import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val AppModule =
module {
single { provideResources(get()) }
-
single { provideBaseDispatcherProvider() }
-
+ single { provideAppDatabase(androidContext()) }
+ factory { get().weatherDao() }
single { provideGson() }
}
@@ -26,6 +27,10 @@ fun provideResources(app: Application): Resources {
return app.resources
}
+fun provideAppDatabase(context: Context): AppDatabase {
+ return AppDatabase.getInstance(context)
+}
+
fun provideBaseDispatcherProvider(): BaseDispatcherProvider {
return DispatcherProvider()
}
diff --git a/app/src/main/java/com/sun/weather/di/DataSourceModule.kt b/app/src/main/java/com/sun/weather/di/DataSourceModule.kt
index 4e18013..e5fc32b 100644
--- a/app/src/main/java/com/sun/weather/di/DataSourceModule.kt
+++ b/app/src/main/java/com/sun/weather/di/DataSourceModule.kt
@@ -1,10 +1,12 @@
-package com.example.weather.di
+package com.sun.weather.di
import com.sun.weather.data.repository.source.WeatherDataSource
+import com.sun.weather.data.repository.source.local.WeatherLocalDataSource
import com.sun.weather.data.repository.source.remote.WeatherRemoteDataSource
import org.koin.dsl.module
val DataSourceModule =
module {
single { WeatherRemoteDataSource(get()) }
+ single { WeatherLocalDataSource(get()) }
}
diff --git a/app/src/main/java/com/sun/weather/di/NetworkModule.kt b/app/src/main/java/com/sun/weather/di/NetworkModule.kt
index be7a350..1999630 100644
--- a/app/src/main/java/com/sun/weather/di/NetworkModule.kt
+++ b/app/src/main/java/com/sun/weather/di/NetworkModule.kt
@@ -1,5 +1,5 @@
-package com.example.weather.di
+package com.sun.weather.di
import android.app.Application
import com.google.gson.Gson
import com.sun.weather.data.repository.source.remote.api.ApiService
@@ -63,6 +63,5 @@ object NetWorkInstant {
internal const val READ_TIMEOUT = 60L
internal const val WRITE_TIMEOUT = 30L
internal const val CONNECT_TIMEOUT = 60L
-
internal const val CACHE_SIZE = 10 * 1024 * 1024L // 10MB
}
diff --git a/app/src/main/java/com/sun/weather/di/ViewModelModule.kt b/app/src/main/java/com/sun/weather/di/ViewModelModule.kt
new file mode 100644
index 0000000..bafa14a
--- /dev/null
+++ b/app/src/main/java/com/sun/weather/di/ViewModelModule.kt
@@ -0,0 +1,15 @@
+package com.sun.weather.di
+
+import com.sun.weather.ui.MainViewModel
+import com.sun.weather.ui.SharedViewModel
+import com.sun.weather.ui.home.HomeViewModel
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.core.module.Module
+import org.koin.dsl.module
+
+val ViewModelModule: Module =
+ module {
+ viewModel { MainViewModel(get()) }
+ viewModel { SharedViewModel() }
+ viewModel { HomeViewModel(get()) }
+ }
diff --git a/app/src/main/java/com/sun/weather/ui/MainActivity.kt b/app/src/main/java/com/sun/weather/ui/MainActivity.kt
index 0fda193..9079ddb 100644
--- a/app/src/main/java/com/sun/weather/ui/MainActivity.kt
+++ b/app/src/main/java/com/sun/weather/ui/MainActivity.kt
@@ -1,12 +1,37 @@
package com.sun.weather.ui
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
import com.sun.weather.R
+import com.sun.weather.base.BaseActivity
+import com.sun.weather.databinding.ActivityMainBinding
+import com.sun.weather.ui.home.HomeFragment
+import org.koin.androidx.viewmodel.ext.android.viewModel
-class MainActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
+class MainActivity : BaseActivity(ActivityMainBinding::inflate) {
+ override val viewModel: MainViewModel by viewModel()
+ override val sharedViewModel: SharedViewModel by viewModel()
+
+ override fun initialize() {
+ viewModel.locationData.observe(
+ this,
+ Observer { location ->
+ val (latitude, longitude) = location
+ // Chuyển dữ liệu tọa độ sang HomeFragment
+ val homeFragment = HomeFragment.newInstance(latitude, longitude)
+ setNextFragment(homeFragment)
+ },
+ )
+
+ // Gọi hàm yêu cầu vị trí người dùng
+ viewModel.requestLocationAndFetchWeather(this)
+ }
+
+ private fun setNextFragment(fragment: Fragment) {
+ supportFragmentManager
+ .beginTransaction()
+ .addToBackStack(fragment::javaClass.name)
+ .replace(R.id.fragment_container, fragment)
+ .commit()
}
}
diff --git a/app/src/main/java/com/sun/weather/ui/MainViewModel.kt b/app/src/main/java/com/sun/weather/ui/MainViewModel.kt
new file mode 100644
index 0000000..75cc51f
--- /dev/null
+++ b/app/src/main/java/com/sun/weather/ui/MainViewModel.kt
@@ -0,0 +1,56 @@
+package com.sun.weather.ui
+
+import android.Manifest
+import android.app.Activity
+import android.app.Application
+import android.content.pm.PackageManager
+import android.location.Location
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.google.android.gms.location.LocationServices
+import com.google.android.gms.tasks.Task
+
+class MainViewModel(application: Application) : AndroidViewModel(application) {
+ private val _locationData = MutableLiveData>()
+ val locationData: LiveData> get() = _locationData
+
+ fun requestLocationAndFetchWeather(activity: Activity) {
+ val context = getApplication().applicationContext
+ if (ActivityCompat.checkSelfPermission(
+ context,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
+ context,
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ ActivityCompat.requestPermissions(
+ activity,
+ arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ ),
+ REQUEST_CODE,
+ )
+ } else {
+ val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
+ val locationTask: Task = fusedLocationProviderClient.lastLocation
+ locationTask.addOnSuccessListener { location ->
+ if (location != null) {
+ val latitude = location.latitude
+ val longitude = location.longitude
+ _locationData.value = Pair(latitude, longitude)
+ } else {
+ Toast.makeText(context, "Location is null", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val REQUEST_CODE = 1000
+ }
+}
diff --git a/app/src/main/java/com/sun/weather/ui/SharedViewModel.kt b/app/src/main/java/com/sun/weather/ui/SharedViewModel.kt
new file mode 100644
index 0000000..8a08ddf
--- /dev/null
+++ b/app/src/main/java/com/sun/weather/ui/SharedViewModel.kt
@@ -0,0 +1,12 @@
+package com.sun.weather.ui
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class SharedViewModel : ViewModel() {
+ var isNetworkAvailable = MutableLiveData()
+
+ fun checkNetwork(isEnable: Boolean) {
+ isNetworkAvailable.postValue(isEnable)
+ }
+}
diff --git a/app/src/main/java/com/sun/weather/ui/home/HomeFragment.kt b/app/src/main/java/com/sun/weather/ui/home/HomeFragment.kt
new file mode 100644
index 0000000..ff3fdd3
--- /dev/null
+++ b/app/src/main/java/com/sun/weather/ui/home/HomeFragment.kt
@@ -0,0 +1,107 @@
+package com.sun.weather.ui.home
+
+import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
+import com.bumptech.glide.Glide
+import com.sun.weather.R
+import com.sun.weather.base.BaseFragment
+import com.sun.weather.data.model.entity.WeatherEntity
+import com.sun.weather.databinding.FragmentHomeBinding
+import com.sun.weather.ui.SharedViewModel
+import org.koin.androidx.viewmodel.ext.android.activityViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import kotlin.math.roundToInt
+
+class HomeFragment : BaseFragment(FragmentHomeBinding::inflate) {
+ override val viewModel: HomeViewModel by viewModel()
+ override val sharedViewModel: SharedViewModel by activityViewModel()
+ private var cityName: String? = null
+ private var latitude: Double = 0.0
+ private var longitude: Double = 0.0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ latitude = it.getDouble(ARG_LATITUDE)
+ longitude = it.getDouble(ARG_LONGITUDE)
+ }
+ }
+
+ override fun initView() {
+ binding.icLocation.setOnClickListener {
+ fetchWeatherData()
+ }
+ }
+
+ override fun initData() {
+ fetchWeatherData()
+ sharedViewModel.isNetworkAvailable.observe(viewLifecycleOwner) { isAvailable ->
+ if (isAvailable) {
+ fetchWeatherData()
+ }
+ }
+ viewModel.currentWeather.observe(viewLifecycleOwner) { weather ->
+ weather?.let {
+ updateUIWithCurrentWeather(it)
+ }
+ }
+ viewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage ->
+ showError(errorMessage)
+ }
+ }
+
+ override fun bindData() {
+ // TODO LATER
+ }
+
+ private fun fetchWeatherData() {
+ viewModel.fetchCurrentWeather(latitude, longitude)
+ }
+
+ private fun updateUIWithCurrentWeather(currentWeather: WeatherEntity) {
+ Log.v("LCD", currentWeather.toString())
+ binding.tvLocation.text = getString(R.string.city_name, currentWeather.city, currentWeather.country)
+ cityName = currentWeather.city
+ binding.tvCurrentDay.text = getString(R.string.today) + formatDate(currentWeather.timeZone!!)
+ binding.tvCurrentTemperature.text = "${currentWeather.weatherCurrent?.currentTemperature?.roundToInt()}°C"
+ binding.tvCurrentText.text = currentWeather.weatherCurrent?.weatherDescription
+ binding.tvCurrentPercentCloud.text = currentWeather.weatherCurrent?.windSpeed.toString()
+ binding.tvCurrentHumidity.text = currentWeather.weatherCurrent?.humidity.toString()
+ binding.tvCurrentPercentCloud1.text = currentWeather.weatherCurrent?.percentCloud.toString()
+ Glide.with(this).load(currentWeather.weatherCurrent?.iconWeather).into(binding.ivCurrentWeather)
+ }
+
+ private fun showError(errorMessage: String) {
+ Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
+ }
+
+ companion object {
+ const val SELECTED_LOCATION = "selected_location"
+ const val ARG_LATITUDE = "latitude"
+ const val ARG_LONGITUDE = "longitude"
+
+ fun newInstance(
+ latitude: Double,
+ longitude: Double,
+ ): HomeFragment {
+ val fragment = HomeFragment()
+ val args = Bundle()
+ args.putDouble(ARG_LATITUDE, latitude)
+ args.putDouble(ARG_LONGITUDE, longitude)
+ fragment.arguments = args
+ return fragment
+ }
+
+ private fun formatDate(timestamp: Long): String {
+ val date = Date(timestamp * SECOND_TO_MILLIS)
+ return SimpleDateFormat(DATE_PATTERN, Locale.getDefault()).format(date)
+ }
+
+ private const val SECOND_TO_MILLIS = 1000
+ private const val DATE_PATTERN = ", dd MMMM"
+ }
+}
diff --git a/app/src/main/java/com/sun/weather/ui/home/HomeViewModel.kt b/app/src/main/java/com/sun/weather/ui/home/HomeViewModel.kt
new file mode 100644
index 0000000..c96b759
--- /dev/null
+++ b/app/src/main/java/com/sun/weather/ui/home/HomeViewModel.kt
@@ -0,0 +1,39 @@
+package com.sun.weather.ui.home
+
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.weather.utils.livedata.SingleLiveData
+import com.sun.weather.data.model.entity.WeatherEntity
+import com.sun.weather.data.repository.WeatherRepository
+import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.singleOrNull
+import kotlinx.coroutines.launch
+
+class HomeViewModel(
+ private val weatherRepository: WeatherRepository,
+) : ViewModel() {
+ var currentWeather = SingleLiveData()
+ var isLoading = MutableLiveData()
+ var errorMessage = MutableLiveData()
+
+ fun fetchCurrentWeather(
+ latitude: Double,
+ longitude: Double,
+ ) {
+ isLoading.value = true
+ viewModelScope.launch {
+ try {
+ val weatherDeferred =
+ async { weatherRepository.getCurrentLocationWeather(latitude, longitude, "vi") }
+ currentWeather.postValue(weatherDeferred.await().singleOrNull())
+ isLoading.postValue(false)
+ } catch (e: Exception) {
+ Log.v("LCD", "Tai View Model:\n" + e.message.toString())
+ errorMessage.postValue(e.message)
+ isLoading.postValue(false)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/sun/weather/utils/listener/OnFetchListener.kt b/app/src/main/java/com/sun/weather/utils/listener/OnFetchListener.kt
deleted file mode 100644
index 96db1a5..0000000
--- a/app/src/main/java/com/sun/weather/utils/listener/OnFetchListener.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.example.weather.utils.listener
-
-import android.location.Location
-
-interface OnFetchListener {
- fun onDataLocation(location: Location?)
-}
diff --git a/app/src/main/java/com/sun/weather/utils/listener/OnItemClickListener.kt b/app/src/main/java/com/sun/weather/utils/listener/OnItemClickListener.kt
index 2356f7b..c69090f 100644
--- a/app/src/main/java/com/sun/weather/utils/listener/OnItemClickListener.kt
+++ b/app/src/main/java/com/sun/weather/utils/listener/OnItemClickListener.kt
@@ -1,5 +1,4 @@
-package com.example.weather.utils.listener
-
+package com.sun.weather.utils.listener
import android.view.View
interface OnItemClickListener {