Action mode delete for topics
This commit is contained in:
parent
f3268deeda
commit
b65bc749ab
12 changed files with 207 additions and 21 deletions
|
@ -10,8 +10,8 @@ data class Subscription(
|
||||||
@PrimaryKey val id: Long, // Internal ID, only used in Repository and activities
|
@PrimaryKey val id: Long, // Internal ID, only used in Repository and activities
|
||||||
@ColumnInfo(name = "baseUrl") val baseUrl: String,
|
@ColumnInfo(name = "baseUrl") val baseUrl: String,
|
||||||
@ColumnInfo(name = "topic") val topic: String,
|
@ColumnInfo(name = "topic") val topic: String,
|
||||||
@ColumnInfo(name = "notifications") val notifications: Int,
|
@ColumnInfo(name = "notifications") val notifications: Int,
|
||||||
@ColumnInfo(name = "lastActive") val lastActive: Long // Unix timestamp
|
@ColumnInfo(name = "lastActive") val lastActive: Long, // Unix timestamp
|
||||||
)
|
)
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
|
|
@ -86,7 +86,6 @@ class AddFragment(private val viewModel: SubscriptionsViewModel, private val onS
|
||||||
val topic = topicNameText.text.toString()
|
val topic = topicNameText.text.toString()
|
||||||
val subscription = viewModel.get(baseUrl, topic)
|
val subscription = viewModel.get(baseUrl, topic)
|
||||||
|
|
||||||
println("sub $subscription")
|
|
||||||
activity?.let {
|
activity?.let {
|
||||||
it.runOnUiThread {
|
it.runOnUiThread {
|
||||||
if (subscription != null) {
|
if (subscription != null) {
|
||||||
|
|
|
@ -88,7 +88,7 @@ class DetailActivity : AppCompatActivity() {
|
||||||
onTestClick()
|
onTestClick()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.detail_menu_delete -> {
|
R.id.detail_menu_unsubscribe -> {
|
||||||
onDeleteClick()
|
onDeleteClick()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package io.heckel.ntfy.ui
|
package io.heckel.ntfy.ui
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.ArgbEvaluator
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.*
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.firebase.messaging.FirebaseMessaging
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
|
@ -18,30 +22,35 @@ import io.heckel.ntfy.data.topicShortUrl
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity(), ActionMode.Callback {
|
||||||
private val viewModel by viewModels<SubscriptionsViewModel> {
|
private val viewModel by viewModels<SubscriptionsViewModel> {
|
||||||
SubscriptionsViewModelFactory((application as Application).repository)
|
SubscriptionsViewModelFactory((application as Application).repository)
|
||||||
}
|
}
|
||||||
|
private lateinit var mainList: RecyclerView
|
||||||
|
private lateinit var adapter: MainAdapter
|
||||||
|
private lateinit var fab: View
|
||||||
|
private var actionMode: ActionMode? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.main_activity)
|
setContentView(R.layout.main_activity)
|
||||||
|
|
||||||
// TODO implement multi-select delete - https://enoent.fr/posts/recyclerview-basics/
|
|
||||||
|
|
||||||
// Action bar
|
// Action bar
|
||||||
title = getString(R.string.main_action_bar_title)
|
title = getString(R.string.main_action_bar_title)
|
||||||
|
|
||||||
// Floating action button ("+")
|
// Floating action button ("+")
|
||||||
val fab: View = findViewById(R.id.fab)
|
fab = findViewById(R.id.fab)
|
||||||
fab.setOnClickListener {
|
fab.setOnClickListener {
|
||||||
onSubscribeButtonClick()
|
onSubscribeButtonClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update main list based on viewModel (& its datasource/livedata)
|
// Update main list based on viewModel (& its datasource/livedata)
|
||||||
val noEntries: View = findViewById(R.id.main_no_subscriptions)
|
val noEntries: View = findViewById(R.id.main_no_subscriptions)
|
||||||
val adapter = SubscriptionsAdapter { subscription -> onSubscriptionItemClick(subscription) }
|
val onSubscriptionClick = { s: Subscription -> onSubscriptionItemClick(s) }
|
||||||
val mainList: RecyclerView = findViewById(R.id.main_subscriptions_list)
|
val onSubscriptionLongClick = { s: Subscription -> onSubscriptionItemLongClick(s) }
|
||||||
|
|
||||||
|
mainList = findViewById(R.id.main_subscriptions_list)
|
||||||
|
adapter = MainAdapter(onSubscriptionClick, onSubscriptionLongClick)
|
||||||
mainList.adapter = adapter
|
mainList.adapter = adapter
|
||||||
|
|
||||||
viewModel.list().observe(this) {
|
viewModel.list().observe(this) {
|
||||||
|
@ -85,7 +94,13 @@ class MainActivity : AppCompatActivity() {
|
||||||
private fun onSubscribe(topic: String, baseUrl: String) {
|
private fun onSubscribe(topic: String, baseUrl: String) {
|
||||||
Log.d(TAG, "Adding subscription ${topicShortUrl(baseUrl, topic)}")
|
Log.d(TAG, "Adding subscription ${topicShortUrl(baseUrl, topic)}")
|
||||||
|
|
||||||
val subscription = Subscription(id = Random.nextLong(), baseUrl = baseUrl, topic = topic, notifications = 0, lastActive = Date().time/1000)
|
val subscription = Subscription(
|
||||||
|
id = Random.nextLong(),
|
||||||
|
baseUrl = baseUrl,
|
||||||
|
topic = topic,
|
||||||
|
notifications = 0,
|
||||||
|
lastActive = Date().time/1000
|
||||||
|
)
|
||||||
viewModel.add(subscription)
|
viewModel.add(subscription)
|
||||||
FirebaseMessaging.getInstance().subscribeToTopic(topic) // FIXME ignores baseUrl
|
FirebaseMessaging.getInstance().subscribeToTopic(topic) // FIXME ignores baseUrl
|
||||||
|
|
||||||
|
@ -93,6 +108,20 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSubscriptionItemClick(subscription: Subscription) {
|
private fun onSubscriptionItemClick(subscription: Subscription) {
|
||||||
|
if (actionMode != null) {
|
||||||
|
handleActionModeClick(subscription)
|
||||||
|
} else {
|
||||||
|
startDetailView(subscription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSubscriptionItemLongClick(subscription: Subscription) {
|
||||||
|
if (actionMode == null) {
|
||||||
|
beginActionMode(subscription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startDetailView(subscription: Subscription) {
|
||||||
Log.d(TAG, "Entering detail view for subscription $subscription")
|
Log.d(TAG, "Entering detail view for subscription $subscription")
|
||||||
|
|
||||||
val intent = Intent(this, DetailActivity::class.java)
|
val intent = Intent(this, DetailActivity::class.java)
|
||||||
|
@ -115,11 +144,133 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleActionModeClick(subscription: Subscription) {
|
||||||
|
adapter.toggleSelection(subscription.id)
|
||||||
|
if (adapter.selected.size == 0) {
|
||||||
|
finishActionMode()
|
||||||
|
} else {
|
||||||
|
actionMode!!.title = adapter.selected.size.toString()
|
||||||
|
redrawList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
this.actionMode = mode
|
||||||
|
if (mode != null) {
|
||||||
|
mode.menuInflater.inflate(R.menu.main_action_mode_menu, menu)
|
||||||
|
mode.title = "1" // One item selected
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||||
|
return when (item?.itemId) {
|
||||||
|
R.id.main_action_mode_unsubscribe -> {
|
||||||
|
onMultiDeleteClick()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMultiDeleteClick() {
|
||||||
|
Log.d(DetailActivity.TAG, "Showing multi-delete dialog for selected items")
|
||||||
|
|
||||||
|
val builder = AlertDialog.Builder(this)
|
||||||
|
builder
|
||||||
|
.setMessage(R.string.main_action_mode_delete_dialog_message)
|
||||||
|
.setPositiveButton(R.string.main_action_mode_delete_dialog_permanently_delete) { _, _ ->
|
||||||
|
adapter.selected.map { viewModel.remove(it) }
|
||||||
|
finishActionMode()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.main_action_mode_delete_dialog_cancel) { _, _ ->
|
||||||
|
finishActionMode()
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
endActionModeAndRedraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun beginActionMode(subscription: Subscription) {
|
||||||
|
actionMode = startActionMode(this)
|
||||||
|
adapter.selected.add(subscription.id)
|
||||||
|
redrawList()
|
||||||
|
|
||||||
|
// Fade out FAB
|
||||||
|
fab.alpha = 1f
|
||||||
|
fab
|
||||||
|
.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.setDuration(ANIMATION_DURATION)
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
fab.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fade status bar color
|
||||||
|
val fromColor = ContextCompat.getColor(this, R.color.primaryColor)
|
||||||
|
val toColor = ContextCompat.getColor(this, R.color.primaryDarkColor)
|
||||||
|
fadeStatusBarColor(fromColor, toColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishActionMode() {
|
||||||
|
actionMode!!.finish()
|
||||||
|
endActionModeAndRedraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endActionModeAndRedraw() {
|
||||||
|
actionMode = null
|
||||||
|
adapter.selected.clear()
|
||||||
|
redrawList()
|
||||||
|
|
||||||
|
// Fade in FAB
|
||||||
|
fab.alpha = 0f
|
||||||
|
fab.visibility = View.VISIBLE
|
||||||
|
fab
|
||||||
|
.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(ANIMATION_DURATION)
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
fab.visibility = View.VISIBLE // Required to replace the old listener
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fade status bar color
|
||||||
|
val fromColor = ContextCompat.getColor(this, R.color.primaryDarkColor)
|
||||||
|
val toColor = ContextCompat.getColor(this, R.color.primaryColor)
|
||||||
|
fadeStatusBarColor(fromColor, toColor)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun redrawList() {
|
||||||
|
mainList.adapter = adapter // Oh, what a hack ...
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fadeStatusBarColor(fromColor: Int, toColor: Int) {
|
||||||
|
// Status bar color fading to match action bar, see https://stackoverflow.com/q/51150077/1440785
|
||||||
|
val statusBarColorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), fromColor, toColor)
|
||||||
|
statusBarColorAnimation.addUpdateListener { animator ->
|
||||||
|
val color = animator.animatedValue as Int
|
||||||
|
window.statusBarColor = color
|
||||||
|
}
|
||||||
|
statusBarColorAnimation.start()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "NtfyMainActivity"
|
const val TAG = "NtfyMainActivity"
|
||||||
const val EXTRA_SUBSCRIPTION_ID = "subscriptionId"
|
const val EXTRA_SUBSCRIPTION_ID = "subscriptionId"
|
||||||
const val EXTRA_SUBSCRIPTION_BASE_URL = "subscriptionBaseUrl"
|
const val EXTRA_SUBSCRIPTION_BASE_URL = "subscriptionBaseUrl"
|
||||||
const val EXTRA_SUBSCRIPTION_TOPIC = "subscriptionTopic"
|
const val EXTRA_SUBSCRIPTION_TOPIC = "subscriptionTopic"
|
||||||
const val REQUEST_CODE_DELETE_SUBSCRIPTION = 1
|
const val REQUEST_CODE_DELETE_SUBSCRIPTION = 1
|
||||||
|
const val ANIMATION_DURATION = 80L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,16 @@ import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.data.Subscription
|
import io.heckel.ntfy.data.Subscription
|
||||||
import io.heckel.ntfy.data.topicShortUrl
|
import io.heckel.ntfy.data.topicShortUrl
|
||||||
|
|
||||||
class SubscriptionsAdapter(private val onClick: (Subscription) -> Unit) :
|
|
||||||
ListAdapter<Subscription, SubscriptionsAdapter.SubscriptionViewHolder>(TopicDiffCallback) {
|
class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLongClick: (Subscription) -> Unit) :
|
||||||
|
ListAdapter<Subscription, MainAdapter.SubscriptionViewHolder>(TopicDiffCallback) {
|
||||||
|
val selected = mutableSetOf<Long>()
|
||||||
|
|
||||||
/* Creates and inflates view and return TopicViewHolder. */
|
/* Creates and inflates view and return TopicViewHolder. */
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context)
|
val view = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.main_fragment_item, parent, false)
|
.inflate(R.layout.main_fragment_item, parent, false)
|
||||||
return SubscriptionViewHolder(view, onClick)
|
return SubscriptionViewHolder(view, selected, onClick, onLongClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gets current topic and uses it to bind view. */
|
/* Gets current topic and uses it to bind view. */
|
||||||
|
@ -28,8 +30,16 @@ class SubscriptionsAdapter(private val onClick: (Subscription) -> Unit) :
|
||||||
holder.bind(subscription)
|
holder.bind(subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleSelection(subscriptionId: Long) {
|
||||||
|
if (selected.contains(subscriptionId)) {
|
||||||
|
selected.remove(subscriptionId)
|
||||||
|
} else {
|
||||||
|
selected.add(subscriptionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 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 SubscriptionViewHolder(itemView: View, val onClick: (Subscription) -> Unit) :
|
class SubscriptionViewHolder(itemView: View, val selected: Set<Long>, val onClick: (Subscription) -> Unit, val onLongClick: (Subscription) -> Unit) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(itemView) {
|
||||||
private var subscription: Subscription? = null
|
private var subscription: Subscription? = null
|
||||||
private val context: Context = itemView.context
|
private val context: Context = itemView.context
|
||||||
|
@ -46,6 +56,10 @@ class SubscriptionsAdapter(private val onClick: (Subscription) -> Unit) :
|
||||||
nameView.text = topicShortUrl(subscription.baseUrl, subscription.topic)
|
nameView.text = topicShortUrl(subscription.baseUrl, subscription.topic)
|
||||||
statusView.text = statusMessage
|
statusView.text = statusMessage
|
||||||
itemView.setOnClickListener { onClick(subscription) }
|
itemView.setOnClickListener { onClick(subscription) }
|
||||||
|
itemView.setOnLongClickListener { onLongClick(subscription); true }
|
||||||
|
if (selected.contains(subscription.id)) {
|
||||||
|
itemView.setBackgroundResource(R.color.primarySelectedRowColor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
app/src/main/res/drawable/baseline_delete_20.xml
Normal file
10
app/src/main/res/drawable/baseline_delete_20.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="20dp"
|
||||||
|
android:height="20dp"
|
||||||
|
android:viewportWidth="20"
|
||||||
|
android:viewportHeight="20"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M4,6v10c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L16,6L4,6zM17,3h-3l-1,-1L7,2L6,3L3,3v2h14L17,3z"/>
|
||||||
|
</vector>
|
|
@ -1,4 +1,4 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
<item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/>
|
<item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/>
|
||||||
<item android:id="@+id/detail_menu_delete" android:title="@string/detail_menu_unsubscribe"/>
|
<item android:id="@+id/detail_menu_unsubscribe" android:title="@string/detail_menu_unsubscribe"/>
|
||||||
</menu>
|
</menu>
|
||||||
|
|
4
app/src/main/res/menu/main_action_mode_menu.xml
Normal file
4
app/src/main/res/menu/main_action_mode_menu.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<item android:id="@+id/main_action_mode_unsubscribe" android:title="@string/detail_menu_unsubscribe"
|
||||||
|
android:icon="@drawable/baseline_delete_20"/>
|
||||||
|
</menu>
|
|
@ -2,9 +2,11 @@
|
||||||
<resources>
|
<resources>
|
||||||
<color name="primaryColor">#338574</color>
|
<color name="primaryColor">#338574</color>
|
||||||
<color name="primaryLightColor">#338574</color>
|
<color name="primaryLightColor">#338574</color>
|
||||||
<color name="primaryDarkColor">#338574</color>
|
<color name="primaryDarkColor">#2A6E60</color>
|
||||||
|
|
||||||
<color name="primaryTextColor">#000000</color>
|
<color name="primaryTextColor">#000000</color>
|
||||||
<color name="primaryLightTextColor">#FFFFFF</color>
|
<color name="primaryLightTextColor">#FFFFFF</color>
|
||||||
|
|
||||||
|
<color name="primarySelectedRowColor">#EEEEEE</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
<string name="main_menu_source_url">https://heckel.io/ntfy-android</string>
|
<string name="main_menu_source_url">https://heckel.io/ntfy-android</string>
|
||||||
<string name="main_menu_website_title">Visit ntfy.sh</string>
|
<string name="main_menu_website_title">Visit ntfy.sh</string>
|
||||||
|
|
||||||
|
<!-- Main activity: Action mode -->
|
||||||
|
<string name="main_action_mode_delete_dialog_message">Do you really want to unsubscribe from selected topic(s) and permanently delete all the messages you received?</string>
|
||||||
|
<string name="main_action_mode_delete_dialog_permanently_delete">Permanently delete</string>
|
||||||
|
<string name="main_action_mode_delete_dialog_cancel">Cancel</string>
|
||||||
|
|
||||||
<!-- Main activity: List and such -->
|
<!-- Main activity: List and such -->
|
||||||
<string name="main_item_status_connecting">connecting …</string>
|
<string name="main_item_status_connecting">connecting …</string>
|
||||||
<string name="main_item_status_reconnecting">reconnecting …</string>
|
<string name="main_item_status_reconnecting">reconnecting …</string>
|
||||||
|
@ -35,7 +40,7 @@
|
||||||
<string name="detail_how_to_intro">To send notifications to this topic, simply PUT or POST to the topic URL.</string>
|
<string name="detail_how_to_intro">To send notifications to this topic, simply PUT or POST to the topic URL.</string>
|
||||||
<string name="detail_how_to_example"><![CDATA[ Example (using curl):<br/><tt>$ curl -d \"Hi\" %1$s</tt> ]]></string>
|
<string name="detail_how_to_example"><![CDATA[ Example (using curl):<br/><tt>$ curl -d \"Hi\" %1$s</tt> ]]></string>
|
||||||
<string name="detail_how_to_link">For more detailed instructions, check out the ntfy.sh website and documentation.</string>
|
<string name="detail_how_to_link">For more detailed instructions, check out the ntfy.sh website and documentation.</string>
|
||||||
<string name="detail_delete_dialog_message">Do you really want to permanently delete this subscription and all its messages?</string>
|
<string name="detail_delete_dialog_message">Do you really want to unsubscribe from this topic and delete all of the messages you received?</string>
|
||||||
<string name="detail_delete_dialog_permanently_delete">Permanently delete</string>
|
<string name="detail_delete_dialog_permanently_delete">Permanently delete</string>
|
||||||
<string name="detail_delete_dialog_cancel">Cancel</string>
|
<string name="detail_delete_dialog_cancel">Cancel</string>
|
||||||
<string name="detail_test_message">This is a test notification from the Ntfy Android app. It was sent at %1$s.</string>
|
<string name="detail_test_message">This is a test notification from the Ntfy Android app. It was sent at %1$s.</string>
|
||||||
|
|
|
@ -4,5 +4,6 @@
|
||||||
<item name="colorPrimaryVariant">@color/primaryDarkColor</item>
|
<item name="colorPrimaryVariant">@color/primaryDarkColor</item>
|
||||||
<item name="colorAccent">@color/primaryLightColor</item>
|
<item name="colorAccent">@color/primaryLightColor</item>
|
||||||
<item name="android:statusBarColor">@color/primaryColor</item>
|
<item name="android:statusBarColor">@color/primaryColor</item>
|
||||||
|
<item name="actionModeBackground">@color/primaryDarkColor</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue