diff --git a/app/src/main/java/com/nextcloud/talk/chat/OverflowMenu.kt b/app/src/main/java/com/nextcloud/talk/chat/OverflowMenu.kt index fc403f60b7a..fed5ae7824a 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/OverflowMenu.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/OverflowMenu.kt @@ -35,8 +35,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import com.nextcloud.talk.R -data class MenuItemData(val title: String, val subtitle: String? = null, val icon: Int? = null, val onClick: () -> Unit) - @Composable fun OverflowMenu(anchor: View?, expanded: Boolean, items: List, onDismiss: () -> Unit) { if (!expanded) return @@ -110,6 +108,8 @@ fun DynamicMenuItem(item: MenuItemData) { } } +data class MenuItemData(val title: String, val subtitle: String? = null, val icon: Int? = null, val onClick: () -> Unit) + private fun View.boundsInWindow(): android.graphics.Rect { val location = IntArray(2) getLocationOnScreen(location) diff --git a/app/src/main/java/com/nextcloud/talk/components/StandardAppBar.kt b/app/src/main/java/com/nextcloud/talk/components/StandardAppBar.kt index b06a68d38b3..d6539b94742 100644 --- a/app/src/main/java/com/nextcloud/talk/components/StandardAppBar.kt +++ b/app/src/main/java/com/nextcloud/talk/components/StandardAppBar.kt @@ -17,6 +17,9 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -32,13 +35,18 @@ import com.nextcloud.talk.R @OptIn(ExperimentalMaterial3Api::class) @Composable -fun StandardAppBar(title: String, menuItems: List Unit>>?) { +fun StandardAppBar( + title: String, + menuItems: List Unit>>?, + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors() +) { val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher var expanded by remember { mutableStateOf(false) } TopAppBar( - title = { Text(text = title) }, + title = { Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis) }, + colors = colors, navigationIcon = { IconButton( onClick = { backDispatcher?.onBackPressed() } diff --git a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenImageActivity.kt b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenImageActivity.kt index 17b6e0b01b3..def7a442c65 100644 --- a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenImageActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenImageActivity.kt @@ -12,159 +12,92 @@ package com.nextcloud.talk.fullscreenfile import android.content.Intent import android.os.Bundle -import android.util.Log -import com.nextcloud.talk.ui.SwipeToCloseLayout -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup.MarginLayoutParams +import android.widget.FrameLayout import androidx.appcompat.app.AppCompatActivity +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.FileProvider -import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding import androidx.fragment.app.DialogFragment import autodagger.AutoInjector import com.google.android.material.snackbar.Snackbar import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.databinding.ActivityFullScreenImageBinding +import com.nextcloud.talk.ui.SwipeToCloseLayout import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment -import com.nextcloud.talk.utils.BitmapShrinker +import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC -import pl.droidsonroids.gif.GifDrawable import java.io.File +import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) class FullScreenImageActivity : AppCompatActivity() { - lateinit var binding: ActivityFullScreenImageBinding - private lateinit var windowInsetsController: WindowInsetsControllerCompat - private lateinit var path: String - private var showFullscreen = false - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_preview, menu) - return true - } - override fun onOptionsItemSelected(item: MenuItem): Boolean = - when (item.itemId) { - android.R.id.home -> { - onBackPressedDispatcher.onBackPressed() - true - } - - R.id.share -> { - val shareUri = FileProvider.getUriForFile( - this, - BuildConfig.APPLICATION_ID, - File(path) - ) - - val shareIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, shareUri) - type = IMAGE_PREFIX_GENERIC - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to))) - - true - } + @Inject + lateinit var viewThemeUtils: ViewThemeUtils - R.id.save -> { - val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance( - intent.getStringExtra("FILE_NAME").toString() - ) - saveFragment.show( - supportFragmentManager, - SaveToStorageDialogFragment.TAG - ) - true - } - - else -> { - super.onOptionsItemSelected(item) - } - } + private lateinit var windowInsetsController: WindowInsetsControllerCompat + private lateinit var path: String + private lateinit var fileName: String + private lateinit var swipeToCloseLayout: SwipeToCloseLayout + private var showFullscreen by mutableStateOf(false) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - binding = ActivityFullScreenImageBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.imageviewToolbar) - WindowCompat.setDecorFitsSystemWindows(window, false) - initWindowInsetsController() - applyWindowInsets() - binding.photoView.setOnPhotoTapListener { _, _, _ -> - toggleFullscreen() - } - binding.photoView.setOnOutsidePhotoTapListener { - toggleFullscreen() - } - binding.gifView.setOnClickListener { - toggleFullscreen() - } - - // Enable enlarging the image more than default 3x maximumScale. - // Medium scale adapted to make double-tap behaviour more consistent. - binding.photoView.maximumScale = MAX_SCALE - binding.photoView.mediumScale = MEDIUM_SCALE - - val fileName = intent.getStringExtra("FILE_NAME") + fileName = intent.getStringExtra("FILE_NAME").orEmpty() val isGif = intent.getBooleanExtra("IS_GIF", false) - - supportActionBar?.title = fileName - supportActionBar?.setDisplayHomeAsUpEnabled(true) - path = applicationContext.cacheDir.absolutePath + "/" + fileName - if (isGif) { - binding.photoView.visibility = View.INVISIBLE - binding.gifView.visibility = View.VISIBLE - val gifFromUri = GifDrawable(path) - binding.gifView.setImageDrawable(gifFromUri) - } else { - binding.gifView.visibility = View.INVISIBLE - binding.photoView.visibility = View.VISIBLE - displayImage(path) - } - binding.swipeToCloseLayout.setOnSwipeToCloseListener(object : SwipeToCloseLayout.OnSwipeToCloseListener { + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT), + navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT) + ) + initWindowInsetsController() + + swipeToCloseLayout = SwipeToCloseLayout(this) + swipeToCloseLayout.setOnSwipeToCloseListener(object : SwipeToCloseLayout.OnSwipeToCloseListener { override fun onSwipeToClose() { finish() } }) - } - private fun displayImage(path: String) { - val displayMetrics = applicationContext.resources.displayMetrics - val doubleScreenWidth = displayMetrics.widthPixels * 2 - val doubleScreenHeight = displayMetrics.heightPixels * 2 - - val bitmap = BitmapShrinker.shrinkBitmap(path, doubleScreenWidth, doubleScreenHeight) - - if (bitmap == null) { - Log.e(TAG, "bitmap could not be decoded from path: $path") - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - return + val composeView = ComposeView(this).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + val colorScheme = viewThemeUtils.getColorScheme(this@FullScreenImageActivity) + MaterialTheme(colorScheme = colorScheme) { + FullScreenImageScreen( + title = fileName, + isGif = isGif, + imagePath = path, + showFullscreen = showFullscreen, + actions = FullScreenImageActions( + onShare = { shareFile() }, + onSave = { showSaveDialog() }, + onToggleFullscreen = { toggleFullscreen() }, + onBitmapError = { showBitmapError() } + ) + ) + } + } } - val bitmapSize: Int = bitmap.byteCount - - // info that 100MB is the limit comes from https://stackoverflow.com/a/53334563 - if (bitmapSize > HUNDRED_MB) { - Log.e(TAG, "bitmap will be too large to display. It won't be displayed to avoid RuntimeException") - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - } else { - binding.photoView.setImageBitmap(bitmap) - } + swipeToCloseLayout.addView( + composeView, + FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) + ) + setContentView(swipeToCloseLayout) } private fun toggleFullscreen() { @@ -183,30 +116,29 @@ class FullScreenImageActivity : AppCompatActivity() { private fun enterImmersiveMode() { windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) - supportActionBar?.hide() } private fun exitImmersiveMode() { windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) - supportActionBar?.show() } - private fun applyWindowInsets() { - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> - val insets = - windowInsets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()) - binding.imageviewToolbar.updateLayoutParams { - topMargin = insets.top - } - binding.imageviewToolbar.updatePadding(left = insets.left, right = insets.right) - WindowInsetsCompat.CONSUMED + private fun shareFile() { + val shareUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, File(path)) + val shareIntent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, shareUri) + type = IMAGE_PREFIX_GENERIC + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } + startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to))) + } + + private fun showSaveDialog() { + val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance(fileName) + saveFragment.show(supportFragmentManager, SaveToStorageDialogFragment.TAG) } - companion object { - private const val TAG = "FullScreenImageActivity" - private const val HUNDRED_MB = 100 * 1024 * 1024 - private const val MAX_SCALE = 6.0f - private const val MEDIUM_SCALE = 2.45f + private fun showBitmapError() { + Snackbar.make(swipeToCloseLayout, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() } } diff --git a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenImageScreen.kt b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenImageScreen.kt new file mode 100644 index 00000000000..21f91b44410 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenImageScreen.kt @@ -0,0 +1,185 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2021 Andy Scherzinger + * SPDX-FileCopyrightText: 2021 Marcel Hibbe + * SPDX-FileCopyrightText: 2021 Dariusz Olszewski + * SPDX-FileCopyrightText: 2026 Andy Scherzinger + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.fullscreenfile + +import android.content.res.Configuration +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.viewinterop.AndroidView +import com.github.chrisbanes.photoview.PhotoView +import com.nextcloud.talk.R +import com.nextcloud.talk.components.StandardAppBar +import com.nextcloud.talk.utils.BitmapShrinker +import pl.droidsonroids.gif.GifDrawable +import pl.droidsonroids.gif.GifImageView + +private const val TAG = "FullScreenImageScreen" +private const val MAX_SCALE = 6.0f +private const val MEDIUM_SCALE = 2.45f +private const val HUNDRED_MB = 100 * 1024 * 1024 +private const val TOOLBAR_ALPHA = 0.5f + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FullScreenImageScreen( + title: String, + isGif: Boolean, + imagePath: String, + showFullscreen: Boolean, + actions: FullScreenImageActions +) { + val toolbarColors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + titleContentColor = Color.White, + navigationIconContentColor = Color.White, + actionIconContentColor = Color.White + ) + + Box(modifier = Modifier.fillMaxSize().background(Color.Black)) { + if (isGif) { + GifView(imagePath = imagePath, onToggleFullscreen = actions.onToggleFullscreen) + } else { + PhotoImageView( + imagePath = imagePath, + onToggleFullscreen = actions.onToggleFullscreen, + onBitmapError = actions.onBitmapError + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .windowInsetsBottomHeight(WindowInsets.navigationBars) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, Color.Black.copy(alpha = TOOLBAR_ALPHA)) + ) + ) + .align(Alignment.BottomCenter) + ) + + if (!showFullscreen) { + val menuItems = buildList { + add(stringResource(R.string.share) to actions.onShare) + add(stringResource(R.string.nc_save_message) to actions.onSave) + } + Box { + Box( + modifier = Modifier + .matchParentSize() + .background( + Brush.verticalGradient( + colors = listOf(Color.Black.copy(alpha = TOOLBAR_ALPHA), Color.Transparent) + ) + ) + ) + StandardAppBar(title = title, menuItems = menuItems, colors = toolbarColors) + } + } + } +} + +@Composable +private fun GifView(imagePath: String, onToggleFullscreen: () -> Unit) { + AndroidView( + factory = { ctx -> + GifImageView(ctx).apply { + setImageDrawable(GifDrawable(imagePath)) + setOnClickListener { onToggleFullscreen() } + } + }, + modifier = Modifier.fillMaxSize() + ) +} + +@Composable +private fun PhotoImageView(imagePath: String, onToggleFullscreen: () -> Unit, onBitmapError: () -> Unit) { + AndroidView( + factory = { ctx -> + PhotoView(ctx).apply { + maximumScale = MAX_SCALE + mediumScale = MEDIUM_SCALE + setOnPhotoTapListener { _, _, _ -> onToggleFullscreen() } + setOnOutsidePhotoTapListener { onToggleFullscreen() } + val displayMetrics = ctx.resources.displayMetrics + val bitmap = BitmapShrinker.shrinkBitmap( + imagePath, + displayMetrics.widthPixels * 2, + displayMetrics.heightPixels * 2 + ) + when { + bitmap == null -> { + Log.e(TAG, "bitmap could not be decoded from path: $imagePath") + onBitmapError() + } + bitmap.byteCount > HUNDRED_MB -> { + Log.e(TAG, "bitmap too large to display, skipping to avoid RuntimeException") + onBitmapError() + } + else -> setImageBitmap(bitmap) + } + } + }, + modifier = Modifier.fillMaxSize() + ) +} + +data class FullScreenImageActions( + val onShare: () -> Unit, + val onSave: () -> Unit, + val onToggleFullscreen: () -> Unit, + val onBitmapError: () -> Unit +) + +@Preview(name = "Light", showBackground = true) +@Composable +private fun PreviewFullScreenImageLight() { + MaterialTheme(colorScheme = lightColorScheme()) { + FullScreenImageScreen( + title = "image.jpg", + isGif = false, + imagePath = "", + showFullscreen = false, + actions = FullScreenImageActions(onShare = {}, onSave = {}, onToggleFullscreen = {}, onBitmapError = {}) + ) + } +} + +@Preview(name = "Dark - RTL Arabic", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, locale = "ar") +@Composable +private fun PreviewFullScreenImageDarkRtl() { + MaterialTheme(colorScheme = darkColorScheme()) { + FullScreenImageScreen( + title = "صورة.jpg", + isGif = false, + imagePath = "", + showFullscreen = false, + actions = FullScreenImageActions(onShare = {}, onSave = {}, onToggleFullscreen = {}, onBitmapError = {}) + ) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt index 0ccf3e64774..1d15732e431 100644 --- a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaActivity.kt @@ -12,34 +12,33 @@ package com.nextcloud.talk.fullscreenfile import android.content.Intent import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.ViewGroup.MarginLayoutParams import android.view.WindowManager import android.widget.FrameLayout +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.FileProvider import androidx.core.net.toUri -import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat -import androidx.core.view.marginBottom -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding import androidx.fragment.app.DialogFragment import androidx.media3.common.AudioAttributes import androidx.media3.common.MediaItem import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.ui.DefaultTimeBar -import androidx.media3.ui.PlayerView import autodagger.AutoInjector import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.databinding.ActivityFullScreenMediaBinding import com.nextcloud.talk.ui.SwipeToCloseLayout import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment import com.nextcloud.talk.utils.Mimetype.VIDEO_PREFIX_GENERIC @@ -47,102 +46,60 @@ import java.io.File @AutoInjector(NextcloudTalkApplication::class) class FullScreenMediaActivity : AppCompatActivity() { - lateinit var binding: ActivityFullScreenMediaBinding private lateinit var path: String - private var player: ExoPlayer? = null - + private lateinit var fileName: String + private var player: ExoPlayer? by mutableStateOf(null) private var playWhenReadyState: Boolean = true private var playBackPosition: Long = 0L private lateinit var windowInsetsController: WindowInsetsControllerCompat - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_preview, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean = - when (item.itemId) { - android.R.id.home -> { - onBackPressedDispatcher.onBackPressed() - true - } - - R.id.share -> { - val shareUri = FileProvider.getUriForFile( - this, - BuildConfig.APPLICATION_ID, - File(path) - ) - - val shareIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, shareUri) - type = VIDEO_PREFIX_GENERIC - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to))) - - true - } - - R.id.save -> { - val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance( - intent.getStringExtra("FILE_NAME").toString() - ) - saveFragment.show( - supportFragmentManager, - SaveToStorageDialogFragment.TAG - ) - true - } - - else -> { - super.onOptionsItemSelected(item) - } - } - - @OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) - val fileName = intent.getStringExtra("FILE_NAME") - val isAudioOnly = intent.getBooleanExtra("AUDIO_ONLY", false) + fileName = intent.getStringExtra("FILE_NAME").orEmpty() + val isAudioOnly = intent.getBooleanExtra("AUDIO_ONLY", false) path = applicationContext.cacheDir.absolutePath + "/" + fileName - binding = ActivityFullScreenMediaBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.mediaviewToolbar) - supportActionBar?.title = fileName - supportActionBar?.setDisplayHomeAsUpEnabled(true) - - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - - binding.playerView.showController() - if (isAudioOnly) { - binding.playerView.controllerShowTimeoutMs = 0 - } - - initWindowInsetsController() - applyWindowInsets() - - binding.playerView.setControllerVisibilityListener( - PlayerView.ControllerVisibilityListener { v -> - if (v != 0) { - enterImmersiveMode() - } else { - exitImmersiveMode() - } - } + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT), + navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT) ) + initWindowInsetsController() - binding.swipeToCloseLayout.setOnSwipeToCloseListener(object : SwipeToCloseLayout.OnSwipeToCloseListener { + val swipeToCloseLayout = SwipeToCloseLayout(this) + swipeToCloseLayout.setOnSwipeToCloseListener(object : SwipeToCloseLayout.OnSwipeToCloseListener { override fun onSwipeToClose() { finish() } }) + + val composeView = ComposeView(this).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme(colorScheme = darkColorScheme()) { + FullScreenMediaScreen( + title = fileName, + player = player, + isAudioOnly = isAudioOnly, + actions = FullScreenMediaActions( + onShare = { shareFile() }, + onSave = { showSaveDialog() }, + onEnterImmersive = { enterImmersiveMode() }, + onExitImmersive = { exitImmersiveMode() } + ) + ) + } + } + } + + swipeToCloseLayout.addView( + composeView, + FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) + ) + setContentView(swipeToCloseLayout) + + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } override fun onStart() { @@ -156,12 +113,12 @@ class FullScreenMediaActivity : AppCompatActivity() { releasePlayer() } + @OptIn(UnstableApi::class) private fun initializePlayer() { player = ExoPlayer.Builder(applicationContext) .setAudioAttributes(AudioAttributes.DEFAULT, true) .setHandleAudioBecomingNoisy(true) .build() - binding.playerView.player = player } private fun preparePlayer() { @@ -190,39 +147,25 @@ class FullScreenMediaActivity : AppCompatActivity() { private fun enterImmersiveMode() { windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) - supportActionBar?.hide() } private fun exitImmersiveMode() { windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) - supportActionBar?.show() } - @OptIn(UnstableApi::class) - private fun applyWindowInsets() { - val playerView = binding.playerView - val exoControls = playerView.findViewById(R.id.exo_bottom_bar) - val exoProgress = playerView.findViewById(R.id.exo_progress) - val progressBottomMargin = exoProgress.marginBottom - - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> - val insets = windowInsets.getInsets( - WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type - .displayCutout() - ) - binding.mediaviewToolbar.updateLayoutParams { - topMargin = insets.top - } - exoControls.updateLayoutParams { - bottomMargin = insets.bottom - } - exoProgress.updateLayoutParams { - bottomMargin = insets.bottom + progressBottomMargin - } - exoControls.updatePadding(left = insets.left, right = insets.right) - exoProgress.updatePadding(left = insets.left, right = insets.right) - binding.mediaviewToolbar.updatePadding(left = insets.left, right = insets.right) - WindowInsetsCompat.CONSUMED + private fun shareFile() { + val shareUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, File(path)) + val shareIntent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, shareUri) + type = VIDEO_PREFIX_GENERIC + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } + startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to))) + } + + private fun showSaveDialog() { + val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance(fileName) + saveFragment.show(supportFragmentManager, SaveToStorageDialogFragment.TAG) } } diff --git a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaScreen.kt b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaScreen.kt new file mode 100644 index 00000000000..e5ce478b073 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenMediaScreen.kt @@ -0,0 +1,216 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2021 Andy Scherzinger + * SPDX-FileCopyrightText: 2021 Marcel Hibbe + * SPDX-FileCopyrightText: 2023 Parneet Singh + * SPDX-FileCopyrightText: 2023 Ezhil Shanmugham + * SPDX-FileCopyrightText: 2026 Enrique López-Mañas + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.fullscreenfile + +import android.content.res.Configuration +import android.view.View +import android.view.ViewGroup.MarginLayoutParams +import android.widget.FrameLayout +import androidx.annotation.OptIn +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.ui.DefaultTimeBar +import androidx.media3.ui.PlayerView +import com.nextcloud.talk.R +import com.nextcloud.talk.components.StandardAppBar + +private const val TOOLBAR_ALPHA = 0.5f + +@OptIn(UnstableApi::class, ExperimentalMaterial3Api::class) +@Composable +fun FullScreenMediaScreen(title: String, player: ExoPlayer?, isAudioOnly: Boolean, actions: FullScreenMediaActions) { + val toolbarColors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + titleContentColor = Color.White, + navigationIconContentColor = Color.White, + actionIconContentColor = Color.White + ) + + var showToolbar by remember { mutableStateOf(true) } + + Box(modifier = Modifier.fillMaxSize().background(Color.Black)) { + MediaPlayerView( + player = player, + isAudioOnly = isAudioOnly, + onControllerVisible = { + showToolbar = true + actions.onExitImmersive() + }, + onControllerHidden = { + showToolbar = false + actions.onEnterImmersive() + } + ) + + BottomGradient(modifier = Modifier.align(Alignment.BottomCenter)) + + if (showToolbar) { + ToolbarOverlay(title = title, toolbarColors = toolbarColors, actions = actions) + } + } +} + +@OptIn(UnstableApi::class) +@Composable +private fun MediaPlayerView( + player: ExoPlayer?, + isAudioOnly: Boolean, + onControllerVisible: () -> Unit, + onControllerHidden: () -> Unit +) { + if (LocalInspectionMode.current) { + Box(modifier = Modifier.fillMaxSize()) + return + } + + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current + val bottomPx = WindowInsets.systemBars.getBottom(density) + val leftPx = WindowInsets.systemBars.getLeft(density, layoutDirection) + val rightPx = WindowInsets.systemBars.getRight(density, layoutDirection) + val originalProgressMarginBottom = remember { intArrayOf(-1) } + + AndroidView( + factory = { ctx -> + PlayerView(ctx).apply { + showController() + if (isAudioOnly) { + controllerShowTimeoutMs = 0 + } + setControllerVisibilityListener( + PlayerView.ControllerVisibilityListener { visibility -> + if (visibility == View.VISIBLE) onControllerVisible() else onControllerHidden() + } + ) + } + }, + update = { playerView -> + playerView.player = player + val exoControls = playerView.findViewById(R.id.exo_bottom_bar) + val exoProgress = playerView.findViewById(R.id.exo_progress) + exoControls?.apply { + updateLayoutParams { bottomMargin = bottomPx } + updatePadding(left = leftPx, right = rightPx) + } + exoProgress?.apply { + if (originalProgressMarginBottom[0] < 0) { + originalProgressMarginBottom[0] = + (layoutParams as? MarginLayoutParams)?.bottomMargin ?: 0 + } + updateLayoutParams { + bottomMargin = bottomPx + originalProgressMarginBottom[0] + } + updatePadding(left = leftPx, right = rightPx) + } + }, + modifier = Modifier.fillMaxSize() + ) +} + +@Composable +private fun BottomGradient(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxWidth() + .windowInsetsBottomHeight(WindowInsets.navigationBars) + .background( + Brush.verticalGradient( + colors = listOf(Color.Transparent, Color.Black.copy(alpha = TOOLBAR_ALPHA)) + ) + ) + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ToolbarOverlay(title: String, toolbarColors: TopAppBarColors, actions: FullScreenMediaActions) { + val menuItems = buildList { + add(stringResource(R.string.share) to actions.onShare) + add(stringResource(R.string.nc_save_message) to actions.onSave) + } + Box { + Box( + modifier = Modifier + .matchParentSize() + .background( + Brush.verticalGradient( + colors = listOf(Color.Black.copy(alpha = TOOLBAR_ALPHA), Color.Transparent) + ) + ) + ) + StandardAppBar(title = title, menuItems = menuItems, colors = toolbarColors) + } +} + +data class FullScreenMediaActions( + val onShare: () -> Unit, + val onSave: () -> Unit, + val onEnterImmersive: () -> Unit, + val onExitImmersive: () -> Unit +) + +@Preview(name = "Light", showBackground = true) +@Composable +private fun PreviewFullScreenMediaLight() { + MaterialTheme(colorScheme = lightColorScheme()) { + FullScreenMediaScreen( + title = "video.mp4", + player = null, + isAudioOnly = false, + actions = FullScreenMediaActions(onShare = {}, onSave = {}, onEnterImmersive = {}, onExitImmersive = {}) + ) + } +} + +@Preview(name = "Dark - RTL Arabic", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, locale = "ar") +@Composable +private fun PreviewFullScreenMediaDarkRtlArabic() { + MaterialTheme(colorScheme = darkColorScheme()) { + FullScreenMediaScreen( + title = "فيديو.mp4", + player = null, + isAudioOnly = false, + actions = FullScreenMediaActions(onShare = {}, onSave = {}, onEnterImmersive = {}, onExitImmersive = {}) + ) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenTextScreen.kt b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenTextScreen.kt new file mode 100644 index 00000000000..c0eeb72e1c9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenTextScreen.kt @@ -0,0 +1,135 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2021 Andy Scherzinger + * SPDX-FileCopyrightText: 2021 Marcel Hibbe + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.fullscreenfile + +import android.content.res.Configuration +import android.widget.TextView +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.nextcloud.talk.R +import com.nextcloud.talk.components.StandardAppBar +import io.noties.markwon.Markwon + +@Composable +fun FullScreenTextScreen(title: String, text: String, isMarkdown: Boolean, actions: FullScreenTextActions) { + val menuItems = buildList { + add(stringResource(R.string.share) to actions.onShare) + add(stringResource(R.string.nc_save_message) to actions.onSave) + if (actions.onOpenInFilesApp != null) { + add(stringResource(R.string.open_in_files_app) to actions.onOpenInFilesApp) + } + } + + Scaffold( + topBar = { StandardAppBar(title = title, menuItems = menuItems) }, + contentWindowInsets = WindowInsets.safeDrawing + ) { paddingValues -> + if (isMarkdown) { + AndroidView( + factory = { ctx -> + TextView(ctx).apply { + setTextIsSelectable(true) + val markwon = Markwon.create(ctx) + markwon.setMarkdown(this, text) + } + }, + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 0.dp) + ) + } else { + SelectionContainer( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + ) { + Text( + text = text, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 0.dp) + ) + } + } + } +} + +data class FullScreenTextActions( + val onShare: () -> Unit, + val onSave: () -> Unit, + val onOpenInFilesApp: (() -> Unit)? = null +) + +private const val PREVIEW_TEXT = """ +# Heading + +This is a sample paragraph with **bold** and *italic* text. + +- Item one +- Item two +- Item three +""" + +@Preview(name = "Light", showBackground = true) +@Composable +private fun PreviewFullScreenTextScreenLight() { + MaterialTheme(colorScheme = lightColorScheme()) { + FullScreenTextScreen( + title = "notes.md", + text = PREVIEW_TEXT, + isMarkdown = false, + actions = FullScreenTextActions(onShare = {}, onSave = {}) + ) + } +} + +@Preview(name = "Dark", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun PreviewFullScreenTextScreenDark() { + MaterialTheme(colorScheme = darkColorScheme()) { + FullScreenTextScreen( + title = "notes.md", + text = PREVIEW_TEXT, + isMarkdown = false, + actions = FullScreenTextActions(onShare = {}, onSave = {}) + ) + } +} + +@Preview(name = "RTL - Arabic", showBackground = true, locale = "ar") +@Composable +private fun PreviewFullScreenTextScreenRtl() { + MaterialTheme(colorScheme = lightColorScheme()) { + FullScreenTextScreen( + title = "ملاحظات.md", + text = "هذا نص تجريبي باللغة العربية لاختبار تخطيط الواجهة من اليمين إلى اليسار.", + isMarkdown = false, + actions = FullScreenTextActions(onShare = {}, onSave = {}) + ) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenTextViewerActivity.kt b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenTextViewerActivity.kt index 00bb5ad6848..dc70b75de1e 100644 --- a/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenTextViewerActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/fullscreenfile/FullScreenTextViewerActivity.kt @@ -8,117 +8,107 @@ */ package com.nextcloud.talk.fullscreenfile +import android.content.ComponentName import android.content.Intent import android.os.Bundle -import android.view.Menu -import android.view.MenuItem +import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity +import androidx.compose.material3.MaterialTheme import androidx.core.content.FileProvider -import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri import androidx.fragment.app.DialogFragment import autodagger.AutoInjector import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.databinding.ActivityFullScreenTextBinding +import com.nextcloud.talk.components.ColoredStatusBar import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.DisplayUtils +import com.nextcloud.talk.utils.AccountUtils.canWeOpenFilesApp import com.nextcloud.talk.utils.Mimetype.TEXT_PREFIX_GENERIC -import io.noties.markwon.Markwon +import com.nextcloud.talk.utils.adjustUIForAPILevel35 +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID import java.io.File import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) class FullScreenTextViewerActivity : AppCompatActivity() { - lateinit var binding: ActivityFullScreenTextBinding @Inject lateinit var viewThemeUtils: ViewThemeUtils - private lateinit var path: String - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_preview, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean = - when (item.itemId) { - android.R.id.home -> { - onBackPressedDispatcher.onBackPressed() - true - } - - R.id.share -> { - val shareUri = FileProvider.getUriForFile( - this, - BuildConfig.APPLICATION_ID, - File(path) - ) - - val shareIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, shareUri) - type = TEXT_PREFIX_GENERIC - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to))) - - true - } - - R.id.save -> { - val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance( - intent.getStringExtra("FILE_NAME").toString() - ) - saveFragment.show( - supportFragmentManager, - SaveToStorageDialogFragment.TAG - ) - true - } - - else -> { - super.onOptionsItemSelected(item) - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - binding = ActivityFullScreenTextBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.textviewToolbar) - - val fileName = intent.getStringExtra("FILE_NAME") + val fileName = intent.getStringExtra("FILE_NAME").orEmpty() val isMarkdown = intent.getBooleanExtra("IS_MARKDOWN", false) - path = applicationContext.cacheDir.absolutePath + "/" + fileName + val fileId = intent.getStringExtra("FILE_ID").orEmpty() + val link = intent.getStringExtra("LINK") + val username = intent.getStringExtra("USERNAME").orEmpty() + val baseUrl = intent.getStringExtra("BASE_URL").orEmpty() + val path = applicationContext.cacheDir.absolutePath + "/" + fileName val text = readFile(path) - if (isMarkdown) { - val markwon = Markwon.create(applicationContext) - markwon.setMarkdown(binding.textView, text) - } else { - binding.textView.text = text + adjustUIForAPILevel35() + + setContent { + val colorScheme = viewThemeUtils.getColorScheme(this) + MaterialTheme(colorScheme = colorScheme) { + ColoredStatusBar() + FullScreenTextScreen( + title = fileName, + text = text, + isMarkdown = isMarkdown, + actions = FullScreenTextActions( + onShare = { shareFile(path) }, + onSave = { showSaveDialog(fileName) }, + onOpenInFilesApp = if (fileId.isNotEmpty()) { + { openInFilesApp(link, fileId, username, baseUrl) } + } else { + null + } + ) + ) + } } + } - supportActionBar?.title = fileName - supportActionBar?.setDisplayHomeAsUpEnabled(true) - - viewThemeUtils.platform.themeStatusBar(this) - viewThemeUtils.material.themeToolbar(binding.textviewToolbar) - viewThemeUtils.material.colorToolbarOverflowIcon(binding.textviewToolbar) - - if (resources != null) { - DisplayUtils.applyColorToNavigationBar( - this.window, - ResourcesCompat.getColor(resources, R.color.bg_default, null) + private fun openInFilesApp(link: String?, fileId: String, username: String, baseUrl: String) { + val accountString = "$username@${baseUrl.replace("https://", "").replace("http://", "")}" + if (canWeOpenFilesApp(this, accountString)) { + val filesAppIntent = Intent(Intent.ACTION_VIEW, null) + val componentName = ComponentName( + getString(R.string.nc_import_accounts_from), + "com.owncloud.android.ui.activity.FileDisplayActivity" ) + filesAppIntent.component = componentName + filesAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + filesAppIntent.setPackage(getString(R.string.nc_import_accounts_from)) + filesAppIntent.putExtra(KEY_ACCOUNT, accountString) + filesAppIntent.putExtra(KEY_FILE_ID, fileId) + startActivity(filesAppIntent) + } else if (!link.isNullOrEmpty()) { + startActivity(Intent(Intent.ACTION_VIEW, link.toUri())) } } + private fun shareFile(path: String) { + val shareUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, File(path)) + val shareIntent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, shareUri) + type = TEXT_PREFIX_GENERIC + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to))) + } + + private fun showSaveDialog(fileName: String) { + val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance(fileName) + saveFragment.show(supportFragmentManager, SaveToStorageDialogFragment.TAG) + } + private fun readFile(fileName: String) = File(fileName).inputStream().readBytes().toString(Charsets.UTF_8) } diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt index f9538348622..c4abfdeb482 100644 --- a/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsViewHolder.kt @@ -61,10 +61,14 @@ abstract class SharedItemsViewHolder( clickTarget.setOnClickListener { fileViewerUtils.openFile( - FileViewerUtils.FileInfo(item.id, item.name, item.fileSize), - item.path, - item.link, - item.mimeType, + FileViewerUtils.FileInfo( + item.id, + item.name, + item.fileSize, + item.path, + item.link, + item.mimeType + ), FileViewerUtils.ProgressUi( progressBar, null, diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt index a7a76d4c514..497e388fbe1 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt @@ -76,33 +76,23 @@ class FileViewerUtils(private val context: Context, private val user: User) { val fileSize = size.toLong() openFile( - FileInfo(fileId, fileName, fileSize), - path, - link, - mimetype, + FileInfo(fileId, fileName, fileSize, path, link, mimetype), progressUi, message.openWhenDownloaded ) } - fun openFile( - fileInfo: FileInfo, - path: String, - link: String?, - mimetype: String?, - progressUi: ProgressUi, - openWhenDownloaded: Boolean - ) { - if (isSupportedForInternalViewer(mimetype) || canBeHandledByExternalApp(mimetype, fileInfo.fileName)) { + fun openFile(fileInfo: FileInfo, progressUi: ProgressUi, openWhenDownloaded: Boolean) { + if (isSupportedForInternalViewer(fileInfo.mimetype) || + canBeHandledByExternalApp(fileInfo.mimetype, fileInfo.fileName) + ) { openOrDownloadFile( fileInfo, - path, - mimetype, progressUi, openWhenDownloaded ) - } else if (!link.isNullOrEmpty()) { - openFileInFilesApp(link, fileInfo.fileId) + } else if (!fileInfo.link.isNullOrEmpty()) { + openFileInFilesApp(fileInfo.link, fileInfo.fileId) } else { Log.e( TAG, @@ -122,28 +112,20 @@ class FileViewerUtils(private val context: Context, private val user: User) { return intent.resolveActivity(context.packageManager) != null } - private fun openOrDownloadFile( - fileInfo: FileInfo, - path: String, - mimetype: String?, - progressUi: ProgressUi, - openWhenDownloaded: Boolean - ) { + private fun openOrDownloadFile(fileInfo: FileInfo, progressUi: ProgressUi, openWhenDownloaded: Boolean) { val file = File(context.cacheDir, fileInfo.fileName) if (file.exists()) { - openFileByMimetype(fileInfo.fileName, mimetype) + openFileByMimetype(fileInfo.fileName, fileInfo.mimetype, fileInfo.link, fileInfo.fileId) } else { downloadFileToCache( fileInfo, - path, - mimetype, progressUi, openWhenDownloaded ) } } - private fun openFileByMimetype(filename: String, mimetype: String?) { + private fun openFileByMimetype(filename: String, mimetype: String?, link: String? = null, fileId: String = "") { if (mimetype != null) { when (mimetype) { AUDIO_MPEG, @@ -161,7 +143,7 @@ class FileViewerUtils(private val context: Context, private val user: User) { -> openImageView(filename, mimetype) TEXT_MARKDOWN, TEXT_PLAIN - -> openTextView(filename, mimetype) + -> openTextView(filename, mimetype, link, fileId) else -> openFileByExternalApp(filename, mimetype) } @@ -236,10 +218,14 @@ class FileViewerUtils(private val context: Context, private val user: User) { context.startActivity(fullScreenMediaIntent) } - private fun openTextView(filename: String, mimetype: String) { + private fun openTextView(filename: String, mimetype: String, link: String?, fileId: String) { val fullScreenTextViewerIntent = Intent(context, FullScreenTextViewerActivity::class.java) fullScreenTextViewerIntent.putExtra("FILE_NAME", filename) fullScreenTextViewerIntent.putExtra("IS_MARKDOWN", isMarkdown(mimetype)) + fullScreenTextViewerIntent.putExtra("FILE_ID", fileId) + fullScreenTextViewerIntent.putExtra("LINK", link) + fullScreenTextViewerIntent.putExtra("USERNAME", user.username) + fullScreenTextViewerIntent.putExtra("BASE_URL", user.baseUrl) context.startActivity(fullScreenTextViewerIntent) } @@ -262,13 +248,7 @@ class FileViewerUtils(private val context: Context, private val user: User) { } @SuppressLint("LongLogTag") - private fun downloadFileToCache( - fileInfo: FileInfo, - path: String, - mimetype: String?, - progressUi: ProgressUi, - openWhenDownloaded: Boolean - ) { + private fun downloadFileToCache(fileInfo: FileInfo, progressUi: ProgressUi, openWhenDownloaded: Boolean) { // check if download worker is already running val workers = WorkManager.getInstance(context).getWorkInfosByTag(fileInfo.fileId) try { @@ -299,7 +279,7 @@ class FileViewerUtils(private val context: Context, private val user: User) { CapabilitiesUtil.getAttachmentFolder(user.capabilities!!.spreedCapability!!) ) .putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileInfo.fileName) - .putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path) + .putString(DownloadFileToCacheWorker.KEY_FILE_PATH, fileInfo.path) .putLong(DownloadFileToCacheWorker.KEY_FILE_SIZE, size) .build() @@ -313,10 +293,12 @@ class FileViewerUtils(private val context: Context, private val user: User) { .observeForever { workInfo: WorkInfo? -> updateViewsByProgress( fileInfo.fileName, - mimetype, + fileInfo.mimetype, workInfo!!, progressUi, - openWhenDownloaded + openWhenDownloaded, + fileInfo.link, + fileInfo.fileId ) } } @@ -326,7 +308,9 @@ class FileViewerUtils(private val context: Context, private val user: User) { mimetype: String?, workInfo: WorkInfo, progressUi: ProgressUi, - openWhenDownloaded: Boolean + openWhenDownloaded: Boolean, + link: String? = null, + fileId: String = "" ) { when (workInfo.state) { WorkInfo.State.RUNNING -> { @@ -341,7 +325,7 @@ class FileViewerUtils(private val context: Context, private val user: User) { } WorkInfo.State.SUCCEEDED -> { if (progressUi.previewImage.isShown && openWhenDownloaded) { - openFileByMimetype(fileName, mimetype) + openFileByMimetype(fileName, mimetype, link, fileId) } else { Log.d( TAG, @@ -400,7 +384,14 @@ class FileViewerUtils(private val context: Context, private val user: User) { data class ProgressUi(val progressBar: ProgressBar?, val messageText: EmojiTextView?, val previewImage: ImageView) - data class FileInfo(val fileId: String, val fileName: String, var fileSize: Long?) + data class FileInfo( + val fileId: String, + val fileName: String, + var fileSize: Long?, + val path: String, + val link: String?, + val mimetype: String? + ) companion object { private val TAG = FileViewerUtils::class.simpleName diff --git a/app/src/main/res/layout/activity_full_screen_image.xml b/app/src/main/res/layout/activity_full_screen_image.xml deleted file mode 100644 index 4c98c772c60..00000000000 --- a/app/src/main/res/layout/activity_full_screen_image.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_full_screen_media.xml b/app/src/main/res/layout/activity_full_screen_media.xml deleted file mode 100644 index cbf8f6f8753..00000000000 --- a/app/src/main/res/layout/activity_full_screen_media.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_full_screen_text.xml b/app/src/main/res/layout/activity_full_screen_text.xml deleted file mode 100644 index 26bd55540e5..00000000000 --- a/app/src/main/res/layout/activity_full_screen_text.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml index 47933e7dd04..e0c258e7872 100644 --- a/app/src/main/res/values-v27/styles.xml +++ b/app/src/main/res/values-v27/styles.xml @@ -21,7 +21,7 @@