mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Use only ClipboardHelper to copy to the clipboard, and do not render feedback on Android 13+
Since Android 13, the system displays its own confirmation when content is copied to the clipboard, so the application does not render its own Toast or Snackbar anymore on those versions. Context.copyToClipboard, CopyToClipboardUseCase and the direct ClipboardManager usage in EventDebugInfoView are removed. ClipboardHelper.copyPlainText(text, isSensitive, onSuccessOnOldDevice) is now the only entry point, and AndroidClipboardHelper is the only class using ClipboardManager. Copy actions from the views are moved to their Presenter through new events, or to the Node when there is no Presenter. A ToastHelper is introduced so that Toasts are kept where they were used before. Snackbars are kept where they were already used. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+12
-1
@@ -15,10 +15,16 @@ import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.viewfolder.api.TextFileViewer
|
||||
import io.element.android.features.viewfolder.impl.file.ColorationMode
|
||||
import io.element.android.features.viewfolder.impl.file.FileContent
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
import io.element.android.libraries.androidutils.toast.ToastHelper
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultTextFileViewer : TextFileViewer {
|
||||
class DefaultTextFileViewer(
|
||||
private val clipboardHelper: ClipboardHelper,
|
||||
private val toastHelper: ToastHelper,
|
||||
) : TextFileViewer {
|
||||
@Composable
|
||||
override fun Render(
|
||||
lines: ImmutableList<String>,
|
||||
@@ -27,6 +33,11 @@ class DefaultTextFileViewer : TextFileViewer {
|
||||
FileContent(
|
||||
lines = lines,
|
||||
colorationMode = ColorationMode.None,
|
||||
onLineClick = { line ->
|
||||
clipboardHelper.copyPlainText(line) {
|
||||
toastHelper.show(CommonStrings.common_line_copied_to_clipboard)
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
+4
-10
@@ -23,12 +23,10 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.androidutils.system.copyToClipboard
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -37,6 +35,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
internal fun FileContent(
|
||||
lines: ImmutableList<String>,
|
||||
colorationMode: ColorationMode,
|
||||
onLineClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
@@ -60,6 +59,7 @@ internal fun FileContent(
|
||||
lineNumber = index + 1,
|
||||
line = line,
|
||||
colorationMode = colorationMode,
|
||||
onClick = { onLineClick(line) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -71,18 +71,12 @@ private fun LineRow(
|
||||
lineNumber: Int,
|
||||
line: String,
|
||||
colorationMode: ColorationMode,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val toastMessage = stringResource(CommonStrings.common_line_copied_to_clipboard)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = {
|
||||
context.copyToClipboard(
|
||||
text = line,
|
||||
toastMessage = toastMessage,
|
||||
)
|
||||
})
|
||||
.clickable(onClick = onClick)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
||||
+1
@@ -11,4 +11,5 @@ package io.element.android.features.viewfolder.impl.file
|
||||
sealed interface ViewFileEvent {
|
||||
data object SaveOnDisk : ViewFileEvent
|
||||
data object Share : ViewFileEvent
|
||||
data class CopyToClipboard(val text: String) : ViewFileEvent
|
||||
}
|
||||
|
||||
+8
@@ -18,8 +18,11 @@ import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
import io.element.android.libraries.androidutils.toast.ToastHelper
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -30,6 +33,8 @@ class ViewFilePresenter(
|
||||
private val fileContentReader: FileContentReader,
|
||||
private val fileShare: FileShare,
|
||||
private val fileSave: FileSave,
|
||||
private val clipboardHelper: ClipboardHelper,
|
||||
private val toastHelper: ToastHelper,
|
||||
) : Presenter<ViewFileState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
@@ -48,6 +53,9 @@ class ViewFilePresenter(
|
||||
when (event) {
|
||||
ViewFileEvent.Share -> coroutineScope.share(path)
|
||||
ViewFileEvent.SaveOnDisk -> coroutineScope.save(path)
|
||||
is ViewFileEvent.CopyToClipboard -> clipboardHelper.copyPlainText(event.text) {
|
||||
toastHelper.show(CommonStrings.common_line_copied_to_clipboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -80,6 +80,7 @@ fun ViewFileView(
|
||||
modifier = Modifier.weight(1f),
|
||||
lines = state.lines.data.toImmutableList(),
|
||||
colorationMode = state.colorationMode,
|
||||
onLineClick = { state.eventSink(ViewFileEvent.CopyToClipboard(it)) },
|
||||
)
|
||||
is AsyncData.Failure -> AsyncFailure(throwable = state.lines.error, onRetry = null)
|
||||
}
|
||||
|
||||
+39
@@ -18,8 +18,13 @@ import io.element.android.features.viewfolder.impl.file.FileSave
|
||||
import io.element.android.features.viewfolder.impl.file.FileShare
|
||||
import io.element.android.features.viewfolder.impl.file.ViewFileEvent
|
||||
import io.element.android.features.viewfolder.impl.file.ViewFilePresenter
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||
import io.element.android.libraries.androidutils.toast.FakeToastHelper
|
||||
import io.element.android.libraries.androidutils.toast.ToastHelper
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
@@ -126,17 +131,51 @@ class ViewFilePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy line on old device shows a toast`() = runTest {
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = true)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createPresenter(clipboardHelper = clipboardHelper, toastHelper = toastHelper)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
awaitItem().eventSink(ViewFileEvent.CopyToClipboard("aLine"))
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo("aLine")
|
||||
assertThat(toastHelper.shownToasts).containsExactly(CommonStrings.common_line_copied_to_clipboard)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy line on new device does not show a toast`() = runTest {
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = false)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createPresenter(clipboardHelper = clipboardHelper, toastHelper = toastHelper)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
awaitItem().eventSink(ViewFileEvent.CopyToClipboard("aLine"))
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo("aLine")
|
||||
assertThat(toastHelper.shownToasts).isEmpty()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPresenter(
|
||||
path: String = "aPath",
|
||||
name: String = "aName",
|
||||
fileContentReader: FileContentReader = FakeFileContentReader(),
|
||||
fileShare: FileShare = FakeFileShare(),
|
||||
fileSave: FileSave = FakeFileSave(),
|
||||
clipboardHelper: ClipboardHelper = FakeClipboardHelper(),
|
||||
toastHelper: ToastHelper = FakeToastHelper(),
|
||||
) = ViewFilePresenter(
|
||||
path = path,
|
||||
name = name,
|
||||
fileContentReader = fileContentReader,
|
||||
fileShare = fileShare,
|
||||
fileSave = fileSave,
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user