Merge pull request #4414 from element-hq/feature/bma/openTxtDocument

Open txt document inside the application
This commit is contained in:
Benoit Marty
2025-03-18 14:18:29 +01:00
committed by GitHub
27 changed files with 427 additions and 139 deletions

View File

@@ -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 {

View File

@@ -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<String>,
modifier: Modifier,
)
}

View File

@@ -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<String>,
modifier: Modifier
) {
FileContent(
lines = lines,
colorationMode = ColorationMode.None,
modifier = modifier
)
}
}

View File

@@ -0,0 +1,152 @@
/*
* 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.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
internal fun FileContent(
lines: ImmutableList<String>,
colorationMode: ColorationMode,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier
) {
if (lines.isEmpty()) {
item {
Spacer(Modifier.size(80.dp))
Text(
text = stringResource(CommonStrings.common_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(
text = line,
toastMessage = context.getString(CommonStrings.common_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)

View File

@@ -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
private fun FileContent(
lines: ImmutableList<String>,
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 {

View File

@@ -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,
)

View File

@@ -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)

View File

@@ -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 = {}
)
}

View File

@@ -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.TextFileView
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 -> TextFileView(
localMedia = localMedia,
textFileViewer = textFileViewer,
modifier = modifier,
)
mimeType == MimeTypes.Pdf -> MediaPdfView(
localMediaViewState = localMediaViewState,
localMedia = localMedia,

View File

@@ -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]

View File

@@ -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 TextFileContentProvider : PreviewParameterProvider<AsyncData<ImmutableList<String>>> {
override val values: Sequence<AsyncData<ImmutableList<String>>>
get() = sequenceOf(
AsyncData.Uninitialized,
AsyncData.Loading(),
AsyncData.Success(persistentListOf("Hello, World!")),
AsyncData.Failure(Exception("Failed to load text")),
)
}

View File

@@ -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 TextFileView(
localMedia: LocalMedia?,
textFileViewer: TextFileViewer,
modifier: Modifier = Modifier,
) {
val data = remember { mutableStateOf<AsyncData<ImmutableList<String>>>(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"))
}
}
}
TextFileContentView(
data = data.value,
textFileViewer = textFileViewer,
modifier = modifier,
)
}
@Composable
private fun TextFileContentView(
data: AsyncData<ImmutableList<String>>,
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 TextFileContentViewPreview(
@PreviewParameter(TextFileContentProvider::class) text: AsyncData<ImmutableList<String>>,
) = ElementPreview {
TextFileContentView(
data = text,
textFileViewer = { lines, modifier ->
Text(
modifier = modifier,
text = lines.firstOrNull() ?: "File content"
)
}
)
}

View File

@@ -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<MediaViewerEntryPoint.Params>()
@@ -125,6 +127,7 @@ class MediaViewerNode @AssistedInject constructor(
val state = presenter.present()
MediaViewerView(
state = state,
textFileViewer = textFileViewer,
modifier = modifier,
onBackClick = ::onDone
)

View File

@@ -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<MediaViewerState>
MediaViewerPageData.Failure(Exception("error"))
),
),
aMediaViewerState(
listOf(
aMediaViewerPageData(
downloadedMedia = AsyncData.Loading(),
mediaInfo = aTxtMediaInfo(),
)
)
),
)
}

View File

@@ -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 = {}
)
}

View File

@@ -252,6 +252,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMedia
setContent {
MediaViewerView(
state = state,
textFileViewer = { _, _ -> },
onBackClick = onBackClick,
)
}

View File

@@ -160,6 +160,7 @@
<string name="common_editing">"Editing"</string>
<string name="common_editing_caption">"Editing caption"</string>
<string name="common_emote">"* %1$s %2$s"</string>
<string name="common_empty_file">"Empty file"</string>
<string name="common_encryption">"Encryption"</string>
<string name="common_encryption_enabled">"Encryption enabled"</string>
<string name="common_enter_your_pin">"Enter your PIN"</string>
@@ -184,6 +185,7 @@ Reason: %1$s."</string>
<string name="common_invite_unknown_profile">"This Matrix ID can\'t be found, so the invite might not be received."</string>
<string name="common_leaving_room">"Leaving room"</string>
<string name="common_light">"Light"</string>
<string name="common_line_copied_to_clipboard">"Line copied to clipboard"</string>
<string name="common_link_copied_to_clipboard">"Link copied to clipboard"</string>
<string name="common_loading">"Loading…"</string>
<string name="common_loading_more">"Loading more…"</string>

View File

@@ -51,6 +51,7 @@ class KonsistClassNameTest {
.withoutName(
"AspectRatioProvider",
"OverlapRatioProvider",
"TextFileContentProvider",
)
.also {
// Check that classes are actually found