diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt new file mode 100644 index 0000000000..6441764a34 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt @@ -0,0 +1,49 @@ +package com.tangem.feature.onboarding.domain + +import com.tangem.utils.extensions.isNotWhitespace + +/** +[REDACTED_AUTHOR] + */ +internal class PartWordFinder { + + fun getLeadPartOfWord(text: String, cursorPosition: Int): String? { + val charText = text.toCharArray().asList() + if (cursorPosition > charText.size || cursorPosition < 0) { + // cursorPosition is out of text size + return null + } + if (cursorPosition == 0) { + // cursorPosition at start of text + return null + } + if (charText[cursorPosition - 1].isWhitespace()) { + // white space to the left of cursorPosition + return null + } + if (charText.size > cursorPosition && charText[cursorPosition].isNotWhitespace()) { + // cursorPosition in the middle of a word + return null + } + + for (charIndex in cursorPosition - 1 downTo 0) { + val char = charText[charIndex] + when { + charIndex == 0 -> { + return text.substring(charIndex, cursorPosition) + } + char.isWhitespace() -> { + return text.substring(charIndex + 1, cursorPosition) + } + } + } + + return null + } + + fun getLastPartOfWord(text: String, word: String, cursorPosition: Int): String { + val leadPart = getLeadPartOfWord(text, cursorPosition) ?: return "" + + return word.substring(leadPart.length, word.length) + } +} diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt index 563152917c..f8974e1207 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt @@ -1,6 +1,8 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.feature.onboarding.domain.InsertSuggestionResult import com.tangem.feature.onboarding.domain.SeedPhraseError import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState @@ -42,6 +44,26 @@ class ImportSeedPhraseStateBuilder { ), ) + fun updateSuggestions( + uiState: OnboardingSeedPhraseState, + suggestions: List, + ): OnboardingSeedPhraseState = uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + suggestionsList = suggestions, + ), + ) + + fun insertSuggestionWord( + uiState: OnboardingSeedPhraseState, + insertResult: InsertSuggestionResult, + ): OnboardingSeedPhraseState { + val newTextFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue.copy( + text = insertResult.text, + selection = TextRange(insertResult.cursorPosition, insertResult.cursorPosition), + ) + return updateTextField(uiState, newTextFieldValue) + } + fun updateError( uiState: OnboardingSeedPhraseState, error: SeedPhraseError?, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 473781349e..ae997b06d7 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject /** @@ -51,6 +52,7 @@ class SeedPhraseViewModel @Inject constructor( get() = uiState.step private val textFieldsDebouncers = mutableMapOf() + private var suggestionWordInserted: AtomicBoolean = AtomicBoolean(false) override fun onCleared() { textFieldsDebouncers.forEach { entry -> entry.value.release() } @@ -138,18 +140,25 @@ class SeedPhraseViewModel @Inject constructor( val inputMnemonic = fieldState.textFieldValue.text when { + suggestionWordInserted.getAndSet(false) -> { + validateMnemonic(inputMnemonic) + return@launchSingle + } isSameText && !isCursorMoved -> { return@launchSingle } isSameText && isCursorMoved -> { + updateSuggestions(fieldState) return@launchSingle } } val isPasteFromClipboard = textFieldValue.text.length - oldTextFieldValue.text.length > 1 if (isPasteFromClipboard) { + updateSuggestions(fieldState) validateMnemonic(inputMnemonic) } else { + updateSuggestions(fieldState) val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER) debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.io) { validateMnemonic(inputMnemonic) @@ -180,6 +189,15 @@ class SeedPhraseViewModel @Inject constructor( updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) } } } + + private suspend fun updateSuggestions(fieldState: TextFieldState) { + val suggestions = interactor.getSuggestions( + text = fieldState.textFieldValue.text, + hasSelection = !fieldState.textFieldValue.selection.collapsed, + cursorPosition = fieldState.textFieldValue.selection.end, + ) + updateUi { uiBuilder.importSeedPhrase.updateSuggestions(uiState, suggestions) } + } // endregion ImportSeedPhrase // region ButtonClickHandlers @@ -250,6 +268,22 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonSuggestedPhraseClick(suggestionIndex: Int) { + viewModelScope.launchSingle { + suggestionWordInserted.set(true) + val textFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue + val word = uiState.importSeedPhraseState.suggestionsList[suggestionIndex] + val cursorPosition = textFieldValue.selection.end + + val insertResult = interactor.insertSuggestionWord( + text = textFieldValue.text, + suggestion = word, + cursorPosition = cursorPosition, + ) + updateUi { + val mediateState = uiBuilder.importSeedPhrase.insertSuggestionWord(uiState, insertResult) + uiBuilder.importSeedPhrase.updateSuggestions(mediateState, emptyList()) + } + } } private fun menuChatClick() {