diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5c2c03d..8a14c41 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,7 +25,9 @@ - + diff --git a/app/src/main/java/io/heckel/ntfy/msg/MessagingService.kt b/app/src/main/java/io/heckel/ntfy/msg/MessagingService.kt index 7c54940..8e8bd1a 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/MessagingService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/MessagingService.kt @@ -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) diff --git a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt index e881996..4c6be7d 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt @@ -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) + } + } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index ee08f18..5772fe8 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -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 { 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 diff --git a/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsViewModel.kt b/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsViewModel.kt index 9c0d1d7..67cb9ab 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsViewModel.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsViewModel.kt @@ -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 { diff --git a/app/src/main/res/layout/detail_activity.xml b/app/src/main/res/layout/detail_activity.xml index 92b493d..d85007b 100644 --- a/app/src/main/res/layout/detail_activity.xml +++ b/app/src/main/res/layout/detail_activity.xml @@ -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"/> diff --git a/app/src/main/res/layout/detail_fragment_item.xml b/app/src/main/res/layout/detail_fragment_item.xml index 0994681..baf8bfc 100644 --- a/app/src/main/res/layout/detail_fragment_item.xml +++ b/app/src/main/res/layout/detail_fragment_item.xml @@ -11,8 +11,8 @@ android:id="@+id/detail_item_date_text" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" - android:layout_marginTop="16dp" - android:textAppearance="@style/TextAppearance.AppCompat.Small" /> + android:layout_marginTop="10dp" + android:textAppearance="@style/TextAppearance.AppCompat.Small"/> diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index ac29407..622a9d8 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -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"/> diff --git a/app/src/main/res/layout/main_fragment_item.xml b/app/src/main/res/layout/main_fragment_item.xml index e8715e7..e9004b7 100644 --- a/app/src/main/res/layout/main_fragment_item.xml +++ b/app/src/main/res/layout/main_fragment_item.xml @@ -1,7 +1,7 @@ @@ -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"/>