Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions app/src/keyboards/java/be/scri/helpers/clipboard/ClipboardHandler.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
107 changes: 21 additions & 86 deletions app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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<String, MutableList<String>>? = null
private var conjugateOutput: MutableMap<String, MutableMap<String, Collection<String>>>? = null
private var conjugateLabels: Set<String> = emptySet()
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -490,9 +490,7 @@ abstract class GeneralKeyboardIME(
*/
override fun onFinishInputView(finishingInput: Boolean) {
super.onFinishInputView(finishingInput)
if (this::clipboardMonitor.isInitialized) {
clipboardMonitor.stopMonitoring()
}
clipboardHandler.stopMonitoring()
moveToIdleState()
}

Expand Down Expand Up @@ -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()
}
}

Expand Down
Loading