A fork of Simple Time Tracker with WearOS support. (Now integrated upstream)
0

Configure Feed

Select the types of activity you want to include in your feed.

add tag adding to data edit

razeeman (Apr 16, 2023, 11:33 AM +0300) 6806d630 38c59a63

+481 -28
+5
app/src/main/res/navigation/nav_graph.xml
··· 150 150 android:name="com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTypeSelectionDialogFragment" 151 151 android:label="DataEditTypeSelectionDialogFragment" 152 152 tools:layout="@layout/data_edit_type_selection_dialog_fragment" /> 153 + <dialog 154 + android:id="@+id/dataEditTagSelectionDialogFragment" 155 + android:name="com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTagSelectionDialogFragment" 156 + android:label="DataEditTagSelectionDialogFragment" 157 + tools:layout="@layout/data_edit_tag_selection_dialog_fragment" /> 153 158 <fragment 154 159 android:id="@+id/recordsAllFragment" 155 160 android:name="com.example.util.simpletimetracker.feature_records_all.view.RecordsAllFragment"
+15
features/feature_data_edit/src/main/res/layout/data_edit_fragment.xml
··· 222 222 app:layout_constraintStart_toStartOf="parent" 223 223 app:layout_constraintTop_toTopOf="parent" /> 224 224 225 + <androidx.recyclerview.widget.RecyclerView 226 + android:id="@+id/rvDataEditAddTagsPreview" 227 + android:layout_width="match_parent" 228 + android:layout_height="wrap_content" 229 + android:layout_marginStart="12dp" 230 + android:layout_marginBottom="12dp" 231 + android:orientation="horizontal" 232 + android:visibility="gone" 233 + app:layout_constraintBottom_toBottomOf="parent" 234 + app:layout_constraintTop_toBottomOf="@id/tvDataEditAddTag" 235 + tools:itemCount="3" 236 + tools:layoutManager="com.google.android.flexbox.FlexboxLayoutManager" 237 + tools:listitem="@layout/item_category_layout" 238 + tools:visibility="visible" /> 239 + 225 240 <androidx.appcompat.widget.AppCompatCheckBox 226 241 android:id="@+id/checkboxDataEditAddTag" 227 242 android:layout_width="wrap_content"
+34
features/feature_data_edit/src/main/res/layout/data_edit_tag_selection_dialog_fragment.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" 3 + xmlns:tools="http://schemas.android.com/tools" 4 + android:layout_width="match_parent" 5 + android:layout_height="match_parent" 6 + android:background="?appDialogBackground" 7 + android:orientation="vertical"> 8 + 9 + <androidx.appcompat.widget.AppCompatImageView 10 + android:id="@+id/ivDataEditTagSelectionArrow" 11 + android:layout_width="match_parent" 12 + android:layout_height="wrap_content" 13 + android:layout_gravity="center" 14 + android:layout_marginTop="8dp" 15 + android:src="@drawable/dialog_arrow" 16 + android:tint="?appContrastColor" /> 17 + 18 + <androidx.recyclerview.widget.RecyclerView 19 + android:id="@+id/rvDataEditTagSelectionContainer" 20 + android:layout_width="match_parent" 21 + android:layout_height="0dp" 22 + android:layout_marginVertical="24dp" 23 + android:layout_weight="1" 24 + tools:listitem="@layout/item_record_type_layout" /> 25 + 26 + <com.google.android.material.button.MaterialButton 27 + android:id="@+id/btnDataEditTagSelectionSave" 28 + style="@style/AppButtonActive" 29 + android:layout_width="match_parent" 30 + android:layout_marginStart="8dp" 31 + android:layout_marginEnd="8dp" 32 + android:text="@string/records_filter_select" /> 33 + 34 + </androidx.appcompat.widget.LinearLayoutCompat>
+12
app/src/main/java/com/example/util/simpletimetracker/di/NavigationDialogMapModule.kt
··· 1 1 package com.example.util.simpletimetracker.di 2 2 3 3 import com.example.util.simpletimetracker.R 4 + import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTagSelectionDialogFragment 4 5 import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTypeSelectionDialogFragment 5 6 import com.example.util.simpletimetracker.feature_dialogs.archive.view.ArchiveDialogFragment 6 7 import com.example.util.simpletimetracker.feature_dialogs.cardOrder.view.CardOrderDialogFragment ··· 24 25 import com.example.util.simpletimetracker.navigation.params.screen.ColorSelectionDialogParams 25 26 import com.example.util.simpletimetracker.navigation.params.screen.DataExportSettingDialogParams 26 27 import com.example.util.simpletimetracker.navigation.params.screen.CustomRangeSelectionParams 28 + import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams 27 29 import com.example.util.simpletimetracker.navigation.params.screen.DataEditTypeSelectionDialogParams 28 30 import com.example.util.simpletimetracker.navigation.params.screen.DateTimeDialogParams 29 31 import com.example.util.simpletimetracker.navigation.params.screen.DefaultTypesSelectionDialogParams ··· 210 212 return NavigationData( 211 213 R.id.dataEditTypeSelectionDialogFragment, 212 214 BundleCreator.empty() 215 + ) 216 + } 217 + 218 + @IntoMap 219 + @Provides 220 + @ScreenKey(DataEditTagSelectionDialogParams::class) 221 + fun dataEditTagSelectionDialog(): NavigationData { 222 + return NavigationData( 223 + R.id.dataEditTagSelectionDialogFragment, 224 + bundleCreatorDelegate(DataEditTagSelectionDialogFragment::createBundle) 213 225 ) 214 226 } 215 227 }
+10 -6
core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordTagViewDataInteractor.kt
··· 29 29 multipleChoiceAvailable: Boolean, 30 30 showHint: Boolean, 31 31 showAddButton: Boolean, 32 + showArchived: Boolean, 33 + showUntaggedButton: Boolean, 32 34 ): List<ViewHolderType> { 33 35 val isDarkTheme = prefsInteractor.getDarkMode() 34 36 val type = recordTypeInteractor.get(typeId) ··· 39 41 } + recordTagInteractor.getUntyped() 40 42 41 43 return recordTags 42 - .filterNot { it.archived } 44 + .let { tags -> if (showArchived) tags else tags.filterNot { it.archived } } 43 45 .takeUnless { it.isEmpty() } 44 46 ?.let { tags -> 45 47 val selected = tags.filter { it.id in selectedTags } ··· 77 79 ) 78 80 }.let(viewData::addAll) 79 81 80 - if (selected.isNotEmpty() || available.isNotEmpty()) { 81 - DividerViewData(2) 82 - .takeIf { multipleChoiceAvailable } 83 - ?.let(viewData::add) 84 - mapRecordTagUntagged(isDarkTheme).let(viewData::add) 82 + if (showUntaggedButton) { 83 + if (selected.isNotEmpty() || available.isNotEmpty()) { 84 + DividerViewData(2) 85 + .takeIf { multipleChoiceAvailable } 86 + ?.let(viewData::add) 87 + mapRecordTagUntagged(isDarkTheme).let(viewData::add) 88 + } 85 89 } 86 90 87 91 categoriesViewDataMapper.mapToRecordTagAddItem(isDarkTheme)
+2
features/feature_change_record/src/main/java/com/example/util/simpletimetracker/feature_change_record/viewModel/ChangeRecordBaseViewModel.kt
··· 554 554 multipleChoiceAvailable = true, 555 555 showHint = true, 556 556 showAddButton = true, 557 + showArchived = false, 558 + showUntaggedButton = true, 557 559 ) 558 560 } 559 561
+112
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogFragment.kt
··· 1 + package com.example.util.simpletimetracker.feature_data_edit.dialog 2 + 3 + import android.content.Context 4 + import android.content.DialogInterface 5 + import android.os.Bundle 6 + import android.view.LayoutInflater 7 + import android.view.ViewGroup 8 + import androidx.appcompat.app.AppCompatActivity 9 + import androidx.fragment.app.viewModels 10 + import com.example.util.simpletimetracker.core.base.BaseBottomSheetFragment 11 + import com.example.util.simpletimetracker.core.extension.blockContentScroll 12 + import com.example.util.simpletimetracker.core.extension.getAllFragments 13 + import com.example.util.simpletimetracker.core.extension.setFullScreen 14 + import com.example.util.simpletimetracker.core.extension.setSkipCollapsed 15 + import com.example.util.simpletimetracker.core.utils.fragmentArgumentDelegate 16 + import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter 17 + import com.example.util.simpletimetracker.feature_base_adapter.category.createCategoryAdapterDelegate 18 + import com.example.util.simpletimetracker.feature_base_adapter.divider.createDividerAdapterDelegate 19 + import com.example.util.simpletimetracker.feature_base_adapter.hint.createHintAdapterDelegate 20 + import com.example.util.simpletimetracker.feature_base_adapter.info.createInfoAdapterDelegate 21 + import com.example.util.simpletimetracker.feature_base_adapter.loader.createLoaderAdapterDelegate 22 + import com.example.util.simpletimetracker.feature_views.extension.setOnClick 23 + import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams 24 + import com.google.android.flexbox.FlexDirection 25 + import com.google.android.flexbox.FlexWrap 26 + import com.google.android.flexbox.FlexboxLayoutManager 27 + import com.google.android.flexbox.JustifyContent 28 + import dagger.hilt.android.AndroidEntryPoint 29 + import com.example.util.simpletimetracker.feature_data_edit.databinding.DataEditTagSelectionDialogFragmentBinding as Binding 30 + 31 + @AndroidEntryPoint 32 + class DataEditTagSelectionDialogFragment : BaseBottomSheetFragment<Binding>() { 33 + 34 + override val inflater: (LayoutInflater, ViewGroup?, Boolean) -> Binding = 35 + Binding::inflate 36 + 37 + private val viewModel: DataEditTagSelectionViewModel by viewModels() 38 + 39 + private val adapter: BaseRecyclerAdapter by lazy { 40 + BaseRecyclerAdapter( 41 + createLoaderAdapterDelegate(), 42 + createHintAdapterDelegate(), 43 + createDividerAdapterDelegate(), 44 + createInfoAdapterDelegate(), 45 + createCategoryAdapterDelegate(viewModel::onTagClick), 46 + ) 47 + } 48 + private val params: DataEditTagSelectionDialogParams by fragmentArgumentDelegate( 49 + key = ARGS_PARAMS, default = DataEditTagSelectionDialogParams() 50 + ) 51 + private var listener: DataEditTagSelectionDialogListener? = null 52 + 53 + override fun onAttach(context: Context) { 54 + super.onAttach(context) 55 + when (context) { 56 + is DataEditTagSelectionDialogListener -> { 57 + listener = context 58 + return 59 + } 60 + is AppCompatActivity -> { 61 + context.getAllFragments() 62 + .firstOrNull { it is DataEditTagSelectionDialogListener && it.isResumed } 63 + ?.let { listener = it as? DataEditTagSelectionDialogListener } 64 + } 65 + } 66 + } 67 + 68 + override fun onDismiss(dialog: DialogInterface) { 69 + listener?.onTagsDismissed() 70 + super.onDismiss(dialog) 71 + } 72 + 73 + override fun initDialog() { 74 + setSkipCollapsed() 75 + setFullScreen() 76 + blockContentScroll(binding.rvDataEditTagSelectionContainer) 77 + } 78 + 79 + override fun initUi(): Unit = with(binding) { 80 + rvDataEditTagSelectionContainer.apply { 81 + layoutManager = FlexboxLayoutManager(requireContext()).apply { 82 + flexDirection = FlexDirection.ROW 83 + justifyContent = JustifyContent.CENTER 84 + flexWrap = FlexWrap.WRAP 85 + } 86 + adapter = this@DataEditTagSelectionDialogFragment.adapter 87 + } 88 + } 89 + 90 + override fun initUx() = with(binding) { 91 + btnDataEditTagSelectionSave.setOnClick(viewModel::onSaveClick) 92 + } 93 + 94 + override fun initViewModel(): Unit = with(viewModel) { 95 + extra = params 96 + viewData.observe(adapter::replace) 97 + tagSelected.observe(::dismiss) 98 + } 99 + 100 + private fun dismiss(tagIds: List<Long>) { 101 + listener?.onTagsSelected(tagIds) 102 + dismiss() 103 + } 104 + 105 + companion object { 106 + private const val ARGS_PARAMS = "args_params" 107 + 108 + fun createBundle(data: DataEditTagSelectionDialogParams) = Bundle().apply { 109 + putParcelable(ARGS_PARAMS, data) 110 + } 111 + } 112 + }
+7
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionDialogListener.kt
··· 1 + package com.example.util.simpletimetracker.feature_data_edit.dialog 2 + 3 + interface DataEditTagSelectionDialogListener { 4 + 5 + fun onTagsSelected(tagIds: List<Long>) 6 + fun onTagsDismissed() 7 + }
+77
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTagSelectionViewModel.kt
··· 1 + package com.example.util.simpletimetracker.feature_data_edit.dialog 2 + 3 + import androidx.lifecycle.LiveData 4 + import androidx.lifecycle.MutableLiveData 5 + import androidx.lifecycle.ViewModel 6 + import androidx.lifecycle.viewModelScope 7 + import com.example.util.simpletimetracker.core.extension.addOrRemove 8 + import com.example.util.simpletimetracker.core.extension.set 9 + import com.example.util.simpletimetracker.core.interactor.RecordTagViewDataInteractor 10 + import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType 11 + import com.example.util.simpletimetracker.feature_base_adapter.category.CategoryViewData 12 + import com.example.util.simpletimetracker.feature_base_adapter.loader.LoaderViewData 13 + import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams 14 + import dagger.hilt.android.lifecycle.HiltViewModel 15 + import javax.inject.Inject 16 + import kotlinx.coroutines.launch 17 + 18 + @HiltViewModel 19 + class DataEditTagSelectionViewModel @Inject constructor( 20 + private val recordTagViewDataInteractor: RecordTagViewDataInteractor, 21 + ) : ViewModel() { 22 + 23 + lateinit var extra: DataEditTagSelectionDialogParams 24 + 25 + val viewData: LiveData<List<ViewHolderType>> by lazy { 26 + return@lazy MutableLiveData<List<ViewHolderType>>().let { initial -> 27 + viewModelScope.launch { 28 + initial.value = listOf(LoaderViewData()) 29 + initial.value = loadViewData() 30 + } 31 + initial 32 + } 33 + } 34 + val tagSelected: LiveData<List<Long>> = MutableLiveData() 35 + 36 + private var selectedIds: MutableList<Long> = mutableListOf() 37 + 38 + fun onTagClick(item: CategoryViewData) { 39 + viewModelScope.launch { 40 + when (item) { 41 + is CategoryViewData.Record.Tagged -> { 42 + selectedIds.addOrRemove(item.id) 43 + } 44 + is CategoryViewData.Record.Untagged -> { 45 + selectedIds.clear() 46 + } 47 + else -> return@launch 48 + } 49 + updateViewData() 50 + } 51 + } 52 + 53 + fun onSaveClick() { 54 + tagSelected.set(selectedIds) 55 + } 56 + 57 + private fun updateViewData() = viewModelScope.launch { 58 + val data = loadViewData() 59 + viewData.set(data) 60 + } 61 + 62 + private suspend fun loadViewData(): List<ViewHolderType> { 63 + val result: MutableList<ViewHolderType> = mutableListOf() 64 + 65 + recordTagViewDataInteractor.getViewData( 66 + selectedTags = selectedIds, 67 + typeId = extra.typeId, 68 + multipleChoiceAvailable = true, 69 + showHint = false, 70 + showAddButton = false, 71 + showArchived = true, 72 + showUntaggedButton = false, 73 + ).let(result::addAll) 74 + 75 + return result 76 + } 77 + }
+1 -1
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogFragment.kt
··· 57 57 } 58 58 59 59 override fun onDismiss(dialog: DialogInterface) { 60 - listener?.onDismissed() 60 + listener?.onTypeDismissed() 61 61 super.onDismiss(dialog) 62 62 } 63 63
+1 -1
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionDialogListener.kt
··· 3 3 interface DataEditTypeSelectionDialogListener { 4 4 5 5 fun onTypeSelected(typeId: Long) 6 - fun onDismissed() 6 + fun onTypeDismissed() 7 7 }
-1
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/dialog/DataEditTypeSelectionViewModel.kt
··· 56 56 } 57 57 58 58 if (typesViewData.isNotEmpty()) { 59 - HintViewData(resourceRepo.getString(R.string.activity_hint)).let(result::add) 60 59 typesViewData.let(result::addAll) 61 60 } else { 62 61 HintViewData(resourceRepo.getString(R.string.record_types_empty)).let(result::add)
+26 -12
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditChangeInteractor.kt
··· 6 6 import com.example.util.simpletimetracker.domain.interactor.RecordInteractor 7 7 import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 8 8 import com.example.util.simpletimetracker.domain.model.RecordsFilter 9 + import com.example.util.simpletimetracker.feature_base_adapter.category.CategoryViewData 10 + import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState 9 11 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState 10 12 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState 11 13 import javax.inject.Inject ··· 21 23 suspend fun changeData( 22 24 typeState: DataEditChangeActivityState, 23 25 commentState: DataEditChangeCommentState, 26 + addTagState: DataEditAddTagsState, 24 27 filters: List<RecordsFilter> 25 28 ) { 26 29 if (filters.isEmpty()) return 27 - val newTypeId = (typeState as? DataEditChangeActivityState.Enabled)?.viewData?.id 28 - val newComment = (commentState as? DataEditChangeCommentState.Enabled)?.viewData 29 - if (newTypeId == null && newComment == null) return 30 + 31 + val newTypeId = (typeState as? DataEditChangeActivityState.Enabled) 32 + ?.viewData?.id 33 + val newComment = (commentState as? DataEditChangeCommentState.Enabled) 34 + ?.viewData 35 + val addTags = (addTagState as? DataEditAddTagsState.Enabled) 36 + ?.viewData?.map(CategoryViewData.Record::id) 37 + 38 + if (newTypeId == null && newComment == null && addTags == null) return 30 39 31 40 val records = recordFilterInteractor.getByFilter(filters) 32 41 val tags = recordTagInteractor.getAll().associateBy { it.id } 33 42 val oldTypeIds = mutableSetOf<Long>() 34 - var newTagIds: List<Long>? = null 35 43 36 44 records.forEach { record -> 37 - if (newTypeId != null && record.typeId != newTypeId) { 38 - // Remove all typed tags. 39 - newTagIds = record.tagIds.filter { tagId -> 40 - tags[tagId]?.typeId == 0L 45 + val finalTypeId = newTypeId ?: record.typeId 46 + val finalComment = newComment ?: record.comment 47 + val finalTagIds: Set<Long> = record.tagIds 48 + .plus(addTags.orEmpty()) 49 + .filter { tagId -> 50 + val tag = tags[tagId] ?: return@filter false 51 + tag.typeId == 0L || tag.typeId == finalTypeId 41 52 } 42 - // Save old typeId before change to update data later. 53 + .toSet() 54 + 55 + // Save old typeId before change to update data later. 56 + if (finalTypeId != record.typeId) { 43 57 oldTypeIds.add(record.typeId) 44 58 } 45 59 46 60 // Change activity 47 61 record.copy( 48 - typeId = newTypeId ?: record.typeId, 49 - comment = newComment ?: record.comment, 50 - tagIds = newTagIds ?: record.tagIds 62 + typeId = finalTypeId, 63 + comment = finalComment, 64 + tagIds = finalTagIds.toList(), 51 65 ).let { 52 66 recordInteractor.add(it) 53 67 }
+27 -1
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/interactor/DateEditViewDataInteractor.kt
··· 1 1 package com.example.util.simpletimetracker.feature_data_edit.interactor 2 2 3 3 import com.example.util.simpletimetracker.core.interactor.RecordFilterInteractor 4 + import com.example.util.simpletimetracker.core.mapper.CategoryViewDataMapper 4 5 import com.example.util.simpletimetracker.core.mapper.RecordTypeViewDataMapper 5 6 import com.example.util.simpletimetracker.core.repo.ResourceRepo 6 7 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 8 + import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 7 9 import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor 8 10 import com.example.util.simpletimetracker.domain.model.RecordsFilter 9 11 import com.example.util.simpletimetracker.feature_data_edit.R 12 + import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState 10 13 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState 11 14 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeButtonState 12 15 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState ··· 16 19 private val resourceRepo: ResourceRepo, 17 20 private val recordFilterInteractor: RecordFilterInteractor, 18 21 private val recordTypeInteractor: RecordTypeInteractor, 22 + private val recordTagInteractor: RecordTagInteractor, 19 23 private val recordTypeViewDataMapper: RecordTypeViewDataMapper, 24 + private val categoryViewDataMapper: CategoryViewDataMapper, 20 25 private val prefsInteractor: PrefsInteractor, 21 26 ) { 22 27 ··· 41 46 suspend fun getChangeActivityState( 42 47 newTypeId: Long, 43 48 ): DataEditChangeActivityState { 49 + val isDarkTheme = prefsInteractor.getDarkMode() 44 50 val type = newTypeId.let { recordTypeInteractor.get(it) } 45 51 46 52 return if (type == null) { ··· 49 55 DataEditChangeActivityState.Enabled( 50 56 recordTypeViewDataMapper.map( 51 57 recordType = type, 52 - isDarkTheme = prefsInteractor.getDarkMode(), 58 + isDarkTheme = isDarkTheme, 53 59 ) 54 60 ) 55 61 } ··· 59 65 newComment: String, 60 66 ): DataEditChangeCommentState { 61 67 return DataEditChangeCommentState.Enabled(newComment) 68 + } 69 + 70 + suspend fun getAddTagState( 71 + tagIds: List<Long>, 72 + ): DataEditAddTagsState { 73 + val isDarkTheme = prefsInteractor.getDarkMode() 74 + val types = recordTypeInteractor.getAll().associateBy { it.id } 75 + 76 + return recordTagInteractor.getAll() 77 + .filter { it.id in tagIds } 78 + .map { 79 + categoryViewDataMapper.mapRecordTag( 80 + tag = it, 81 + type = types[it.typeId], 82 + isDarkTheme = isDarkTheme, 83 + ) 84 + } 85 + .takeUnless { it.isEmpty() } 86 + ?.let(DataEditAddTagsState::Enabled) 87 + ?: DataEditAddTagsState.Disabled 62 88 } 63 89 64 90 suspend fun getChangeButtonState(
+9
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/model/DataEditAddTagsState.kt
··· 1 + package com.example.util.simpletimetracker.feature_data_edit.model 2 + 3 + import com.example.util.simpletimetracker.feature_base_adapter.category.CategoryViewData 4 + 5 + sealed interface DataEditAddTagsState { 6 + 7 + object Disabled : DataEditAddTagsState 8 + data class Enabled(val viewData: List<CategoryViewData.Record>) : DataEditAddTagsState 9 + }
+53 -4
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/view/DataEditFragment.kt
··· 9 9 import com.example.util.simpletimetracker.core.base.BaseFragment 10 10 import com.example.util.simpletimetracker.core.extension.hideKeyboard 11 11 import com.example.util.simpletimetracker.core.extension.showKeyboard 12 + import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter 13 + import com.example.util.simpletimetracker.feature_base_adapter.category.createCategoryAdapterDelegate 14 + import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTagSelectionDialogListener 12 15 import com.example.util.simpletimetracker.feature_data_edit.dialog.DataEditTypeSelectionDialogListener 16 + import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState 13 17 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState 14 18 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeButtonState 15 19 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState 16 20 import com.example.util.simpletimetracker.feature_data_edit.viewModel.DataEditViewModel 17 21 import com.example.util.simpletimetracker.feature_views.extension.setOnClick 22 + import com.google.android.flexbox.FlexDirection 23 + import com.google.android.flexbox.FlexWrap 24 + import com.google.android.flexbox.FlexboxLayoutManager 25 + import com.google.android.flexbox.JustifyContent 18 26 import dagger.hilt.android.AndroidEntryPoint 19 27 import com.example.util.simpletimetracker.feature_data_edit.databinding.DataEditFragmentBinding as Binding 20 28 21 29 @AndroidEntryPoint 22 30 class DataEditFragment : 23 31 BaseFragment<Binding>(), 24 - DataEditTypeSelectionDialogListener { 32 + DataEditTypeSelectionDialogListener, 33 + DataEditTagSelectionDialogListener { 25 34 26 35 override val inflater: (LayoutInflater, ViewGroup?, Boolean) -> Binding = 27 36 Binding::inflate 28 37 29 38 private val viewModel: DataEditViewModel by viewModels() 30 39 31 - override fun initUi() = with(binding) { 32 - btnDataEditSelectRecords.setOnClick(throttle(viewModel::onSelectRecordsClick)) 40 + private val addTagsPreviewAdapter: BaseRecyclerAdapter by lazy { 41 + BaseRecyclerAdapter( 42 + createCategoryAdapterDelegate(), 43 + ) 44 + } 45 + 46 + override fun initUi(): Unit = with(binding) { 47 + rvDataEditAddTagsPreview.apply { 48 + layoutManager = FlexboxLayoutManager(requireContext()).apply { 49 + flexDirection = FlexDirection.ROW 50 + justifyContent = JustifyContent.FLEX_START 51 + flexWrap = FlexWrap.WRAP 52 + } 53 + adapter = addTagsPreviewAdapter 54 + } 33 55 } 34 56 35 57 override fun initUx() = with(binding) { 58 + btnDataEditSelectRecords.setOnClick(throttle(viewModel::onSelectRecordsClick)) 36 59 checkboxDataEditChangeActivity.setOnClick(viewModel::onChangeActivityClick) 37 60 checkboxDataEditChangeComment.setOnClick(viewModel::onChangeCommentClick) 61 + checkboxDataEditAddTag.setOnClick(viewModel::onAddTagsClick) 38 62 etDataEditChangeComment.doAfterTextChanged { viewModel.onCommentChange(it.toString()) } 39 63 btnDataEditChange.setOnClick(throttle(viewModel::onChangeClick)) 40 64 } ··· 44 68 selectedRecordsCountViewData.observe(tvDataEditSelectedRecords::setText) 45 69 changeActivityState.observe(::setChangeActivityState) 46 70 changeCommentState.observe(::setChangeCommentState) 71 + addTagsState.observe(::setAddTagState) 47 72 changeButtonState.observe(::setChangeButtonState) 48 73 keyboardVisibility.observe { visible -> 49 74 if (visible) showKeyboard(etDataEditChangeComment) ··· 64 89 viewModel.onTypeSelected(typeId) 65 90 } 66 91 67 - override fun onDismissed() { 92 + override fun onTypeDismissed() { 68 93 viewModel.onTypeDismissed() 94 + } 95 + 96 + override fun onTagsSelected(tagIds: List<Long>) { 97 + viewModel.onTagsSelected(tagIds) 98 + } 99 + 100 + override fun onTagsDismissed() { 101 + viewModel.onTagsDismissed() 69 102 } 70 103 71 104 private fun setChangeActivityState( ··· 104 137 if (etDataEditChangeComment.text.toString() != state.viewData) { 105 138 etDataEditChangeComment.setText(state.viewData) 106 139 } 140 + } 141 + } 142 + } 143 + 144 + private fun setAddTagState( 145 + state: DataEditAddTagsState, 146 + ) = with(binding) { 147 + when (state) { 148 + is DataEditAddTagsState.Disabled -> { 149 + checkboxDataEditAddTag.isChecked = false 150 + rvDataEditAddTagsPreview.isVisible = false 151 + } 152 + is DataEditAddTagsState.Enabled -> { 153 + checkboxDataEditAddTag.isChecked = true 154 + rvDataEditAddTagsPreview.isVisible = true 155 + addTagsPreviewAdapter.replace(state.viewData) 107 156 } 108 157 } 109 158 }
+79 -2
features/feature_data_edit/src/main/java/com/example/util/simpletimetracker/feature_data_edit/viewModel/DataEditViewModel.kt
··· 8 8 import com.example.util.simpletimetracker.core.extension.toParams 9 9 import com.example.util.simpletimetracker.core.repo.DataEditRepo 10 10 import com.example.util.simpletimetracker.core.repo.ResourceRepo 11 + import com.example.util.simpletimetracker.domain.extension.orZero 12 + import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 13 + import com.example.util.simpletimetracker.domain.model.RecordTag 11 14 import com.example.util.simpletimetracker.domain.model.RecordsFilter 12 15 import com.example.util.simpletimetracker.feature_data_edit.R 13 16 import com.example.util.simpletimetracker.feature_data_edit.interactor.DateEditChangeInteractor 14 17 import com.example.util.simpletimetracker.feature_data_edit.interactor.DateEditViewDataInteractor 18 + import com.example.util.simpletimetracker.feature_data_edit.model.DataEditAddTagsState 15 19 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeActivityState 16 20 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeButtonState 17 21 import com.example.util.simpletimetracker.feature_data_edit.model.DataEditChangeCommentState 18 22 import com.example.util.simpletimetracker.navigation.Router 19 23 import com.example.util.simpletimetracker.navigation.params.notification.SnackBarParams 24 + import com.example.util.simpletimetracker.navigation.params.screen.DataEditTagSelectionDialogParams 20 25 import com.example.util.simpletimetracker.navigation.params.screen.DataEditTypeSelectionDialogParams 21 26 import com.example.util.simpletimetracker.navigation.params.screen.RecordsFilterParams 22 27 import dagger.hilt.android.lifecycle.HiltViewModel ··· 30 35 private val dataEditRepo: DataEditRepo, 31 36 private val dataEditViewDataInteractor: DateEditViewDataInteractor, 32 37 private val dataEditChangeInteractor: DateEditChangeInteractor, 38 + private val recordTagInteractor: RecordTagInteractor, 33 39 ) : ViewModel() { 34 40 35 41 val selectedRecordsCountViewData: LiveData<String> by lazy { ··· 44 50 val changeCommentState: LiveData<DataEditChangeCommentState> by lazy { 45 51 MutableLiveData(commentState) 46 52 } 53 + val addTagsState: LiveData<DataEditAddTagsState> by lazy { 54 + MutableLiveData(addTagState) 55 + } 47 56 val changeButtonState: LiveData<DataEditChangeButtonState> by lazy { 48 57 MutableLiveData<DataEditChangeButtonState>().let { initial -> 49 58 viewModelScope.launch { initial.value = loadChangeButtonState() } ··· 55 64 private var filters: List<RecordsFilter> = emptyList() 56 65 private var typeState: DataEditChangeActivityState = DataEditChangeActivityState.Disabled 57 66 private var commentState: DataEditChangeCommentState = DataEditChangeCommentState.Disabled 67 + private var addTagState: DataEditAddTagsState = DataEditAddTagsState.Disabled 58 68 59 69 private val changeButtonEnabled: Boolean 60 70 get() = typeState is DataEditChangeActivityState.Enabled || 61 - commentState is DataEditChangeCommentState.Enabled 71 + commentState is DataEditChangeCommentState.Enabled || 72 + addTagState is DataEditAddTagsState.Enabled 62 73 63 74 fun onSelectRecordsClick() { 64 75 router.setResultListener(FILTER_TAG) { ··· 75 86 router.navigate(DataEditTypeSelectionDialogParams) 76 87 } else { 77 88 typeState = DataEditChangeActivityState.Disabled 89 + checkAddTagStateConsistency() 78 90 updateChangeActivityState() 79 91 updateChangeButtonState() 80 92 } ··· 82 94 83 95 fun onTypeSelected(typeId: Long) = viewModelScope.launch { 84 96 typeState = dataEditViewDataInteractor.getChangeActivityState(typeId) 97 + checkAddTagStateConsistency() 85 98 updateChangeActivityState() 86 99 updateChangeButtonState() 87 100 } ··· 101 114 updateChangeButtonState() 102 115 } 103 116 117 + fun onAddTagsClick() { 118 + if (addTagState is DataEditAddTagsState.Disabled) { 119 + // Show tag selection with typed tags for changed activity (if it is selected) 120 + // or for filtered activity (if it is only one). 121 + // Otherwise show only general tags. 122 + val typeId = getTypeForTagSelection().orZero() 123 + router.navigate(DataEditTagSelectionDialogParams(typeId)) 124 + } else { 125 + addTagState = DataEditAddTagsState.Disabled 126 + updateAddTagState() 127 + updateChangeButtonState() 128 + } 129 + } 130 + 131 + fun onTagsSelected(tagIds: List<Long>) = viewModelScope.launch { 132 + addTagState = dataEditViewDataInteractor.getAddTagState(tagIds) 133 + updateAddTagState() 134 + updateChangeButtonState() 135 + } 136 + 137 + fun onTagsDismissed() { 138 + updateAddTagState() 139 + } 140 + 104 141 fun onCommentChange(text: String) { 105 142 val state = commentState 106 143 if (state is DataEditChangeCommentState.Enabled && state.viewData != text) { ··· 115 152 dataEditChangeInteractor.changeData( 116 153 typeState = typeState, 117 154 commentState = commentState, 118 - filters = filters 155 + addTagState = addTagState, 156 + filters = filters, 119 157 ) 120 158 dataEditRepo.inProgress.set(false) 121 159 showMessage(R.string.data_edit_success_message) ··· 124 162 125 163 private fun onFilterSelected(filters: List<RecordsFilter>) { 126 164 this.filters = filters 165 + checkAddTagStateConsistency() 127 166 updateSelectedRecordsCountViewData() 128 167 } 129 168 130 169 private fun showMessage(stringResId: Int) { 131 170 val params = SnackBarParams(message = resourceRepo.getString(stringResId)) 132 171 router.show(params) 172 + } 173 + 174 + private fun checkAddTagStateConsistency() = viewModelScope.launch { 175 + // If there are some tags selected to add, 176 + val tags = (addTagState as? DataEditAddTagsState.Enabled) 177 + ?.viewData 178 + ?: return@launch 179 + val allTags = recordTagInteractor.getAll().associateBy(RecordTag::id) 180 + // Find if there is a specific type selected by filter or change activity state, 181 + val typeId = getTypeForTagSelection().orZero() 182 + // Filter tags selected to add to have typed tags only for this selected activity. 183 + val newTags = tags.filter { 184 + val tag = allTags[it.id] ?: return@filter false 185 + tag.typeId == 0L || tag.typeId == typeId 186 + } 187 + 188 + addTagState = if (newTags.isNotEmpty()) { 189 + DataEditAddTagsState.Enabled(newTags) 190 + } else { 191 + DataEditAddTagsState.Disabled 192 + } 193 + updateAddTagState() 194 + updateChangeButtonState() 195 + } 196 + 197 + private fun getTypeForTagSelection(): Long? { 198 + return (typeState as? DataEditChangeActivityState.Enabled) 199 + ?.viewData?.id 200 + ?: filters 201 + .filterIsInstance<RecordsFilter.Activity>() 202 + .firstOrNull() 203 + ?.typeIds 204 + ?.takeIf { it.size == 1 } 205 + ?.firstOrNull() 133 206 } 134 207 135 208 private fun updateSelectedRecordsCountViewData() = viewModelScope.launch { ··· 147 220 148 221 private fun updateChangeCommentState() = viewModelScope.launch { 149 222 changeCommentState.set(commentState) 223 + } 224 + 225 + private fun updateAddTagState() = viewModelScope.launch { 226 + addTagsState.set(addTagState) 150 227 } 151 228 152 229 private fun updateChangeButtonState() = viewModelScope.launch {
+2
features/feature_tag_selection/src/main/java/com/example/util/simpletimetracker/feature_tag_selection/interactor/RecordTagSelectionViewDataInteractor.kt
··· 27 27 multipleChoiceAvailable = !closeAfterOneTagSelected, 28 28 showHint = false, 29 29 showAddButton = false, 30 + showArchived = false, 31 + showUntaggedButton = true, 30 32 ).let(result::addAll) 31 33 32 34 return result
+9
navigation/src/main/java/com/example/util/simpletimetracker/navigation/params/screen/DataEditTagSelectionDialogParams.kt
··· 1 + package com.example.util.simpletimetracker.navigation.params.screen 2 + 3 + import android.os.Parcelable 4 + import kotlinx.parcelize.Parcelize 5 + 6 + @Parcelize 7 + data class DataEditTagSelectionDialogParams( 8 + val typeId: Long = 0L 9 + ) : Parcelable, ScreenParams