From 7282f4d2285f18eb08ca840ed1ed887f0e816470 Mon Sep 17 00:00:00 2001 From: Prince Yadav <66916296+prince-0408@users.noreply.github.com> Date: Sun, 9 Aug 2026 18:36:38 +0530 Subject: [PATCH] refactor: extract ClipboardHandler from GeneralKeyboardIME (#426) --- .../helpers/clipboard/ClipboardHandler.kt | 126 ++++++++++++++++++ .../be/scri/services/GeneralKeyboardIME.kt | 107 +++------------ 2 files changed, 147 insertions(+), 86 deletions(-) create mode 100644 app/src/keyboards/java/be/scri/helpers/clipboard/ClipboardHandler.kt diff --git a/app/src/keyboards/java/be/scri/helpers/clipboard/ClipboardHandler.kt b/app/src/keyboards/java/be/scri/helpers/clipboard/ClipboardHandler.kt new file mode 100644 index 00000000..32af7fea --- /dev/null +++ b/app/src/keyboards/java/be/scri/helpers/clipboard/ClipboardHandler.kt @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package be.scri.helpers.clipboard + +import android.view.View +import androidx.recyclerview.widget.GridLayoutManager +import be.scri.models.ScribeState +import be.scri.services.GeneralKeyboardIME +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * Manages in-keyboard clipboard monitoring, suggestion chips, and history panel operations + * for [GeneralKeyboardIME]. + * + * @property ime The [GeneralKeyboardIME] instance this handler is associated with. + */ +class ClipboardHandler( + private val ime: GeneralKeyboardIME, +) { + var latestClipText: String? = null + internal set + var hasNewClip: Boolean = false + internal set + + private lateinit var clipboardMonitor: ClipboardMonitor + private var clipboardAdapter: ClipboardAdapter? = null + private val clipboardRepository by lazy { ClipboardRepository(ime) } + + fun initClipboardMonitor() { + clipboardMonitor = + ClipboardMonitor(ime) { text -> + latestClipText = text + hasNewClip = true + if (ime.currentState == ScribeState.IDLE && ime.isUiManagerInitialized) { + ime.uiManager.showClipboardSuggestionChip(text) + } + } + } + + fun startMonitoring() { + if (this::clipboardMonitor.isInitialized) { + clipboardMonitor.startMonitoring() + } + } + + fun stopMonitoring() { + if (this::clipboardMonitor.isInitialized) { + clipboardMonitor.stopMonitoring() + } + } + + fun onClipboardSuggestionClicked() { + latestClipText?.let { text -> + ime.currentInputConnection?.commitText(text, 1) + } + hideClipboardSuggestionChip() + } + + fun hideClipboardSuggestionChip() { + hasNewClip = false + latestClipText = null + if (ime.isUiManagerInitialized) { + ime.uiManager.hideClipboardSuggestionChip() + } + } + + fun openClipboardPanel() { + if (!ime.isUiManagerInitialized) return + ime.uiManager.showClipboardPanel() + + val recyclerView = ime.binding.clipboardItemsList + val emptyText = ime.binding.clipboardEmptyText + + clipboardAdapter = + ClipboardAdapter( + items = emptyList(), + onItemClick = { item -> + ime.currentInputConnection?.commitText(item.text, 1) + closeClipboardPanel() + }, + onItemDelete = { item -> + CoroutineScope(Dispatchers.Main).launch { + clipboardRepository.deleteItem(item.id) + refreshClipboardPanel() + } + }, + onItemPinToggle = { item -> + CoroutineScope(Dispatchers.Main).launch { + clipboardRepository.togglePin(item.id, item.isPinned) + refreshClipboardPanel() + } + }, + ) + recyclerView.adapter = clipboardAdapter + recyclerView.layoutManager = GridLayoutManager(ime, 2) + + ime.binding.clipboardPanelClose.setOnClickListener { closeClipboardPanel() } + ime.binding.clipboardClearAll.setOnClickListener { + CoroutineScope(Dispatchers.Main).launch { + clipboardRepository.clearAll() + refreshClipboardPanel() + } + } + + CoroutineScope(Dispatchers.Main).launch { + val items = clipboardRepository.getAllItems() + clipboardAdapter?.updateItems(items) + emptyText.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE + recyclerView.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE + } + } + + fun closeClipboardPanel() { + if (!ime.isUiManagerInitialized) return + ime.uiManager.hideClipboardPanel() + } + + private suspend fun refreshClipboardPanel() { + val items = clipboardRepository.getAllItems() + clipboardAdapter?.updateItems(items) + ime.binding.clipboardEmptyText.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE + ime.binding.clipboardItemsList.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE + } +} diff --git a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt index 16115def..46971e1d 100644 --- a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt @@ -67,13 +67,12 @@ import be.scri.helpers.SHIFT_OFF import be.scri.helpers.SHIFT_ON_ONE_CHAR import be.scri.helpers.SHIFT_ON_PERMANENT import be.scri.helpers.SuggestionHandler -import be.scri.helpers.clipboard.ClipboardMonitor +import be.scri.helpers.clipboard.ClipboardHandler import be.scri.helpers.data.AutocompletionDataManager import be.scri.helpers.english.ENInterfaceVariables.ALREADY_PLURAL_MSG import be.scri.helpers.ui.KeyboardUIManager import be.scri.models.ScribeState import be.scri.views.KeyboardView -import kotlinx.coroutines.launch import java.util.Locale private const val DATA_SIZE_2 = 2 @@ -126,9 +125,17 @@ abstract class GeneralKeyboardIME( internal val binding: InputMethodViewBinding get() = uiManager.binding - internal var hasNewClip: Boolean = false - internal var latestClipText: String? = null - private lateinit var clipboardMonitor: ClipboardMonitor + internal val clipboardHandler by lazy { ClipboardHandler(this) } + internal var hasNewClip: Boolean + get() = clipboardHandler.hasNewClip + set(value) { + clipboardHandler.hasNewClip = value + } + internal var latestClipText: String? + get() = clipboardHandler.latestClipText + set(value) { + clipboardHandler.latestClipText = value + } // MARK: State Variables @@ -145,6 +152,8 @@ abstract class GeneralKeyboardIME( private lateinit var autocompletionManager: AutocompletionDataManager private var dataContract: DataContract? = null + internal val isUiManagerInitialized: Boolean get() = this::uiManager.isInitialized + var emojiKeywords: HashMap>? = null private var conjugateOutput: MutableMap>>? = null private var conjugateLabels: Set = emptySet() @@ -226,14 +235,7 @@ abstract class GeneralKeyboardIME( suggestionHandler = SuggestionHandler(this) autocompletionManager = dbManagers.autocompletionManager autocompletionHandler = AutocompletionHandler(this) - clipboardMonitor = - ClipboardMonitor(this) { text -> - latestClipText = text - hasNewClip = true - if (currentState == ScribeState.IDLE && this::uiManager.isInitialized) { - uiManager.showClipboardSuggestionChip(text) - } - } + clipboardHandler.initClipboardMonitor() } override fun onDestroy() { @@ -400,9 +402,7 @@ abstract class GeneralKeyboardIME( restarting: Boolean, ) { super.onStartInputView(editorInfo, restarting) - if (this::clipboardMonitor.isInitialized) { - clipboardMonitor.startMonitoring() - } + clipboardHandler.startMonitoring() emojiAutoSuggestionEnabled = getIsEmojiSuggestionsEnabled(applicationContext, language) autoSuggestEmojis = null suggestionHandler.clearAllSuggestionsAndHideButtonUI() @@ -490,9 +490,7 @@ abstract class GeneralKeyboardIME( */ override fun onFinishInputView(finishingInput: Boolean) { super.onFinishInputView(finishingInput) - if (this::clipboardMonitor.isInitialized) { - clipboardMonitor.stopMonitoring() - } + clipboardHandler.stopMonitoring() moveToIdleState() } @@ -2718,82 +2716,19 @@ abstract class GeneralKeyboardIME( } override fun onClipboardSuggestionClicked() { - latestClipText?.let { text -> - currentInputConnection?.commitText(text, 1) - } - hideClipboardSuggestionChip() + clipboardHandler.onClipboardSuggestionClicked() } fun hideClipboardSuggestionChip() { - hasNewClip = false - latestClipText = null - if (this::uiManager.isInitialized) { - uiManager.hideClipboardSuggestionChip() - } - } - - private var clipboardAdapter: be.scri.helpers.clipboard.ClipboardAdapter? = null - private val clipboardRepository by lazy { - be.scri.helpers.clipboard - .ClipboardRepository(this) + clipboardHandler.hideClipboardSuggestionChip() } fun openClipboardPanel() { - if (!this::uiManager.isInitialized) return - uiManager.showClipboardPanel() - - val recyclerView = binding.clipboardItemsList - val emptyText = binding.clipboardEmptyText - - clipboardAdapter = - be.scri.helpers.clipboard.ClipboardAdapter( - items = emptyList(), - onItemClick = { item -> - currentInputConnection?.commitText(item.text, 1) - closeClipboardPanel() - }, - onItemDelete = { item -> - kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Main).launch { - clipboardRepository.deleteItem(item.id) - refreshClipboardPanel() - } - }, - onItemPinToggle = { item -> - kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Main).launch { - clipboardRepository.togglePin(item.id, item.isPinned) - refreshClipboardPanel() - } - }, - ) - recyclerView.adapter = clipboardAdapter - recyclerView.layoutManager = androidx.recyclerview.widget.GridLayoutManager(this, 2) - - binding.clipboardPanelClose.setOnClickListener { closeClipboardPanel() } - binding.clipboardClearAll.setOnClickListener { - kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Main).launch { - clipboardRepository.clearAll() - refreshClipboardPanel() - } - } - - kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Main).launch { - val items = clipboardRepository.getAllItems() - clipboardAdapter?.updateItems(items) - emptyText.visibility = if (items.isEmpty()) android.view.View.VISIBLE else android.view.View.GONE - recyclerView.visibility = if (items.isEmpty()) android.view.View.GONE else android.view.View.VISIBLE - } + clipboardHandler.openClipboardPanel() } fun closeClipboardPanel() { - if (!this::uiManager.isInitialized) return - uiManager.hideClipboardPanel() - } - - private suspend fun refreshClipboardPanel() { - val items = clipboardRepository.getAllItems() - clipboardAdapter?.updateItems(items) - binding.clipboardEmptyText.visibility = if (items.isEmpty()) android.view.View.VISIBLE else android.view.View.GONE - binding.clipboardItemsList.visibility = if (items.isEmpty()) android.view.View.GONE else android.view.View.VISIBLE + clipboardHandler.closeClipboardPanel() } }