Image viewer

This commit is contained in:
Philipp Heckel 2022-01-10 15:36:50 -05:00
parent 64612dc47f
commit 79053c62fb
15 changed files with 78 additions and 42 deletions

View file

@ -93,4 +93,7 @@ dependencies {
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.liveDataVersion"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
// Image viewer
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
}

View file

@ -13,7 +13,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- Only required on SDK <= 28 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <!-- Required to install packages downloaded through ntfy; craazyy! -->
<application
android:name=".app.Application"

View file

@ -2,6 +2,7 @@ package io.heckel.ntfy.ui
import android.app.DownloadManager
import android.content.*
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.OpenableColumns
@ -17,6 +18,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.stfalcon.imageviewer.StfalconImageViewer
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Attachment
import io.heckel.ntfy.data.Notification
@ -25,7 +27,6 @@ import io.heckel.ntfy.data.PROGRESS_NONE
import io.heckel.ntfy.msg.AttachmentDownloadWorker
import io.heckel.ntfy.util.*
import java.util.*
import kotlin.math.exp
class DetailAdapter(private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
@ -131,21 +132,13 @@ class DetailAdapter(private val onClick: (Notification) -> Unit, private val onL
}
val attachment = notification.attachment
val exists = if (attachment.contentUri != null) fileExists(context, attachment.contentUri) else false
maybeRenderAttachmentImage(context, attachment, exists)
renderAttachmentBox(context, notification, attachment, exists)
val image = attachment.contentUri != null && exists && supportedImage(attachment.type)
maybeRenderMenu(context, notification, attachment, exists)
maybeRenderAttachmentImage(context, attachment, image)
maybeRenderAttachmentBox(context, notification, attachment, exists, image)
}
private fun renderAttachmentBox(context: Context, notification: Notification, attachment: Attachment, exists: Boolean) {
attachmentInfoView.text = formatAttachmentDetails(context, attachment, exists)
attachmentIconView.setImageResource(if (attachment.type?.startsWith("image/") == true) {
R.drawable.ic_file_image_gray_24dp
} else if (attachment.type?.startsWith("video/") == true) {
R.drawable.ic_file_video_gray_24dp
} else if (attachment.type?.startsWith("audio/") == true) {
R.drawable.ic_file_audio_gray_24dp
} else {
R.drawable.ic_file_document_gray_24dp
})
private fun maybeRenderMenu(context: Context, notification: Notification, attachment: Attachment, exists: Boolean) {
val menuButtonPopupMenu = createAttachmentPopup(context, menuButton, notification, attachment, exists) // Heavy lifting not during on-click
if (menuButtonPopupMenu != null) {
menuButton.setOnClickListener { menuButtonPopupMenu.show() }
@ -153,6 +146,25 @@ class DetailAdapter(private val onClick: (Notification) -> Unit, private val onL
} else {
menuButton.visibility = View.GONE
}
}
private fun maybeRenderAttachmentBox(context: Context, notification: Notification, attachment: Attachment, exists: Boolean, image: Boolean) {
if (image) {
attachmentBoxView.visibility = View.GONE
return
}
attachmentInfoView.text = formatAttachmentDetails(context, attachment, exists)
attachmentIconView.setImageResource(if (attachment.type?.startsWith("image/") == true) {
R.drawable.ic_file_image_red_24dp
} else if (attachment.type?.startsWith("video/") == true) {
R.drawable.ic_file_video_orange_24dp
} else if (attachment.type?.startsWith("audio/") == true) {
R.drawable.ic_file_audio_purple_24dp
} else if ("application/vnd.android.package-archive" == attachment.type) {
R.drawable.ic_file_app_gray_24dp
} else {
R.drawable.ic_file_document_blue_24dp
})
val attachmentBoxPopupMenu = createAttachmentPopup(context, attachmentBoxView, notification, attachment, exists) // Heavy lifting not during on-click
if (attachmentBoxPopupMenu != null) {
attachmentBoxView.setOnClickListener { attachmentBoxPopupMenu.show() }
@ -231,7 +243,7 @@ class DetailAdapter(private val onClick: (Notification) -> Unit, private val onL
if (expired) {
infos.add("not downloaded, link expired")
} else if (expires) {
infos.add("not downloaded, link expires ${formatDateShort(attachment.expires!!)}")
infos.add("not downloaded, expires ${formatDateShort(attachment.expires!!)}")
} else {
infos.add("not downloaded")
}
@ -270,17 +282,24 @@ class DetailAdapter(private val onClick: (Notification) -> Unit, private val onL
}
}
private fun maybeRenderAttachmentImage(context: Context, att: Attachment, exists: Boolean) {
val fileIsImage = att.contentUri != null && exists && supportedImage(att.type)
if (!fileIsImage) {
private fun maybeRenderAttachmentImage(context: Context, attachment: Attachment, image: Boolean) {
if (!image) {
attachmentImageView.visibility = View.GONE
return
}
try {
val resolver = context.applicationContext.contentResolver
val bitmapStream = resolver.openInputStream(Uri.parse(att.contentUri))
val bitmapStream = resolver.openInputStream(Uri.parse(attachment.contentUri))
val bitmap = BitmapFactory.decodeStream(bitmapStream)
attachmentImageView.setImageBitmap(bitmap)
attachmentImageView.setOnClickListener {
val loadImage = { view: ImageView, image: Bitmap -> view.setImageBitmap(image) }
StfalconImageViewer.Builder(context, listOf(bitmap), loadImage)
.allowZooming(true)
.withTransitionFrom(attachmentImageView)
.withHiddenStatusBar(false)
.show()
}
attachmentImageView.visibility = View.VISIBLE
} catch (_: Exception) {
attachmentImageView.visibility = View.GONE

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17.6,9.48l1.84,-3.18c0.16,-0.31 0.04,-0.69 -0.26,-0.85c-0.29,-0.15 -0.65,-0.06 -0.83,0.22l-1.88,3.24c-2.86,-1.21 -6.08,-1.21 -8.94,0L5.65,5.67c-0.19,-0.29 -0.58,-0.38 -0.87,-0.2C4.5,5.65 4.41,6.01 4.56,6.3L6.4,9.48C3.3,11.25 1.28,14.44 1,18h22C22.72,14.44 20.7,11.25 17.6,9.48zM7,15.25c-0.69,0 -1.25,-0.56 -1.25,-1.25c0,-0.69 0.56,-1.25 1.25,-1.25S8.25,13.31 8.25,14C8.25,14.69 7.69,15.25 7,15.25zM17,15.25c-0.69,0 -1.25,-0.56 -1.25,-1.25c0,-0.69 0.56,-1.25 1.25,-1.25s1.25,0.56 1.25,1.25C18.25,14.69 17.69,15.25 17,15.25z"
android:fillColor="#555555"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M14,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8L14,2zM6,20V4h7v5h5v11H6zM16,11h-4v3.88c-0.36,-0.24 -0.79,-0.38 -1.25,-0.38c-1.24,0 -2.25,1.01 -2.25,2.25c0,1.24 1.01,2.25 2.25,2.25S13,17.99 13,16.75V13h3V11z"
android:fillColor="#555555"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,3l0.01,10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55C7.79,13 6,14.79 6,17s1.79,4 4.01,4S14,19.21 14,17L14,7h4L18,3h-6zM10.01,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"
android:fillColor="#B300FF"/>
</vector>

View file

@ -5,5 +5,5 @@
android:viewportHeight="24">
<path
android:pathData="M8,16h8v2L8,18zM8,12h8v2L8,14zM14,2L6,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM18,20L6,20L6,4h7v5h5v11z"
android:fillColor="#555555"/>
android:fillColor="#00ADFF"/>
</vector>

View file

@ -5,5 +5,5 @@
android:viewportHeight="24">
<path
android:pathData="M19,5v14L5,19L5,5h14m0,-2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14.14,11.86l-3,3.87L9,13.14 6,17h12l-3.86,-5.14z"
android:fillColor="#555555"/>
android:fillColor="#E30000"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M14,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8L14,2zM6,20V4h7v5h5v11H6zM14,14l2,-1.06v4.12L14,16v1c0,0.55 -0.45,1 -1,1H9c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h4c0.55,0 1,0.45 1,1V14z"
android:fillColor="#555555"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,6.47L5.76,10H20v8H4V6.47M22,4h-4l2,4h-3l-2,-4h-2l2,4h-3l-2,-4H8l2,4H7L5,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V4z"
android:fillColor="#FF9800"/>
</vector>

View file

@ -89,7 +89,7 @@
app:layout_constraintTop_toBottomOf="@id/detail_item_attachment_image"
app:layout_constraintBottom_toTopOf="@id/detail_item_attachment_box"
app:layout_constraintHorizontal_bias="0.0" android:layout_marginTop="2dp"
android:layout_marginBottom="3dp"/>
/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/detail_item_tags_text"
@ -104,7 +104,7 @@
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_cancel_gray_24dp"
android:id="@+id/detail_item_attachment_icon" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/detail_item_attachment_info" android:layout_marginEnd="3dp"
app:layout_constraintEnd_toStartOf="@+id/detail_item_attachment_info" android:layout_marginEnd="5dp"
app:layout_constraintBottom_toBottomOf="parent"
/>
<TextView

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><g><path d="M17.6,9.48l1.84-3.18c0.16-0.31,0.04-0.69-0.26-0.85c-0.29-0.15-0.65-0.06-0.83,0.22l-1.88,3.24 c-2.86-1.21-6.08-1.21-8.94,0L5.65,5.67c-0.19-0.29-0.58-0.38-0.87-0.2C4.5,5.65,4.41,6.01,4.56,6.3L6.4,9.48 C3.3,11.25,1.28,14.44,1,18h22C22.72,14.44,20.7,11.25,17.6,9.48z M7,15.25c-0.69,0-1.25-0.56-1.25-1.25 c0-0.69,0.56-1.25,1.25-1.25S8.25,13.31,8.25,14C8.25,14.69,7.69,15.25,7,15.25z M17,15.25c-0.69,0-1.25-0.56-1.25-1.25 c0-0.69,0.56-1.25,1.25-1.25s1.25,0.56,1.25,1.25C18.25,14.69,17.69,15.25,17,15.25z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 711 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z"/></svg>

After

Width:  |  Height:  |  Size: 296 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 3l.01 10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4.01 4S14 19.21 14 17V7h4V3h-6zm-1.99 16c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/></svg>

After

Width:  |  Height:  |  Size: 311 B

View file

@ -18,6 +18,7 @@ allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" } // For StfalconImageViewer
}
}