From b6bbb0bc7af543af41ae9da5867f85fd50d8598b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 14 Mar 2025 14:27:51 +0100 Subject: [PATCH 1/9] Render txt files content. PdfViewer increase the top padding. --- features/viewfolder/api/build.gradle.kts | 2 +- .../features/viewfolder/api/TextFileViewer.kt | 20 ++++ .../viewfolder/impl/DefaultTextFileViewer.kt | 33 ++++++ .../viewfolder/impl/file/ViewFileView.kt | 2 +- libraries/mediaviewer/impl/build.gradle.kts | 1 + .../impl/local/DefaultLocalMediaRenderer.kt | 6 +- .../mediaviewer/impl/local/LocalMediaView.kt | 8 ++ .../mediaviewer/impl/local/pdf/PdfViewer.kt | 3 +- .../impl/local/txt/TxtFileProvider.kt | 23 ++++ .../mediaviewer/impl/local/txt/TxtFileView.kt | 110 ++++++++++++++++++ .../impl/viewer/MediaViewerNode.kt | 3 + .../impl/viewer/MediaViewerView.kt | 8 ++ 12 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt create mode 100644 features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileProvider.kt create mode 100644 libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileView.kt diff --git a/features/viewfolder/api/build.gradle.kts b/features/viewfolder/api/build.gradle.kts index b24ad7490e..4df696b2b2 100644 --- a/features/viewfolder/api/build.gradle.kts +++ b/features/viewfolder/api/build.gradle.kts @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt new file mode 100644 index 0000000000..fa1d3511e4 --- /dev/null +++ b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.viewfolder.api + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import kotlinx.collections.immutable.ImmutableList + +fun interface TextFileViewer { + @Composable + fun Render( + lines: ImmutableList, + modifier: Modifier, + ) +} diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt new file mode 100644 index 0000000000..55743f54cc --- /dev/null +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.viewfolder.impl + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.squareup.anvil.annotations.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.di.AppScope +import kotlinx.collections.immutable.ImmutableList +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultTextFileViewer @Inject constructor() : TextFileViewer { + @Composable + override fun Render( + lines: ImmutableList, + modifier: Modifier + ) { + FileContent( + lines = lines, + colorationMode = ColorationMode.None, + modifier = modifier + ) + } +} diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt index bae8166f49..f6780504ad 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt @@ -115,7 +115,7 @@ fun ViewFileView( } @Composable -private fun FileContent( +internal fun FileContent( lines: ImmutableList, colorationMode: ColorationMode, modifier: Modifier = Modifier, diff --git a/libraries/mediaviewer/impl/build.gradle.kts b/libraries/mediaviewer/impl/build.gradle.kts index bbe984fe9c..46a9944a84 100644 --- a/libraries/mediaviewer/impl/build.gradle.kts +++ b/libraries/mediaviewer/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.flick) + implementation(projects.features.viewfolder.api) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) implementation(projects.libraries.core) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt index 8120788c5f..3d3276c612 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.libraries.di.AppScope import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMediaRenderer @@ -20,7 +21,9 @@ import me.saket.telephoto.zoomable.rememberZoomableState import javax.inject.Inject @ContributesBinding(AppScope::class) -class DefaultLocalMediaRenderer @Inject constructor() : LocalMediaRenderer { +class DefaultLocalMediaRenderer @Inject constructor( + private val textFileViewer: TextFileViewer, +) : LocalMediaRenderer { @Composable override fun Render(localMedia: LocalMedia) { val localMediaViewState = rememberLocalMediaViewState( @@ -33,6 +36,7 @@ class DefaultLocalMediaRenderer @Inject constructor() : LocalMediaRenderer { bottomPaddingInPixels = 0, localMedia = localMedia, localMediaViewState = localMediaViewState, + textFileViewer = textFileViewer, onClick = {} ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index 8752b19080..f58bd34066 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.local import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage @@ -19,6 +20,7 @@ import io.element.android.libraries.mediaviewer.impl.local.audio.MediaAudioView import io.element.android.libraries.mediaviewer.impl.local.file.MediaFileView import io.element.android.libraries.mediaviewer.impl.local.image.MediaImageView import io.element.android.libraries.mediaviewer.impl.local.pdf.MediaPdfView +import io.element.android.libraries.mediaviewer.impl.local.txt.TxtFileView import io.element.android.libraries.mediaviewer.impl.local.video.MediaVideoView @Composable @@ -26,6 +28,7 @@ fun LocalMediaView( localMedia: LocalMedia?, bottomPaddingInPixels: Int, onClick: () -> Unit, + textFileViewer: TextFileViewer, modifier: Modifier = Modifier, isDisplayed: Boolean = true, localMediaViewState: LocalMediaViewState = rememberLocalMediaViewState(), @@ -46,6 +49,11 @@ fun LocalMediaView( localMedia = localMedia, modifier = modifier, ) + mimeType == MimeTypes.PlainText -> TxtFileView( + localMedia = localMedia, + textFileViewer = textFileViewer, + modifier = modifier, + ) mimeType == MimeTypes.Pdf -> MediaPdfView( localMediaViewState = localMediaViewState, localMedia = localMedia, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt index b8b87f4605..3493c277cb 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt @@ -37,6 +37,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.roundToPx import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.mediaviewer.impl.viewer.topAppBarHeight import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import me.saket.telephoto.zoomable.zoomable @@ -126,7 +127,7 @@ private fun PdfPagesContentView( ) { // Add a fake item to the top so that the first item is not at the top of the screen. item { - Spacer(modifier = Modifier.height(80.dp)) + Spacer(modifier = Modifier.height(topAppBarHeight)) } items(pdfPages.size) { index -> val pdfPage = pdfPages[index] diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileProvider.kt new file mode 100644 index 0000000000..29b50ef85b --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.txt + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncData +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +open class TxtFileProvider : PreviewParameterProvider>> { + override val values: Sequence>> + get() = sequenceOf( + AsyncData.Uninitialized, + AsyncData.Loading(), + AsyncData.Success(persistentListOf("Hello, World!")), + AsyncData.Failure(Exception("Failed to load text")), + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileView.kt new file mode 100644 index 0000000000..461efa0dc9 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileView.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.txt + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +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 io.element.android.features.viewfolder.api.TextFileViewer +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.impl.viewer.topAppBarHeight +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +@Composable +fun TxtFileView( + localMedia: LocalMedia?, + textFileViewer: TextFileViewer, + modifier: Modifier = Modifier, +) { + val data = remember { mutableStateOf>>(AsyncData.Uninitialized) } + val context = LocalContext.current + LaunchedEffect(localMedia?.uri) { + data.value = AsyncData.Loading() + if (localMedia?.uri != null) { + // Load the file content + val result = runCatching { + context.contentResolver.openInputStream(localMedia.uri).use { + it?.bufferedReader()?.readLines()?.toList().orEmpty() + } + } + data.value = if (result.isSuccess) { + AsyncData.Success(result.getOrNull().orEmpty().toImmutableList()) + } else { + AsyncData.Failure(result.exceptionOrNull() ?: Exception("An error occurred")) + } + } + } + TxtFileViewContent( + data = data.value, + textFileViewer = textFileViewer, + modifier = modifier, + ) +} + +@Composable +private fun TxtFileViewContent( + data: AsyncData>, + textFileViewer: TextFileViewer, + modifier: Modifier = Modifier, +) { + when (data) { + AsyncData.Uninitialized, + is AsyncData.Loading -> Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + is AsyncData.Failure -> Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text(text = data.error.message ?: stringResource(id = CommonStrings.error_unknown)) + } + is AsyncData.Success -> { + textFileViewer.Render( + lines = data.data, + modifier = modifier + .fillMaxSize() + .padding(top = topAppBarHeight), + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun TxtFileViewPreview( + @PreviewParameter(TxtFileProvider::class) text: AsyncData>, +) = ElementPreview { + TxtFileViewContent( + data = text, + textFileViewer = { lines, modifier -> + Text( + modifier = modifier, + text = lines.firstOrNull() ?: "File content" + ) + } + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt index d994f374f9..44d41ee794 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt @@ -17,6 +17,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.compound.theme.ForcedDarkElementTheme +import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.libraries.architecture.inputs import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.RoomScope @@ -42,6 +43,7 @@ class MediaViewerNode @AssistedInject constructor( coroutineDispatchers: CoroutineDispatchers, systemClock: SystemClock, pagerKeysHandler: PagerKeysHandler, + private val textFileViewer: TextFileViewer, ) : Node(buildContext, plugins = plugins), MediaViewerNavigator { private val inputs = inputs() @@ -128,6 +130,7 @@ class MediaViewerNode @AssistedInject constructor( val state = presenter.present() MediaViewerView( state = state, + textFileViewer = textFileViewer, modifier = modifier, onBackClick = ::onDone ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index 5ebe8e8087..501c434674 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo @@ -83,9 +84,12 @@ import me.saket.telephoto.zoomable.OverzoomEffect import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.rememberZoomableState +val topAppBarHeight = 88.dp + @Composable fun MediaViewerView( state: MediaViewerState, + textFileViewer: TextFileViewer, onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -143,6 +147,7 @@ fun MediaViewerView( showOverlay = showOverlay, bottomPaddingInPixels = bottomPaddingInPixels, data = dataForPage, + textFileViewer = textFileViewer, onDismiss = onBackClick, onRetry = { state.eventSink(MediaViewerEvents.LoadMedia(dataForPage)) @@ -267,6 +272,7 @@ private fun MediaViewerPage( showOverlay: Boolean, bottomPaddingInPixels: Int, data: MediaViewerPageData.MediaViewerData, + textFileViewer: TextFileViewer, onDismiss: () -> Unit, onRetry: () -> Unit, onDismissError: () -> Unit, @@ -316,6 +322,7 @@ private fun MediaViewerPage( localMediaViewState = localMediaViewState, localMedia = downloadedMedia.dataOrNull(), mediaInfo = data.mediaInfo, + textFileViewer = textFileViewer, onClick = { if (playableState is PlayableState.NotPlayable) { currentOnShowOverlayChange(!currentShowOverlay) @@ -563,6 +570,7 @@ private fun ErrorView( internal fun MediaViewerViewPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = ElementPreviewDark { MediaViewerView( state = state, + textFileViewer = { _, _ -> }, onBackClick = {} ) } From c12405cb18206f89278c11ca31fb7f64d3619946 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 14 Mar 2025 16:05:05 +0000 Subject: [PATCH 2/9] Update screenshots --- ...braries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png | 3 +++ ...braries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png | 3 +++ ...braries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png | 3 +++ ...braries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png | 3 +++ ...aries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png | 3 +++ ...aries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png | 3 +++ ...aries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png | 3 +++ ...aries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png | 3 +++ 8 files changed, 24 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png new file mode 100644 index 0000000000..1b3302c89c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:615107c5e6d654779b1a8fb0ac3e5511f03ac0eac2a5c3ae0424972b92401ca2 +size 5244 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png new file mode 100644 index 0000000000..1b3302c89c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:615107c5e6d654779b1a8fb0ac3e5511f03ac0eac2a5c3ae0424972b92401ca2 +size 5244 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png new file mode 100644 index 0000000000..cfe67410f0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:783bfa52d591b0ae98ac55cf5da8c4a7d7a28275e2714ec7278d4a7947009f4d +size 6187 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png new file mode 100644 index 0000000000..a788378045 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3e94d893ac1bc9fc13806556332f6639469abc620a7617a730a8bbc9153f826 +size 7070 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png new file mode 100644 index 0000000000..cba2bf39b2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a360e21538876df4d8aa1b4a3e95e4982df6307a69df4d887416cbbd76b8cd99 +size 5250 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png new file mode 100644 index 0000000000..cba2bf39b2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a360e21538876df4d8aa1b4a3e95e4982df6307a69df4d887416cbbd76b8cd99 +size 5250 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png new file mode 100644 index 0000000000..cf3bd3a0ea --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e374bcd5e119bf944eeaf28876b7e902c1bbd2e6bbf84e0f30ace9732675b9ae +size 6142 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png new file mode 100644 index 0000000000..47cb88a813 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bd12380891a5af74a827ce14393bb5f79d9019eec7653e10ecc2923cec3c2a9 +size 6945 From 5825160d295655f200caf6725bddf214e2b831bf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 17 Mar 2025 09:29:00 +0100 Subject: [PATCH 3/9] Fix test compilation error. --- .../libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt index bfc294098b..8bd23c2089 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt @@ -252,6 +252,7 @@ private fun AndroidComposeTestRule.setMedia setContent { MediaViewerView( state = state, + textFileViewer = { _, _ -> }, onBackClick = onBackClick, ) } From ba59fe27ba62c65c1209dbc40f209a18c663cd23 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Mar 2025 11:24:13 +0100 Subject: [PATCH 4/9] Add preview for Text media. --- .../libraries/mediaviewer/api/MediaInfo.kt | 21 +++++++++++++++++++ .../impl/viewer/MediaViewerStateProvider.kt | 9 ++++++++ 2 files changed, 30 insertions(+) diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt index 7426251ca0..f5440a48b6 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt @@ -156,3 +156,24 @@ fun aVoiceMediaInfo( waveform = waveForm, duration = duration, ) + +fun aTxtMediaInfo( + filename: String = "a text file.txt", + caption: String? = null, + senderName: String? = null, + dateSent: String? = null, + dateSentFull: String? = null, +): MediaInfo = MediaInfo( + filename = filename, + caption = caption, + mimeType = MimeTypes.PlainText, + formattedFileSize = "2kB", + fileExtension = "txt", + senderId = UserId("@alice:server.org"), + senderName = senderName, + senderAvatar = null, + dateSent = dateSent, + dateSentFull = dateSentFull, + waveform = null, + duration = null, +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 95557c13d6..6686cd9fce 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.aPdfMediaInfo +import io.element.android.libraries.mediaviewer.api.aTxtMediaInfo import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo import io.element.android.libraries.mediaviewer.api.anApkMediaInfo import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo @@ -159,6 +160,14 @@ open class MediaViewerStateProvider : PreviewParameterProvider MediaViewerPageData.Failure(Exception("error")) ), ), + aMediaViewerState( + listOf( + aMediaViewerPageData( + downloadedMedia = AsyncData.Loading(), + mediaInfo = aTxtMediaInfo(), + ) + ) + ), ) } From af866e3407af897338b5ebe28df6293c5a993244 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Mar 2025 11:35:11 +0100 Subject: [PATCH 5/9] Fix issues reported by Konsist. --- .../mediaviewer/impl/local/LocalMediaView.kt | 4 ++-- ...TxtFileProvider.kt => TextFileContentProvider.kt} | 2 +- .../local/txt/{TxtFileView.kt => TextFileView.kt} | 12 ++++++------ .../android/tests/konsist/KonsistClassNameTest.kt | 1 + 4 files changed, 10 insertions(+), 9 deletions(-) rename libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/{TxtFileProvider.kt => TextFileContentProvider.kt} (89%) rename libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/{TxtFileView.kt => TextFileView.kt} (94%) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index f58bd34066..10ac3a7c2a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -20,7 +20,7 @@ import io.element.android.libraries.mediaviewer.impl.local.audio.MediaAudioView import io.element.android.libraries.mediaviewer.impl.local.file.MediaFileView import io.element.android.libraries.mediaviewer.impl.local.image.MediaImageView import io.element.android.libraries.mediaviewer.impl.local.pdf.MediaPdfView -import io.element.android.libraries.mediaviewer.impl.local.txt.TxtFileView +import io.element.android.libraries.mediaviewer.impl.local.txt.TextFileView import io.element.android.libraries.mediaviewer.impl.local.video.MediaVideoView @Composable @@ -49,7 +49,7 @@ fun LocalMediaView( localMedia = localMedia, modifier = modifier, ) - mimeType == MimeTypes.PlainText -> TxtFileView( + mimeType == MimeTypes.PlainText -> TextFileView( localMedia = localMedia, textFileViewer = textFileViewer, modifier = modifier, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileContentProvider.kt similarity index 89% rename from libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileProvider.kt rename to libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileContentProvider.kt index 29b50ef85b..00480f2d42 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileContentProvider.kt @@ -12,7 +12,7 @@ import io.element.android.libraries.architecture.AsyncData import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -open class TxtFileProvider : PreviewParameterProvider>> { +open class TextFileContentProvider : PreviewParameterProvider>> { override val values: Sequence>> get() = sequenceOf( AsyncData.Uninitialized, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt similarity index 94% rename from libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileView.kt rename to libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt index 461efa0dc9..c025ab8bed 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TxtFileView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt @@ -32,7 +32,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @Composable -fun TxtFileView( +fun TextFileView( localMedia: LocalMedia?, textFileViewer: TextFileViewer, modifier: Modifier = Modifier, @@ -55,7 +55,7 @@ fun TxtFileView( } } } - TxtFileViewContent( + TextFileContentView( data = data.value, textFileViewer = textFileViewer, modifier = modifier, @@ -63,7 +63,7 @@ fun TxtFileView( } @Composable -private fun TxtFileViewContent( +private fun TextFileContentView( data: AsyncData>, textFileViewer: TextFileViewer, modifier: Modifier = Modifier, @@ -95,10 +95,10 @@ private fun TxtFileViewContent( @PreviewsDayNight @Composable -internal fun TxtFileViewPreview( - @PreviewParameter(TxtFileProvider::class) text: AsyncData>, +internal fun TextFileContentViewPreview( + @PreviewParameter(TextFileContentProvider::class) text: AsyncData>, ) = ElementPreview { - TxtFileViewContent( + TextFileContentView( data = text, textFileViewer = { lines, modifier -> Text( diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt index b5e75b904d..020245b906 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt @@ -51,6 +51,7 @@ class KonsistClassNameTest { .withoutName( "AspectRatioProvider", "OverlapRatioProvider", + "TextFileContentProvider", ) .also { // Check that classes are actually found From e02517b9a398a7d72e365988b1dd8bde6432b662 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Mar 2025 11:38:36 +0100 Subject: [PATCH 6/9] Extract FileContent to its own file. --- .../viewfolder/impl/file/FileContent.kt | 151 ++++++++++++++++++ .../viewfolder/impl/file/ViewFileView.kt | 136 ---------------- 2 files changed, 151 insertions(+), 136 deletions(-) create mode 100644 features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt new file mode 100644 index 0000000000..1340fd3fcf --- /dev/null +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt @@ -0,0 +1,151 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.viewfolder.impl.file + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +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.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 kotlinx.collections.immutable.ImmutableList + +@Composable +internal fun FileContent( + lines: ImmutableList, + colorationMode: ColorationMode, + modifier: Modifier = Modifier, +) { + LazyColumn( + modifier = modifier + ) { + if (lines.isEmpty()) { + item { + Spacer(Modifier.size(80.dp)) + Text( + text = "Empty file", + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.tertiary, + modifier = Modifier.fillMaxWidth() + ) + } + } else { + itemsIndexed( + items = lines, + ) { index, line -> + LineRow( + lineNumber = index + 1, + line = line, + colorationMode = colorationMode, + ) + } + } + } +} + +@Composable +private fun LineRow( + lineNumber: Int, + line: String, + colorationMode: ColorationMode, +) { + val context = LocalContext.current + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = { + context.copyToClipboard( + line, + "Line copied to clipboard", + ) + }) + ) { + Text( + modifier = Modifier + .widthIn(min = 36.dp) + .padding(horizontal = 4.dp), + text = "$lineNumber", + textAlign = TextAlign.End, + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodyMdMedium, + ) + val color = ElementTheme.colors.textSecondary + val width = 0.5.dp.value + Text( + modifier = Modifier + .weight(1f) + .drawWithContent { + // Using .height(IntrinsicSize.Min) on the Row does not work well inside LazyColumn + drawLine( + color = color, + start = Offset(0f, 0f), + end = Offset(0f, size.height), + strokeWidth = width + ) + drawContent() + } + .padding(horizontal = 4.dp), + text = line, + color = line.toColor(colorationMode), + style = ElementTheme.typography.fontBodyMdRegular + ) + } +} + +/** + * Convert a line to a color. + * Ex for logcat: + * `01-23 13:14:50.740 25818 25818 D org.matrix.rust.sdk: elementx: SyncIndicator = Hide | RustRoomListService.kt:81` + * ^ use this char to determine the color + * Ex for Rust logs: + * `2024-01-26T10:22:26.947416Z WARN elementx: Restore with non-empty map | MatrixClientsHolder.kt:68` + * ^ use this char to determine the color, see [LogLevel] + */ +@Composable +private fun String.toColor(colorationMode: ColorationMode): Color { + return when (colorationMode) { + ColorationMode.Logcat -> when (getOrNull(31)) { + 'D' -> colorDebug + 'I' -> colorInfo + 'W' -> colorWarning + 'E' -> colorError + 'A' -> colorError + else -> ElementTheme.colors.textPrimary + } + ColorationMode.RustLogs -> when (getOrNull(32)) { + 'E' -> ElementTheme.colors.textPrimary + 'G' -> colorDebug + 'O' -> colorInfo + 'N' -> colorWarning + 'R' -> colorError + else -> ElementTheme.colors.textPrimary + } + ColorationMode.None -> ElementTheme.colors.textPrimary + } +} + +private val colorDebug = Color(0xFF299999) +private val colorInfo = Color(0xFFABC023) +private val colorWarning = Color(0xFFBBB529) +private val colorError = Color(0xFFFF6B68) + diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt index f6780504ad..3851a4a554 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt @@ -7,32 +7,16 @@ package io.element.android.features.viewfolder.impl.file -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable 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.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.libraries.androidutils.system.copyToClipboard import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.async.AsyncFailure import io.element.android.libraries.designsystem.components.async.AsyncLoading @@ -46,7 +30,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) @@ -114,125 +97,6 @@ fun ViewFileView( ) } -@Composable -internal fun FileContent( - lines: ImmutableList, - colorationMode: ColorationMode, - modifier: Modifier = Modifier, -) { - LazyColumn( - modifier = modifier - ) { - if (lines.isEmpty()) { - item { - Spacer(Modifier.size(80.dp)) - Text( - text = "Empty file", - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.tertiary, - modifier = Modifier.fillMaxWidth() - ) - } - } else { - itemsIndexed( - items = lines, - ) { index, line -> - LineRow( - lineNumber = index + 1, - line = line, - colorationMode = colorationMode, - ) - } - } - } -} - -@Composable -private fun LineRow( - lineNumber: Int, - line: String, - colorationMode: ColorationMode, -) { - val context = LocalContext.current - Row( - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = { - context.copyToClipboard( - line, - "Line copied to clipboard", - ) - }) - ) { - Text( - modifier = Modifier - .widthIn(min = 36.dp) - .padding(horizontal = 4.dp), - text = "$lineNumber", - textAlign = TextAlign.End, - color = ElementTheme.colors.textSecondary, - style = ElementTheme.typography.fontBodyMdMedium, - ) - val color = ElementTheme.colors.textSecondary - val width = 0.5.dp.value - Text( - modifier = Modifier - .weight(1f) - .drawWithContent { - // Using .height(IntrinsicSize.Min) on the Row does not work well inside LazyColumn - drawLine( - color = color, - start = Offset(0f, 0f), - end = Offset(0f, size.height), - strokeWidth = width - ) - drawContent() - } - .padding(horizontal = 4.dp), - text = line, - color = line.toColor(colorationMode), - style = ElementTheme.typography.fontBodyMdRegular - ) - } -} - -/** - * Convert a line to a color. - * Ex for logcat: - * `01-23 13:14:50.740 25818 25818 D org.matrix.rust.sdk: elementx: SyncIndicator = Hide | RustRoomListService.kt:81` - * ^ use this char to determine the color - * Ex for Rust logs: - * `2024-01-26T10:22:26.947416Z WARN elementx: Restore with non-empty map | MatrixClientsHolder.kt:68` - * ^ use this char to determine the color, see [LogLevel] - */ -@Composable -private fun String.toColor(colorationMode: ColorationMode): Color { - return when (colorationMode) { - ColorationMode.Logcat -> when (getOrNull(31)) { - 'D' -> colorDebug - 'I' -> colorInfo - 'W' -> colorWarning - 'E' -> colorError - 'A' -> colorError - else -> ElementTheme.colors.textPrimary - } - ColorationMode.RustLogs -> when (getOrNull(32)) { - 'E' -> ElementTheme.colors.textPrimary - 'G' -> colorDebug - 'O' -> colorInfo - 'N' -> colorWarning - 'R' -> colorError - else -> ElementTheme.colors.textPrimary - } - ColorationMode.None -> ElementTheme.colors.textPrimary - } -} - -private val colorDebug = Color(0xFF299999) -private val colorInfo = Color(0xFFABC023) -private val colorWarning = Color(0xFFBBB529) -private val colorError = Color(0xFFFF6B68) - @PreviewsDayNight @Composable internal fun ViewFileViewPreview(@PreviewParameter(ViewFileStateProvider::class) state: ViewFileState) = ElementPreview { From 2b7b2e1358bed038ca223de4fd9760e2cfadbda6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Mar 2025 11:43:13 +0100 Subject: [PATCH 7/9] i18n --- .../android/features/viewfolder/impl/file/FileContent.kt | 8 +++++--- libraries/ui-strings/src/main/res/values/localazy.xml | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt index 1340fd3fcf..612a69ed83 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt @@ -23,11 +23,13 @@ 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 @Composable @@ -43,7 +45,7 @@ internal fun FileContent( item { Spacer(Modifier.size(80.dp)) Text( - text = "Empty file", + text = stringResource(CommonStrings.common_empty_file), textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.tertiary, modifier = Modifier.fillMaxWidth() @@ -75,8 +77,8 @@ private fun LineRow( .fillMaxWidth() .clickable(onClick = { context.copyToClipboard( - line, - "Line copied to clipboard", + text = line, + toastMessage = context.getString(CommonStrings.common_line_copied_to_clipboard), ) }) ) { diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 1b50a9e72c..b842315b7e 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -160,6 +160,7 @@ "Editing" "Editing caption" "* %1$s %2$s" + "Empty file" "Encryption" "Encryption enabled" "Enter your PIN" @@ -184,6 +185,7 @@ Reason: %1$s." "This Matrix ID can\'t be found, so the invite might not be received." "Leaving room" "Light" + "Line copied to clipboard" "Link copied to clipboard" "Loading…" "Loading more…" From c88501046b60b63e4f94736591b14226a0fcb5cb Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 18 Mar 2025 10:57:59 +0000 Subject: [PATCH 8/9] Update screenshots --- ...ediaviewer.impl.local.txt_TextFileContentView_Day_0_en.png} | 0 ...ediaviewer.impl.local.txt_TextFileContentView_Day_1_en.png} | 0 ...ediaviewer.impl.local.txt_TextFileContentView_Day_2_en.png} | 0 ...ediaviewer.impl.local.txt_TextFileContentView_Day_3_en.png} | 0 ...iaviewer.impl.local.txt_TextFileContentView_Night_0_en.png} | 0 ...iaviewer.impl.local.txt_TextFileContentView_Night_1_en.png} | 0 ...iaviewer.impl.local.txt_TextFileContentView_Night_2_en.png} | 0 ...iaviewer.impl.local.txt_TextFileContentView_Night_3_en.png} | 0 ...libraries.mediaviewer.impl.viewer_MediaViewerView_16_en.png | 3 +++ 9 files changed, 3 insertions(+) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_0_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_1_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_2_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_3_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_0_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_1_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_2_en.png} (100%) rename tests/uitests/src/test/snapshots/images/{libraries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png => libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_3_en.png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_16_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_1_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_2_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_2_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_2_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_3_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Day_3_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_1_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_2_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_2_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_2_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_3_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TxtFileView_Night_3_en.png rename to tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_16_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_16_en.png new file mode 100644 index 0000000000..504244640a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_16_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dec2e6f9dcfc2e92fef730a26599d6a4e2e09b6b9999dc912a82917f65908417 +size 6798 From 4d5f63f9f024666c021cecb0d38467ec4fad87b8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Mar 2025 12:30:40 +0100 Subject: [PATCH 9/9] Remove blank line --- .../element/android/features/viewfolder/impl/file/FileContent.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt index 612a69ed83..8a233d4506 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt @@ -150,4 +150,3 @@ private val colorDebug = Color(0xFF299999) private val colorInfo = Color(0xFFABC023) private val colorWarning = Color(0xFFBBB529) private val colorError = Color(0xFFFF6B68) -