Skip to content

Commit

Permalink
FavouriteScreen Complete
Browse files Browse the repository at this point in the history
  • Loading branch information
Lê Chí Dũng committed Sep 9, 2024
1 parent 77952bd commit 68febac
Show file tree
Hide file tree
Showing 22 changed files with 299 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.sun.weather.data.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "favourite_locations")
data class FavouriteLocation(
val id: Long? = null,
val cityName: String,
val countryName: String,
@PrimaryKey(autoGenerate = true) val id: Long? = null,
@ColumnInfo(name = "city_name") val cityName: String,
@ColumnInfo(name = "country_name") val countryName: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ data class WeatherEntity(
}

object WeatherEntry {
// Local database entries
const val TBL_WEATHER_NAME = "weather_forecasts"
const val CURRENTLY_OBJECT = "currently"
const val HOURLY_OBJECT = "hourly"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
package com.sun.weather.data.repository

import com.sun.weather.data.model.FavouriteLocation
import com.sun.weather.data.model.Weather
import com.sun.weather.data.model.entity.WeatherEntity
import kotlinx.coroutines.flow.Flow

interface WeatherRepository {
fun getSelectedLocation(key: String): String

fun isFavoriteLocationExists(
cityName: String,
countryName: String,
): Boolean

fun saveCurrentWeather(currentWeather: WeatherEntity)

fun saveWeeklyForecastLocal(weeklyForecast: WeatherEntity)

fun getLocalWeather(id: String): Weather?

fun saveHourlyForecastLocal(hourlyForecast: WeatherEntity)
suspend fun insertFavoriteWeather(favouriteLocation: FavouriteLocation)

fun insertFavoriteWeather(favouriteLocation: FavouriteLocation)
suspend fun getAllFavorite(): List<FavouriteLocation>

fun getAllFavorite(): List<FavouriteLocation>
suspend fun removeFavoriteItem(id: Long)

fun removeFavoriteItem(id: Long)
suspend fun isFavoriteLocationExists(cityName: String): Int

fun getCurrentWeather(
city: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.sun.weather.data.repository

import com.sun.weather.data.model.FavouriteLocation
import com.sun.weather.data.model.entity.WeatherEntity
import com.sun.weather.data.model.toWeatherEntity
import com.sun.weather.data.repository.source.WeatherDataSource
Expand All @@ -11,6 +11,22 @@ class WeatherRepositoryImpl(
private val localDataSource: WeatherDataSource.Local,
private val remoteDataSource: WeatherDataSource.Remote,
) : KoinComponent, WeatherRepository {
override suspend fun isFavoriteLocationExists(cityName: String): Int {
return localDataSource.isFavoriteLocationExists(cityName)
}

override suspend fun insertFavoriteWeather(favouriteLocation: FavouriteLocation) {
localDataSource.insertFavourite(favouriteLocation)
}

override suspend fun getAllFavorite(): List<FavouriteLocation> {
return localDataSource.getAllFavourite()
}

override suspend fun removeFavoriteItem(id: Long) {
localDataSource.removeFavouriteItem(id)
}

override fun getCurrentWeather(
city: String,
language: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sun.weather.data.repository.source

import com.sun.weather.data.model.CurrentWeather
import com.sun.weather.data.model.FavouriteLocation
import com.sun.weather.data.model.HourlyForecast
import com.sun.weather.data.model.WeeklyForecast
import com.sun.weather.data.model.entity.WeatherEntity
Expand All @@ -20,6 +21,14 @@ interface WeatherDataSource {
suspend fun getLocalWeather(id: String): WeatherEntity?

suspend fun deleteWeather(id: String)

suspend fun insertFavourite(favourite: FavouriteLocation)

suspend fun getAllFavourite(): List<FavouriteLocation>

suspend fun removeFavouriteItem(favouriteId: Long)

suspend fun isFavoriteLocationExists(cityName: String): Int
}

interface Remote {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package com.sun.weather.data.repository.source.local

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.sun.weather.data.model.FavouriteLocation
import com.sun.weather.data.model.entity.WeatherEntity
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.FavouriteDao
import com.sun.weather.data.repository.source.local.dao.WeatherDao

@Database(entities = [WeatherEntity::class], version = 1, exportSchema = false)
@Database(entities = [WeatherEntity::class, FavouriteLocation::class], version = 2, exportSchema = false)
@TypeConverters(WeatherBasicConverter::class, WeatherBasicListConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun weatherDao(): WeatherDao

abstract fun favouriteDao(): FavouriteDao
companion object {
private const val DB_NAME = "Weather.db"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package com.sun.weather.data.repository.source.local

import com.sun.weather.data.model.FavouriteLocation
import com.sun.weather.data.model.entity.WeatherEntity
import com.sun.weather.data.repository.source.WeatherDataSource
import com.sun.weather.data.repository.source.local.dao.FavouriteDao
import com.sun.weather.data.repository.source.local.dao.WeatherDao

class WeatherLocalDataSource(
private val weatherDao: WeatherDao,
private val favouriteDao: FavouriteDao,
) : WeatherDataSource.Local {
override suspend fun insertCurrentWeather(
current: WeatherEntity,
hourly: WeatherEntity,
daily: WeatherEntity,
) {
// Todo implement later
}

override suspend fun insertCurrentWeather(weather: WeatherEntity) {
// Todo implement later
}

override suspend fun getAllLocalWeathers(): List<WeatherEntity> {
Expand All @@ -28,4 +33,20 @@ class WeatherLocalDataSource(
override suspend fun deleteWeather(id: String) {
weatherDao.deleteWeather(id)
}

override suspend fun insertFavourite(favourite: FavouriteLocation) {
favouriteDao.insertFavourite(favourite)
}

override suspend fun getAllFavourite(): List<FavouriteLocation> {
return favouriteDao.getAllFavourite()
}

override suspend fun removeFavouriteItem(favouriteId: Long) {
favouriteDao.removeFavouriteItem(favouriteId)
}

override suspend fun isFavoriteLocationExists(cityName: String): Int {
return favouriteDao.countCityByName(cityName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.sun.weather.data.repository.source.local.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.sun.weather.data.model.FavouriteLocation

@Dao
interface FavouriteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFavourite(favourite: FavouriteLocation)

@Query("SELECT * FROM favourite_locations")
suspend fun getAllFavourite(): List<FavouriteLocation>

@Query("DELETE FROM favourite_locations WHERE id = :favouriteId")
suspend fun removeFavouriteItem(favouriteId: Long)

@Query("SELECT COUNT(*) FROM favourite_locations WHERE city_name = :cityName")
suspend fun countCityByName(cityName: String): Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ data class ErrorResponse(

return RetrofitException.toHttpError(response)
}

// We don't know what happened. We need to simply convert to an unknown error
return RetrofitException.toUnexpectedError(throwable)
}
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/sun/weather/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ val AppModule =
single { provideBaseDispatcherProvider() }
single { provideAppDatabase(androidContext()) }
factory { get<AppDatabase>().weatherDao() }
factory { get<AppDatabase>().favouriteDao() }
single { provideGson() }
}

Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/com/sun/weather/di/ViewModelModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.sun.weather.di
import com.sun.weather.ui.MainViewModel
import com.sun.weather.ui.SharedViewModel
import com.sun.weather.ui.detail.DetailViewModel
import com.sun.weather.ui.favourite.FavouriteViewModel
import com.sun.weather.ui.home.HomeViewModel
import com.sun.weather.ui.search.SearchViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
Expand All @@ -13,7 +14,8 @@ val ViewModelModule: Module =
module {
viewModel { MainViewModel(get()) }
viewModel { SharedViewModel() }
viewModel { HomeViewModel(get()) }
viewModel { HomeViewModel(get(), get()) }
viewModel { SearchViewModel(get()) }
viewModel { DetailViewModel(get()) }
viewModel { FavouriteViewModel(get()) }
}
12 changes: 12 additions & 0 deletions app/src/main/java/com/sun/weather/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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.favourite.FavouriteFragment
import com.sun.weather.ui.home.HomeFragment
import org.koin.androidx.viewmodel.ext.android.viewModel

Expand All @@ -23,6 +24,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
},
)
viewModel.requestLocationAndFetchWeather(this)
setNavigation()
}

private fun setNextFragment(fragment: Fragment) {
Expand All @@ -32,4 +34,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
.replace(R.id.fragment_container, fragment)
.commit()
}

private fun setNavigation() {
binding.bottomNavigation.setOnItemSelectedListener {
when (it.itemId) {
R.id.mi_home -> setNextFragment(HomeFragment.newInstance())
R.id.mi_favorite -> setNextFragment(FavouriteFragment.newInstance())
}
true
}
}
}
51 changes: 51 additions & 0 deletions app/src/main/java/com/sun/weather/ui/favourite/FavouriteAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.sun.weather.ui.favourite

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.sun.weather.data.model.FavouriteLocation
import com.sun.weather.databinding.ItemFavouriteBinding
import com.sun.weather.utils.listener.OnItemClickListener

class FavouriteAdapter(private var listener: OnItemClickListener) : RecyclerView.Adapter<FavouriteAdapter.ViewHolder>() {
private val mListWeathers by lazy { mutableListOf<FavouriteLocation>() }

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(ItemFavouriteBinding.inflate(inflater, parent, false))
}

override fun getItemCount(): Int {
return mListWeathers.size
}

override fun onBindViewHolder(
holder: ViewHolder,
position: Int,
) {
holder.bindData(mListWeathers[position])
holder.buttonFavourite.setOnClickListener {
listener.onItemClickListener(holder.itemView, position)
}
}

fun updateData(listData: List<FavouriteLocation>) {
mListWeathers.clear()
if (listData != null) {
mListWeathers.addAll(listData)
}
notifyDataSetChanged()
}

inner class ViewHolder(private val binding: ItemFavouriteBinding) : RecyclerView.ViewHolder(binding.root) {
val buttonFavourite = binding.btnHeart

fun bindData(newItem: FavouriteLocation) {
binding.tvCityName.text = newItem.cityName
binding.tvCountryName.text = newItem.countryName
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.sun.weather.ui.favourite

import android.util.Log
import android.view.View
import android.widget.ImageButton
import androidx.recyclerview.widget.LinearLayoutManager
import com.sun.weather.R
import com.sun.weather.base.BaseFragment
import com.sun.weather.databinding.FragmentFavouriteBinding
import com.sun.weather.ui.SharedViewModel
import com.sun.weather.utils.ext.goBackFragment
import com.sun.weather.utils.listener.OnItemClickListener
import org.koin.androidx.viewmodel.ext.android.activityViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class FavouriteFragment() : BaseFragment<FragmentFavouriteBinding>(FragmentFavouriteBinding::inflate), OnItemClickListener {
override val viewModel: FavouriteViewModel by viewModel()
override lateinit var sharedViewModel: SharedViewModel
private lateinit var favoriteAdapter: FavouriteAdapter

override fun initView() {
sharedViewModel = activityViewModel<SharedViewModel>().value
favoriteAdapter = FavouriteAdapter(this)
binding.rvFavorite.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = favoriteAdapter
}

binding.toolBar.findViewById<ImageButton>(R.id.btn_back).setOnClickListener {
goBackFragment()
}
}

override fun initData() {
viewModel.getAllFavouriteLocations()
}

override fun bindData() {
viewModel.favouriteLocations.observe(viewLifecycleOwner) { favouriteList ->
favouriteList?.let {
favoriteAdapter.updateData(it)
}
}

viewModel.error.observe(viewLifecycleOwner) { errorMessage ->
Log.v("FavouriteFragment", "Error: $errorMessage")
}
}

override fun onItemClickListener(
view: View,
position: Int,
) {
val favouriteLocation = viewModel.favouriteLocations.value?.get(position)
favouriteLocation?.id?.let { viewModel.removeFavouriteItem(it) }
}
companion object {
fun newInstance() = FavouriteFragment()
}
}
Loading

0 comments on commit 68febac

Please sign in to comment.