mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +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:
+1
@@ -13,6 +13,7 @@ sealed interface RoomDetailsEvent {
|
||||
data object MuteNotification : RoomDetailsEvent
|
||||
data object UnmuteNotification : RoomDetailsEvent
|
||||
data class CopyToClipboard(val text: String) : RoomDetailsEvent
|
||||
data object CopyRoomId : RoomDetailsEvent
|
||||
data class SetFavorite(val isFavorite: Boolean) : RoomDetailsEvent
|
||||
data object MarkAsRead : RoomDetailsEvent
|
||||
data object MarkAsUnread : RoomDetailsEvent
|
||||
|
||||
+10
-2
@@ -32,6 +32,7 @@ import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermission
|
||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
||||
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
import io.element.android.libraries.androidutils.toast.ToastHelper
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher
|
||||
@@ -74,6 +75,7 @@ class RoomDetailsPresenter(
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val clipboardHelper: ClipboardHelper,
|
||||
private val toastHelper: ToastHelper,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
@@ -167,8 +169,14 @@ class RoomDetailsPresenter(
|
||||
}
|
||||
is RoomDetailsEvent.SetFavorite -> scope.setFavorite(event.isFavorite)
|
||||
is RoomDetailsEvent.CopyToClipboard -> {
|
||||
clipboardHelper.copyPlainText(event.text)
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
clipboardHelper.copyPlainText(event.text) {
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
}
|
||||
}
|
||||
is RoomDetailsEvent.CopyRoomId -> {
|
||||
clipboardHelper.copyPlainText(room.roomId.value) {
|
||||
toastHelper.show(CommonStrings.common_copied_to_clipboard)
|
||||
}
|
||||
}
|
||||
is RoomDetailsEvent.MarkAsRead -> scope.markAsRead()
|
||||
is RoomDetailsEvent.MarkAsUnread -> scope.markAsUnread()
|
||||
|
||||
+3
-10
@@ -34,7 +34,6 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -48,7 +47,6 @@ import io.element.android.features.userprofile.api.UserProfileState
|
||||
import io.element.android.features.userprofile.api.UserProfileVerificationState
|
||||
import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
|
||||
import io.element.android.libraries.androidutils.system.copyToClipboard
|
||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.MatrixBadgeAtom
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRowMolecule
|
||||
@@ -339,6 +337,7 @@ fun RoomDetailsView(
|
||||
DebugInfoSection(
|
||||
roomId = state.roomId,
|
||||
roomVersion = state.roomVersion,
|
||||
onCopyRoomIdClick = { state.eventSink(RoomDetailsEvent.CopyRoomId) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -865,10 +864,9 @@ private fun OtherActionsSection(
|
||||
private fun DebugInfoSection(
|
||||
roomId: RoomId,
|
||||
roomVersion: String?,
|
||||
onCopyRoomIdClick: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
PreferenceCategory {
|
||||
val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard)
|
||||
ListItem(
|
||||
content = {
|
||||
Text("Internal room ID")
|
||||
@@ -882,12 +880,7 @@ private fun DebugInfoSection(
|
||||
},
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Code())),
|
||||
trailingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Copy())),
|
||||
onClick = {
|
||||
context.copyToClipboard(
|
||||
text = roomId.value,
|
||||
toastMessage = toastMessage,
|
||||
)
|
||||
},
|
||||
onClick = onCopyRoomIdClick,
|
||||
)
|
||||
ListItem(
|
||||
content = {
|
||||
|
||||
+3
-2
@@ -118,8 +118,9 @@ class RoomMemberDetailsPresenter(
|
||||
encryptionService.withdrawVerification(roomMemberId)
|
||||
}
|
||||
is UserProfileEvent.CopyToClipboard -> {
|
||||
clipboardHelper.copyPlainText(event.text)
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
clipboardHelper.copyPlainText(event.text) {
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
}
|
||||
}
|
||||
else -> userProfileState.eventSink(event)
|
||||
}
|
||||
|
||||
+45
@@ -19,6 +19,8 @@ import io.element.android.features.roomdetails.impl.members.details.RoomMemberDe
|
||||
import io.element.android.features.userprofile.shared.aUserProfileState
|
||||
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.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -49,6 +51,7 @@ import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||
import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
@@ -85,6 +88,7 @@ class RoomDetailsPresenterTest {
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
encryptionService: FakeEncryptionService = FakeEncryptionService(),
|
||||
clipboardHelper: ClipboardHelper = FakeClipboardHelper(),
|
||||
toastHelper: ToastHelper = FakeToastHelper(),
|
||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
navigator: RoomDetailsNavigator = FakeRoomDetailsNavigator(),
|
||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||
@@ -115,12 +119,53 @@ class RoomDetailsPresenterTest {
|
||||
dispatchers = dispatchers,
|
||||
analyticsService = analyticsService,
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
notificationCleaner = notificationCleaner,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy room id on old device shows a toast`() = runTest {
|
||||
val room = aJoinedRoom(
|
||||
roomPermissions = roomPermissions(),
|
||||
)
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = true)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createRoomDetailsPresenter(
|
||||
room = room,
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) {
|
||||
awaitItem().eventSink(RoomDetailsEvent.CopyRoomId)
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo(room.roomId.value)
|
||||
assertThat(toastHelper.shownToasts).containsExactly(CommonStrings.common_copied_to_clipboard)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - copy room id on new device does not show a toast`() = runTest {
|
||||
val room = aJoinedRoom(
|
||||
roomPermissions = roomPermissions(),
|
||||
)
|
||||
val clipboardHelper = FakeClipboardHelper(isOldDevice = false)
|
||||
val toastHelper = FakeToastHelper()
|
||||
val presenter = createRoomDetailsPresenter(
|
||||
room = room,
|
||||
clipboardHelper = clipboardHelper,
|
||||
toastHelper = toastHelper,
|
||||
)
|
||||
presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) {
|
||||
awaitItem().eventSink(RoomDetailsEvent.CopyRoomId)
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo(room.roomId.value)
|
||||
assertThat(toastHelper.shownToasts).isEmpty()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - initial state is created from initial room info`() = runTest {
|
||||
val room = aJoinedRoom(
|
||||
|
||||
Reference in New Issue
Block a user