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:
+3
-2
@@ -81,8 +81,9 @@ class BlockedUsersPresenter(
|
||||
unblockUserAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
is BlockedUsersEvent.CopyToClipboard -> {
|
||||
clipboardHelper.copyPlainText(event.userId.value)
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
clipboardHelper.copyPlainText(event.userId.value) {
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
}
|
||||
}
|
||||
BlockedUsersEvent.ConfirmUnblock -> {
|
||||
pendingUserToUnblock?.let {
|
||||
|
||||
+1
@@ -19,4 +19,5 @@ sealed interface DeveloperSettingsEvent {
|
||||
data object DismissMarkAllRoomsAsReadConfirmation : DeveloperSettingsEvent
|
||||
data object OpenPushRules : DeveloperSettingsEvent
|
||||
data object DismissPushRulesError : DeveloperSettingsEvent
|
||||
data class CopyToClipboard(val text: String) : DeveloperSettingsEvent
|
||||
}
|
||||
|
||||
+10
@@ -26,7 +26,9 @@ import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.MarkAllRoomsAsRead
|
||||
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||
import io.element.android.libraries.androidutils.toast.ToastHelper
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
@@ -38,6 +40,7 @@ import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -61,6 +64,8 @@ class DeveloperSettingsPresenter(
|
||||
private val markAllRoomsAsRead: MarkAllRoomsAsRead,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val notificationSettingsService: NotificationSettingsService,
|
||||
private val clipboardHelper: ClipboardHelper,
|
||||
private val toastHelper: ToastHelper,
|
||||
) : Presenter<DeveloperSettingsState> {
|
||||
@AssistedFactory
|
||||
fun interface Factory {
|
||||
@@ -130,6 +135,11 @@ class DeveloperSettingsPresenter(
|
||||
DeveloperSettingsEvent.DismissPushRulesError -> {
|
||||
pushRulesAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
is DeveloperSettingsEvent.CopyToClipboard -> {
|
||||
clipboardHelper.copyPlainText(event.text) {
|
||||
toastHelper.show(CommonStrings.common_copied_to_clipboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-12
@@ -15,14 +15,12 @@ import androidx.compose.foundation.progressSemantics
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.preferences.impl.R
|
||||
import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsView
|
||||
import io.element.android.libraries.androidutils.system.copyToClipboard
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
@@ -84,7 +82,10 @@ fun DeveloperSettingsView(
|
||||
AppDeveloperSettingsView(
|
||||
state = state.appDeveloperSettingsState,
|
||||
)
|
||||
SessionCategory(deviceId = state.deviceId)
|
||||
SessionCategory(
|
||||
deviceId = state.deviceId,
|
||||
onCopyToClipboard = { state.eventSink(DeveloperSettingsEvent.CopyToClipboard(it)) },
|
||||
)
|
||||
NotificationCategory(
|
||||
onPushRulesClick = { state.eventSink(DeveloperSettingsEvent.OpenPushRules) },
|
||||
onPushHistoryClick = onPushHistoryClick,
|
||||
@@ -182,19 +183,15 @@ fun DeveloperSettingsView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SessionCategory(deviceId: DeviceId) {
|
||||
private fun SessionCategory(
|
||||
deviceId: DeviceId,
|
||||
onCopyToClipboard: (String) -> Unit,
|
||||
) {
|
||||
PreferenceCategory(title = "Session") {
|
||||
val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard)
|
||||
val context = LocalContext.current
|
||||
ListItem(
|
||||
content = { Text("DeviceId") },
|
||||
supportingContent = { Text(text = deviceId.value) },
|
||||
onClick = {
|
||||
context.copyToClipboard(
|
||||
text = deviceId.value,
|
||||
toastMessage = toastMessage,
|
||||
)
|
||||
}
|
||||
onClick = { onCopyToClipboard(deviceId.value) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -17,4 +17,5 @@ sealed interface AppDeveloperSettingsEvent {
|
||||
data class SetCustomElementCallBaseUrl(val baseUrl: String?) : AppDeveloperSettingsEvent
|
||||
data class SetTracingLogLevel(val logLevel: LogLevelItem) : AppDeveloperSettingsEvent
|
||||
data class ToggleTracingLogPack(val logPack: TraceLogPack, val enabled: Boolean) : AppDeveloperSettingsEvent
|
||||
data class CopyToClipboard(val text: String) : AppDeveloperSettingsEvent
|
||||
}
|
||||
|
||||
+10
@@ -22,6 +22,8 @@ import io.element.android.features.preferences.impl.developer.tracing.toLogLevel
|
||||
import io.element.android.features.preferences.impl.developer.tracing.toLogLevelItem
|
||||
import io.element.android.features.preferences.impl.model.EnabledFeature
|
||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
|
||||
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.core.extensions.runCatchingExceptions
|
||||
@@ -29,6 +31,7 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -44,6 +47,8 @@ class AppDeveloperSettingsPresenter(
|
||||
private val rageshakePresenter: Presenter<RageshakePreferencesState>,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val clipboardHelper: ClipboardHelper,
|
||||
private val toastHelper: ToastHelper,
|
||||
) : Presenter<AppDeveloperSettingsState> {
|
||||
@Composable
|
||||
override fun present(): AppDeveloperSettingsState {
|
||||
@@ -106,6 +111,11 @@ class AppDeveloperSettingsPresenter(
|
||||
}
|
||||
appPreferencesStore.setTracingLogPacks(currentPacks)
|
||||
}
|
||||
is AppDeveloperSettingsEvent.CopyToClipboard -> {
|
||||
clipboardHelper.copyPlainText(event.text) {
|
||||
toastHelper.show(CommonStrings.common_copied_to_clipboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-16
@@ -14,7 +14,6 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
@@ -23,7 +22,6 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.preferences.impl.R
|
||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView
|
||||
import io.element.android.libraries.androidutils.system.copyToClipboard
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
|
||||
@@ -108,6 +106,7 @@ fun AppDeveloperSettingsView(
|
||||
GitCategory(
|
||||
gitBranch = state.gitBranch,
|
||||
gitSha = state.gitSha,
|
||||
onCopyToClipboard = { state.eventSink(AppDeveloperSettingsEvent.CopyToClipboard(it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -116,29 +115,18 @@ fun AppDeveloperSettingsView(
|
||||
private fun GitCategory(
|
||||
gitBranch: String,
|
||||
gitSha: String,
|
||||
onCopyToClipboard: (String) -> Unit,
|
||||
) {
|
||||
PreferenceCategory(title = "Git") {
|
||||
val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard)
|
||||
val context = LocalContext.current
|
||||
ListItem(
|
||||
content = { Text("Git branch") },
|
||||
supportingContent = { Text(text = gitBranch) },
|
||||
onClick = {
|
||||
context.copyToClipboard(
|
||||
text = gitBranch,
|
||||
toastMessage = toastMessage,
|
||||
)
|
||||
}
|
||||
onClick = { onCopyToClipboard(gitBranch) }
|
||||
)
|
||||
ListItem(
|
||||
content = { Text("Git SHA") },
|
||||
supportingContent = { Text(text = gitSha) },
|
||||
onClick = {
|
||||
context.copyToClipboard(
|
||||
text = gitSha,
|
||||
toastMessage = toastMessage,
|
||||
)
|
||||
}
|
||||
onClick = { onCopyToClipboard(gitSha) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -19,7 +19,11 @@ import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeMarkAllRoomsAsRead
|
||||
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
|
||||
import io.element.android.libraries.androidutils.toast.FakeToastHelper
|
||||
import io.element.android.libraries.androidutils.toast.ToastHelper
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.data.megaBytes
|
||||
@@ -33,6 +37,7 @@ import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
@@ -250,6 +255,38 @@ class DeveloperSettingsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy to clipboard on old device shows a toast`() = runTest {
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = true)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createDeveloperSettingsPresenter(
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem().eventSink(DeveloperSettingsEvent.CopyToClipboard("text"))
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo("text")
|
||||
assertThat(toastHelper.shownToasts).containsExactly(CommonStrings.common_copied_to_clipboard)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy to clipboard on new device does not show a toast`() = runTest {
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = false)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createDeveloperSettingsPresenter(
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem().eventSink(DeveloperSettingsEvent.CopyToClipboard("text"))
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo("text")
|
||||
assertThat(toastHelper.shownToasts).isEmpty()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDeveloperSettingsPresenter(
|
||||
navigator: DeveloperSettingsNavigator = DeveloperSettingsNavigator { _, _ -> lambdaError() },
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
@@ -262,6 +299,8 @@ class DeveloperSettingsPresenterTest {
|
||||
markAllRoomsAsRead: FakeMarkAllRoomsAsRead = FakeMarkAllRoomsAsRead(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
|
||||
clipboardHelper: ClipboardHelper = FakeClipboardHelper(),
|
||||
toastHelper: ToastHelper = FakeToastHelper(),
|
||||
): DeveloperSettingsPresenter {
|
||||
return DeveloperSettingsPresenter(
|
||||
navigator = navigator,
|
||||
@@ -277,6 +316,8 @@ class DeveloperSettingsPresenterTest {
|
||||
markAllRoomsAsRead = markAllRoomsAsRead,
|
||||
buildMeta = buildMeta,
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+42
-1
@@ -12,12 +12,17 @@ package io.element.android.features.preferences.impl.developer.appsettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||
import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState
|
||||
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.featureflag.api.Feature
|
||||
import io.element.android.libraries.featureflag.test.FakeFeature
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
@@ -151,6 +156,38 @@ class AppDeveloperSettingsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy to clipboard on old device shows a toast`() = runTest {
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = true)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createAppDeveloperSettingsPresenter(
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem().eventSink(AppDeveloperSettingsEvent.CopyToClipboard("text"))
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo("text")
|
||||
assertThat(toastHelper.shownToasts).containsExactly(CommonStrings.common_copied_to_clipboard)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy to clipboard on new device does not show a toast`() = runTest {
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = false)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createAppDeveloperSettingsPresenter(
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem().eventSink(AppDeveloperSettingsEvent.CopyToClipboard("text"))
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo("text")
|
||||
assertThat(toastHelper.shownToasts).isEmpty()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAppDeveloperSettingsPresenter(
|
||||
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(
|
||||
getAvailableFeaturesResult = { _, _ ->
|
||||
@@ -164,6 +201,8 @@ class AppDeveloperSettingsPresenterTest {
|
||||
}
|
||||
),
|
||||
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
clipboardHelper: ClipboardHelper = FakeClipboardHelper(),
|
||||
toastHelper: ToastHelper = FakeToastHelper(),
|
||||
): AppDeveloperSettingsPresenter {
|
||||
return AppDeveloperSettingsPresenter(
|
||||
featureFlagService = featureFlagService,
|
||||
@@ -172,7 +211,9 @@ class AppDeveloperSettingsPresenterTest {
|
||||
buildMeta = aBuildMeta(
|
||||
gitRevision = "1234567890",
|
||||
gitBranchName = "feature/awesome-feature"
|
||||
)
|
||||
),
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user