Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.wordpress.android.support.he.model

import java.io.File

sealed class VideoDownloadState {
object Idle : VideoDownloadState()
object Downloading : VideoDownloadState()
object Error : VideoDownloadState()
data class Success(val file: File) : VideoDownloadState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,23 @@ import androidx.compose.ui.window.DialogProperties
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import org.wordpress.android.R
import org.wordpress.android.support.he.ui.HESupportActivity.Companion.AUTHORIZATION_TAG
import org.wordpress.android.ui.compose.theme.AppThemeM3

@Composable
fun AttachmentFullscreenImagePreview(
imageUrl: String,
onGetAuthorizationHeaderArgument: () -> String,
onDismiss: () -> Unit,
onDownload: () -> Unit = {}
onDownload: () -> Unit = {},
) {
var scale by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }

// Cache authorization header to avoid repeated function calls during composition
val authorizationHeader = remember { onGetAuthorizationHeaderArgument() }

// Load semantics
val loadingImageDescription = stringResource(R.string.he_support_loading_image)
val attachmentImageDescription = stringResource(R.string.he_support_attachment_image)
Expand Down Expand Up @@ -90,6 +95,9 @@ fun AttachmentFullscreenImagePreview(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.crossfade(true)
.apply {
addHeader(AUTHORIZATION_TAG, authorizationHeader)
}
.build(),
contentDescription = attachmentImageDescription,
modifier = Modifier
Expand Down Expand Up @@ -181,6 +189,7 @@ private fun AttachmentFullscreenImagePreviewPreview() {
AppThemeM3(isDarkTheme = false) {
AttachmentFullscreenImagePreview(
imageUrl = "https://via.placeholder.com/800x600",
onGetAuthorizationHeaderArgument = { "" },
onDismiss = { },
onDownload = { }
)
Expand All @@ -193,6 +202,7 @@ private fun AttachmentFullscreenImagePreviewPreviewDark() {
AppThemeM3(isDarkTheme = true) {
AttachmentFullscreenImagePreview(
imageUrl = "https://via.placeholder.com/800x600",
onGetAuthorizationHeaderArgument = { "" },
onDismiss = { },
onDownload = { }
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.wordpress.android.support.he.ui

import android.view.ViewGroup
import androidx.core.net.toUri
import android.widget.FrameLayout
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -21,7 +20,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -37,67 +36,64 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.core.net.toUri
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.ui.PlayerView
import org.wordpress.android.R
import org.wordpress.android.support.he.util.VideoUrlResolver
import org.wordpress.android.support.he.model.VideoDownloadState
import org.wordpress.android.util.AppLog
import java.io.File

@Composable
fun AttachmentFullscreenVideoPlayer(
videoUrl: String,
onDismiss: () -> Unit,
onDownload: () -> Unit = {},
videoUrlResolver: VideoUrlResolver? = null
downloadState: VideoDownloadState,
onStartVideoDownload: (String) -> Unit,
onResetVideoDownloadState: () -> Unit = {},
) {
val context = LocalContext.current
var hasError by remember { mutableStateOf(false) }
var resolvedUrl by remember { mutableStateOf<String?>(null) }
var isResolving by remember { mutableStateOf(true) }
var localVideoFile by remember { mutableStateOf<File?>(null) }

// Start download when composable is first launched
LaunchedEffect(videoUrl) {
onStartVideoDownload(videoUrl)
}

// Resolve URL redirects before playing
androidx.compose.runtime.LaunchedEffect(videoUrl) {
if (videoUrlResolver != null) {
resolvedUrl = videoUrlResolver.resolveUrl(videoUrl)
} else {
resolvedUrl = videoUrl
// Update local file when download succeeds
LaunchedEffect(downloadState) {
if (downloadState is VideoDownloadState.Success) {
localVideoFile = downloadState.file
}
isResolving = false
}

val exoPlayer = remember(resolvedUrl) {
// Don't create player until URL is resolved
val url = resolvedUrl ?: return@remember null
val exoPlayer = remember(localVideoFile) {
// Don't create player until video is downloaded
val file = localVideoFile ?: return@remember null

SimpleExoPlayer.Builder(context).build().apply {
// Add error listener
// Add error listener for logging
addListener(object : Player.EventListener {
override fun onPlayerError(error: com.google.android.exoplayer2.ExoPlaybackException) {
hasError = true
AppLog.e(AppLog.T.SUPPORT, "Video playback error", error)
}
})

// Simple configuration using MediaItem
val mediaItem = MediaItem.fromUri(url.toUri())
// Play from local file
val mediaItem = MediaItem.fromUri(file.toUri())
setMediaItem(mediaItem)
prepare()
playWhenReady = true
repeatMode = Player.REPEAT_MODE_OFF
}
}

DisposableEffect(Unit) {
onDispose {
exoPlayer?.stop()
exoPlayer?.release()
}
}

Dialog(
onDismissRequest = {
exoPlayer?.stop()
onDismiss()
closeFullScreen(exoPlayer, onDismiss, onResetVideoDownloadState)
},
properties = DialogProperties(
usePlatformDefaultWidth = false,
Expand All @@ -110,15 +106,16 @@ fun AttachmentFullscreenVideoPlayer(
.fillMaxSize()
.background(Color.Black)
) {
when {
isResolving -> {
// Show loading indicator while resolving URL
when (downloadState) {
is VideoDownloadState.Idle,
is VideoDownloadState.Downloading -> {
// Show loading indicator while downloading video
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
color = Color.White
)
}
hasError -> {
is VideoDownloadState.Error -> {
// Show error message when video fails to load
Column(
modifier = Modifier
Expand Down Expand Up @@ -146,17 +143,16 @@ fun AttachmentFullscreenVideoPlayer(
)
Button(
onClick = {
exoPlayer?.stop()
onDownload()
onDismiss()
closeFullScreen(exoPlayer, onDismiss, onResetVideoDownloadState)
}
) {
Text(stringResource(R.string.he_support_download_video_button))
}
}
}
else -> {
// Show video player when URL is resolved and no error
is VideoDownloadState.Success -> {
// Show video player when video is downloaded successfully
exoPlayer?.let { player ->
AndroidView(
factory = { ctx ->
Expand Down Expand Up @@ -190,9 +186,8 @@ fun AttachmentFullscreenVideoPlayer(
// Download button
IconButton(
onClick = {
exoPlayer?.stop()
onDownload.invoke()
onDismiss.invoke()
onDownload()
closeFullScreen(exoPlayer, onDismiss, onResetVideoDownloadState)
}
) {
Icon(
Expand All @@ -206,8 +201,7 @@ fun AttachmentFullscreenVideoPlayer(
// Close button
IconButton(
onClick = {
exoPlayer?.stop()
onDismiss()
closeFullScreen(exoPlayer, onDismiss, onResetVideoDownloadState)
}
) {
Icon(
Expand All @@ -221,3 +215,14 @@ fun AttachmentFullscreenVideoPlayer(
}
}
}

fun closeFullScreen(
exoPlayer: SimpleExoPlayer?,
onDismiss: () -> Unit,
onResetVideoDownloadState: () -> Unit,
) {
exoPlayer?.stop()
exoPlayer?.release()
onDismiss()
onResetVideoDownloadState()
}
Loading