diff --git a/.idea/misc.xml b/.idea/misc.xml
index f23a13b..59a266e 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,7 +5,7 @@
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 71ce279..94a25f7 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 034a26f..4d45170 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -20,9 +20,11 @@ android {
}
buildFeatures {
- viewBinding = true
+ viewBinding true
+ dataBinding true
}
+
buildTypes {
release {
minifyEnabled false
@@ -40,6 +42,7 @@ android {
dependencies {
implementation 'com.google.android.gms:play-services-location:19.0.1'
+
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
@@ -48,7 +51,11 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
+
+ def lifecycle = "2.4.1"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle"
//SwipeRefreshLayout
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
@@ -59,23 +66,28 @@ dependencies {
//Coil
implementation "io.coil-kt:coil:1.1.1"
+ def viewBinding = "1.5.3"
+ implementation "com.github.kirich1409:viewbindingpropertydelegate:$viewBinding"
+ // To use only without reflection variants of viewBinding
+ implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:$viewBinding"
+
def navigation = "2.4.1"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation"
implementation "androidx.navigation:navigation-ui-ktx:$navigation"
implementation "androidx.navigation:navigation-runtime-ktx:$navigation"
-
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
+ def coroutines = "1.6.0"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
// region Network
def retrofit = "2.9.0"
- implementation "com.squareup.retrofit2:retrofit:${retrofit}"
- implementation "com.squareup.retrofit2:converter-gson:${retrofit}"
+ implementation "com.squareup.retrofit2:retrofit:$retrofit"
+ implementation "com.squareup.retrofit2:converter-gson:$retrofit"
def okhttp = "4.9.3"
- implementation "com.squareup.okhttp3:okhttp:${okhttp}"
- debugImplementation "com.squareup.okhttp3:logging-interceptor:${okhttp}"
+ implementation "com.squareup.okhttp3:okhttp:$okhttp"
+ debugImplementation "com.squareup.okhttp3:logging-interceptor:$okhttp"
// endregion
testImplementation 'junit:junit:4.13.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f82e4e2..013d722 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
> = MutableLiveData()
+ val weather: LiveData> = _weather
+
+ private val _weatherList: MutableLiveData>> = MutableLiveData()
+ val weatherList: LiveData>> = _weatherList
+
+ private val _cityId: MutableLiveData>> = MutableLiveData()
+ val cityId: LiveData>> = _cityId
+
+ fun onGetWeather(cityId : Int) {
+ viewModelScope.launch {
+ kotlin.runCatching {
+ getWeatherUseCase(cityId)
+ }.onSuccess {
+ _weather.value = Result.success(it)
+ }.onFailure {
+ _weather.value = Result.failure(it)
+ }
+ }
+ }
+
+ fun onGetWeatherList(lat: Double, lon: Double, cnt: Int) {
+ viewModelScope.launch {
+ kotlin.runCatching {
+ getWeatherListUseCase(lat, lon, cnt)
+ }.onSuccess {
+ _weatherList.value = Result.success(it)
+ }.onFailure {
+ _weatherList.value = Result.failure(it)
+ }
+ }
+ }
+
+ fun onGetCityId(cityName: String) {
+ viewModelScope.launch {
+ kotlin.runCatching {
+ getWeatherUseCase(cityName).id
+ }.onSuccess {
+ _cityId.value = Event(Result.success(it))
+ }.onFailure {
+ _cityId.value = Event(Result.failure(it))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt
index ef3f1a7..403f339 100644
--- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt
@@ -3,24 +3,22 @@ package ru.itis.karakurik.androidLab2.presentation.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
+import by.kirich1409.viewbindingdelegate.viewBinding
+import ru.itis.karakurik.androidLab2.R
import ru.itis.karakurik.androidLab2.databinding.ActivityMainBinding
import ru.itis.karakurik.androidLab2.extentions.findController
-class MainActivity : AppCompatActivity() {
- private var binding: ActivityMainBinding? = null
+class MainActivity : AppCompatActivity(R.layout.activity_main) {
+ private val binding by viewBinding(ActivityMainBinding::bind)
private var controller: NavController? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = ActivityMainBinding.inflate(layoutInflater).also {
- setContentView(it.root)
- }
- controller = binding?.navHostFragmentMain?.id?.let { findController(it) }
+ controller = binding.navHostFragmentMain.id.let { findController(it) }
}
override fun onDestroy() {
super.onDestroy()
- binding = null
controller = null
}
}
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt
index bfb668b..9ee987e 100644
--- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt
@@ -1,75 +1,100 @@
package ru.itis.karakurik.androidLab2.presentation.fragments
+import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import coil.imageLoader
+import androidx.lifecycle.ViewModelProvider
import coil.load
-import coil.request.CachePolicy
-import coil.request.ImageRequest
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import ru.itis.karakurik.androidLab2.R
import ru.itis.karakurik.androidLab2.databinding.FragmentDetailsBinding
import ru.itis.karakurik.androidLab2.di.DiContainer
import ru.itis.karakurik.androidLab2.domain.entity.Weather
-import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase
+import ru.itis.karakurik.androidLab2.presentation.MainViewModel
+import ru.itis.karakurik.androidLab2.presentation.convertors.TempColorConverter
+import ru.itis.karakurik.androidLab2.presentation.utils.ViewModelFactory
+import timber.log.Timber
-class DetailsFragment : Fragment(R.layout.fragment_details) {
- private var cityId: Int? = null
- private var weather: Weather? = null
+class DetailsFragment : Fragment() {
+ private var _binding: FragmentDetailsBinding? = null
+ private val binding get() = _binding!!
- private val getWeatherUseCase: GetWeatherUseCase by lazy {
- GetWeatherUseCase(DiContainer.weatherRepository, Dispatchers.Default)
+ private val viewModel by lazy {
+ ViewModelProvider(
+ this,
+ ViewModelFactory(
+ DiContainer.getWeatherUseCase,
+ DiContainer.getWeatherListUseCase
+ )
+ )[MainViewModel::class.java]
}
- private var binding: FragmentDetailsBinding? = null
-
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_details, container, false)
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentDetailsBinding.inflate(inflater, container, false)
+ return binding.root
}
override fun onViewCreated(
view: View,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
- binding = FragmentDetailsBinding.bind(view)
- arguments?.let {
- cityId = it.getInt("city_id")
+ initObservers()
+
+ arguments?.getInt(getString(R.string.city_id))?.let {
+ viewModel.onGetWeather(it)
}
+ }
- lifecycleScope.launch {
- weather = cityId?.let { getWeatherUseCase(it) }
+ private fun initObservers() {
+ viewModel.weather.observe(viewLifecycleOwner) { result ->
+ result.fold(onSuccess = {
+ setWeatherData(it)
+ }, onFailure = {
+ Timber.e(it.message.toString())
+ })
+ }
+ }
- binding?.run {
- weather?.let {
- tvCity.text = it.name
- tvTemp.text = "${it.temp}°C"
- tvTempMin.text = "${it.tempMin}°C"
- tvTempMax.text = "${it.tempMax}°C"
- tvWindDeg.text = it.windDeg.toString()
- tvWindSpeed.text = "${it.windSpeed}km/h"
- tvHumidity.text = "${it.humidity}%"
- tvPressure.text = "${it.pressure}P"
- ivAir.context.imageLoader.execute(
- ImageRequest.Builder(ivAir.context)
- .data(it.iconUrl)
- .memoryCachePolicy(CachePolicy.DISABLED)
- .placeholder(R.drawable.air)
- .target(ivAir)
- .build()
- )
+ @SuppressLint("SetTextI18n")
+ private fun setWeatherData(weather: Weather) {
+ with(binding) {
+ weather.run {
+ setWeather(weather)
+ tvCity.text = name
+ tvTemp.text = "${temp}°C"
+ tvTempMin.text = "${tempMin}°C"
+ tvTempMax.text = "${tempMax}°C"
+ tvWindDeg.text = windDeg.toString()
+ tvWindSpeed.text = "${windSpeed}km/h"
+ tvHumidity.text = "${humidity}%"
+ tvPressure.text = "${pressure}P"
+ ivAir.load(iconUrl)
+
+ arguments?.getString(R.string.transition_name.toString())?.let {
+ ivAir.transitionName = it
}
+
+ tvTemp.setTextColor(
+ ContextCompat.getColor(
+ tvTemp.context,
+ TempColorConverter.getColor(weather.temp)
+ )
+ )
}
}
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+ _binding = null
+ }
}
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt
index bcd0a06..24e22c1 100644
--- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt
@@ -1,107 +1,179 @@
package ru.itis.karakurik.androidLab2.presentation.fragments.list
-import android.Manifest
+import android.Manifest.permission.*
import android.content.Intent
-import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PERMISSION_DENIED
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.SearchView
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearSmoothScroller
+import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.material.snackbar.Snackbar
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.launch
import ru.itis.karakurik.androidLab2.R
import ru.itis.karakurik.androidLab2.databinding.FragmentSearchBinding
import ru.itis.karakurik.androidLab2.di.DiContainer
-import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase
-import ru.itis.karakurik.androidLab2.domain.usecase.GetWeathersUseCase
+import ru.itis.karakurik.androidLab2.presentation.MainViewModel
import ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler.ListRecyclerAdapter
+import ru.itis.karakurik.androidLab2.presentation.utils.ViewModelFactory
import timber.log.Timber
private const val COUNT_OF_CITIES_IN_LIST = 20
private const val DEFAULT_LAT = 55.7887
private const val DEFAULT_LON = 49.1221
-private const val REQUEST_CODE_100 = 100
+private const val TRANSITION_NAME = "transition_name"
-class SearchFragment : Fragment(R.layout.fragment_search) {
- private var binding: FragmentSearchBinding? = null
- private var listRecyclerAdapter: ListRecyclerAdapter? = null
+class SearchFragment : Fragment() {
+ companion object {
+ private val permissions = arrayOf(
+ ACCESS_FINE_LOCATION,
+ ACCESS_COARSE_LOCATION,
+ ACCESS_LOCATION_EXTRA_COMMANDS
+ )
+ }
+
+ private var _binding: FragmentSearchBinding? = null
+ private val binding get() = _binding!!
+ private var listRecyclerAdapter: ListRecyclerAdapter? = null
private var userLat: Double = DEFAULT_LAT
private var userLon: Double = DEFAULT_LON
private var userLocation: FusedLocationProviderClient? = null
- private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
-
- private val getWeatherUseCase: GetWeatherUseCase by lazy {
- GetWeatherUseCase(DiContainer.weatherRepository, Dispatchers.Default)
+ private val viewModel by lazy {
+ ViewModelProvider(
+ requireActivity(),
+ ViewModelFactory(
+ DiContainer.getWeatherUseCase,
+ DiContainer.getWeatherListUseCase
+ )
+ )[MainViewModel::class.java]
}
- private val getWeatherListUseCase: GetWeathersUseCase by lazy {
- GetWeathersUseCase(DiContainer.weatherRepository, Dispatchers.Default)
+ private val requestPermissions =
+ registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { it ->
+ var allPermissionsGranted = true
+ for (granted in it.values) {
+ allPermissionsGranted = allPermissionsGranted and granted
+ }
+ if (allPermissionsGranted) {
+ getUserLocation()
+ } else {
+ Toast.makeText(
+ context,
+ "Разрешения не даны",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+
+ private val smoothScroller: RecyclerView.SmoothScroller by lazy {
+ object : LinearSmoothScroller(context) {
+ override fun getVerticalSnapPreference(): Int {
+ return SNAP_TO_START
+ }
+ }
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View {
- return inflater.inflate(R.layout.fragment_search, container, false)
+ _binding = FragmentSearchBinding.inflate(inflater, container, false)
+ return binding.root
}
override fun onViewCreated(
view: View,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
- binding = FragmentSearchBinding.bind(view)
+ initObservers()
getUserLocation()
initRecyclerView()
-
handleSearchQuery()
-
initSwipeRefreshList()
}
+ private fun initObservers() {
+ with(viewModel) {
+ cityId.observe(viewLifecycleOwner) { event ->
+ event.getContentIfNotHandled()?.let { result ->
+ result.fold(
+ onSuccess = {
+ showDetailsFragment(it)
+ },
+ onFailure = {
+ Toast.makeText(
+ context,
+ "Не удалось найти такой город",
+ Toast.LENGTH_LONG
+ ).show()
+ Timber.d("Do not found city")
+ }
+ )
+ }
+ }
+
+ weatherList.observe(viewLifecycleOwner) { result ->
+ result.fold(
+ onSuccess = {
+ listRecyclerAdapter?.submitList(it)
+ scrollRecyclerViewToPosition(0)
+ },
+ onFailure = {
+ Timber.e("Error due to get weathers")
+ Timber.e(it.message.toString())
+ Toast.makeText(
+ context,
+ "Не удалось найти",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ )
+ }
+ }
+ }
+
private fun initSwipeRefreshList() {
- binding?.swipeRefreshLayout?.let {
+ binding.swipeRefreshLayout.let {
it.setOnRefreshListener {
getUserLocation()
+ updateWeatherList(userLat, userLon, COUNT_OF_CITIES_IN_LIST)
it.isRefreshing = false
}
}
}
private fun getUserLocation() {
- context?.let {
+ context?.let { context ->
if (ActivityCompat.checkSelfPermission(
- it,
- Manifest.permission.ACCESS_COARSE_LOCATION
- ) == PackageManager.PERMISSION_DENIED ||
+ context,
+ ACCESS_COARSE_LOCATION
+ ) == PERMISSION_DENIED ||
ActivityCompat.checkSelfPermission(
- it,
- Manifest.permission.ACCESS_FINE_LOCATION
- ) == PackageManager.PERMISSION_DENIED
+ context,
+ ACCESS_FINE_LOCATION
+ ) == PERMISSION_DENIED ||
+ ActivityCompat.checkSelfPermission(
+ context,
+ ACCESS_LOCATION_EXTRA_COMMANDS
+ ) == PERMISSION_DENIED
) {
- val permissions = arrayOf(
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION
- )
Timber.d("Request location permissions")
- requestPermissions(permissions, REQUEST_CODE_100)
+ requestPermissions.launch(permissions)
} else {
Timber.d("Get user location")
userLocation = LocationServices.getFusedLocationProviderClient(requireContext())
@@ -110,14 +182,14 @@ class SearchFragment : Fragment(R.layout.fragment_search) {
userLon = location.longitude
userLat = location.latitude
Timber.d("Location found")
- binding?.let { it ->
- Snackbar.make(it.root, "Местоположение найдено", Snackbar.LENGTH_LONG)
- .show()
- }
- getWeatherList(userLat, userLon, COUNT_OF_CITIES_IN_LIST)
+ Toast.makeText(
+ context,
+ "Местоположение найдено",
+ Toast.LENGTH_SHORT
+ ).show()
} else {
Timber.d("Location not found")
- binding?.let { it ->
+ binding.let { it ->
Snackbar.make(
it.root,
"Местоположение не найдено\nВключите геолокацию",
@@ -137,47 +209,11 @@ class SearchFragment : Fragment(R.layout.fragment_search) {
}
}
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray
- ) {
- when (requestCode) {
- REQUEST_CODE_100 -> {
- if (grantResults.isNotEmpty()
- && grantResults[0] == PackageManager.PERMISSION_GRANTED
- && grantResults[1] == PackageManager.PERMISSION_GRANTED
- ) {
- Timber.d("Permissions granted")
- getUserLocation()
- } else {
- Timber.d("Permissions denied")
- binding?.let {
- Snackbar.make(it.root, "Разрешения не даны", Snackbar.LENGTH_SHORT)
- .show()
- }
- }
- }
- }
- }
-
-
private fun handleSearchQuery() {
- binding?.svSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ binding.svSearch.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
- lifecycleScope.launch {
- Timber.d("Pressed query button")
- getCityId(query)?.let {
- showDetailsFragment(it)
- } ?: run {
- Toast.makeText(
- context,
- "Не удалось найти такой город",
- Toast.LENGTH_LONG
- ).show()
- Timber.d("Do not found city")
- }
- }
+ Timber.d("Pressed query button")
+ viewModel.onGetCityId(query)
return false
}
@@ -188,46 +224,39 @@ class SearchFragment : Fragment(R.layout.fragment_search) {
})
}
- private suspend fun getCityId(
- city: String
- ) = kotlin.runCatching {
- getWeatherUseCase(city).id
- }.getOrNull()
-
-
private fun initRecyclerView() {
- binding?.rvSearch?.run {
+ binding.rvSearch.run {
listRecyclerAdapter = ListRecyclerAdapter { id ->
showDetailsFragment(id)
}
adapter = listRecyclerAdapter
}
-
- getWeatherList(userLat, userLon, COUNT_OF_CITIES_IN_LIST)
+ updateWeatherList(userLat, userLon, COUNT_OF_CITIES_IN_LIST)
}
- private fun getWeatherList(lat: Double, lon: Double, cnt: Int) {
+ private fun updateWeatherList(lat: Double, lon: Double, cnt: Int) {
+ viewModel.onGetWeatherList(lat, lon, cnt)
Timber.d("Get weathers list")
- lifecycleScope.launch {
- try {
- val weatherList = getWeatherListUseCase(lat, lon, cnt)
- listRecyclerAdapter?.submitList(weatherList)
- binding?.rvSearch?.layoutManager?.scrollToPosition(0)
- } catch (ex: Exception) {
- Timber.e("Error due to get weathers")
- Timber.e(ex.message.toString())
- Toast.makeText(
- context,
- "Не удалось найти",
- Toast.LENGTH_LONG
- ).show()
- }
- }
}
- private fun showDetailsFragment(id: Int) = findNavController().navigate(
+ private fun showDetailsFragment(
+ id: Int,
+ ) = findNavController().navigate(
SearchFragmentDirections.actionFragmentSearchToFragmentDetails(
- id
+ id,
+ "Item_$id"
)
)
+
+ private fun scrollRecyclerViewToPosition(position: Int) {
+ binding.rvSearch.layoutManager?.startSmoothScroll(smoothScroller.apply {
+ targetPosition = position
+ })
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ listRecyclerAdapter = null
+ _binding = null
+ }
}
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt
index 503d9e2..6f96bf5 100644
--- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt
@@ -5,6 +5,7 @@ import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
+import coil.load
import ru.itis.karakurik.androidLab2.databinding.ListItemCityBinding
import ru.itis.karakurik.androidLab2.domain.entity.Weather
import ru.itis.karakurik.androidLab2.presentation.convertors.TempColorConverter.getColor
@@ -15,6 +16,7 @@ class ListItemViewHolder(
) : RecyclerView.ViewHolder(binding.root) {
fun bind(weather: Weather) {
+
with(binding) {
tvCityItem.text = weather.name
tvTempItem.text = weather.temp.toString()
@@ -24,6 +26,10 @@ class ListItemViewHolder(
getColor(weather.temp)
)
)
+ ivIconItem.run {
+ load(weather.iconUrl)
+ transitionName = "Item_${weather.id}"
+ }
root.setOnClickListener {
onItemClick(weather.id)
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt
index fd468f5..97ae06d 100644
--- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt
@@ -21,9 +21,4 @@ class ListRecyclerAdapter(
) = holder.bind(
getItem(position)
)
-
- /*override fun submitList(list: MutableList?) {
- super.submitList(if (list == null) null else ArrayList(list))
- }*/
-
}
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/Event.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/Event.kt
new file mode 100644
index 0000000..ac71031
--- /dev/null
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/Event.kt
@@ -0,0 +1,25 @@
+/**
+ * Used as a wrapper for data that is exposed via a LiveData that represents an event.
+ */
+open class Event(private val content: T) {
+
+ var hasBeenHandled = false
+ private set // Allow external read but not write
+
+ /**
+ * Returns the content and prevents its use again.
+ */
+ fun getContentIfNotHandled(): T? {
+ return if (hasBeenHandled) {
+ null
+ } else {
+ hasBeenHandled = true
+ content
+ }
+ }
+
+ /**
+ * Returns the content, even if it's already been handled.
+ */
+ fun peekContent(): T = content
+}
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/ViewModelFactory.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/ViewModelFactory.kt
new file mode 100644
index 0000000..1ccf37f
--- /dev/null
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/ViewModelFactory.kt
@@ -0,0 +1,26 @@
+package ru.itis.karakurik.androidLab2.presentation.utils
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherListUseCase
+import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase
+import ru.itis.karakurik.androidLab2.presentation.MainViewModel
+import java.lang.IllegalArgumentException
+
+class ViewModelFactory(
+ private val getWeatherUseCase: GetWeatherUseCase,
+ private val getWeatherListUseCase: GetWeatherListUseCase
+) : ViewModelProvider.Factory {
+
+ override fun create(modelClass: Class): T {
+ return when {
+ modelClass.isAssignableFrom(MainViewModel::class.java) ->
+ MainViewModel(
+ getWeatherUseCase,
+ getWeatherListUseCase
+ ) as? T ?: throw IllegalArgumentException("Unknown ViewModel class")
+
+ else -> throw IllegalArgumentException("Unknown ViewModel class")
+ }
+ }
+}
diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml
index bb2c9c5..49038c8 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -1,239 +1,248 @@
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+ android:gravity="top"
+ android:textColor="@color/black"
+ android:textSize="76sp"
+ tools:text="29.99°C"/>
+ android:orientation="horizontal">
-
-
-
+
+
+
+
+
+
+
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+
+
+
+
-
-
-
+ android:orientation="horizontal">
-
-
-
+
+
+
+
+
+
+
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+
+
+
+
+ android:orientation="horizontal">
-
-
-
+
+
+
+
+
+
+
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+
+
+
+
-
-
+
diff --git a/app/src/main/res/layout/list_item_city.xml b/app/src/main/res/layout/list_item_city.xml
index 8a2e615..f8a33f0 100644
--- a/app/src/main/res/layout/list_item_city.xml
+++ b/app/src/main/res/layout/list_item_city.xml
@@ -1,29 +1,37 @@
+ app:cardCornerRadius="32dp">
+ android:orientation="horizontal"
+ android:paddingHorizontal="20dp"
+ android:paddingVertical="10dp">
+
+
+ android:textSize="32sp"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ tools:text="Kazan" />
+ tools:text="10"
+ tools:textColor="#00ff00" />
diff --git a/app/src/main/res/navigation/nav_graph_main.xml b/app/src/main/res/navigation/nav_graph_main.xml
index 7ad4887..5d4f3b8 100644
--- a/app/src/main/res/navigation/nav_graph_main.xml
+++ b/app/src/main/res/navigation/nav_graph_main.xml
@@ -13,7 +13,8 @@
+ app:destination="@id/fragment_details">
+
-
+
+
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index db0a143..c0e2204 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,6 +1,7 @@