diff --git a/app/src/main/java/ru/practicum/android/diploma/data/impl/SharedPrefsRepositoryImpl.kt b/app/src/main/java/ru/practicum/android/diploma/data/impl/SharedPrefsRepositoryImpl.kt index b6f10a7f68..22a82308d1 100644 --- a/app/src/main/java/ru/practicum/android/diploma/data/impl/SharedPrefsRepositoryImpl.kt +++ b/app/src/main/java/ru/practicum/android/diploma/data/impl/SharedPrefsRepositoryImpl.kt @@ -3,19 +3,36 @@ package ru.practicum.android.diploma.data.impl import android.content.SharedPreferences import com.google.gson.Gson import ru.practicum.android.diploma.domain.SharedPrefsRepository +import ru.practicum.android.diploma.domain.models.Country +import ru.practicum.android.diploma.domain.models.Industries +import ru.practicum.android.diploma.domain.models.Region import ru.practicum.android.diploma.domain.models.SaveFiltersSharedPrefs class SharedPrefsRepositoryImpl( private val sharedPreferences: SharedPreferences, private val gson: Gson ) : SharedPrefsRepository { - override suspend fun readSharedPrefs(): SaveFiltersSharedPrefs? { - val json = sharedPreferences.getString(HISTORY, null) ?: return null + override suspend fun readSharedPrefs(): SaveFiltersSharedPrefs { + val json = sharedPreferences.getString(HISTORY, null) ?: return SaveFiltersSharedPrefs( + Industries("", "", false), + Country("", ""), + Region("", "", null), + "", + false + ) return gson.fromJson(json, SaveFiltersSharedPrefs::class.java) } override suspend fun writeSharedPrefs(filters: SaveFiltersSharedPrefs) { - sharedPreferences.edit().putString(HISTORY, gson.toJson(filters)).apply() + val oldShared = readSharedPrefs() + val newShared = oldShared.copy( + industries = filters.industries ?: oldShared.industries, + country = filters.country ?: oldShared.country, + region = filters.region ?: oldShared.region, + currency = filters.currency ?: oldShared.currency, + noCurrency = filters.noCurrency + ) + sharedPreferences.edit().putString(HISTORY, gson.toJson(newShared)).apply() } override suspend fun clearSharedPrefs() { diff --git a/app/src/main/java/ru/practicum/android/diploma/di/ViewModelModule.kt b/app/src/main/java/ru/practicum/android/diploma/di/ViewModelModule.kt index 831590f0fc..bacaa315f1 100644 --- a/app/src/main/java/ru/practicum/android/diploma/di/ViewModelModule.kt +++ b/app/src/main/java/ru/practicum/android/diploma/di/ViewModelModule.kt @@ -3,6 +3,7 @@ package ru.practicum.android.diploma.di import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module import ru.practicum.android.diploma.presentation.viewmodels.FilterCountryViewModel +import ru.practicum.android.diploma.presentation.viewmodels.FilterIndustryViewModel import ru.practicum.android.diploma.presentation.viewmodels.FilterPlaceOfWorkViewModel import ru.practicum.android.diploma.presentation.viewmodels.FilterRegionViewModel import ru.practicum.android.diploma.presentation.viewmodels.FilterViewModel @@ -35,4 +36,7 @@ val viewModelModule = module { viewModel { FilterRegionViewModel(get()) } + viewModel { + FilterIndustryViewModel(get()) + } } diff --git a/app/src/main/java/ru/practicum/android/diploma/domain/models/Industries.kt b/app/src/main/java/ru/practicum/android/diploma/domain/models/Industries.kt index 618dccd9a3..ee9ab4bcb3 100644 --- a/app/src/main/java/ru/practicum/android/diploma/domain/models/Industries.kt +++ b/app/src/main/java/ru/practicum/android/diploma/domain/models/Industries.kt @@ -1,7 +1,11 @@ package ru.practicum.android.diploma.domain.models +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize data class Industries( val id: String, val name: String, val isChecked: Boolean -) +) : Parcelable diff --git a/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterIndustryViewModel.kt b/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterIndustryViewModel.kt new file mode 100644 index 0000000000..4347dc2fc2 --- /dev/null +++ b/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterIndustryViewModel.kt @@ -0,0 +1,89 @@ +package ru.practicum.android.diploma.presentation.viewmodels + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import ru.practicum.android.diploma.domain.FilterInteractor +import ru.practicum.android.diploma.domain.models.Industries +import ru.practicum.android.diploma.domain.models.SaveFiltersSharedPrefs +import ru.practicum.android.diploma.util.ResponseData + +class FilterIndustryViewModel( + private val interactor: FilterInteractor +) : ViewModel() { + private val _industries = MutableLiveData>() + val industries: LiveData> + get() = _industries + + private val _hasSelected = MutableLiveData(false) + val hasSelected: LiveData + get() = _hasSelected + + private val _selectedIndustry = MutableLiveData() + val selectedIndustry: LiveData + get() = _selectedIndustry + + fun writeSharedPrefs() { + viewModelScope.launch { + interactor.writeSharedPrefs( + SaveFiltersSharedPrefs( + industries = _selectedIndustry.value, + null, + null, + null, + false + ) + ) + } + } + + fun itemChecked(industries: Industries) { + val newList = ArrayList() + _industries.value?.forEach { industry -> + if (industry.id == industries.id) { + newList.add(industry.copy(isChecked = !industry.isChecked)) + _hasSelected.postValue(!industry.isChecked) + if (!industry.isChecked) _selectedIndustry.postValue(industry) + } else { + newList.add(industry.copy(isChecked = false)) + } + } + _industries.postValue(newList) + } + + fun updateListIndustries() { + viewModelScope.launch { + val industry = interactor.readSharedPrefs()?.industries + interactor.getIndustries().collect { list -> + when (list) { + is ResponseData.Data -> { + whenList(industry, list) + } + + is ResponseData.Error -> {} + } + } + } + } + + fun whenList(industry: Industries?, list: ResponseData.Data>) { + if (industry != null) { + val newList = ArrayList() + list.value.forEach { industryItem -> + if (industryItem.id == industry.id) { + newList.add(industryItem.copy(isChecked = true)) + _selectedIndustry.postValue(industryItem) + _hasSelected.postValue(true) + } else { + newList.add(industryItem) + } + } + _industries.postValue(newList) + } else { + _industries.postValue(list.value) + } + } +} + diff --git a/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterPlaceOfWorkViewModel.kt b/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterPlaceOfWorkViewModel.kt index 44efc6f809..d55d36915c 100644 --- a/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterPlaceOfWorkViewModel.kt +++ b/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterPlaceOfWorkViewModel.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.launch import ru.practicum.android.diploma.domain.FilterInteractor import ru.practicum.android.diploma.domain.models.Country import ru.practicum.android.diploma.domain.models.Region +import ru.practicum.android.diploma.domain.models.SaveFiltersSharedPrefs import ru.practicum.android.diploma.ui.state.PlaceOfWorkScreenState import ru.practicum.android.diploma.util.ResponseData @@ -17,6 +18,9 @@ class FilterPlaceOfWorkViewModel( ) : ViewModel() { private val screenStateLiveData = MutableLiveData() + private val _filtersSave = MutableLiveData() + val sharedPrefs: LiveData + get() = _filtersSave fun render(): LiveData { return screenStateLiveData @@ -42,6 +46,20 @@ class FilterPlaceOfWorkViewModel( } } + fun saveSharedPrefs(country: Country?, region: Region?) { + viewModelScope.launch { + filterInteractor.writeSharedPrefs( + SaveFiltersSharedPrefs( + industries = null, + country = country, + region = region, + currency = null, + noCurrency = false + ) + ) + } + } + fun saveFields(country: Country, region: Region) { if (country.name.isEmpty()) { getCountryName(region, true) @@ -86,4 +104,10 @@ class FilterPlaceOfWorkViewModel( private fun setState(state: PlaceOfWorkScreenState) { screenStateLiveData.postValue(state) } + + fun readSharedPrefs() { + viewModelScope.launch { + _filtersSave.postValue(filterInteractor.readSharedPrefs()) + } + } } diff --git a/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterViewModel.kt b/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterViewModel.kt index 55ed48f00f..2769935ea5 100644 --- a/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterViewModel.kt +++ b/app/src/main/java/ru/practicum/android/diploma/presentation/viewmodels/FilterViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import ru.practicum.android.diploma.domain.FilterInteractor import ru.practicum.android.diploma.domain.models.SaveFiltersSharedPrefs +import ru.practicum.android.diploma.domain.models.Industries import ru.practicum.android.diploma.ui.state.FilterScreenState class FilterViewModel( @@ -16,6 +17,9 @@ class FilterViewModel( private val screenStateLiveData = MutableLiveData() + private val _noCurrency = MutableLiveData() + private val _industry = MutableLiveData() + fun render(): LiveData { return screenStateLiveData } @@ -67,7 +71,13 @@ class FilterViewModel( } else { setState(FilterScreenState.NoPlaceOfWork) } + } + fun setNoCurrencySelected(answer: Boolean) { + _noCurrency.postValue(answer) } + fun setIndustrySelected(industries: Industries?) { + _industry.postValue(industries) + } } diff --git a/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterFragment.kt b/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterFragment.kt index 06ee7f3195..386303bcc1 100644 --- a/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterFragment.kt +++ b/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterFragment.kt @@ -28,6 +28,8 @@ import ru.practicum.android.diploma.domain.models.Industries import ru.practicum.android.diploma.domain.models.Region import ru.practicum.android.diploma.domain.models.SaveFiltersSharedPrefs import ru.practicum.android.diploma.presentation.viewmodels.FilterViewModel +import ru.practicum.android.diploma.ui.fragments.FilterIndustryFragment.Companion.INDUSTRY_ITEM_KEY +import ru.practicum.android.diploma.ui.fragments.FilterIndustryFragment.Companion.INDUSTRY_KEY import ru.practicum.android.diploma.ui.fragments.FilterPlaceOfWorkFragment.Companion.PLACE_OF_WORK_COUNTRY_KEY import ru.practicum.android.diploma.ui.fragments.FilterPlaceOfWorkFragment.Companion.PLACE_OF_WORK_KEY import ru.practicum.android.diploma.ui.fragments.FilterPlaceOfWorkFragment.Companion.PLACE_OF_WORK_REGION_KEY @@ -43,7 +45,7 @@ class FilterFragment : Fragment() { private val binding: FragmentFilterBinding by viewBinding(CreateMethod.INFLATE) private val viewModel by viewModel() - + private var init = true private var region = Region("", "", null) private var country = Country("", "") private var industries = Industries("", "", false) @@ -65,6 +67,7 @@ class FilterFragment : Fragment() { binding.salary.doOnTextChanged { text, _, _, _ -> if (!text.isNullOrEmpty()) { binding.btnGroup.isVisible = true + viewModel.saveFilter(makeFilterSettings()) } else { checkFields() } @@ -89,6 +92,7 @@ class FilterFragment : Fragment() { } is FilterScreenState.Industry -> { + binding.industryTextInput.setText(state.industry) setIndustryEndIcon() } @@ -96,6 +100,7 @@ class FilterFragment : Fragment() { binding.industryTextInput.text?.clear() setNoIndustryEndIcon() checkFields() + industries = Industries("", "", false) } FilterScreenState.NoPlaceOfWork -> { @@ -160,6 +165,14 @@ class FilterFragment : Fragment() { val countryName = setPlaceOfWorkName(country.name, region.name) viewModel.setPlaceOfWork(countryName) } + setFragmentResultListener(INDUSTRY_KEY) { _, bundle -> + val industryJson = bundle.getString(INDUSTRY_ITEM_KEY).toString() + val type = object : TypeToken() {}.type + val industry = Gson().fromJson(industryJson, type) + industries = industry + viewModel.setIndustry(industry.name) + viewModel.setIndustrySelected(industry) + } } private fun setPlaceOfWorkName(countryName: String, regionName: String): String { @@ -179,6 +192,8 @@ class FilterFragment : Fragment() { binding.clearButton.setOnClickListener { viewModel.clear() } // Пример использования Checkbox, если включена опция показа только с зарплатой binding.salaryFlagCheckbox.setOnCheckedChangeListener { _, isChecked -> // + viewModel.saveFilter(makeFilterSettings()) + viewModel.setNoCurrencySelected(isChecked) // viewModel.setSalaryOnlyCheckbox(isChecked) } binding.workTextInput.setOnClickListener { diff --git a/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterIndustryFragment.kt b/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterIndustryFragment.kt index 1e50e7bbff..43bdd093e9 100644 --- a/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterIndustryFragment.kt +++ b/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterIndustryFragment.kt @@ -4,15 +4,36 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.core.view.isVisible +import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment +import androidx.fragment.app.setFragmentResult import androidx.navigation.fragment.findNavController import by.kirich1409.viewbindingdelegate.CreateMethod import by.kirich1409.viewbindingdelegate.viewBinding +import com.google.gson.Gson +import org.koin.androidx.viewmodel.ext.android.viewModel import ru.practicum.android.diploma.databinding.FragmentSelectIndustryBinding +import ru.practicum.android.diploma.presentation.viewmodels.FilterIndustryViewModel +import ru.practicum.android.diploma.util.adapter.industry.IndustryAdapter class FilterIndustryFragment : Fragment() { + companion object { + const val INDUSTRY_KEY = "industryKey" + const val INDUSTRY_ITEM_KEY = "INDUSTRY_ITEM_KEY" + } + private val binding: FragmentSelectIndustryBinding by viewBinding(CreateMethod.INFLATE) + private val viewModel: FilterIndustryViewModel by viewModel() + private val adapter by lazy { + IndustryAdapter( + onClick = { industryList -> + viewModel.itemChecked(industryList) + } + ) + } override fun onCreateView( inflater: LayoutInflater, @@ -24,10 +45,40 @@ class FilterIndustryFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewModel.updateListIndustries() + binding.industryRecycleView.adapter = adapter + + viewModel.industries.observe(viewLifecycleOwner) { list -> + adapter.industry = list + } + + viewModel.hasSelected.observe(viewLifecycleOwner) { + binding.applyButton.isVisible = it + } + + binding.industrySearchQuery.doOnTextChanged { text, start, before, count -> + if (binding.industrySearchQuery.text.isNotEmpty()) { + binding.searchIconLoupe.isVisible = false + binding.clearCrossIc.isVisible = true + } else { + binding.searchIconLoupe.isVisible = true + binding.clearCrossIc.isVisible = false + } + } + + binding.clearCrossIc.setOnClickListener { + binding.industrySearchQuery.setText("") + } binding.industryFilterToolbar.setNavigationOnClickListener { findNavController().navigateUp() } - } + binding.applyButton.setOnClickListener { + val json = Gson().toJson(viewModel.selectedIndustry.value) + setFragmentResult(INDUSTRY_KEY, bundleOf(INDUSTRY_ITEM_KEY to json)) + viewModel.writeSharedPrefs() + findNavController().popBackStack() + } + } } diff --git a/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterPlaceOfWorkFragment.kt b/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterPlaceOfWorkFragment.kt index ef575ab4a4..15b437db25 100644 --- a/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterPlaceOfWorkFragment.kt +++ b/app/src/main/java/ru/practicum/android/diploma/ui/fragments/FilterPlaceOfWorkFragment.kt @@ -46,6 +46,7 @@ class FilterPlaceOfWorkFragment : Fragment() { private var countryId = "" private var country = Country("", "") private var region = Region("", "", null) + private var init = true override fun onCreateView( @@ -59,6 +60,21 @@ class FilterPlaceOfWorkFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewModel.readSharedPrefs() + if (init) { + viewModel.sharedPrefs.observe(viewLifecycleOwner) { filters -> + if (filters.country != null) { + viewModel.setCountryName(country) + if (filters.region != null) { + region = filters.region + viewModel.setRegionName(filters.region) + getCountryName(region) + } + } + } + init = false + } + initButtonListeners() initTextBehaviour() initResultListeners() @@ -143,6 +159,8 @@ class FilterPlaceOfWorkFragment : Fragment() { country = Gson().fromJson(json, type) countryId = country.id viewModel.setCountryName(country) + setNoRegionEndIcon() + binding.regionTextInput.setText("") } setFragmentResultListener(REGION_REQUEST_KEY) { _, bundle -> @@ -194,6 +212,7 @@ class FilterPlaceOfWorkFragment : Fragment() { } private fun saveFilters() { + viewModel.saveSharedPrefs(country, region) viewModel.saveFields(country, region) } diff --git a/app/src/main/java/ru/practicum/android/diploma/util/Options.kt b/app/src/main/java/ru/practicum/android/diploma/util/Options.kt index 01a5f32383..b87e137cc8 100644 --- a/app/src/main/java/ru/practicum/android/diploma/util/Options.kt +++ b/app/src/main/java/ru/practicum/android/diploma/util/Options.kt @@ -11,15 +11,17 @@ data class Options( ) { companion object { fun toMap(options: Options): Map = with(options) { - listOfNotNull( - "text" to searchText, - "per_page" to itemsPerPage.toString(), - "page" to page.toString(), - if (area?.isNotEmpty() == true) "area" to area.toString() else null, - if (industry?.isNotEmpty() == true) "industry" to area.toString() else null, - if (salary?.isNotEmpty() == true) "salary" to area.toString() else null, - "only_with_salary" to withSalary.toString() - ).toMap() + buildMap { + put("text", searchText) + put("per_page", itemsPerPage.toString()) + put("page", page.toString()) + if (area?.isNotEmpty() == true) { + put("area", area.toString()) + } + if (industry?.isNotEmpty() == true) put("industry", industry.toString()) + if (salary?.isNotEmpty() == true) put("salary", salary.toString()) + put("only_with_salary", withSalary.toString()) + } } } } diff --git a/app/src/main/java/ru/practicum/android/diploma/util/adapter/industry/IndustryAdapter.kt b/app/src/main/java/ru/practicum/android/diploma/util/adapter/industry/IndustryAdapter.kt new file mode 100644 index 0000000000..fffeca7202 --- /dev/null +++ b/app/src/main/java/ru/practicum/android/diploma/util/adapter/industry/IndustryAdapter.kt @@ -0,0 +1,30 @@ +package ru.practicum.android.diploma.util.adapter.industry + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import ru.practicum.android.diploma.databinding.IndustryItemViewBinding +import ru.practicum.android.diploma.domain.models.Industries + +class IndustryAdapter( + private val onClick: (Industries) -> Unit +) : RecyclerView.Adapter() { + var industry: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IndustryViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + return IndustryViewHolder(IndustryItemViewBinding.inflate(layoutInflater, parent, false), onClick) + } + + override fun getItemCount(): Int { + return industry.size + } + + override fun onBindViewHolder(holder: IndustryViewHolder, position: Int) { + holder.bind(industry[position]) + } +} diff --git a/app/src/main/java/ru/practicum/android/diploma/util/adapter/industry/IndustryViewHolder.kt b/app/src/main/java/ru/practicum/android/diploma/util/adapter/industry/IndustryViewHolder.kt new file mode 100644 index 0000000000..156b3c3d09 --- /dev/null +++ b/app/src/main/java/ru/practicum/android/diploma/util/adapter/industry/IndustryViewHolder.kt @@ -0,0 +1,18 @@ +package ru.practicum.android.diploma.util.adapter.industry + +import androidx.recyclerview.widget.RecyclerView +import ru.practicum.android.diploma.databinding.IndustryItemViewBinding +import ru.practicum.android.diploma.domain.models.Industries + +class IndustryViewHolder( + private val binding: IndustryItemViewBinding, + private val onClick: (Industries) -> Unit +) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Industries) { + binding.industryUnit.text = item.name + binding.roundButton.isChecked = item.isChecked + binding.root.setOnClickListener { + onClick(item) + } + } +} diff --git a/app/src/main/java/ru/practicum/android/diploma/util/extension/BundleExt.kt b/app/src/main/java/ru/practicum/android/diploma/util/extension/BundleExt.kt new file mode 100644 index 0000000000..6b9927a5a0 --- /dev/null +++ b/app/src/main/java/ru/practicum/android/diploma/util/extension/BundleExt.kt @@ -0,0 +1,7 @@ +package ru.practicum.android.diploma.util.extension + +import android.os.Bundle +import androidx.core.os.BundleCompat + +inline fun Bundle.parcelable(key: String): T? = + BundleCompat.getParcelable(this, key, T::class.java) diff --git a/app/src/main/res/layout/fragment_filter.xml b/app/src/main/res/layout/fragment_filter.xml index 4e26d65535..778c7d06ad 100644 --- a/app/src/main/res/layout/fragment_filter.xml +++ b/app/src/main/res/layout/fragment_filter.xml @@ -78,7 +78,6 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/margin_size_16" android:layout_marginTop="@dimen/margin_size_24" - android:hint="@string/salary_expectation" app:cursorColor="@color/blue" app:endIconDrawable="@drawable/ic_close_cross_14px" diff --git a/app/src/main/res/layout/industry_item_view.xml b/app/src/main/res/layout/industry_item_view.xml index 1db3bc7261..e208f9137e 100644 --- a/app/src/main/res/layout/industry_item_view.xml +++ b/app/src/main/res/layout/industry_item_view.xml @@ -26,8 +26,9 @@ android:id="@+id/round_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center" android:buttonTint="@color/blue" + android:clickable="false" + android:gravity="center" app:layout_constraintBottom_toBottomOf="@id/industry_unit" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/industry_unit" />