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 if (hint) Toast.makeText(context, "Copied — clears in 30s", Toast.LENGTH_SHORT).show()39 pending?.cancel()40 pending = scheduleClear(41 scope = scope,42 expected = value,43 readCurrent = { cm.primaryClip?.getItemAt(0)?.text?.toString() },44 clear = { cm.setPrimaryClip(ClipData.newPlainText("", "")) },45 )46 }4748 private fun sensitiveClip(label: String, value: String): ClipData =49 ClipData.newPlainText(label, value).apply {50 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {51 description.extras = PersistableBundle().apply {52 putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)53 }54 }55 }5657 5859606162 internal fun scheduleClear(63 scope: CoroutineScope,64 expected: String,65 readCurrent: () -> String?,66 clear: () -> Unit,67 delayMs: Long = CLEAR_DELAY_MS,68 ): Job = scope.launch {69 delay(delayMs)70 if (readCurrent() == expected) clear()71 }72}