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
4 changes: 2 additions & 2 deletions app/src/main/java/com/nextcloud/talk/chat/OverflowMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<MenuItemData>, onDismiss: () -> Unit) {
if (!expanded) return
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 10 additions & 2 deletions app/src/main/java/com/nextcloud/talk/components/StandardAppBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,13 +35,18 @@ import com.nextcloud.talk.R

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StandardAppBar(title: String, menuItems: List<Pair<String, () -> Unit>>?) {
fun StandardAppBar(
title: String,
menuItems: List<Pair<String, () -> 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() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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<MarginLayoutParams> {
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()
}
}
Loading
Loading