diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 958e4cb..acaad9f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,12 +13,11 @@
android:theme="@style/AppTheme">
+ android:label="@string/app_name">
-
diff --git a/app/src/main/java/io/heckel/ntfy/data/ConnectionManager.kt b/app/src/main/java/io/heckel/ntfy/data/ConnectionManager.kt
index 9e74c0b..ed1afd6 100644
--- a/app/src/main/java/io/heckel/ntfy/data/ConnectionManager.kt
+++ b/app/src/main/java/io/heckel/ntfy/data/ConnectionManager.kt
@@ -61,7 +61,7 @@ class ConnectionManager(private val repository: Repository) {
} finally {
conn.disconnect()
}
- updateStatus(subscriptionId, Status.CONNECTING)
+ updateStatus(subscriptionId, Status.RECONNECTING)
println("Connection terminated: $topicUrl")
}
diff --git a/app/src/main/java/io/heckel/ntfy/data/Models.kt b/app/src/main/java/io/heckel/ntfy/data/Models.kt
index 9d389ba..ae3bed7 100644
--- a/app/src/main/java/io/heckel/ntfy/data/Models.kt
+++ b/app/src/main/java/io/heckel/ntfy/data/Models.kt
@@ -1,7 +1,7 @@
package io.heckel.ntfy.data
enum class Status {
- CONNECTED, CONNECTING
+ CONNECTED, CONNECTING, RECONNECTING
}
data class Subscription(
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 5ae9148..a070440 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt
@@ -11,9 +11,9 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputEditText
import io.heckel.ntfy.R
-class AddFragment(private val listener: Listener) : DialogFragment() {
- interface Listener {
- fun onAddClicked(topic: String, baseUrl: String)
+class AddFragment(private val listener: AddSubscriptionListener) : DialogFragment() {
+ interface AddSubscriptionListener {
+ fun onAddSubscription(topic: String, baseUrl: String)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -34,7 +34,7 @@ class AddFragment(private val listener: Listener) : DialogFragment() {
} else {
getString(R.string.add_dialog_base_url_default)
}
- listener.onAddClicked(topic, baseUrl)
+ listener.onAddSubscription(topic, baseUrl)
}
.setNegativeButton(R.string.add_dialog_button_cancel) { _, _ ->
dialog?.cancel()
diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
deleted file mode 100644
index c084353..0000000
--- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.heckel.ntfy.ui
-
-import android.os.Bundle
-import android.widget.Button
-import android.widget.TextView
-import androidx.activity.viewModels
-import androidx.appcompat.app.AppCompatActivity
-import io.heckel.ntfy.R
-import io.heckel.ntfy.data.topicShortUrl
-
-class DetailActivity : AppCompatActivity() {
- private val subscriptionsViewModel by viewModels {
- SubscriptionsViewModelFactory()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.detail_activity)
-
- var subscriptionId: Long? = null
-
- /* Connect variables to UI elements. */
- val topicText: TextView = findViewById(R.id.topic_detail_url)
- val removeButton: Button = findViewById(R.id.remove_button)
-
- val bundle: Bundle? = intent.extras
- if (bundle != null) {
- subscriptionId = bundle.getLong(SUBSCRIPTION_ID)
- }
-
- // TODO This should probably fail hard if topicId is null
-
- /* If currentTopicId is not null, get corresponding topic and set name, image and
- description */
- subscriptionId?.let {
- val subscription = subscriptionsViewModel.get(it)
- topicText.text = subscription?.let { s -> topicShortUrl(s) }
-
- removeButton.setOnClickListener {
- if (subscription != null) {
- subscriptionsViewModel.remove(subscription)
- }
- finish()
- }
- }
- }
-}
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 26bd4c1..cdbcf47 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt
@@ -22,10 +22,11 @@ import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.data.topicShortUrl
import kotlin.random.Random
+
const val SUBSCRIPTION_ID = "topic_id"
-class MainActivity : AppCompatActivity(), AddFragment.Listener {
- private val subscriptionViewModel by viewModels {
+class MainActivity : AppCompatActivity(), AddFragment.AddSubscriptionListener {
+ private val subscriptionsViewModel by viewModels {
SubscriptionsViewModelFactory()
}
@@ -33,30 +34,42 @@ class MainActivity : AppCompatActivity(), AddFragment.Listener {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
+ // Action bar
+ title = getString(R.string.main_action_bar_title)
+ supportActionBar?.setIcon(R.drawable.ntfy) // FIXME this doesn't work
+
// Floating action button ("+")
val fab: View = findViewById(R.id.fab)
fab.setOnClickListener {
- fabOnClick()
+ onAddButtonClick()
}
// Update main list based on topicsViewModel (& its datasource/livedata)
- val adapter = TopicsAdapter { topic -> subscriptionOnClick(topic) }
- val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
- recyclerView.adapter = adapter
+ val noSubscriptionsText: View = findViewById(R.id.main_no_subscriptions_text)
+ val adapter = SubscriptionsAdapter(this) { subscription -> onUnsubscribe(subscription) }
+ val mainList: RecyclerView = findViewById(R.id.main_subscriptions_list)
+ mainList.adapter = adapter
- subscriptionViewModel.list().observe(this) {
+ subscriptionsViewModel.list().observe(this) {
it?.let {
adapter.submitList(it as MutableList)
+ if (it.isEmpty()) {
+ mainList.visibility = View.GONE
+ noSubscriptionsText.visibility = View.VISIBLE
+ } else {
+ mainList.visibility = View.VISIBLE
+ noSubscriptionsText.visibility = View.GONE
+ }
}
}
// Set up notification channel
createNotificationChannel()
- subscriptionViewModel.setListener { n -> displayNotification(n) }
+ subscriptionsViewModel.setListener { n -> displayNotification(n) }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.menu, menu)
+ menuInflater.inflate(R.menu.main_action_bar_menu, menu)
return true
}
@@ -74,22 +87,18 @@ class MainActivity : AppCompatActivity(), AddFragment.Listener {
}
}
- /* Opens detail view when list item is clicked. */
- private fun subscriptionOnClick(subscription: Subscription) {
- val intent = Intent(this, DetailActivity()::class.java)
- intent.putExtra(SUBSCRIPTION_ID, subscription.id)
- startActivity(intent)
+ private fun onUnsubscribe(subscription: Subscription) {
+ subscriptionsViewModel.remove(subscription)
}
- /* Adds topic to topicList when FAB is clicked. */
- private fun fabOnClick() {
+ private fun onAddButtonClick() {
val newFragment = AddFragment(this)
newFragment.show(supportFragmentManager, "AddFragment")
}
- override fun onAddClicked(topic: String, baseUrl: String) {
+ override fun onAddSubscription(topic: String, baseUrl: String) {
val subscription = Subscription(Random.nextLong(), topic, baseUrl, Status.CONNECTING, 0)
- subscriptionViewModel.add(subscription)
+ subscriptionsViewModel.add(subscription)
}
private fun displayNotification(n: Notification) {
diff --git a/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsAdapter.kt
index 4e5680c..d630eba 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsAdapter.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/SubscriptionsAdapter.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.PopupMenu
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@@ -11,54 +12,65 @@ import androidx.recyclerview.widget.RecyclerView
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Status
import io.heckel.ntfy.data.Subscription
-import io.heckel.ntfy.data.topicUrl
+import io.heckel.ntfy.data.topicShortUrl
-class TopicsAdapter(private val onClick: (Subscription) -> Unit) :
- ListAdapter(TopicDiffCallback) {
+class SubscriptionsAdapter(private val context: Context, private val onClick: (Subscription) -> Unit) :
+ ListAdapter(TopicDiffCallback) {
/* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
- class TopicViewHolder(itemView: View, val onClick: (Subscription) -> Unit) :
+ class SubscriptionViewHolder(itemView: View, val onUnsubscribe: (Subscription) -> Unit) :
RecyclerView.ViewHolder(itemView) {
- private var topic: Subscription? = null
+ private var subscription: Subscription? = null
private val context: Context = itemView.context
private val nameView: TextView = itemView.findViewById(R.id.topic_text)
private val statusView: TextView = itemView.findViewById(R.id.topic_status)
init {
- itemView.setOnClickListener {
- topic?.let {
- onClick(it)
- }
+ val popup = PopupMenu(context, itemView)
+ popup.inflate(R.menu.main_item_popup_menu)
+ popup.setOnMenuItemClickListener { item ->
+ when (item.itemId) {
+ R.id.main_item_popup_unsubscribe -> {
+ subscription?.let { s -> onUnsubscribe(s) }
+ true
+ }
+ else -> false
+ }
+ }
+ itemView.setOnLongClickListener {
+ subscription?.let { popup.show() }
+ true
}
}
fun bind(subscription: Subscription) {
- this.topic = subscription
- val statusText = when (subscription.status) {
- Status.CONNECTING -> context.getString(R.string.status_connecting)
- else -> context.getString(R.string.status_connected)
- }
- val statusMessage = if (subscription.messages == 1) {
- context.getString(R.string.status_text_one, statusText, subscription.messages)
+ this.subscription = subscription
+ val notificationsCountMessage = if (subscription.messages == 1) {
+ context.getString(R.string.main_item_status_text_one, subscription.messages)
} else {
- context.getString(R.string.status_text_not_one, statusText, subscription.messages)
+ context.getString(R.string.main_item_status_text_not_one, subscription.messages)
}
- nameView.text = topicUrl(subscription)
- statusView.text = statusMessage
+ val statusText = when (subscription.status) {
+ Status.CONNECTING -> notificationsCountMessage + ", " + context.getString(R.string.main_item_status_connecting)
+ Status.RECONNECTING -> notificationsCountMessage + ", " + context.getString(R.string.main_item_status_reconnecting)
+ else -> notificationsCountMessage
+ }
+ nameView.text = topicShortUrl(subscription)
+ statusView.text = statusText
}
}
/* Creates and inflates view and return TopicViewHolder. */
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopicViewHolder {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.main_fragment_item, parent, false)
- return TopicViewHolder(view, onClick)
+ return SubscriptionViewHolder(view, onClick)
}
/* Gets current topic and uses it to bind view. */
- override fun onBindViewHolder(holder: TopicViewHolder, position: Int) {
- val topic = getItem(position)
- holder.bind(topic)
+ override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) {
+ val subscription = getItem(position)
+ holder.bind(subscription)
}
}
diff --git a/app/src/main/res/layout/add_dialog_fragment.xml b/app/src/main/res/layout/add_dialog_fragment.xml
index d37bc9e..eeab923 100644
--- a/app/src/main/res/layout/add_dialog_fragment.xml
+++ b/app/src/main/res/layout/add_dialog_fragment.xml
@@ -19,7 +19,8 @@
+ android:layout_height="wrap_content" android:hint="@string/add_dialog_topic_name_hint"
+ android:maxLines="1" android:inputType="text"/>
+ android:hint="@string/add_dialog_base_url_hint" android:inputType="textUri" android:maxLines="1"/>
diff --git a/app/src/main/res/layout/detail_activity.xml b/app/src/main/res/layout/detail_activity.xml
deleted file mode 100644
index 29d8eba..0000000
--- a/app/src/main/res/layout/detail_activity.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
index c114882..b71c789 100644
--- a/app/src/main/res/layout/main_activity.xml
+++ b/app/src/main/res/layout/main_activity.xml
@@ -1,35 +1,36 @@
-
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ android:id="@+id/main_subscriptions_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ app:layoutManager="LinearLayoutManager" android:visibility="gone"/>
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/main_fragment_item.xml b/app/src/main/res/layout/main_fragment_item.xml
index c3f53bf..e3dea63 100644
--- a/app/src/main/res/layout/main_fragment_item.xml
+++ b/app/src/main/res/layout/main_fragment_item.xml
@@ -2,7 +2,8 @@
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical" android:clickable="true" android:focusable="true">
+ android:textAppearance="@style/TextAppearance.AppCompat.Small" android:layout_marginStart="16dp"/>
diff --git a/app/src/main/res/menu/menu.xml b/app/src/main/res/menu/main_action_bar_menu.xml
similarity index 100%
rename from app/src/main/res/menu/menu.xml
rename to app/src/main/res/menu/main_action_bar_menu.xml
diff --git a/app/src/main/res/menu/main_item_popup_menu.xml b/app/src/main/res/menu/main_item_popup_menu.xml
new file mode 100644
index 0000000..313c9b1
--- /dev/null
+++ b/app/src/main/res/menu/main_item_popup_menu.xml
@@ -0,0 +1,4 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1c4f646..7f6c9fd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,4 +1,5 @@
+
Ntfy
@@ -6,21 +7,20 @@
ntfy
- Subscribed topics
+ Subscribed topics
Show source & license
https://heckel.io/ntfy-android
Visit ntfy.sh
https://ntfy.sh
- Connected
- Connecting
- %1$s, %2$d notification
- %1$s, %2$d notifications
- fab
-
-
- Unsubscribe
+ connecting …
+ reconnecting …
+ %1$d notification received
+ %1$d notifications received
+ Unsubscribe
+ Add subscription
+ It looks like you don\'t have any subscriptions yet.
Subscribe to topic