Merge branch 'main' into wallet
# Conflicts: # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt # features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt # libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt
This commit is contained in:
commit
0ef6b69a79
912 changed files with 17051 additions and 4425 deletions
|
|
@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.width
|
|||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -51,6 +52,7 @@ import androidx.media3.common.Timeline
|
|||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.bumble.appyx.core.node.LocalNodeTargetVisibility
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.audio.api.AudioFocus
|
||||
|
|
@ -130,6 +132,8 @@ private fun ExoPlayerMediaAudioView(
|
|||
mutableStateOf(null)
|
||||
}
|
||||
|
||||
val isTargetVisible = LocalNodeTargetVisibility.current
|
||||
|
||||
val playableState: PlayableState.Playable by remember {
|
||||
derivedStateOf {
|
||||
PlayableState.Playable(
|
||||
|
|
@ -196,13 +200,21 @@ private fun ExoPlayerMediaAudioView(
|
|||
exoPlayer.pause()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(isTargetVisible) {
|
||||
if (!isTargetVisible) {
|
||||
exoPlayer.pause()
|
||||
}
|
||||
}
|
||||
if (localMedia?.uri != null) {
|
||||
LaunchedEffect(localMedia.uri) {
|
||||
val mediaItem = MediaItem.fromUri(localMedia.uri)
|
||||
exoPlayer.setMediaItem(mediaItem)
|
||||
exoPlayer.prepare()
|
||||
}
|
||||
} else {
|
||||
exoPlayer.setMediaItems(emptyList())
|
||||
LaunchedEffect(Unit) {
|
||||
exoPlayer.setMediaItems(emptyList())
|
||||
}
|
||||
}
|
||||
val context = LocalContext.current
|
||||
val waveform = info?.waveform
|
||||
|
|
@ -247,7 +259,7 @@ private fun ExoPlayerMediaAudioView(
|
|||
}
|
||||
},
|
||||
update = { playerView ->
|
||||
playerView.isVisible = metadata.hasArtwork()
|
||||
playerView.isVisible = metadata.hasArtwork() && isTargetVisible
|
||||
},
|
||||
onRelease = { playerView ->
|
||||
playerView.player = null
|
||||
|
|
@ -317,16 +329,19 @@ private fun ExoPlayerMediaAudioView(
|
|||
)
|
||||
}
|
||||
|
||||
OnLifecycleEvent { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_CREATE -> exoPlayer.addListener(playerListener)
|
||||
Lifecycle.Event.ON_RESUME -> exoPlayer.prepare()
|
||||
Lifecycle.Event.ON_PAUSE -> exoPlayer.pause()
|
||||
Lifecycle.Event.ON_DESTROY -> {
|
||||
exoPlayer.release()
|
||||
DisposableEffect(exoPlayer) {
|
||||
exoPlayer.addListener(playerListener)
|
||||
onDispose {
|
||||
if (!exoPlayer.isReleased) {
|
||||
exoPlayer.removeListener(playerListener)
|
||||
exoPlayer.release()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
OnLifecycleEvent { _, event ->
|
||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||
exoPlayer.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
|||
import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState
|
||||
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage
|
||||
import me.saket.telephoto.zoomable.coil3.ZoomableAsyncImage
|
||||
import me.saket.telephoto.zoomable.rememberZoomableImageState
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ import io.element.android.libraries.mediaviewer.impl.details.aMediaDeleteConfirm
|
|||
import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
private const val LONG_CAPTION = "This is a very long caption that should be scrollable in the media viewer. " +
|
||||
"It contains multiple lines of text to demonstrate the scrolling behavior. " +
|
||||
"Line 1: Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
|
||||
"Line 2: Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " +
|
||||
"Line 3: Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. " +
|
||||
"Line 4: Duis aute irure dolor in reprehenderit in voluptate velit esse cillum. " +
|
||||
"Line 5: Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia."
|
||||
|
||||
open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> {
|
||||
override val values: Sequence<MediaViewerState>
|
||||
get() = sequenceOf(
|
||||
|
|
@ -170,6 +178,22 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
|
|||
)
|
||||
)
|
||||
),
|
||||
anImageMediaInfo(
|
||||
senderName = "Alice",
|
||||
dateSent = "21 NOV, 2024",
|
||||
caption = LONG_CAPTION,
|
||||
).let {
|
||||
aMediaViewerState(
|
||||
listOf(
|
||||
aMediaViewerPageData(
|
||||
downloadedMedia = AsyncData.Success(
|
||||
LocalMedia(Uri.EMPTY, it)
|
||||
),
|
||||
mediaInfo = it,
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,18 +17,24 @@ import androidx.compose.animation.fadeOut
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -39,14 +45,17 @@ import androidx.compose.runtime.snapshotFlow
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -69,6 +78,7 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
|
|||
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.designsystem.utils.hasCompactHeightWindowSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
|
|
@ -102,8 +112,9 @@ fun MediaViewerView(
|
|||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
var showOverlay by remember { mutableStateOf(true) }
|
||||
|
||||
val defaultBottomPaddingInPixels = if (LocalInspectionMode.current) 303 else 0
|
||||
val currentData = state.listData.getOrNull(state.currentIndex)
|
||||
val defaultBottomPaddingInPixels = if (LocalInspectionMode.current && !hasCompactHeightWindowSize()) 303 else 0
|
||||
|
||||
BackHandler { onBackClick() }
|
||||
Scaffold(
|
||||
modifier,
|
||||
|
|
@ -153,10 +164,11 @@ fun MediaViewerView(
|
|||
// So we need to update this value only when the `settledPage` value changes. It seems like a bug that needs to be fixed in Compose.
|
||||
page == pagerState.settledPage
|
||||
}
|
||||
val navigationBarPadding = WindowInsets.navigationBars.getBottom(LocalDensity.current)
|
||||
MediaViewerPage(
|
||||
isDisplayed = isDisplayed,
|
||||
showOverlay = showOverlay,
|
||||
bottomPaddingInPixels = bottomPaddingInPixels,
|
||||
bottomPaddingInPixels = (bottomPaddingInPixels - navigationBarPadding).coerceAtLeast(0),
|
||||
data = dataForPage,
|
||||
textFileViewer = textFileViewer,
|
||||
onDismiss = onBackClick,
|
||||
|
|
@ -175,9 +187,7 @@ fun MediaViewerView(
|
|||
// Bottom bar
|
||||
AnimatedVisibility(visible = showOverlay, enter = fadeIn(), exit = fadeOut()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding()
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
MediaViewerBottomBar(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
|
|
@ -538,19 +548,46 @@ private fun MediaViewerBottomBar(
|
|||
if (showDivider) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
Text(
|
||||
val scrollState = rememberScrollState()
|
||||
val showBottomShadow by remember { derivedStateOf { scrollState.value < scrollState.maxValue } }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
text = caption,
|
||||
maxLines = 5,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
.heightIn(max = if (hasCompactHeightWindowSize()) maxCaptionHeightLandscape else maxCaptionHeightPortrait),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.verticalScroll(scrollState)
|
||||
.navigationBarsPadding(),
|
||||
text = caption,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
if (showBottomShadow) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp)
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
bgCanvasWithTransparency,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val maxCaptionHeightPortrait = 200.dp
|
||||
private val maxCaptionHeightLandscape = 128.dp
|
||||
|
||||
@Composable
|
||||
private fun ThumbnailView(
|
||||
thumbnailSource: MediaSource?,
|
||||
|
|
@ -604,3 +641,14 @@ internal fun MediaViewerViewPreview(@PreviewParameter(MediaViewerStateProvider::
|
|||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(device = "${Devices.PHONE}, orientation=landscape")
|
||||
@Composable
|
||||
internal fun MediaViewerViewLandscapePreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = ElementPreviewDark {
|
||||
MediaViewerView(
|
||||
state = state,
|
||||
audioFocus = null,
|
||||
textFileViewer = { _, _ -> },
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_media_browser_delete_confirmation_subtitle">"このファイルはルームから削除され、他のユーザーは確認することができなくなります。"</string>
|
||||
<string name="screen_media_browser_delete_confirmation_title">"ファイルを削除しますか?"</string>
|
||||
<string name="screen_media_browser_download_error_message">"インターネット接続を確認した上、再度お試しください。"</string>
|
||||
<string name="screen_media_browser_files_empty_state_subtitle">"このルームに投稿された文書ファイルや音声ファイル・メッセージはここに表示されます。"</string>
|
||||
<string name="screen_media_browser_files_empty_state_title">"アップロードされたファイルはありません"</string>
|
||||
<string name="screen_media_browser_list_loading_files">"ファイルを読み込み中…"</string>
|
||||
<string name="screen_media_browser_list_loading_media">"メディアを読み込み中…"</string>
|
||||
<string name="screen_media_browser_list_mode_files">"ファイル"</string>
|
||||
<string name="screen_media_browser_list_mode_media">"メディア"</string>
|
||||
<string name="screen_media_browser_media_empty_state_subtitle">"このルームに投稿された画像と動画はここに表示されます。"</string>
|
||||
<string name="screen_media_browser_media_empty_state_title">"アップロードされたメディアはありません"</string>
|
||||
<string name="screen_media_browser_title">"ファイルとメディア"</string>
|
||||
<string name="screen_media_details_file_format">"ファイル形式"</string>
|
||||
<string name="screen_media_details_filename">"ファイル名"</string>
|
||||
<string name="screen_media_details_no_more_files_to_show">"これ以上ファイルはありません"</string>
|
||||
<string name="screen_media_details_no_more_media_to_show">"これ以上メディアはありません"</string>
|
||||
<string name="screen_media_details_uploaded_by">"アップロード元"</string>
|
||||
<string name="screen_media_details_uploaded_on">"アップロード先"</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue