Skip to content

Commit

Permalink
move icon selection to core to reuse in tag module
Browse files Browse the repository at this point in the history
  • Loading branch information
Razeeman committed Apr 6, 2024
1 parent 9d8ae72 commit 7aff1a2
Show file tree
Hide file tree
Showing 21 changed files with 422 additions and 329 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.util.simpletimetracker.core.delegates
package com.example.util.simpletimetracker.core.delegates.colorSelection

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
Expand All @@ -22,7 +22,6 @@ interface ColorSelectionViewModelDelegate {
fun onColorClick(item: ColorViewData)
fun onColorPaletteClick()
fun onCustomColorSelected(colorInt: Int)
suspend fun update()

interface Parent {
suspend fun update()
Expand Down Expand Up @@ -55,7 +54,7 @@ class ColorSelectionViewModelDelegateImpl @Inject constructor(
this.parent = parent
}

override suspend fun update() {
suspend fun update() {
updateColors()
}

Expand Down
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))
}
}
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)
}
}
}
Loading

0 comments on commit 7aff1a2

Please sign in to comment.