1package com.peachpasswords.shared23import android.content.ClipData4import android.content.ClipDescription5import android.content.ClipboardManager6import android.content.Context7import android.os.Build8import android.os.PersistableBundle9import android.widget.Toast10import kotlinx.coroutines.CoroutineScope11import kotlinx.coroutines.Dispatchers12import kotlinx.coroutines.Job13import kotlinx.coroutines.SupervisorJob14import kotlinx.coroutines.delay15import kotlinx.coroutines.launch16171819202122232425262728object PeachClipboard {29 const val CLEAR_DELAY_MS = 30_000L3031 private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())32 private var pending: Job? = null3334 fun copy(context: Context, label: String, value: String, hint: Boolean = true) {35 if (value.isEmpty()) return36 val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager37 cm.setPrimaryClip(sensitiveClip(label, value))38 CopyFeedbackBus.pulse()39 if (hint) Toast.makeText(context, "Copied — clears in 30s", Toast.LENGTH_SHORT).show()40 pending?.cancel()41 pending = scheduleClear(42 scope = scope,43 expected = value,44 readCurrent = { cm.primaryClip?.getItemAt(0)?.text?.toString() },45 clear = { cm.setPrimaryClip(ClipData.newPlainText("", "")) },46 )47 }4849 private fun sensitiveClip(label: String, value: String): ClipData =50 ClipData.newPlainText(label, value).apply {51 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {52 description.extras = PersistableBundle().apply {53 putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)54 }55 }56 }5758 5960616263 internal fun scheduleClear(64 scope: CoroutineScope,65 expected: String,66 readCurrent: () -> String?,67 clear: () -> Unit,68 delayMs: Long = CLEAR_DELAY_MS,69 ): Job = scope.launch {70 delay(delayMs)71 if (readCurrent() == expected) clear()72 }73}