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 @@ - - - - - - - - - -