Loading app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +13 −8 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle import android.text.Html import android.util.Base64 Loading Loading @@ -509,6 +510,17 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun onNotificationClick(notification: Notification) { if (actionMode != null) { handleActionModeClick(notification) } else if (notification.click != "") { try { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(notification.click))) } catch (e: Exception) { Log.w(TAG, "Cannot open click URL", e) runOnUiThread { Toast .makeText(this@DetailActivity, getString(R.string.detail_item_cannot_open_click_url, e.message), Toast.LENGTH_LONG) .show() } } } else { copyToClipboard(notification) } Loading @@ -516,14 +528,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun copyToClipboard(notification: Notification) { runOnUiThread { val message = decodeMessage(notification) val text = message + "\n\n" + Date(notification.timestamp * 1000).toString() val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("notification message", text) clipboard.setPrimaryClip(clip) Toast .makeText(this, getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG) .show() copyToClipboard(this, notification) } } Loading app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +91 −79 Original line number Diff line number Diff line Loading @@ -20,13 +20,11 @@ import androidx.recyclerview.widget.RecyclerView import com.stfalcon.imageviewer.StfalconImageViewer import io.heckel.ntfy.R import io.heckel.ntfy.db.* import io.heckel.ntfy.util.Log import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.util.* class DetailAdapter(private val activity: Activity, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) : ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) { Loading Loading @@ -98,8 +96,11 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo if (selected.contains(notification.id)) { itemView.setBackgroundResource(Colors.itemSelectedBackground(context)) } val attachment = notification.attachment val exists = if (attachment?.contentUri != null) fileExists(context, attachment.contentUri) else false renderPriority(context, notification) maybeRenderAttachment(context, notification) maybeRenderMenu(context, notification, exists) maybeRenderAttachment(context, notification, exists) } private fun renderPriority(context: Context, notification: Notification) { Loading @@ -126,23 +127,20 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo } } private fun maybeRenderAttachment(context: Context, notification: Notification) { private fun maybeRenderAttachment(context: Context, notification: Notification, exists: Boolean) { if (notification.attachment == null) { menuButton.visibility = View.GONE attachmentImageView.visibility = View.GONE attachmentBoxView.visibility = View.GONE return } val attachment = notification.attachment val exists = if (attachment.contentUri != null) fileExists(context, attachment.contentUri) else false 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 maybeRenderMenu(context: Context, notification: Notification, attachment: Attachment, exists: Boolean) { val menuButtonPopupMenu = createAttachmentPopup(context, menuButton, notification, attachment, exists) // Heavy lifting not during on-click private fun maybeRenderMenu(context: Context, notification: Notification, exists: Boolean) { val menuButtonPopupMenu = maybeCreateMenuPopup(context, menuButton, notification, exists) // Heavy lifting not during on-click if (menuButtonPopupMenu != null) { menuButton.setOnClickListener { menuButtonPopupMenu.show() } menuButton.visibility = View.VISIBLE Loading @@ -158,7 +156,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo } attachmentInfoView.text = formatAttachmentDetails(context, attachment, exists) attachmentIconView.setImageResource(mimeTypeToIconResource(attachment.type)) val attachmentBoxPopupMenu = createAttachmentPopup(context, attachmentBoxView, notification, attachment, exists) // Heavy lifting not during on-click val attachmentBoxPopupMenu = maybeCreateMenuPopup(context, attachmentBoxView, notification, exists) // Heavy lifting not during on-click if (attachmentBoxPopupMenu != null) { attachmentBoxView.setOnClickListener { attachmentBoxPopupMenu.show() } } else { Loading @@ -171,17 +169,21 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo attachmentBoxView.visibility = View.VISIBLE } private fun createAttachmentPopup(context: Context, anchor: View?, notification: Notification, attachment: Attachment, exists: Boolean): PopupMenu? { private fun maybeCreateMenuPopup(context: Context, anchor: View?, notification: Notification, exists: Boolean): PopupMenu? { val popup = PopupMenu(context, anchor) popup.menuInflater.inflate(R.menu.menu_detail_attachment, popup.menu) val attachment = notification.attachment // May be null val hasAttachment = attachment != null val downloadItem = popup.menu.findItem(R.id.detail_item_menu_download) val cancelItem = popup.menu.findItem(R.id.detail_item_menu_cancel) val openItem = popup.menu.findItem(R.id.detail_item_menu_open) val browseItem = popup.menu.findItem(R.id.detail_item_menu_browse) val deleteItem = popup.menu.findItem(R.id.detail_item_menu_delete) val copyUrlItem = popup.menu.findItem(R.id.detail_item_menu_copy_url) val expired = attachment.expires != null && attachment.expires < System.currentTimeMillis()/1000 val inProgress = attachment.progress in 0..99 val copyContentsItem = popup.menu.findItem(R.id.detail_item_menu_contents) val expired = attachment?.expires != null && attachment.expires < System.currentTimeMillis()/1000 val inProgress = attachment?.progress in 0..99 if (attachment != null) { if (attachment.contentUri != null) { openItem.setOnMenuItemClickListener { try { Loading Loading @@ -251,13 +253,23 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo DownloadManager.cancel(context, notification.id) true } openItem.isVisible = exists browseItem.isVisible = exists downloadItem.isVisible = !exists && !expired && !inProgress deleteItem.isVisible = exists copyUrlItem.isVisible = !expired cancelItem.isVisible = inProgress val noOptions = !openItem.isVisible && !browseItem.isVisible && !downloadItem.isVisible && !copyUrlItem.isVisible && !cancelItem.isVisible && !deleteItem.isVisible } if (notification.click != "") { copyContentsItem.setOnMenuItemClickListener { copyToClipboard(context, notification) true } } openItem.isVisible = hasAttachment && exists browseItem.isVisible = hasAttachment && exists downloadItem.isVisible = hasAttachment && !exists && !expired && !inProgress deleteItem.isVisible = hasAttachment && exists copyUrlItem.isVisible = hasAttachment && !expired cancelItem.isVisible = hasAttachment && inProgress copyContentsItem.isVisible = notification.click != "" val noOptions = !openItem.isVisible && !browseItem.isVisible && !downloadItem.isVisible && !copyUrlItem.isVisible && !cancelItem.isVisible && !deleteItem.isVisible && !copyContentsItem.isVisible if (noOptions) { return null } Loading app/src/main/java/io/heckel/ntfy/util/Util.kt +14 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ package io.heckel.ntfy.util import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.content.ClipData import android.content.ClipboardManager import android.content.ContentResolver import android.content.Context import android.content.res.Configuration Loading @@ -18,6 +20,7 @@ import android.util.TypedValue import android.view.View import android.view.Window import android.widget.ImageView import android.widget.Toast import androidx.appcompat.app.AppCompatDelegate import io.heckel.ntfy.R import io.heckel.ntfy.db.Notification Loading Loading @@ -366,3 +369,14 @@ fun ensureSafeNewFile(dir: File, name: String): File { } throw Exception("Cannot find safe file") } fun copyToClipboard(context: Context, notification: Notification) { val message = decodeMessage(notification) val text = message + "\n\n" + formatDateShort(notification.timestamp) val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("notification message", text) clipboard.setPrimaryClip(clip) Toast .makeText(context, context.getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG) .show() } app/src/main/res/menu/menu_detail_attachment.xml +1 −0 Original line number Diff line number Diff line Loading @@ -6,4 +6,5 @@ <item android:id="@+id/detail_item_menu_browse" android:title="@string/detail_item_menu_browse"/> <item android:id="@+id/detail_item_menu_delete" android:title="@string/detail_item_menu_delete"/> <item android:id="@+id/detail_item_menu_copy_url" android:title="@string/detail_item_menu_copy_url"/> <item android:id="@+id/detail_item_menu_contents" android:title="@string/detail_item_menu_copy_contents"/> </menu> app/src/main/res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -144,9 +144,12 @@ <string name="detail_item_menu_cancel">Cancel download</string> <string name="detail_item_menu_copy_url">Copy URL</string> <string name="detail_item_menu_copy_url_copied">Copied URL to clipboard</string> <string name="detail_item_menu_copy_contents">Copy notification</string> <string name="detail_item_menu_copy_contents_copied">Copied notification clipboard</string> <string name="detail_item_cannot_download">Cannot open or download attachment. Link expired and no local file found.</string> <string name="detail_item_cannot_open">Cannot open attachment: %1$s</string> <string name="detail_item_cannot_open_not_found">Cannot open attachment: File may have been deleted, or there is no app to open the file.</string> <string name="detail_item_cannot_open_click_url">Cannot open click URL: %1$s</string> <string name="detail_item_delete_failed">Cannot delete attachment: %1$s</string> <string name="detail_item_download_failed">Attachment download failed: %1$s</string> <string name="detail_item_download_info_not_downloaded">not downloaded</string> Loading Loading
app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +13 −8 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle import android.text.Html import android.util.Base64 Loading Loading @@ -509,6 +510,17 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun onNotificationClick(notification: Notification) { if (actionMode != null) { handleActionModeClick(notification) } else if (notification.click != "") { try { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(notification.click))) } catch (e: Exception) { Log.w(TAG, "Cannot open click URL", e) runOnUiThread { Toast .makeText(this@DetailActivity, getString(R.string.detail_item_cannot_open_click_url, e.message), Toast.LENGTH_LONG) .show() } } } else { copyToClipboard(notification) } Loading @@ -516,14 +528,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun copyToClipboard(notification: Notification) { runOnUiThread { val message = decodeMessage(notification) val text = message + "\n\n" + Date(notification.timestamp * 1000).toString() val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("notification message", text) clipboard.setPrimaryClip(clip) Toast .makeText(this, getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG) .show() copyToClipboard(this, notification) } } Loading
app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +91 −79 Original line number Diff line number Diff line Loading @@ -20,13 +20,11 @@ import androidx.recyclerview.widget.RecyclerView import com.stfalcon.imageviewer.StfalconImageViewer import io.heckel.ntfy.R import io.heckel.ntfy.db.* import io.heckel.ntfy.util.Log import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.util.* class DetailAdapter(private val activity: Activity, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) : ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) { Loading Loading @@ -98,8 +96,11 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo if (selected.contains(notification.id)) { itemView.setBackgroundResource(Colors.itemSelectedBackground(context)) } val attachment = notification.attachment val exists = if (attachment?.contentUri != null) fileExists(context, attachment.contentUri) else false renderPriority(context, notification) maybeRenderAttachment(context, notification) maybeRenderMenu(context, notification, exists) maybeRenderAttachment(context, notification, exists) } private fun renderPriority(context: Context, notification: Notification) { Loading @@ -126,23 +127,20 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo } } private fun maybeRenderAttachment(context: Context, notification: Notification) { private fun maybeRenderAttachment(context: Context, notification: Notification, exists: Boolean) { if (notification.attachment == null) { menuButton.visibility = View.GONE attachmentImageView.visibility = View.GONE attachmentBoxView.visibility = View.GONE return } val attachment = notification.attachment val exists = if (attachment.contentUri != null) fileExists(context, attachment.contentUri) else false 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 maybeRenderMenu(context: Context, notification: Notification, attachment: Attachment, exists: Boolean) { val menuButtonPopupMenu = createAttachmentPopup(context, menuButton, notification, attachment, exists) // Heavy lifting not during on-click private fun maybeRenderMenu(context: Context, notification: Notification, exists: Boolean) { val menuButtonPopupMenu = maybeCreateMenuPopup(context, menuButton, notification, exists) // Heavy lifting not during on-click if (menuButtonPopupMenu != null) { menuButton.setOnClickListener { menuButtonPopupMenu.show() } menuButton.visibility = View.VISIBLE Loading @@ -158,7 +156,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo } attachmentInfoView.text = formatAttachmentDetails(context, attachment, exists) attachmentIconView.setImageResource(mimeTypeToIconResource(attachment.type)) val attachmentBoxPopupMenu = createAttachmentPopup(context, attachmentBoxView, notification, attachment, exists) // Heavy lifting not during on-click val attachmentBoxPopupMenu = maybeCreateMenuPopup(context, attachmentBoxView, notification, exists) // Heavy lifting not during on-click if (attachmentBoxPopupMenu != null) { attachmentBoxView.setOnClickListener { attachmentBoxPopupMenu.show() } } else { Loading @@ -171,17 +169,21 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo attachmentBoxView.visibility = View.VISIBLE } private fun createAttachmentPopup(context: Context, anchor: View?, notification: Notification, attachment: Attachment, exists: Boolean): PopupMenu? { private fun maybeCreateMenuPopup(context: Context, anchor: View?, notification: Notification, exists: Boolean): PopupMenu? { val popup = PopupMenu(context, anchor) popup.menuInflater.inflate(R.menu.menu_detail_attachment, popup.menu) val attachment = notification.attachment // May be null val hasAttachment = attachment != null val downloadItem = popup.menu.findItem(R.id.detail_item_menu_download) val cancelItem = popup.menu.findItem(R.id.detail_item_menu_cancel) val openItem = popup.menu.findItem(R.id.detail_item_menu_open) val browseItem = popup.menu.findItem(R.id.detail_item_menu_browse) val deleteItem = popup.menu.findItem(R.id.detail_item_menu_delete) val copyUrlItem = popup.menu.findItem(R.id.detail_item_menu_copy_url) val expired = attachment.expires != null && attachment.expires < System.currentTimeMillis()/1000 val inProgress = attachment.progress in 0..99 val copyContentsItem = popup.menu.findItem(R.id.detail_item_menu_contents) val expired = attachment?.expires != null && attachment.expires < System.currentTimeMillis()/1000 val inProgress = attachment?.progress in 0..99 if (attachment != null) { if (attachment.contentUri != null) { openItem.setOnMenuItemClickListener { try { Loading Loading @@ -251,13 +253,23 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo DownloadManager.cancel(context, notification.id) true } openItem.isVisible = exists browseItem.isVisible = exists downloadItem.isVisible = !exists && !expired && !inProgress deleteItem.isVisible = exists copyUrlItem.isVisible = !expired cancelItem.isVisible = inProgress val noOptions = !openItem.isVisible && !browseItem.isVisible && !downloadItem.isVisible && !copyUrlItem.isVisible && !cancelItem.isVisible && !deleteItem.isVisible } if (notification.click != "") { copyContentsItem.setOnMenuItemClickListener { copyToClipboard(context, notification) true } } openItem.isVisible = hasAttachment && exists browseItem.isVisible = hasAttachment && exists downloadItem.isVisible = hasAttachment && !exists && !expired && !inProgress deleteItem.isVisible = hasAttachment && exists copyUrlItem.isVisible = hasAttachment && !expired cancelItem.isVisible = hasAttachment && inProgress copyContentsItem.isVisible = notification.click != "" val noOptions = !openItem.isVisible && !browseItem.isVisible && !downloadItem.isVisible && !copyUrlItem.isVisible && !cancelItem.isVisible && !deleteItem.isVisible && !copyContentsItem.isVisible if (noOptions) { return null } Loading
app/src/main/java/io/heckel/ntfy/util/Util.kt +14 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ package io.heckel.ntfy.util import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.content.ClipData import android.content.ClipboardManager import android.content.ContentResolver import android.content.Context import android.content.res.Configuration Loading @@ -18,6 +20,7 @@ import android.util.TypedValue import android.view.View import android.view.Window import android.widget.ImageView import android.widget.Toast import androidx.appcompat.app.AppCompatDelegate import io.heckel.ntfy.R import io.heckel.ntfy.db.Notification Loading Loading @@ -366,3 +369,14 @@ fun ensureSafeNewFile(dir: File, name: String): File { } throw Exception("Cannot find safe file") } fun copyToClipboard(context: Context, notification: Notification) { val message = decodeMessage(notification) val text = message + "\n\n" + formatDateShort(notification.timestamp) val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("notification message", text) clipboard.setPrimaryClip(clip) Toast .makeText(context, context.getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG) .show() }
app/src/main/res/menu/menu_detail_attachment.xml +1 −0 Original line number Diff line number Diff line Loading @@ -6,4 +6,5 @@ <item android:id="@+id/detail_item_menu_browse" android:title="@string/detail_item_menu_browse"/> <item android:id="@+id/detail_item_menu_delete" android:title="@string/detail_item_menu_delete"/> <item android:id="@+id/detail_item_menu_copy_url" android:title="@string/detail_item_menu_copy_url"/> <item android:id="@+id/detail_item_menu_contents" android:title="@string/detail_item_menu_copy_contents"/> </menu>
app/src/main/res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -144,9 +144,12 @@ <string name="detail_item_menu_cancel">Cancel download</string> <string name="detail_item_menu_copy_url">Copy URL</string> <string name="detail_item_menu_copy_url_copied">Copied URL to clipboard</string> <string name="detail_item_menu_copy_contents">Copy notification</string> <string name="detail_item_menu_copy_contents_copied">Copied notification clipboard</string> <string name="detail_item_cannot_download">Cannot open or download attachment. Link expired and no local file found.</string> <string name="detail_item_cannot_open">Cannot open attachment: %1$s</string> <string name="detail_item_cannot_open_not_found">Cannot open attachment: File may have been deleted, or there is no app to open the file.</string> <string name="detail_item_cannot_open_click_url">Cannot open click URL: %1$s</string> <string name="detail_item_delete_failed">Cannot delete attachment: %1$s</string> <string name="detail_item_download_failed">Attachment download failed: %1$s</string> <string name="detail_item_download_info_not_downloaded">not downloaded</string> Loading