Files
notifications-master/app/src/main/java/se/ajpanton/notificationsmaster/ImageViewerDialogFragment.kt
T

126 lines
5.2 KiB
Kotlin

package se.ajpanton.notificationsmaster
import android.app.Dialog
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.os.bundleOf
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import se.ajpanton.notificationsmaster.data.EncryptedImageStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** Full-screen image viewer kept within MainActivity so opening it does not re-lock the app. */
class ImageViewerDialogFragment : DialogFragment() {
private lateinit var root: FrameLayout
private lateinit var image: ZoomImageView
private lateinit var progress: ProgressBar
private var controlsVisible = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
root = FrameLayout(requireContext()).apply { setBackgroundColor(Color.BLACK) }
image = ZoomImageView(requireContext()).apply {
visibility = View.GONE
onSingleTap = ::toggleSystemControls
}
progress = ProgressBar(requireContext()).apply { isIndeterminate = true }
root.addView(image, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
root.addView(progress, FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER))
return Dialog(requireContext()).apply {
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(root)
}
}
override fun onStart() {
super.onStart()
dialog?.window?.apply {
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
WindowCompat.setDecorFitsSystemWindows(this, false)
attributes = attributes.apply {
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
hideSystemControls()
loadImage(requireArguments().getString(ARGUMENT_IMAGE_ID)!!)
}
private fun loadImage(imageId: String) {
val targetWidth = resources.displayMetrics.widthPixels
val targetHeight = resources.displayMetrics.heightPixels
val appContext = requireContext().applicationContext
lifecycleScope.launch {
val bitmap = withContext(Dispatchers.IO) { runCatching {
EncryptedImageStore(appContext).read(imageId)?.let { bytes ->
decodeForViewer(bytes, targetWidth, targetHeight)
}
}.getOrNull() }
if (!isAdded) return@launch
progress.visibility = View.GONE
if (bitmap == null) {
root.addView(TextView(requireContext()).apply {
text = "[image]"
setTextColor(Color.WHITE)
textSize = 18f
}, FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER))
} else {
image.setImageBitmap(bitmap)
image.visibility = View.VISIBLE
}
}
}
private fun decodeForViewer(bytes: ByteArray, targetWidth: Int, targetHeight: Int): Bitmap? {
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bounds)
var sampleSize = 1
while (bounds.outWidth / sampleSize > targetWidth * MAX_SCREEN_SCALE ||
bounds.outHeight / sampleSize > targetHeight * MAX_SCREEN_SCALE
) sampleSize *= 2
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size, BitmapFactory.Options().apply { inSampleSize = sampleSize })
}
private fun toggleSystemControls() {
if (controlsVisible) hideSystemControls() else showSystemControls()
}
private fun hideSystemControls() {
val window = dialog?.window ?: return
WindowInsetsControllerCompat(window, root).apply {
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
hide(WindowInsetsCompat.Type.systemBars())
}
controlsVisible = false
}
private fun showSystemControls() {
val window = dialog?.window ?: return
WindowInsetsControllerCompat(window, root).show(WindowInsetsCompat.Type.systemBars())
controlsVisible = true
}
companion object {
private const val ARGUMENT_IMAGE_ID = "image_id"
private const val MAX_SCREEN_SCALE = 4
fun newInstance(imageId: String) = ImageViewerDialogFragment().apply {
arguments = bundleOf(ARGUMENT_IMAGE_ID to imageId)
}
}
}