Skip to content

Commit

Permalink
DetailScreen 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 423de25 commit 5fdcd3b
Show file tree
Hide file tree
Showing 20 changed files with 373 additions and 53 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@ dependencies {
// WorkManager
implementation 'androidx.work:work-runtime-ktx:2.9.0'

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
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.WeatherDao

@Database(
entities = [Weather::class],
version = 1,
exportSchema = false,
)
@Database(entities = [WeatherEntity::class], version = 1, exportSchema = false)
@TypeConverters(WeatherBasicConverter::class, WeatherBasicListConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun weatherDao(): WeatherDao
Expand All @@ -24,7 +21,7 @@ abstract class AppDatabase : RoomDatabase() {
@Volatile
private var instance: AppDatabase? = null

fun getInstance(context: Context): AppDatabase =
fun getInstance(context: Context) =
instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
package com.sun.weather.data.repository.source.local

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.WeatherDao

class WeatherLocalDataSource(
private val weatherDao: WeatherDao,
) : WeatherDataSource.Local {
override suspend fun insertCurrentWeather(
current: Weather,
hourly: Weather,
daily: Weather,
current: WeatherEntity,
hourly: WeatherEntity,
daily: WeatherEntity,
) {
// TODO Implement later
}

override suspend fun insertCurrentWeather(weather: Weather) {
// TODO Implement later
override suspend fun insertCurrentWeather(weather: WeatherEntity) {
}

override suspend fun getAllLocalWeathers(): List<Weather> {
override suspend fun getAllLocalWeathers(): List<WeatherEntity> {
return weatherDao.getAllData()
}

override suspend fun getLocalWeather(id: String): Weather? {
override suspend fun getLocalWeather(id: String): WeatherEntity? {
return weatherDao.getWeather(id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import com.sun.weather.data.repository.source.WeatherDataSource
import com.sun.weather.data.repository.source.remote.api.ApiService
import com.sun.weather.utils.Constant

class WeatherRemoteDataSource(private val apiService: ApiService) : WeatherDataSource.Remote {
class WeatherRemoteDataSource(
private val baseApiService: ApiService,
private val proApiService: ApiService,
) : WeatherDataSource.Remote {
override suspend fun getCurrentWeather(
city: String,
language: String,
): CurrentWeather {
return apiService.getCurrentWeather(
return baseApiService.getCurrentWeather(
city,
Constant.BASE_API_KEY,
Constant.UNITS_VALUE,
Expand All @@ -38,7 +41,7 @@ class WeatherRemoteDataSource(private val apiService: ApiService) : WeatherDataS
city: String,
language: String,
): HourlyForecast {
return apiService.getHourlyForecast(
return proApiService.getHourlyForecast(
city,
Constant.UNITS_VALUE,
Constant.FORECAST_HOUR,
Expand All @@ -51,7 +54,7 @@ class WeatherRemoteDataSource(private val apiService: ApiService) : WeatherDataS
city: String,
language: String,
): WeeklyForecast {
return apiService.getWeeklyForecast(
return proApiService.getWeeklyForecast(
city,
Constant.UNITS_VALUE,
Constant.FORECAST_DAY,
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
@@ -1,6 +1,7 @@
package com.sun.weather.di

import android.app.Application
import android.content.Context
import android.content.res.Resources
import com.example.weather.utils.dispatchers.BaseDispatcherProvider
import com.example.weather.utils.dispatchers.DispatcherProvider
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/java/com/sun/weather/di/DataSourceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ 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.core.qualifier.named
import org.koin.dsl.module

val DataSourceModule =
module {
single<WeatherDataSource.Remote> { WeatherRemoteDataSource(get()) }
single<WeatherDataSource.Remote> {
WeatherRemoteDataSource(
baseApiService = get(named("baseApiService")),
proApiService = get(named("proApiService")),
)
}
single<WeatherDataSource.Local> { WeatherLocalDataSource(get()) }
}
20 changes: 11 additions & 9 deletions app/src/main/java/com/sun/weather/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

package com.sun.weather.di

import android.app.Application
import com.google.gson.Gson
import com.sun.weather.data.repository.source.remote.api.ApiService
Expand All @@ -14,12 +14,11 @@ import java.util.concurrent.TimeUnit
val NetworkModule =
module {
single { provideOkHttpCache(get()) }

single { provideOkHttpClient(get()) }

single { provideRetrofit(get(), get()) }

single { provideApiService(get()) }
single<Retrofit>(named("baseRetrofit")) { provideRetrofit(Constant.BASE_URL, get(), get()) }
single<Retrofit>(named("proRetrofit")) { provideRetrofit(Constant.PRO_URL, get(), get()) }
single<ApiService>(named("baseApiService")) { provideApiService(get(named("baseRetrofit"))) }
single<ApiService>(named("proApiService")) { provideApiService(get(named("proRetrofit"))) }
}

fun provideOkHttpCache(app: Application): Cache {
Expand All @@ -43,16 +42,19 @@ fun provideOkHttpClient(cache: Cache): OkHttpClient {
NetWorkInstant.CONNECT_TIMEOUT,
TimeUnit.SECONDS,
)

return httpClientBuilder.build()
}

fun provideRetrofit(
baseUrl: String,
gson: Gson,
okHttpClient: OkHttpClient,
): Retrofit {
return Retrofit.Builder().baseUrl(Constant.BASE_URL).addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient).build()
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}

fun provideApiService(retrofit: Retrofit): ApiService {
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/com/sun/weather/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import com.sun.weather.data.repository.WeatherRepositoryImpl
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.core.qualifier.named
import org.koin.dsl.module

val RepositoryModule =
module {
single {
provideWeatherRepository(
WeatherLocalDataSource(get()),
WeatherRemoteDataSource(get()),
WeatherRemoteDataSource(
baseApiService = get(named("baseApiService")),
proApiService = get(named("proApiService")),
),
)
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/sun/weather/di/ViewModelModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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.home.HomeViewModel
import com.sun.weather.ui.search.SearchViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
Expand All @@ -14,4 +15,5 @@ val ViewModelModule: Module =
viewModel { SharedViewModel() }
viewModel { HomeViewModel(get()) }
viewModel { SearchViewModel(get()) }
viewModel { DetailViewModel(get()) }
}
6 changes: 2 additions & 4 deletions app/src/main/java/com/sun/weather/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
this,
Observer { location ->
val (latitude, longitude) = location
// Chuyển dữ liệu tọa độ sang HomeFragment
val homeFragment = HomeFragment.newInstance(latitude, longitude)
sharedViewModel.setLocation(latitude, longitude)
val homeFragment = HomeFragment.newInstance()
setNextFragment(homeFragment)
},
)

// Gọi hàm yêu cầu vị trí người dùng
viewModel.requestLocationAndFetchWeather(this)
}

Expand Down
57 changes: 57 additions & 0 deletions app/src/main/java/com/sun/weather/ui/detail/DailyAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sun.weather.ui.detail

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.sun.weather.data.model.WeatherBasic
import com.sun.weather.databinding.ForecastDailyBinding
import com.sun.weather.utils.Constant
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

class DailyAdapter(private var forecastList: List<WeatherBasic>) : RecyclerView.Adapter<DailyAdapter.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(ForecastDailyBinding.inflate(inflater, parent, false))
}

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

override fun onBindViewHolder(
holder: ViewHolder,
position: Int,
) {
holder.bindData(forecastList[position])
}

inner class ViewHolder(private val binding: ForecastDailyBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindData(item: WeatherBasic) {
val dayFormat = SimpleDateFormat(DAY_ONLY_PATTERN, Locale.getDefault())
val day = dayFormat.format(Date(item.dateTime!! * SECOND_TO_MILLIS))
binding.tvDay.text = day
binding.tvStatus.text = item.weatherDescription
binding.tvMaxTemp.text = String.format("%.1f", item.maxTemperature) + "°C"
binding.tvMinTemp.text = String.format("%.1f", item.minTemperature) + "°C"
val iconWeatherUrl = "${Constant.BASE_ICON_URL}${item.iconWeather}@2x.png"
Glide.with(itemView.context.applicationContext)
.load(iconWeatherUrl)
.into(binding.imgStatus)
}
}

fun updateData(list: List<WeatherBasic>) {
forecastList = list
notifyDataSetChanged()
}
companion object {
const val DAY_ONLY_PATTERN = "dd-MM-yyyy"
const val SECOND_TO_MILLIS = 1000
}
}
92 changes: 92 additions & 0 deletions app/src/main/java/com/sun/weather/ui/detail/DetailFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.sun.weather.ui.detail

import android.icu.text.SimpleDateFormat
import android.os.Bundle
import android.util.Log
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.FragmentDetailBinding
import com.sun.weather.ui.SharedViewModel
import com.sun.weather.utils.ext.goBackFragment
import org.koin.androidx.viewmodel.ext.android.activityViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.util.Date
import java.util.Locale

class DetailFragment : BaseFragment<FragmentDetailBinding>(FragmentDetailBinding::inflate) {
override val viewModel: DetailViewModel by viewModel()
override lateinit var sharedViewModel: SharedViewModel
private lateinit var dailyAdapter: DailyAdapter
private lateinit var hourlyAdapter: HourlyAdapter
private var isNetworkAvailable: Boolean = true
private var cityName: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
cityName = it.getString(ARG_CITY_NAME)
}
}

override fun initView() {
sharedViewModel = activityViewModel<SharedViewModel>().value
isNetworkAvailable = sharedViewModel.isNetworkAvailable.value ?: true
binding.toolbar.findViewById<ImageButton>(R.id.btn_back).setOnClickListener {
goBackFragment()
}

hourlyAdapter = HourlyAdapter(mutableListOf())
binding.recyclerViewHourly.apply {
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
adapter = hourlyAdapter
}

dailyAdapter = DailyAdapter(mutableListOf())
binding.recylerViewDaily.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = dailyAdapter
}
binding.tvToolbarTitle.text = getString(R.string.city_name_app_bar, getString(R.string.forecast_detail), cityName)
val currentDate = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date())
binding.tvCurrentTime.text = currentDate
}

override fun initData() {
cityName?.let { name ->
Log.v(MY_TAG, "initData: cityName: $name, isNetworkAvailable: $isNetworkAvailable")
viewModel.loadWeeklyForecast(name, isNetworkAvailable, "vi")
viewModel.loadHourlyForecast(name, isNetworkAvailable, "vi")
}
}

override fun bindData() {
viewModel.weeklyForecast.observe(viewLifecycleOwner) { forecast ->
dailyAdapter.updateData(forecast)
}

viewModel.hourlyForecast.observe(viewLifecycleOwner) { forecast ->
hourlyAdapter.updateData(forecast)
}

viewModel.error.observe(viewLifecycleOwner) { errorMessage ->
Log.d(MY_TAG, "onError: $errorMessage")
}
}

companion object {
private const val ARG_CITY_NAME = "city_name"
const val MY_TAG = "DetailFragment"

fun newInstance(cityName: String): DetailFragment {
val fragment = DetailFragment()
val args =
Bundle().apply {
putString(ARG_CITY_NAME, cityName)
}
fragment.arguments = args
return fragment
}
}
}
Loading

0 comments on commit 5fdcd3b

Please sign in to comment.