Make messages with links selectable
This commit is contained in:
parent
0cff98e72e
commit
4771ccc6c0
5 changed files with 51 additions and 9 deletions
|
@ -188,7 +188,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
val onNotificationClick = { n: Notification -> onNotificationClick(n) }
|
val onNotificationClick = { n: Notification -> onNotificationClick(n) }
|
||||||
val onNotificationLongClick = { n: Notification -> onNotificationLongClick(n) }
|
val onNotificationLongClick = { n: Notification -> onNotificationLongClick(n) }
|
||||||
|
|
||||||
adapter = DetailAdapter(this, repository, onNotificationClick, onNotificationLongClick)
|
adapter = DetailAdapter(this, lifecycleScope, repository, onNotificationClick, onNotificationLongClick)
|
||||||
mainList = findViewById(R.id.detail_notification_list)
|
mainList = findViewById(R.id.detail_notification_list)
|
||||||
mainList.adapter = adapter
|
mainList.adapter = adapter
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,11 @@ import android.content.*
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.drawable.RippleDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -29,11 +31,10 @@ import io.heckel.ntfy.msg.DownloadWorker
|
||||||
import io.heckel.ntfy.msg.NotificationService
|
import io.heckel.ntfy.msg.NotificationService
|
||||||
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
|
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
|
||||||
import io.heckel.ntfy.util.*
|
import io.heckel.ntfy.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class DetailAdapter(private val activity: Activity, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
|
||||||
|
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
||||||
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
||||||
val selected = mutableSetOf<String>() // Notification IDs
|
val selected = mutableSetOf<String>() // Notification IDs
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val view = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.fragment_detail_item, parent, false)
|
.inflate(R.layout.fragment_detail_item, parent, false)
|
||||||
return DetailViewHolder(activity, repository, view, selected, onClick, onLongClick)
|
return DetailViewHolder(activity, lifecycleScope, repository, view, selected, onClick, onLongClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gets current topic and uses it to bind view. */
|
/* Gets current topic and uses it to bind view. */
|
||||||
|
@ -62,9 +63,10 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
|
/* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
|
||||||
class DetailViewHolder(private val activity: Activity, private val repository: Repository, itemView: View, private val selected: Set<String>, val onClick: (Notification) -> Unit, val onLongClick: (Notification) -> Unit) :
|
class DetailViewHolder(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, itemView: View, private val selected: Set<String>, val onClick: (Notification) -> Unit, val onLongClick: (Notification) -> Unit) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(itemView) {
|
||||||
private var notification: Notification? = null
|
private var notification: Notification? = null
|
||||||
|
private val layout: View = itemView.findViewById(R.id.detail_item_layout)
|
||||||
private val cardView: CardView = itemView.findViewById(R.id.detail_item_card)
|
private val cardView: CardView = itemView.findViewById(R.id.detail_item_card)
|
||||||
private val priorityImageView: ImageView = itemView.findViewById(R.id.detail_item_priority_image)
|
private val priorityImageView: ImageView = itemView.findViewById(R.id.detail_item_priority_image)
|
||||||
private val dateView: TextView = itemView.findViewById(R.id.detail_item_date_text)
|
private val dateView: TextView = itemView.findViewById(R.id.detail_item_date_text)
|
||||||
|
@ -86,6 +88,17 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
|
||||||
|
|
||||||
dateView.text = formatDateShort(notification.timestamp)
|
dateView.text = formatDateShort(notification.timestamp)
|
||||||
messageView.text = maybeAppendActionErrors(formatMessage(notification), notification)
|
messageView.text = maybeAppendActionErrors(formatMessage(notification), notification)
|
||||||
|
messageView.setOnClickListener {
|
||||||
|
// Click & Long-click listeners on the text as well, because "autoLink=web" makes them
|
||||||
|
// clickable, and so we cannot rely on the underlying card to perform the action.
|
||||||
|
// It's weird because "layout" is the ripple-able, but the card is clickable.
|
||||||
|
// See https://github.com/binwiederhier/ntfy/issues/226
|
||||||
|
layout.ripple(lifecycleScope)
|
||||||
|
onClick(notification)
|
||||||
|
}
|
||||||
|
messageView.setOnLongClickListener {
|
||||||
|
onLongClick(notification); true
|
||||||
|
}
|
||||||
newDotImageView.visibility = if (notification.notificationId == 0) View.GONE else View.VISIBLE
|
newDotImageView.visibility = if (notification.notificationId == 0) View.GONE else View.VISIBLE
|
||||||
cardView.setOnClickListener { onClick(notification) }
|
cardView.setOnClickListener { onClick(notification) }
|
||||||
cardView.setOnLongClickListener { onLongClick(notification); true }
|
cardView.setOnLongClickListener { onLongClick(notification); true }
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.graphics.drawable.RippleDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
@ -25,6 +26,10 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.*
|
import io.heckel.ntfy.db.*
|
||||||
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
|
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
@ -368,6 +373,29 @@ fun View.makeEndIconSmaller(resources: Resources) {
|
||||||
requestLayout()
|
requestLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shows the ripple effect on the view, if it is ripple-able, see https://stackoverflow.com/a/56314062/1440785
|
||||||
|
fun View.showRipple() {
|
||||||
|
if (background is RippleDrawable) {
|
||||||
|
background.state = intArrayOf(android.R.attr.state_pressed, android.R.attr.state_enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hides the ripple effect on the view, if it is ripple-able, see https://stackoverflow.com/a/56314062/1440785
|
||||||
|
fun View.hideRipple() {
|
||||||
|
if (background is RippleDrawable) {
|
||||||
|
background.state = intArrayOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggles the ripple effect on the view, if it is ripple-able
|
||||||
|
fun View.ripple(scope: CoroutineScope) {
|
||||||
|
showRipple()
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
delay(200)
|
||||||
|
hideRipple()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TextWatcher that only implements the afterTextChanged method
|
// TextWatcher that only implements the afterTextChanged method
|
||||||
class AfterChangedTextWatcher(val afterTextChangedFn: (s: Editable?) -> Unit) : TextWatcher {
|
class AfterChangedTextWatcher(val afterTextChangedFn: (s: Editable?) -> Unit) : TextWatcher {
|
||||||
override fun afterTextChanged(s: Editable?) {
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
app:cardPreventCornerOverlap="true"
|
app:cardPreventCornerOverlap="true"
|
||||||
app:cardUseCompatPadding="true">
|
app:cardUseCompatPadding="true">
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/detail_item_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
|
|
@ -3,8 +3,8 @@ Features:
|
||||||
|
|
||||||
Bugs:
|
Bugs:
|
||||||
* Accurate naming of "mute notifications" from "pause notifications" (#224, thanks to @shadow00 for reporting)
|
* Accurate naming of "mute notifications" from "pause notifications" (#224, thanks to @shadow00 for reporting)
|
||||||
|
* Make messages with links selectable (#226, thanks to @StoyanDimitrov for reporting)
|
||||||
|
|
||||||
**Thanks for testing:**
|
**Thanks for testing:**
|
||||||
|
|
||||||
Thanks to [@cmeis](https://github.com/cmeis), [@StoyanDimitrov](https://github.com/StoyanDimitrov), [@Fallenbagel](https://github.com/Fallenbagel) for testing, and
|
Thanks to @cmeis, @StoyanDimitrov, @Fallenbagel for testing, and to @Joeharrison94 for the input.
|
||||||
to [@Joeharrison94](https://github.com/Joeharrison94) for the input.
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue