Browse Source

Merge pull request #2653 from element-hq/feature/bma/copyPermalink

Copy permalink
pull/2655/head
Benoit Marty 6 months ago committed by GitHub
parent
commit
4a2703810b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      changelog.d/2650.feature
  2. 16
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt
  3. 4
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt
  4. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt
  5. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt
  6. 21
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
  7. 15
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt
  8. 7
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
  9. 14
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
  10. 5
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
  11. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png
  12. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png
  13. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png
  14. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png
  15. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png
  16. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png
  17. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png
  18. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png
  19. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png
  20. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png
  21. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png
  22. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png
  23. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png
  24. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png
  25. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png
  26. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png
  27. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png
  28. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png

1
changelog.d/2650.feature

@ -0,0 +1 @@ @@ -0,0 +1 @@
Add action to copy permalink

16
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

@ -89,6 +89,7 @@ import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState @@ -89,6 +89,7 @@ import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -273,6 +274,7 @@ class MessagesPresenter @AssistedInject constructor( @@ -273,6 +274,7 @@ class MessagesPresenter @AssistedInject constructor(
) = launch {
when (action) {
TimelineItemAction.Copy -> handleCopyContents(targetEvent)
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.Reply,
@ -435,6 +437,20 @@ class MessagesPresenter @AssistedInject constructor( @@ -435,6 +437,20 @@ class MessagesPresenter @AssistedInject constructor(
event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) }
}
private suspend fun handleCopyLink(event: TimelineItem.Event) {
event.eventId ?: return
room.getPermalinkFor(event.eventId).fold(
onSuccess = { permalink ->
clipboardHelper.copyPlainText(permalink)
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_link_copied_to_clipboard))
},
onFailure = {
Timber.e(it, "Failed to get permalink for event ${event.eventId}")
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_error))
}
)
}
private suspend fun handleCopyContents(event: TimelineItem.Event) {
val content = when (event.content) {
is TimelineItemTextBasedContent -> event.content.body

4
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt

@ -96,6 +96,7 @@ class ActionListPresenter @Inject constructor( @@ -96,6 +96,7 @@ class ActionListPresenter @Inject constructor(
is TimelineItemStateContent -> {
buildList {
add(TimelineItemAction.Copy)
add(TimelineItemAction.CopyLink)
if (isDeveloperModeEnabled) {
add(TimelineItemAction.ViewSource)
}
@ -119,6 +120,7 @@ class ActionListPresenter @Inject constructor( @@ -119,6 +120,7 @@ class ActionListPresenter @Inject constructor(
if (timelineItem.content.canBeCopied()) {
add(TimelineItemAction.Copy)
}
add(TimelineItemAction.CopyLink)
if (isDeveloperModeEnabled) {
add(TimelineItemAction.ViewSource)
}
@ -136,6 +138,7 @@ class ActionListPresenter @Inject constructor( @@ -136,6 +138,7 @@ class ActionListPresenter @Inject constructor(
add(TimelineItemAction.Reply)
add(TimelineItemAction.Forward)
}
add(TimelineItemAction.CopyLink)
if (isDeveloperModeEnabled) {
add(TimelineItemAction.ViewSource)
}
@ -176,6 +179,7 @@ class ActionListPresenter @Inject constructor( @@ -176,6 +179,7 @@ class ActionListPresenter @Inject constructor(
if (timelineItem.content.canBeCopied()) {
add(TimelineItemAction.Copy)
}
add(TimelineItemAction.CopyLink)
if (isDeveloperModeEnabled) {
add(TimelineItemAction.ViewSource)
}

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt

@ -135,6 +135,7 @@ fun aTimelineItemActionList(): ImmutableList<TimelineItemAction> { @@ -135,6 +135,7 @@ fun aTimelineItemActionList(): ImmutableList<TimelineItemAction> {
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.Edit,
TimelineItemAction.Redact,
TimelineItemAction.ReportContent,
@ -146,6 +147,7 @@ fun aTimelineItemPollActionList(): ImmutableList<TimelineItemAction> { @@ -146,6 +147,7 @@ fun aTimelineItemPollActionList(): ImmutableList<TimelineItemAction> {
TimelineItemAction.EndPoll,
TimelineItemAction.Reply,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
TimelineItemAction.Redact,

1
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt

@ -31,6 +31,7 @@ sealed class TimelineItemAction( @@ -31,6 +31,7 @@ sealed class TimelineItemAction(
) {
data object Forward : TimelineItemAction(CommonStrings.action_forward, CompoundDrawables.ic_compound_forward)
data object Copy : TimelineItemAction(CommonStrings.action_copy, CompoundDrawables.ic_compound_copy)
data object CopyLink : TimelineItemAction(CommonStrings.action_copy_link_to_message, CompoundDrawables.ic_compound_link)
data object Redact : TimelineItemAction(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true)
data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply)
data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply)

21
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt

@ -231,6 +231,27 @@ class MessagesPresenterTest { @@ -231,6 +231,27 @@ class MessagesPresenterTest {
}
}
@Test
fun `present - handle action copy link`() = runTest {
val clipboardHelper = FakeClipboardHelper()
val event = aMessageEvent()
val matrixRoom = FakeMatrixRoom(
permalinkResult = { Result.success("a link") },
)
val presenter = createMessagesPresenter(
clipboardHelper = clipboardHelper,
matrixRoom = matrixRoom,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitFirstItem()
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
assertThat(clipboardHelper.clipboardContents).isEqualTo("a link")
}
}
@Test
fun `present - handle action reply`() = runTest {
val presenter = createMessagesPresenter()

15
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt

@ -153,6 +153,7 @@ class ActionListPresenterTest { @@ -153,6 +153,7 @@ class ActionListPresenterTest {
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
)
@ -193,6 +194,7 @@ class ActionListPresenterTest { @@ -193,6 +194,7 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Forward,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
)
@ -232,6 +234,7 @@ class ActionListPresenterTest { @@ -232,6 +234,7 @@ class ActionListPresenterTest {
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
TimelineItemAction.Redact,
@ -272,6 +275,7 @@ class ActionListPresenterTest { @@ -272,6 +275,7 @@ class ActionListPresenterTest {
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent,
TimelineItemAction.Redact,
@ -315,6 +319,7 @@ class ActionListPresenterTest { @@ -315,6 +319,7 @@ class ActionListPresenterTest {
TimelineItemAction.Forward,
TimelineItemAction.Edit,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
@ -357,6 +362,7 @@ class ActionListPresenterTest { @@ -357,6 +362,7 @@ class ActionListPresenterTest {
TimelineItemAction.Forward,
TimelineItemAction.Edit,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
)
)
@ -396,6 +402,7 @@ class ActionListPresenterTest { @@ -396,6 +402,7 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
@ -435,6 +442,7 @@ class ActionListPresenterTest { @@ -435,6 +442,7 @@ class ActionListPresenterTest {
displayEmojiReactions = false,
actions = persistentListOf(
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.ViewSource,
)
)
@ -473,6 +481,7 @@ class ActionListPresenterTest { @@ -473,6 +481,7 @@ class ActionListPresenterTest {
displayEmojiReactions = false,
actions = persistentListOf(
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
)
)
)
@ -513,6 +522,7 @@ class ActionListPresenterTest { @@ -513,6 +522,7 @@ class ActionListPresenterTest {
TimelineItemAction.Forward,
TimelineItemAction.Edit,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.Redact,
)
)
@ -595,6 +605,7 @@ class ActionListPresenterTest { @@ -595,6 +605,7 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Edit,
TimelineItemAction.Copy,
TimelineItemAction.CopyLink,
TimelineItemAction.Redact,
)
)
@ -632,6 +643,7 @@ class ActionListPresenterTest { @@ -632,6 +643,7 @@ class ActionListPresenterTest {
TimelineItemAction.Reply,
TimelineItemAction.Edit,
TimelineItemAction.EndPoll,
TimelineItemAction.CopyLink,
TimelineItemAction.Redact,
)
)
@ -668,6 +680,7 @@ class ActionListPresenterTest { @@ -668,6 +680,7 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.EndPoll,
TimelineItemAction.CopyLink,
TimelineItemAction.Redact,
)
)
@ -703,6 +716,7 @@ class ActionListPresenterTest { @@ -703,6 +716,7 @@ class ActionListPresenterTest {
displayEmojiReactions = true,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.CopyLink,
TimelineItemAction.Redact,
)
)
@ -738,6 +752,7 @@ class ActionListPresenterTest { @@ -738,6 +752,7 @@ class ActionListPresenterTest {
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
TimelineItemAction.CopyLink,
TimelineItemAction.Redact,
)
)

7
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt

@ -328,5 +328,12 @@ interface MatrixRoom : Closeable { @@ -328,5 +328,12 @@ interface MatrixRoom : Closeable {
*/
fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver>
/**
* Get the permalink for the provided [eventId].
* @param eventId The event id to get the permalink for.
* @return The permalink, or a failure.
*/
suspend fun getPermalinkFor(eventId: EventId): Result<String>
override fun close() = destroy()
}

14
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.impl.room
import io.element.android.appconfig.MatrixConfiguration
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.matrix.api.core.EventId
@ -711,6 +712,19 @@ class RustMatrixRoom( @@ -711,6 +712,19 @@ class RustMatrixRoom(
)
}
override suspend fun getPermalinkFor(eventId: EventId): Result<String> {
// FIXME Use the SDK API once https://github.com/matrix-org/matrix-rust-sdk/issues/3259 has been done
// Now use a simple builder
return runCatching {
buildString {
append(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL)
append(roomId.value)
append("/")
append(eventId.value)
}
}
}
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
return runCatching {
MediaUploadHandlerImpl(files, handle())

5
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt

@ -84,6 +84,7 @@ class FakeMatrixRoom( @@ -84,6 +84,7 @@ class FakeMatrixRoom(
override val activeMemberCount: Long = 234L,
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
private var permalinkResult: () -> Result<String> = { Result.success("link") },
canRedactOwn: Boolean = false,
canRedactOther: Boolean = false,
) : MatrixRoom {
@ -273,6 +274,10 @@ class FakeMatrixRoom( @@ -273,6 +274,10 @@ class FakeMatrixRoom(
return cancelSendResult
}
override suspend fun getPermalinkFor(eventId: EventId): Result<String> {
return permalinkResult()
}
override suspend fun editMessage(
originalEventId: EventId?,
transactionId: TransactionId?,

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save