126 lines
5.2 KiB
Kotlin
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)
|
|
}
|
|
}
|
|
}
|