From 6806d6300fe2a7afc3c66cbe78fe35bc59751c66 Mon Sep 17 00:00:00 2001 From: razeeman Date: Sun, 16 Apr 2023 11:33:36 +0300 Subject: [PATCH] add tag adding to data edit --- .../di/NavigationDialogMapModule.kt | 12 ++ app/src/main/res/navigation/nav_graph.xml | 5 + .../interactor/RecordTagViewDataInteractor.kt | 16 ++- .../viewModel/ChangeRecordBaseViewModel.kt | 2 + .../DataEditTagSelectionDialogFragment.kt | 112 ++++++++++++++++++ .../DataEditTagSelectionDialogListener.kt | 7 ++ .../dialog/DataEditTagSelectionViewModel.kt | 77 ++++++++++++ .../DataEditTypeSelectionDialogFragment.kt | 2 +- .../DataEditTypeSelectionDialogListener.kt | 2 +- .../dialog/DataEditTypeSelectionViewModel.kt | 1 - .../interactor/DateEditChangeInteractor.kt | 38 ++++-- .../interactor/DateEditViewDataInteractor.kt | 28 ++++- .../model/DataEditAddTagsState.kt | 9 ++ .../view/DataEditFragment.kt | 57 ++++++++- .../viewModel/DataEditViewModel.kt | 81 ++++++++++++- .../main/res/layout/data_edit_fragment.xml | 15 +++ ...ata_edit_tag_selection_dialog_fragment.xml | 34 ++++++ .../RecordTagSelectionViewDataInteractor.kt | 2 + .../DataEditTagSelectionDialogParams.kt | 9 ++ 19 files changed, 481 insertions(+), 28 deletions(-) create mode 100644 features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogFragment.kt create mode 100644 features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogListener.kt create mode 100644 features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionViewModel.kt create mode 100644 features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/model/DataEditAddTagsState.kt create mode 100644 features/feature_data_edit/src/main/res/layout/data_edit_tag_selection_dialog_fragment.xml create mode 100644 navigation/src/main/java/com/example/util/simpletimetracker/navigation/params/screen/DataEditTagSelectionDialogParams.kt diff --git a/app/src/main/java/com/example/util/simpletimetracker/di/NavigationDialogMapModule.kt b/app/src/main/java/com/example/util/simpletimetracker/di/NavigationDialogMapModule.kt index c20cdf49..74e751d3 100644 --- a/app/src/main/java/com/example/util/simpletimetracker/di/NavigationDialogMapModule.kt +++ b/app/src/main/java/com/example/util/simpletimetracker/di/NavigationDialogMapModule.kt @@ -1,6 +1,7 @@ package com.example.util.simpletimetracker.di import com.example.util.simpletimetracker.R +import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTagSelectionDialogFragment import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTypeSelectionDialogFragment import com.example.util.simpletimetracker.feature_dialogs.archive.view.ArchiveDialogFragment import com.example.util.simpletimetracker.feature_dialogs.cardOrder.view.CardOrderDialogFragment @@ -24,6 +25,7 @@ import com.example.util.simpletimetracker.navigation.params.screen.ChartFilterDi import com.example.util.simpletimetracker.navigation.params.screen.ColorSelectionDialogParams import com.example.util.simpletimetracker.navigation.params.screen.DataExportSettingDialogParams import com.example.util.simpletimetracker.navigation.params.screen.CustomRangeSelectionParams +import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams import com.example.util.simpletimetracker.navigation.params.screen.DataEditTypeSelectionDialogParams import com.example.util.simpletimetracker.navigation.params.screen.DateTimeDialogParams import com.example.util.simpletimetracker.navigation.params.screen.DefaultTypesSelectionDialogParams @@ -212,4 +214,14 @@ class NavigationDialogMapModule { BundleCreator.empty() ) } + + @IntoMap + @Provides + @ScreenKey(DataEditTagSelectionDialogParams::class) + fun dataEditTagSelectionDialog(): NavigationData { + return NavigationData( + R.id.dataEditTagSelectionDialogFragment, + bundleCreatorDelegate(DataEditTagSelectionDialogFragment::createBundle) + ) + } } \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 56351160..9e583d75 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -150,6 +150,11 @@ android:name="com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTypeSelectionDialogFragment" android:label="DataEditTypeSelectionDialogFragment" tools:layout="@layout/data_edit_type_selection_dialog_fragment" /> + { val isDarkTheme = prefsInteractor.getDarkMode() val type = recordTypeInteractor.get(typeId) @@ -39,7 +41,7 @@ class RecordTagViewDataInteractor @Inject constructor( } + recordTagInteractor.getUntyped() return recordTags - .filterNot { it.archived } + .let { tags -> if (showArchived) tags else tags.filterNot { it.archived } } .takeUnless { it.isEmpty() } ?.let { tags -> val selected = tags.filter { it.id in selectedTags } @@ -77,11 +79,13 @@ class RecordTagViewDataInteractor @Inject constructor( ) }.let(viewData::addAll) - if (selected.isNotEmpty() || available.isNotEmpty()) { - DividerViewData(2) - .takeIf { multipleChoiceAvailable } - ?.let(viewData::add) - mapRecordTagUntagged(isDarkTheme).let(viewData::add) + if (showUntaggedButton) { + if (selected.isNotEmpty() || available.isNotEmpty()) { + DividerViewData(2) + .takeIf { multipleChoiceAvailable } + ?.let(viewData::add) + mapRecordTagUntagged(isDarkTheme).let(viewData::add) + } } categoriesViewDataMapper.mapToRecordTagAddItem(isDarkTheme) diff --git a/features/feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/viewModel/ChangeRecordBaseViewModel.kt b/features/feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/viewModel/ChangeRecordBaseViewModel.kt index a088bf68..0c51d0dc 100644 --- a/features/feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/viewModel/ChangeRecordBaseViewModel.kt +++ b/features/feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/viewModel/ChangeRecordBaseViewModel.kt @@ -554,6 +554,8 @@ abstract class ChangeRecordBaseViewModel( multipleChoiceAvailable = true, showHint = true, showAddButton = true, + showArchived = false, + showUntaggedButton = true, ) } diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogFragment.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogFragment.kt new file mode 100644 index 00000000..b4bc4d7b --- /dev/null +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogFragment.kt @@ -0,0 +1,112 @@ +package com.example.util.simpletimetracker.feature_data_edit.dialog + +import android.content.Context +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.viewModels +import com.example.util.simpletimetracker.core.base.BaseBottomSheetFragment +import com.example.util.simpletimetracker.core.extension.blockContentScroll +import com.example.util.simpletimetracker.core.extension.getAllFragments +import com.example.util.simpletimetracker.core.extension.setFullScreen +import com.example.util.simpletimetracker.core.extension.setSkipCollapsed +import com.example.util.simpletimetracker.core.utils.fragmentArgumentDelegate +import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter +import com.example.util.simpletimetracker.feature_base_adapter.category.createCategoryAdapterDelegate +import com.example.util.simpletimetracker.feature_base_adapter.divider.createDividerAdapterDelegate +import com.example.util.simpletimetracker.feature_base_adapter.hint.createHintAdapterDelegate +import com.example.util.simpletimetracker.feature_base_adapter.info.createInfoAdapterDelegate +import com.example.util.simpletimetracker.feature_base_adapter.loader.createLoaderAdapterDelegate +import com.example.util.simpletimetracker.feature_views.extension.setOnClick +import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams +import com.google.android.flexbox.FlexDirection +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.FlexboxLayoutManager +import com.google.android.flexbox.JustifyContent +import dagger.hilt.android.AndroidEntryPoint +import com.example.util.simpletimetracker.feature_data_edit.databinding.DataEditTagSelectionDialogFragmentBinding as Binding + +@AndroidEntryPoint +class DataEditTagSelectionDialogFragment : BaseBottomSheetFragment() { + + override val inflater: (LayoutInflater, ViewGroup?, Boolean) -> Binding = + Binding::inflate + + private val viewModel: DataEditTagSelectionViewModel by viewModels() + + private val adapter: BaseRecyclerAdapter by lazy { + BaseRecyclerAdapter( + createLoaderAdapterDelegate(), + createHintAdapterDelegate(), + createDividerAdapterDelegate(), + createInfoAdapterDelegate(), + createCategoryAdapterDelegate(viewModel::onTagClick), + ) + } + private val params: DataEditTagSelectionDialogParams by fragmentArgumentDelegate( + key = ARGS_PARAMS, default = DataEditTagSelectionDialogParams() + ) + private var listener: DataEditTagSelectionDialogListener? = null + + override fun onAttach(context: Context) { + super.onAttach(context) + when (context) { + is DataEditTagSelectionDialogListener -> { + listener = context + return + } + is AppCompatActivity -> { + context.getAllFragments() + .firstOrNull { it is DataEditTagSelectionDialogListener && it.isResumed } + ?.let { listener = it as? DataEditTagSelectionDialogListener } + } + } + } + + override fun onDismiss(dialog: DialogInterface) { + listener?.onTagsDismissed() + super.onDismiss(dialog) + } + + override fun initDialog() { + setSkipCollapsed() + setFullScreen() + blockContentScroll(binding.rvDataEditTagSelectionContainer) + } + + override fun initUi(): Unit = with(binding) { + rvDataEditTagSelectionContainer.apply { + layoutManager = FlexboxLayoutManager(requireContext()).apply { + flexDirection = FlexDirection.ROW + justifyContent = JustifyContent.CENTER + flexWrap = FlexWrap.WRAP + } + adapter = this@DataEditTagSelectionDialogFragment.adapter + } + } + + override fun initUx() = with(binding) { + btnDataEditTagSelectionSave.setOnClick(viewModel::onSaveClick) + } + + override fun initViewModel(): Unit = with(viewModel) { + extra = params + viewData.observe(adapter::replace) + tagSelected.observe(::dismiss) + } + + private fun dismiss(tagIds: List) { + listener?.onTagsSelected(tagIds) + dismiss() + } + + companion object { + private const val ARGS_PARAMS = "args_params" + + fun createBundle(data: DataEditTagSelectionDialogParams) = Bundle().apply { + putParcelable(ARGS_PARAMS, data) + } + } +} \ No newline at end of file diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogListener.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogListener.kt new file mode 100644 index 00000000..b7949529 --- /dev/null +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogListener.kt @@ -0,0 +1,7 @@ +package com.example.util.simpletimetracker.feature_data_edit.dialog + +interface DataEditTagSelectionDialogListener { + + fun onTagsSelected(tagIds: List) + fun onTagsDismissed() +} \ No newline at end of file diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionViewModel.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionViewModel.kt new file mode 100644 index 00000000..2e9ff245 --- /dev/null +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionViewModel.kt @@ -0,0 +1,77 @@ +package com.example.util.simpletimetracker.feature_data_edit.dialog + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.util.simpletimetracker.core.extension.addOrRemove +import com.example.util.simpletimetracker.core.extension.set +import com.example.util.simpletimetracker.core.interactor.RecordTagViewDataInteractor +import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType +import com.example.util.simpletimetracker.feature_base_adapter.category.CategoryViewData +import com.example.util.simpletimetracker.feature_base_adapter.loader.LoaderViewData +import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.launch + +@HiltViewModel +class DataEditTagSelectionViewModel @Inject constructor( + private val recordTagViewDataInteractor: RecordTagViewDataInteractor, +) : ViewModel() { + + lateinit var extra: DataEditTagSelectionDialogParams + + val viewData: LiveData> by lazy { + return@lazy MutableLiveData>().let { initial -> + viewModelScope.launch { + initial.value = listOf(LoaderViewData()) + initial.value = loadViewData() + } + initial + } + } + val tagSelected: LiveData> = MutableLiveData() + + private var selectedIds: MutableList = mutableListOf() + + fun onTagClick(item: CategoryViewData) { + viewModelScope.launch { + when (item) { + is CategoryViewData.Record.Tagged -> { + selectedIds.addOrRemove(item.id) + } + is CategoryViewData.Record.Untagged -> { + selectedIds.clear() + } + else -> return@launch + } + updateViewData() + } + } + + fun onSaveClick() { + tagSelected.set(selectedIds) + } + + private fun updateViewData() = viewModelScope.launch { + val data = loadViewData() + viewData.set(data) + } + + private suspend fun loadViewData(): List { + val result: MutableList = mutableListOf() + + recordTagViewDataInteractor.getViewData( + selectedTags = selectedIds, + typeId = extra.typeId, + multipleChoiceAvailable = true, + showHint = false, + showAddButton = false, + showArchived = true, + showUntaggedButton = false, + ).let(result::addAll) + + return result + } +} diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogFragment.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogFragment.kt index d4782381..a09f911e 100644 --- a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogFragment.kt +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogFragment.kt @@ -57,7 +57,7 @@ class DataEditTypeSelectionDialogFragment : BaseBottomSheetFragment() { } override fun onDismiss(dialog: DialogInterface) { - listener?.onDismissed() + listener?.onTypeDismissed() super.onDismiss(dialog) } diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogListener.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogListener.kt index f3d6c819..998c098f 100644 --- a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogListener.kt +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogListener.kt @@ -3,5 +3,5 @@ package com.example.util.simpletimetracker.feature_data_edit.dialog interface DataEditTypeSelectionDialogListener { fun onTypeSelected(typeId: Long) - fun onDismissed() + fun onTypeDismissed() } \ No newline at end of file diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionViewModel.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionViewModel.kt index e4816e68..8777a13d 100644 --- a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionViewModel.kt +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionViewModel.kt @@ -56,7 +56,6 @@ class DataEditTypeSelectionViewModel @Inject constructor( } if (typesViewData.isNotEmpty()) { - HintViewData(resourceRepo.getString(R.string.activity_hint)).let(result::add) typesViewData.let(result::addAll) } else { HintViewData(resourceRepo.getString(R.string.record_types_empty)).let(result::add) diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditChangeInteractor.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditChangeInteractor.kt index ee1c1339..47dde3b4 100644 --- a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditChangeInteractor.kt +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditChangeInteractor.kt @@ -6,6 +6,8 @@ import com.example.util.simpletimetracker.domain.interactor.NotificationGoalTime import com.example.util.simpletimetracker.domain.interactor.RecordInteractor import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor import com.example.util.simpletimetracker.domain.model.RecordsFilter +import com.example.util.simpletimetracker.feature_base_adapter.category.CategoryViewData +import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState import javax.inject.Inject @@ -21,33 +23,45 @@ class DateEditChangeInteractor @Inject constructor( suspend fun changeData( typeState: DataEditChangeActivityState, commentState: DataEditChangeCommentState, + addTagState: DataEditAddTagsState, filters: List ) { if (filters.isEmpty()) return - val newTypeId = (typeState as? DataEditChangeActivityState.Enabled)?.viewData?.id - val newComment = (commentState as? DataEditChangeCommentState.Enabled)?.viewData - if (newTypeId == null && newComment == null) return + + val newTypeId = (typeState as? DataEditChangeActivityState.Enabled) + ?.viewData?.id + val newComment = (commentState as? DataEditChangeCommentState.Enabled) + ?.viewData + val addTags = (addTagState as? DataEditAddTagsState.Enabled) + ?.viewData?.map(CategoryViewData.Record::id) + + if (newTypeId == null && newComment == null && addTags == null) return val records = recordFilterInteractor.getByFilter(filters) val tags = recordTagInteractor.getAll().associateBy { it.id } val oldTypeIds = mutableSetOf() - var newTagIds: List? = null records.forEach { record -> - if (newTypeId != null && record.typeId != newTypeId) { - // Remove all typed tags. - newTagIds = record.tagIds.filter { tagId -> - tags[tagId]?.typeId == 0L + val finalTypeId = newTypeId ?: record.typeId + val finalComment = newComment ?: record.comment + val finalTagIds: Set = record.tagIds + .plus(addTags.orEmpty()) + .filter { tagId -> + val tag = tags[tagId] ?: return@filter false + tag.typeId == 0L || tag.typeId == finalTypeId } - // Save old typeId before change to update data later. + .toSet() + + // Save old typeId before change to update data later. + if (finalTypeId != record.typeId) { oldTypeIds.add(record.typeId) } // Change activity record.copy( - typeId = newTypeId ?: record.typeId, - comment = newComment ?: record.comment, - tagIds = newTagIds ?: record.tagIds + typeId = finalTypeId, + comment = finalComment, + tagIds = finalTagIds.toList(), ).let { recordInteractor.add(it) } diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditViewDataInteractor.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditViewDataInteractor.kt index 3cc3ec2b..e1c27fee 100644 --- a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditViewDataInteractor.kt +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditViewDataInteractor.kt @@ -1,12 +1,15 @@ package com.example.util.simpletimetracker.feature_data_edit.interactor import com.example.util.simpletimetracker.core.interactor.RecordFilterInteractor +import com.example.util.simpletimetracker.core.mapper.CategoryViewDataMapper import com.example.util.simpletimetracker.core.mapper.RecordTypeViewDataMapper import com.example.util.simpletimetracker.core.repo.ResourceRepo import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor +import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor import com.example.util.simpletimetracker.domain.model.RecordsFilter import com.example.util.simpletimetracker.feature_data_edit.R +import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeButtonState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState @@ -16,7 +19,9 @@ class DateEditViewDataInteractor @Inject constructor( private val resourceRepo: ResourceRepo, private val recordFilterInteractor: RecordFilterInteractor, private val recordTypeInteractor: RecordTypeInteractor, + private val recordTagInteractor: RecordTagInteractor, private val recordTypeViewDataMapper: RecordTypeViewDataMapper, + private val categoryViewDataMapper: CategoryViewDataMapper, private val prefsInteractor: PrefsInteractor, ) { @@ -41,6 +46,7 @@ class DateEditViewDataInteractor @Inject constructor( suspend fun getChangeActivityState( newTypeId: Long, ): DataEditChangeActivityState { + val isDarkTheme = prefsInteractor.getDarkMode() val type = newTypeId.let { recordTypeInteractor.get(it) } return if (type == null) { @@ -49,7 +55,7 @@ class DateEditViewDataInteractor @Inject constructor( DataEditChangeActivityState.Enabled( recordTypeViewDataMapper.map( recordType = type, - isDarkTheme = prefsInteractor.getDarkMode(), + isDarkTheme = isDarkTheme, ) ) } @@ -61,6 +67,26 @@ class DateEditViewDataInteractor @Inject constructor( return DataEditChangeCommentState.Enabled(newComment) } + suspend fun getAddTagState( + tagIds: List, + ): DataEditAddTagsState { + val isDarkTheme = prefsInteractor.getDarkMode() + val types = recordTypeInteractor.getAll().associateBy { it.id } + + return recordTagInteractor.getAll() + .filter { it.id in tagIds } + .map { + categoryViewDataMapper.mapRecordTag( + tag = it, + type = types[it.typeId], + isDarkTheme = isDarkTheme, + ) + } + .takeUnless { it.isEmpty() } + ?.let(DataEditAddTagsState::Enabled) + ?: DataEditAddTagsState.Disabled + } + suspend fun getChangeButtonState( enabled: Boolean, ): DataEditChangeButtonState { diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/model/DataEditAddTagsState.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/model/DataEditAddTagsState.kt new file mode 100644 index 00000000..24365b73 --- /dev/null +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/model/DataEditAddTagsState.kt @@ -0,0 +1,9 @@ +package com.example.util.simpletimetracker.feature_data_edit.model + +import com.example.util.simpletimetracker.feature_base_adapter.category.CategoryViewData + +sealed interface DataEditAddTagsState { + + object Disabled : DataEditAddTagsState + data class Enabled(val viewData: List) : DataEditAddTagsState +} \ No newline at end of file diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/view/DataEditFragment.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/view/DataEditFragment.kt index d1dd965a..5c637389 100644 --- a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/view/DataEditFragment.kt +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/view/DataEditFragment.kt @@ -9,32 +9,56 @@ import androidx.fragment.app.viewModels import com.example.util.simpletimetracker.core.base.BaseFragment import com.example.util.simpletimetracker.core.extension.hideKeyboard import com.example.util.simpletimetracker.core.extension.showKeyboard +import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter +import com.example.util.simpletimetracker.feature_base_adapter.category.createCategoryAdapterDelegate +import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTagSelectionDialogListener import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTypeSelectionDialogListener +import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeButtonState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState import com.example.util.simpletimetracker.feature_data_edit.viewModel.DataEditViewModel import com.example.util.simpletimetracker.feature_views.extension.setOnClick +import com.google.android.flexbox.FlexDirection +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.FlexboxLayoutManager +import com.google.android.flexbox.JustifyContent import dagger.hilt.android.AndroidEntryPoint import com.example.util.simpletimetracker.feature_data_edit.databinding.DataEditFragmentBinding as Binding @AndroidEntryPoint class DataEditFragment : BaseFragment(), - DataEditTypeSelectionDialogListener { + DataEditTypeSelectionDialogListener, + DataEditTagSelectionDialogListener { override val inflater: (LayoutInflater, ViewGroup?, Boolean) -> Binding = Binding::inflate private val viewModel: DataEditViewModel by viewModels() - override fun initUi() = with(binding) { - btnDataEditSelectRecords.setOnClick(throttle(viewModel::onSelectRecordsClick)) + private val addTagsPreviewAdapter: BaseRecyclerAdapter by lazy { + BaseRecyclerAdapter( + createCategoryAdapterDelegate(), + ) + } + + override fun initUi(): Unit = with(binding) { + rvDataEditAddTagsPreview.apply { + layoutManager = FlexboxLayoutManager(requireContext()).apply { + flexDirection = FlexDirection.ROW + justifyContent = JustifyContent.FLEX_START + flexWrap = FlexWrap.WRAP + } + adapter = addTagsPreviewAdapter + } } override fun initUx() = with(binding) { + btnDataEditSelectRecords.setOnClick(throttle(viewModel::onSelectRecordsClick)) checkboxDataEditChangeActivity.setOnClick(viewModel::onChangeActivityClick) checkboxDataEditChangeComment.setOnClick(viewModel::onChangeCommentClick) + checkboxDataEditAddTag.setOnClick(viewModel::onAddTagsClick) etDataEditChangeComment.doAfterTextChanged { viewModel.onCommentChange(it.toString()) } btnDataEditChange.setOnClick(throttle(viewModel::onChangeClick)) } @@ -44,6 +68,7 @@ class DataEditFragment : selectedRecordsCountViewData.observe(tvDataEditSelectedRecords::setText) changeActivityState.observe(::setChangeActivityState) changeCommentState.observe(::setChangeCommentState) + addTagsState.observe(::setAddTagState) changeButtonState.observe(::setChangeButtonState) keyboardVisibility.observe { visible -> if (visible) showKeyboard(etDataEditChangeComment) @@ -64,10 +89,18 @@ class DataEditFragment : viewModel.onTypeSelected(typeId) } - override fun onDismissed() { + override fun onTypeDismissed() { viewModel.onTypeDismissed() } + override fun onTagsSelected(tagIds: List) { + viewModel.onTagsSelected(tagIds) + } + + override fun onTagsDismissed() { + viewModel.onTagsDismissed() + } + private fun setChangeActivityState( state: DataEditChangeActivityState, ) = with(binding) { @@ -108,6 +141,22 @@ class DataEditFragment : } } + private fun setAddTagState( + state: DataEditAddTagsState, + ) = with(binding) { + when (state) { + is DataEditAddTagsState.Disabled -> { + checkboxDataEditAddTag.isChecked = false + rvDataEditAddTagsPreview.isVisible = false + } + is DataEditAddTagsState.Enabled -> { + checkboxDataEditAddTag.isChecked = true + rvDataEditAddTagsPreview.isVisible = true + addTagsPreviewAdapter.replace(state.viewData) + } + } + } + private fun setChangeButtonState( state: DataEditChangeButtonState ) = with(binding) { diff --git a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/viewModel/DataEditViewModel.kt b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/viewModel/DataEditViewModel.kt index 671f9519..3d233063 100644 --- a/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/viewModel/DataEditViewModel.kt +++ b/features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/viewModel/DataEditViewModel.kt @@ -8,15 +8,20 @@ import com.example.util.simpletimetracker.core.extension.set import com.example.util.simpletimetracker.core.extension.toParams import com.example.util.simpletimetracker.core.repo.DataEditRepo import com.example.util.simpletimetracker.core.repo.ResourceRepo +import com.example.util.simpletimetracker.domain.extension.orZero +import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor +import com.example.util.simpletimetracker.domain.model.RecordTag import com.example.util.simpletimetracker.domain.model.RecordsFilter import com.example.util.simpletimetracker.feature_data_edit.R import com.example.util.simpletimetracker.feature_data_edit.interactor.DateEditChangeInteractor import com.example.util.simpletimetracker.feature_data_edit.interactor.DateEditViewDataInteractor +import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeButtonState import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState import com.example.util.simpletimetracker.navigation.Router import com.example.util.simpletimetracker.navigation.params.notification.SnackBarParams +import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams import com.example.util.simpletimetracker.navigation.params.screen.DataEditTypeSelectionDialogParams import com.example.util.simpletimetracker.navigation.params.screen.RecordsFilterParams import dagger.hilt.android.lifecycle.HiltViewModel @@ -30,6 +35,7 @@ class DataEditViewModel @Inject constructor( private val dataEditRepo: DataEditRepo, private val dataEditViewDataInteractor: DateEditViewDataInteractor, private val dataEditChangeInteractor: DateEditChangeInteractor, + private val recordTagInteractor: RecordTagInteractor, ) : ViewModel() { val selectedRecordsCountViewData: LiveData by lazy { @@ -44,6 +50,9 @@ class DataEditViewModel @Inject constructor( val changeCommentState: LiveData by lazy { MutableLiveData(commentState) } + val addTagsState: LiveData by lazy { + MutableLiveData(addTagState) + } val changeButtonState: LiveData by lazy { MutableLiveData().let { initial -> viewModelScope.launch { initial.value = loadChangeButtonState() } @@ -55,10 +64,12 @@ class DataEditViewModel @Inject constructor( private var filters: List = emptyList() private var typeState: DataEditChangeActivityState = DataEditChangeActivityState.Disabled private var commentState: DataEditChangeCommentState = DataEditChangeCommentState.Disabled + private var addTagState: DataEditAddTagsState = DataEditAddTagsState.Disabled private val changeButtonEnabled: Boolean get() = typeState is DataEditChangeActivityState.Enabled || - commentState is DataEditChangeCommentState.Enabled + commentState is DataEditChangeCommentState.Enabled || + addTagState is DataEditAddTagsState.Enabled fun onSelectRecordsClick() { router.setResultListener(FILTER_TAG) { @@ -75,6 +86,7 @@ class DataEditViewModel @Inject constructor( router.navigate(DataEditTypeSelectionDialogParams) } else { typeState = DataEditChangeActivityState.Disabled + checkAddTagStateConsistency() updateChangeActivityState() updateChangeButtonState() } @@ -82,6 +94,7 @@ class DataEditViewModel @Inject constructor( fun onTypeSelected(typeId: Long) = viewModelScope.launch { typeState = dataEditViewDataInteractor.getChangeActivityState(typeId) + checkAddTagStateConsistency() updateChangeActivityState() updateChangeButtonState() } @@ -101,6 +114,30 @@ class DataEditViewModel @Inject constructor( updateChangeButtonState() } + fun onAddTagsClick() { + if (addTagState is DataEditAddTagsState.Disabled) { + // Show tag selection with typed tags for changed activity (if it is selected) + // or for filtered activity (if it is only one). + // Otherwise show only general tags. + val typeId = getTypeForTagSelection().orZero() + router.navigate(DataEditTagSelectionDialogParams(typeId)) + } else { + addTagState = DataEditAddTagsState.Disabled + updateAddTagState() + updateChangeButtonState() + } + } + + fun onTagsSelected(tagIds: List) = viewModelScope.launch { + addTagState = dataEditViewDataInteractor.getAddTagState(tagIds) + updateAddTagState() + updateChangeButtonState() + } + + fun onTagsDismissed() { + updateAddTagState() + } + fun onCommentChange(text: String) { val state = commentState if (state is DataEditChangeCommentState.Enabled && state.viewData != text) { @@ -115,7 +152,8 @@ class DataEditViewModel @Inject constructor( dataEditChangeInteractor.changeData( typeState = typeState, commentState = commentState, - filters = filters + addTagState = addTagState, + filters = filters, ) dataEditRepo.inProgress.set(false) showMessage(R.string.data_edit_success_message) @@ -124,6 +162,7 @@ class DataEditViewModel @Inject constructor( private fun onFilterSelected(filters: List) { this.filters = filters + checkAddTagStateConsistency() updateSelectedRecordsCountViewData() } @@ -132,6 +171,40 @@ class DataEditViewModel @Inject constructor( router.show(params) } + private fun checkAddTagStateConsistency() = viewModelScope.launch { + // If there are some tags selected to add, + val tags = (addTagState as? DataEditAddTagsState.Enabled) + ?.viewData + ?: return@launch + val allTags = recordTagInteractor.getAll().associateBy(RecordTag::id) + // Find if there is a specific type selected by filter or change activity state, + val typeId = getTypeForTagSelection().orZero() + // Filter tags selected to add to have typed tags only for this selected activity. + val newTags = tags.filter { + val tag = allTags[it.id] ?: return@filter false + tag.typeId == 0L || tag.typeId == typeId + } + + addTagState = if (newTags.isNotEmpty()) { + DataEditAddTagsState.Enabled(newTags) + } else { + DataEditAddTagsState.Disabled + } + updateAddTagState() + updateChangeButtonState() + } + + private fun getTypeForTagSelection(): Long? { + return (typeState as? DataEditChangeActivityState.Enabled) + ?.viewData?.id + ?: filters + .filterIsInstance() + .firstOrNull() + ?.typeIds + ?.takeIf { it.size == 1 } + ?.firstOrNull() + } + private fun updateSelectedRecordsCountViewData() = viewModelScope.launch { val data = loadSelectedRecordsCountViewData() selectedRecordsCountViewData.set(data) @@ -149,6 +222,10 @@ class DataEditViewModel @Inject constructor( changeCommentState.set(commentState) } + private fun updateAddTagState() = viewModelScope.launch { + addTagsState.set(addTagState) + } + private fun updateChangeButtonState() = viewModelScope.launch { val data = loadChangeButtonState() changeButtonState.set(data) diff --git a/features/feature_data_edit/src/main/res/layout/data_edit_fragment.xml b/features/feature_data_edit/src/main/res/layout/data_edit_fragment.xml index aa3d335c..103d057c 100644 --- a/features/feature_data_edit/src/main/res/layout/data_edit_fragment.xml +++ b/features/feature_data_edit/src/main/res/layout/data_edit_fragment.xml @@ -222,6 +222,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + diff --git a/features/feature_tag_selection/src/main/java/com/example/util/simpletimetracker/feature_tag_selection/interactor/RecordTagSelectionViewDataInteractor.kt b/features/feature_tag_selection/src/main/java/com/example/util/simpletimetracker/feature_tag_selection/interactor/RecordTagSelectionViewDataInteractor.kt index 45fe6eb4..6535baec 100644 --- a/features/feature_tag_selection/src/main/java/com/example/util/simpletimetracker/feature_tag_selection/interactor/RecordTagSelectionViewDataInteractor.kt +++ b/features/feature_tag_selection/src/main/java/com/example/util/simpletimetracker/feature_tag_selection/interactor/RecordTagSelectionViewDataInteractor.kt @@ -27,6 +27,8 @@ class RecordTagSelectionViewDataInteractor @Inject constructor( multipleChoiceAvailable = !closeAfterOneTagSelected, showHint = false, showAddButton = false, + showArchived = false, + showUntaggedButton = true, ).let(result::addAll) return result diff --git a/navigation/src/main/java/com/example/util/simpletimetracker/navigation/params/screen/DataEditTagSelectionDialogParams.kt b/navigation/src/main/java/com/example/util/simpletimetracker/navigation/params/screen/DataEditTagSelectionDialogParams.kt new file mode 100644 index 00000000..b76ead19 --- /dev/null +++ b/navigation/src/main/java/com/example/util/simpletimetracker/navigation/params/screen/DataEditTagSelectionDialogParams.kt @@ -0,0 +1,9 @@ +package com.example.util.simpletimetracker.navigation.params.screen + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DataEditTagSelectionDialogParams( + val typeId: Long = 0L +) : Parcelable, ScreenParams -- 2.54.0