Notification action when clicked, add more validation on add topic
This commit is contained in:
parent
2a64f44916
commit
7d561a5068
9 changed files with 96 additions and 42 deletions
|
@ -25,7 +25,9 @@
|
|||
</activity>
|
||||
|
||||
<!-- Detail activity -->
|
||||
<activity android:name=".ui.DetailActivity">
|
||||
<activity
|
||||
android:name=".ui.DetailActivity"
|
||||
android:parentActivityName=".ui.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ui.MainActivity" />
|
||||
|
|
|
@ -2,7 +2,10 @@ package io.heckel.ntfy.msg
|
|||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.RingtoneManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
|
@ -10,10 +13,9 @@ import androidx.core.app.NotificationCompat
|
|||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.data.Database
|
||||
import io.heckel.ntfy.data.Notification
|
||||
import io.heckel.ntfy.data.Repository
|
||||
import io.heckel.ntfy.data.topicShortUrl
|
||||
import io.heckel.ntfy.data.*
|
||||
import io.heckel.ntfy.ui.DetailActivity
|
||||
import io.heckel.ntfy.ui.MainActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -56,8 +58,7 @@ class MessagingService : FirebaseMessagingService() {
|
|||
|
||||
// Send notification
|
||||
Log.d(TAG, "Sending notification for message: from=${remoteMessage.from}, data=${data}")
|
||||
val title = topicShortUrl(baseUrl, topic)
|
||||
sendNotification(title, message)
|
||||
sendNotification(subscription, message)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +72,19 @@ class MessagingService : FirebaseMessagingService() {
|
|||
job.cancel()
|
||||
}
|
||||
|
||||
private fun sendNotification(title: String, message: String) {
|
||||
private fun sendNotification(subscription: Subscription, message: String) {
|
||||
val title = topicShortUrl(subscription.baseUrl, subscription.topic)
|
||||
|
||||
// Create an Intent for the activity you want to start
|
||||
val intent = Intent(this, DetailActivity::class.java)
|
||||
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
|
||||
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
|
||||
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
|
||||
val pendingIntent: PendingIntent? = TaskStackBuilder.create(this).run {
|
||||
addNextIntentWithParentStack(intent) // Add the intent, which inflates the back stack
|
||||
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) // Get the PendingIntent containing the entire back stack
|
||||
}
|
||||
|
||||
val channelId = getString(R.string.notification_channel_id)
|
||||
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||
val notificationBuilder = NotificationCompat.Builder(this, channelId)
|
||||
|
@ -79,6 +92,9 @@ class MessagingService : FirebaseMessagingService() {
|
|||
.setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setSound(defaultSoundUri)
|
||||
.setContentIntent(pendingIntent) // Click target for notification
|
||||
.setAutoCancel(true) // Cancel when notification is clicked
|
||||
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channelName = getString(R.string.notification_channel_name)
|
||||
|
|
|
@ -1,28 +1,38 @@
|
|||
package io.heckel.ntfy.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.CheckBox
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.app.Application
|
||||
import io.heckel.ntfy.data.Repository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AddFragment(private val listener: AddSubscriptionListener) : DialogFragment() {
|
||||
interface AddSubscriptionListener {
|
||||
fun onSubscribe(topic: String, baseUrl: String)
|
||||
}
|
||||
class AddFragment(private val viewModel: SubscriptionsViewModel, private val onSubscribe: (topic: String, baseUrl: String) -> Unit) : DialogFragment() {
|
||||
private lateinit var topicNameText: TextInputEditText
|
||||
private lateinit var baseUrlText: TextInputEditText
|
||||
private lateinit var useAnotherServerCheckbox: CheckBox
|
||||
private lateinit var subscribeButton: Button
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let {
|
||||
// Build root view
|
||||
val view = requireActivity().layoutInflater.inflate(R.layout.add_dialog_fragment, null)
|
||||
val topicNameText = view.findViewById(R.id.add_dialog_topic_text) as TextInputEditText
|
||||
val baseUrlText = view.findViewById(R.id.add_dialog_base_url_text) as TextInputEditText
|
||||
val useAnotherServerCheckbox = view.findViewById(R.id.add_dialog_use_another_server_checkbox) as CheckBox
|
||||
topicNameText = view.findViewById(R.id.add_dialog_topic_text) as TextInputEditText
|
||||
baseUrlText = view.findViewById(R.id.add_dialog_base_url_text) as TextInputEditText
|
||||
useAnotherServerCheckbox = view.findViewById(R.id.add_dialog_use_another_server_checkbox) as CheckBox
|
||||
|
||||
// FIXME For now, other servers are disabled
|
||||
useAnotherServerCheckbox.visibility = View.GONE
|
||||
|
@ -32,12 +42,8 @@ class AddFragment(private val listener: AddSubscriptionListener) : DialogFragmen
|
|||
.setView(view)
|
||||
.setPositiveButton(R.string.add_dialog_button_subscribe) { _, _ ->
|
||||
val topic = topicNameText.text.toString()
|
||||
val baseUrl = if (useAnotherServerCheckbox.isChecked) {
|
||||
baseUrlText.text.toString()
|
||||
} else {
|
||||
getString(R.string.app_base_url)
|
||||
}
|
||||
listener.onSubscribe(topic, baseUrl)
|
||||
val baseUrl = getBaseUrl()
|
||||
onSubscribe(topic, baseUrl)
|
||||
}
|
||||
.setNegativeButton(R.string.add_dialog_button_cancel) { _, _ ->
|
||||
dialog?.cancel()
|
||||
|
@ -48,20 +54,9 @@ class AddFragment(private val listener: AddSubscriptionListener) : DialogFragmen
|
|||
alert.setOnShowListener {
|
||||
val dialog = it as AlertDialog
|
||||
|
||||
val subscribeButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
subscribeButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
subscribeButton.isEnabled = false
|
||||
|
||||
val validateInput: () -> Unit = {
|
||||
if (useAnotherServerCheckbox.isChecked) {
|
||||
subscribeButton.isEnabled = topicNameText.text.toString().isNotBlank()
|
||||
&& "[-_A-Za-z0-9]+".toRegex().matches(topicNameText.text.toString())
|
||||
&& baseUrlText.text.toString().isNotBlank()
|
||||
&& "^https?://.+".toRegex().matches(baseUrlText.text.toString())
|
||||
} else {
|
||||
subscribeButton.isEnabled = topicNameText.text.toString().isNotBlank()
|
||||
&& "[-_A-Za-z0-9]+".toRegex().matches(topicNameText.text.toString())
|
||||
}
|
||||
}
|
||||
val textWatcher = object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
validateInput()
|
||||
|
@ -85,4 +80,35 @@ class AddFragment(private val listener: AddSubscriptionListener) : DialogFragmen
|
|||
alert
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
|
||||
private fun validateInput() = lifecycleScope.launch(Dispatchers.IO) {
|
||||
val baseUrl = getBaseUrl()
|
||||
val topic = topicNameText.text.toString()
|
||||
val subscription = viewModel.get(baseUrl, topic)
|
||||
|
||||
println("sub $subscription")
|
||||
activity?.let {
|
||||
it.runOnUiThread {
|
||||
if (subscription != null) {
|
||||
subscribeButton.isEnabled = false
|
||||
} else if (useAnotherServerCheckbox.isChecked) {
|
||||
subscribeButton.isEnabled = topic.isNotBlank()
|
||||
&& "[-_A-Za-z0-9]+".toRegex().matches(topic)
|
||||
&& baseUrl.isNotBlank()
|
||||
&& "^https?://.+".toRegex().matches(baseUrl)
|
||||
} else {
|
||||
subscribeButton.isEnabled = topic.isNotBlank()
|
||||
&& "[-_A-Za-z0-9]+".toRegex().matches(topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBaseUrl(): String {
|
||||
return if (useAnotherServerCheckbox.isChecked) {
|
||||
baseUrlText.text.toString()
|
||||
} else {
|
||||
getString(R.string.app_base_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,16 +8,19 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.app.Application
|
||||
import io.heckel.ntfy.data.Subscription
|
||||
import io.heckel.ntfy.data.topicShortUrl
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class MainActivity : AppCompatActivity(), AddFragment.AddSubscriptionListener {
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val viewModel by viewModels<SubscriptionsViewModel> {
|
||||
SubscriptionsViewModelFactory((application as Application).repository)
|
||||
}
|
||||
|
@ -75,11 +78,11 @@ class MainActivity : AppCompatActivity(), AddFragment.AddSubscriptionListener {
|
|||
}
|
||||
|
||||
private fun onSubscribeButtonClick() {
|
||||
val newFragment = AddFragment(this)
|
||||
val newFragment = AddFragment(viewModel) { topic, baseUrl -> onSubscribe(topic, baseUrl) }
|
||||
newFragment.show(supportFragmentManager, "AddFragment")
|
||||
}
|
||||
|
||||
override fun onSubscribe(topic: String, baseUrl: String) {
|
||||
private fun onSubscribe(topic: String, baseUrl: String) {
|
||||
val subscription = Subscription(id = Random.nextLong(), baseUrl = baseUrl, topic = topic, notifications = 0, lastActive = Date().time/1000)
|
||||
viewModel.add(subscription)
|
||||
FirebaseMessaging.getInstance().subscribeToTopic(topic) // FIXME ignores baseUrl
|
||||
|
|
|
@ -21,6 +21,10 @@ class SubscriptionsViewModel(private val repository: Repository) : ViewModel() {
|
|||
fun remove(subscriptionId: Long) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.removeSubscription(subscriptionId)
|
||||
}
|
||||
|
||||
suspend fun get(baseUrl: String, topic: String): Subscription? {
|
||||
return repository.getSubscription(baseUrl, topic)
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionsViewModelFactory(private val repository: Repository) : ViewModelProvider.Factory {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:layoutManager="LinearLayoutManager" android:visibility="gone"/>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
android:id="@+id/detail_item_date_text"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"/>
|
||||
<TextView
|
||||
android:text="This is a very very very long message. It could be as long as 1024 charaters, which is a lot more than you'd think. No, really so far this message is barely 180 characters long. I can't believe how long 1024 bytes are. This is outrageous. Oh you know what, I think I won't type the whole thing. This seems a little too long for a sample text. Well, anyway, it was nice chatting. So far this message is about 400 bytes long. So maybe just double what you see and that's that."
|
||||
|
@ -20,7 +20,7 @@
|
|||
android:id="@+id/detail_item_message_text"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:layoutManager="LinearLayoutManager" android:visibility="gone"/>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" android:clickable="true" android:focusable="true">
|
||||
<ImageView
|
||||
|
@ -16,7 +16,7 @@
|
|||
android:text="ntfy.sh/example"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/main_item_text"
|
||||
android:layout_marginTop="16dp" android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="10dp" android:layout_marginStart="12dp"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
|
||||
|
||||
|
@ -24,7 +24,8 @@
|
|||
android:text="Subscribed, 0 notifications"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/main_item_status"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:layout_marginStart="12dp"/>
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:layout_marginStart="12dp"
|
||||
android:layout_marginBottom="10dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
Loading…
Reference in a new issue