-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move icon selection to core to reuse in tag module
- Loading branch information
Showing
21 changed files
with
422 additions
and
329 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
298 changes: 298 additions & 0 deletions
298
...ple/util/simpletimetracker/core/delegates/iconSelection/IconSelectionViewModelDelegate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
package com.example.util.simpletimetracker.core.delegates.iconSelection | ||
|
||
import androidx.lifecycle.LiveData | ||
import androidx.lifecycle.MutableLiveData | ||
import com.example.util.simpletimetracker.core.base.ViewModelDelegate | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.interactor.IconSelectionDelegateViewDataInteractor | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.mapper.ChangeRecordTypeMapper | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeIconCategoryInfoViewData | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeIconCategoryViewData | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeIconSelectorStateViewData | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeIconStateViewData | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeIconSwitchViewData | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeIconViewData | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeScrollViewData | ||
import com.example.util.simpletimetracker.core.extension.set | ||
import com.example.util.simpletimetracker.core.mapper.IconEmojiMapper | ||
import com.example.util.simpletimetracker.core.view.buttonsRowView.ButtonsRowViewData | ||
import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor | ||
import com.example.util.simpletimetracker.domain.model.AppColor | ||
import com.example.util.simpletimetracker.domain.model.IconImageState | ||
import com.example.util.simpletimetracker.domain.model.IconType | ||
import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType | ||
import com.example.util.simpletimetracker.feature_base_adapter.emoji.EmojiViewData | ||
import com.example.util.simpletimetracker.feature_base_adapter.loader.LoaderViewData | ||
import com.example.util.simpletimetracker.navigation.Router | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.delay | ||
import kotlinx.coroutines.launch | ||
import javax.inject.Inject | ||
|
||
interface IconSelectionViewModelDelegate { | ||
val icons: LiveData<ChangeRecordTypeIconStateViewData> | ||
val iconCategories: LiveData<List<ViewHolderType>> | ||
val iconsTypeViewData: LiveData<List<ViewHolderType>> | ||
val iconSelectorViewData: LiveData<ChangeRecordTypeIconSelectorStateViewData> | ||
val iconsScrollPosition: LiveData<ChangeRecordTypeScrollViewData> | ||
val expandIconTypeSwitch: LiveData<Unit> | ||
|
||
fun attach(parent: Parent) | ||
fun onIconTypeClick(viewData: ButtonsRowViewData) | ||
fun onIconCategoryClick(viewData: ChangeRecordTypeIconCategoryViewData) | ||
fun onIconClick(item: ChangeRecordTypeIconViewData) | ||
fun onIconsScrolled(firstVisiblePosition: Int, lastVisiblePosition: Int) | ||
fun onIconImageSearchClicked() | ||
fun onIconImageSearch(search: String) | ||
fun onEmojiClick(item: EmojiViewData) | ||
fun onIconTextChange(text: String) | ||
fun onEmojiSelected(emojiText: String) | ||
fun onScrolled() | ||
|
||
interface Parent { | ||
fun keyboardVisibility(isVisible: Boolean) | ||
suspend fun updateRecordPreviewViewData() | ||
fun getColor(): AppColor | ||
} | ||
} | ||
|
||
class IconSelectionViewModelDelegateImpl @Inject constructor( | ||
private val router: Router, | ||
private val iconEmojiMapper: IconEmojiMapper, | ||
private val prefsInteractor: PrefsInteractor, | ||
private val changeRecordTypeMapper: ChangeRecordTypeMapper, | ||
private val viewDataInteractor: IconSelectionDelegateViewDataInteractor, | ||
) : IconSelectionViewModelDelegate, | ||
ViewModelDelegate() { | ||
|
||
override val icons: LiveData<ChangeRecordTypeIconStateViewData> by lazy { | ||
return@lazy MutableLiveData<ChangeRecordTypeIconStateViewData>().let { initial -> | ||
delegateScope.launch { initial.value = loadIconsViewData() } | ||
initial | ||
} | ||
} | ||
override val iconCategories: LiveData<List<ViewHolderType>> by lazy { | ||
return@lazy MutableLiveData<List<ViewHolderType>>().let { initial -> | ||
delegateScope.launch { initial.value = loadIconCategoriesViewData() } | ||
initial | ||
} | ||
} | ||
override val iconsTypeViewData: LiveData<List<ViewHolderType>> by lazy { | ||
return@lazy MutableLiveData(loadIconsTypeViewData()) | ||
} | ||
override val iconSelectorViewData: LiveData<ChangeRecordTypeIconSelectorStateViewData> by lazy { | ||
return@lazy MutableLiveData<ChangeRecordTypeIconSelectorStateViewData>().let { initial -> | ||
delegateScope.launch { initial.value = loadIconSelectorViewData() } | ||
initial | ||
} | ||
} | ||
override val iconsScrollPosition: LiveData<ChangeRecordTypeScrollViewData> = MutableLiveData() | ||
override val expandIconTypeSwitch: LiveData<Unit> = MutableLiveData() | ||
|
||
var newIconName: String = "" | ||
|
||
private var parent: IconSelectionViewModelDelegate.Parent? = null | ||
private var iconType: IconType = IconType.IMAGE | ||
private var iconImageState: IconImageState = IconImageState.Chooser | ||
private var iconSearch: String = "" | ||
private var iconSearchJob: Job? = null | ||
|
||
override fun attach(parent: IconSelectionViewModelDelegate.Parent) { | ||
this.parent = parent | ||
} | ||
|
||
override fun onIconTypeClick(viewData: ButtonsRowViewData) { | ||
if (viewData !is ChangeRecordTypeIconSwitchViewData) return | ||
if (viewData.iconType == iconType) return | ||
delegateScope.launch { | ||
parent?.keyboardVisibility(false) | ||
iconType = viewData.iconType | ||
updateIconsTypeViewData() | ||
updateIconSelectorViewData() | ||
updateIconCategories(selectedIndex = 0) | ||
updateIconsLoad() | ||
updateIcons() | ||
} | ||
} | ||
|
||
suspend fun update() { | ||
updateIcons() | ||
} | ||
|
||
override fun onIconCategoryClick(viewData: ChangeRecordTypeIconCategoryViewData) { | ||
if (viewData.getUniqueId() == 0L) { | ||
expandIconTypeSwitch.set(Unit) | ||
} | ||
(icons.value as? ChangeRecordTypeIconStateViewData.Icons) | ||
?.items | ||
?.indexOfFirst { (it as? ChangeRecordTypeIconCategoryInfoViewData)?.type == viewData.type } | ||
?.let(::updateIconScrollPosition) | ||
} | ||
|
||
override fun onIconClick(item: ChangeRecordTypeIconViewData) { | ||
delegateScope.launch { | ||
if (item.iconName != newIconName) { | ||
newIconName = item.iconName | ||
parent?.updateRecordPreviewViewData() | ||
} | ||
} | ||
} | ||
|
||
override fun onIconsScrolled( | ||
firstVisiblePosition: Int, | ||
lastVisiblePosition: Int, | ||
) { | ||
val items = (icons.value as? ChangeRecordTypeIconStateViewData.Icons?) | ||
?.items ?: return | ||
val infoItems = items.filterIsInstance<ChangeRecordTypeIconCategoryInfoViewData>() | ||
|
||
// Last image category has small number of icons, need to check if it is visible, | ||
// otherwise it would never be selected by the second check. | ||
infoItems | ||
.firstOrNull { it.isLast } | ||
?.takeIf { items.indexOf(it) <= lastVisiblePosition } | ||
?.let { | ||
updateIconCategories(it.getUniqueId()) | ||
return | ||
} | ||
|
||
infoItems | ||
.lastOrNull { items.indexOf(it) <= firstVisiblePosition } | ||
?.let { updateIconCategories(it.getUniqueId()) } | ||
} | ||
|
||
override fun onIconImageSearchClicked() { | ||
val newState = when (iconImageState) { | ||
is IconImageState.Chooser -> IconImageState.Search | ||
is IconImageState.Search -> IconImageState.Chooser | ||
} | ||
iconImageState = newState | ||
|
||
if (iconImageState is IconImageState.Chooser) { | ||
parent?.keyboardVisibility(false) | ||
expandIconTypeSwitch.set(Unit) | ||
} | ||
delegateScope.launch { | ||
updateIconSelectorViewData() | ||
updateIconCategories(selectedIndex = 0) | ||
updateIconsLoad() | ||
updateIcons() | ||
} | ||
} | ||
|
||
override fun onIconImageSearch(search: String) { | ||
if (iconType == IconType.TEXT) return | ||
|
||
if (search != iconSearch) { | ||
iconSearchJob?.cancel() | ||
iconSearchJob = delegateScope.launch { | ||
iconSearch = search | ||
delay(500) | ||
updateIcons() | ||
} | ||
} | ||
} | ||
|
||
override fun onEmojiClick(item: EmojiViewData) { | ||
if (iconEmojiMapper.hasSkinToneVariations(item.emojiCodes)) { | ||
openEmojiSelectionDialog(item) | ||
} else { | ||
delegateScope.launch { | ||
if (item.emojiText != newIconName) { | ||
newIconName = item.emojiText | ||
parent?.updateRecordPreviewViewData() | ||
} | ||
} | ||
} | ||
} | ||
|
||
override fun onIconTextChange(text: String) { | ||
delegateScope.launch { | ||
if (text != newIconName) { | ||
newIconName = text | ||
parent?.updateRecordPreviewViewData() | ||
} | ||
} | ||
} | ||
|
||
override fun onEmojiSelected(emojiText: String) { | ||
delegateScope.launch { | ||
if (emojiText != newIconName) { | ||
newIconName = emojiText | ||
parent?.updateRecordPreviewViewData() | ||
} | ||
} | ||
} | ||
|
||
override fun onScrolled() { | ||
iconsScrollPosition.set(ChangeRecordTypeScrollViewData.NoScroll) | ||
} | ||
|
||
private fun openEmojiSelectionDialog(item: EmojiViewData) { | ||
val parent = parent ?: return | ||
val params = changeRecordTypeMapper.mapEmojiSelectionParams( | ||
color = parent.getColor(), | ||
emojiCodes = item.emojiCodes, | ||
) | ||
|
||
router.navigate(params) | ||
} | ||
|
||
private suspend fun updateIcons() { | ||
val data = loadIconsViewData() | ||
icons.set(data) | ||
} | ||
|
||
private fun updateIconsLoad() { | ||
val items = listOf(LoaderViewData()) | ||
val data = ChangeRecordTypeIconStateViewData.Icons(items) | ||
icons.set(data) | ||
} | ||
|
||
private suspend fun loadIconsViewData(): ChangeRecordTypeIconStateViewData { | ||
val color = parent?.getColor() ?: AppColor(0, "") | ||
return viewDataInteractor.getIconsViewData( | ||
newColor = color, | ||
iconType = iconType, | ||
iconImageState = iconImageState, | ||
iconSearch = iconSearch, | ||
) | ||
} | ||
|
||
private fun updateIconCategories(selectedIndex: Long) { | ||
val data = loadIconCategoriesViewData(selectedIndex) | ||
iconCategories.set(data) | ||
} | ||
|
||
private fun loadIconCategoriesViewData(selectedIndex: Long = 0): List<ViewHolderType> { | ||
return viewDataInteractor.getIconCategoriesViewData( | ||
iconType = iconType, | ||
selectedIndex = selectedIndex, | ||
) | ||
} | ||
|
||
private fun updateIconsTypeViewData() { | ||
val data = loadIconsTypeViewData() | ||
iconsTypeViewData.set(data) | ||
} | ||
|
||
private fun loadIconsTypeViewData(): List<ViewHolderType> { | ||
return changeRecordTypeMapper.mapToIconSwitchViewData(iconType) | ||
} | ||
|
||
private suspend fun updateIconSelectorViewData() { | ||
val data = loadIconSelectorViewData() | ||
iconSelectorViewData.set(data) | ||
} | ||
|
||
private suspend fun loadIconSelectorViewData(): ChangeRecordTypeIconSelectorStateViewData { | ||
return changeRecordTypeMapper.mapToIconSelectorViewData( | ||
iconImageState = iconImageState, | ||
iconType = iconType, | ||
isDarkTheme = prefsInteractor.getDarkMode(), | ||
) | ||
} | ||
|
||
private fun updateIconScrollPosition(position: Int) { | ||
iconsScrollPosition.set(ChangeRecordTypeScrollViewData.ScrollTo(position)) | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
...racker/core/delegates/iconSelection/interactor/IconSelectionDelegateViewDataInteractor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package com.example.util.simpletimetracker.core.delegates.iconSelection.interactor | ||
|
||
import com.example.util.simpletimetracker.core.delegates.iconSelection.mapper.ChangeRecordTypeMapper | ||
import com.example.util.simpletimetracker.core.delegates.iconSelection.viewData.ChangeRecordTypeIconStateViewData | ||
import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor | ||
import com.example.util.simpletimetracker.domain.model.AppColor | ||
import com.example.util.simpletimetracker.domain.model.IconImageState | ||
import com.example.util.simpletimetracker.domain.model.IconType | ||
import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import javax.inject.Inject | ||
|
||
class IconSelectionDelegateViewDataInteractor @Inject constructor( | ||
private val prefsInteractor: PrefsInteractor, | ||
private val mapper: ChangeRecordTypeMapper, | ||
) { | ||
|
||
suspend fun getIconsViewData( | ||
newColor: AppColor, | ||
iconType: IconType, | ||
iconImageState: IconImageState, | ||
iconSearch: String, | ||
): ChangeRecordTypeIconStateViewData = withContext(Dispatchers.IO) { | ||
val isDarkTheme = prefsInteractor.getDarkMode() | ||
val search = if (iconImageState == IconImageState.Search) iconSearch else "" | ||
|
||
when (iconType) { | ||
IconType.IMAGE -> { | ||
val items = mapper.mapIconImageData( | ||
newColor = newColor, | ||
search = search, | ||
isDarkTheme = isDarkTheme, | ||
) | ||
ChangeRecordTypeIconStateViewData.Icons(items) | ||
} | ||
IconType.TEXT -> { | ||
ChangeRecordTypeIconStateViewData.Text | ||
} | ||
IconType.EMOJI -> { | ||
val items = mapper.mapIconEmojiData( | ||
newColor = newColor, | ||
search = search, | ||
isDarkTheme = isDarkTheme, | ||
) | ||
ChangeRecordTypeIconStateViewData.Icons(items) | ||
} | ||
} | ||
} | ||
|
||
fun getIconCategoriesViewData( | ||
iconType: IconType, | ||
selectedIndex: Long, | ||
): List<ViewHolderType> { | ||
return when (iconType) { | ||
IconType.IMAGE -> mapper.mapIconImageCategories(selectedIndex) | ||
IconType.TEXT -> emptyList() | ||
IconType.EMOJI -> mapper.mapIconEmojiCategories(selectedIndex) | ||
} | ||
} | ||
} |
Oops, something went wrong.