WIP: Muted until

This commit is contained in:
Philipp Heckel 2021-11-21 14:54:13 -05:00
parent 0c6f1cd540
commit 71b5d56f6a
17 changed files with 479 additions and 42 deletions

View file

@ -58,6 +58,7 @@ dependencies {
// WorkManager
implementation "androidx.work:work-runtime-ktx:2.6.0"
implementation 'androidx.preference:preference:1.1.1'
// Room (SQLite)
def roomVersion = "2.3.0"

View file

@ -0,0 +1,118 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "7b0ef556331f6d2dd3515425837c3d3a",
"entities": [
{
"tableName": "Subscription",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "baseUrl",
"columnName": "baseUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "topic",
"columnName": "topic",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instant",
"columnName": "instant",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mutedUntil",
"columnName": "mutedUntil",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_Subscription_baseUrl_topic",
"unique": true,
"columnNames": [
"baseUrl",
"topic"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)"
}
],
"foreignKeys": []
},
{
"tableName": "Notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `message` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscriptionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "notificationId",
"columnName": "notificationId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7b0ef556331f6d2dd3515425837c3d3a')"
]
}
}

View file

@ -7,15 +7,11 @@
- WAKE_LOCK & RECEIVE_BOOT_COMPLETED are required to restart the foreground service
if it is stopped; see https://robertohuertas.com/2019/06/29/android_foreground_services/
-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!--
Application
- usesCleartextTraffic is required to support "use another server" feature
-->
<application
android:name=".app.Application"
android:allowBackup="true"
@ -25,7 +21,8 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<activity android:name=".ui.SubscriptionSettingsActivity">
</activity>
<!-- Main activity -->
<activity
android:name=".ui.MainActivity"
@ -35,28 +32,22 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Detail activity -->
</activity> <!-- Detail activity -->
<activity
android:name=".ui.DetailActivity"
android:parentActivityName=".ui.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity" />
</activity>
<!-- Subscriber foreground service for hosts other than ntfy.sh -->
<service android:name=".msg.SubscriberService" />
<!-- Subscriber service restart on reboot -->
<receiver android:enabled="true" android:name=".msg.SubscriberService$StartReceiver">
android:value=".ui.MainActivity"/>
</activity> <!-- Subscriber foreground service for hosts other than ntfy.sh -->
<service android:name=".msg.SubscriberService"/> <!-- Subscriber service restart on reboot -->
<receiver
android:name=".msg.SubscriberService$StartReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<!-- Firebase messaging -->
</receiver> <!-- Firebase messaging -->
<service
android:name=".msg.FirebaseService"
android:exported="false">

View file

@ -5,6 +5,7 @@ import androidx.room.*
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.flow.Flow
import java.util.*
@Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true)])
data class Subscription(
@ -12,12 +13,16 @@ data class Subscription(
@ColumnInfo(name = "baseUrl") val baseUrl: String,
@ColumnInfo(name = "topic") val topic: String,
@ColumnInfo(name = "instant") val instant: Boolean,
@ColumnInfo(name = "mutedUntil") val mutedUntil: Long,
//val notificationSchedule: String,
//val notificationSound: String,
@Ignore val totalCount: Int = 0, // Total notifications
@Ignore val newCount: Int = 0, // New notifications
@Ignore val lastActive: Long = 0, // Unix timestamp
@Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE
) {
constructor(id: Long, baseUrl: String, topic: String, instant: Boolean) : this(id, baseUrl, topic, instant, 0, 0, 0, ConnectionState.NOT_APPLICABLE)
constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long) :
this(id, baseUrl, topic, instant, mutedUntil, 0, 0, 0, ConnectionState.NOT_APPLICABLE)
}
enum class ConnectionState {
@ -29,6 +34,7 @@ data class SubscriptionWithMetadata(
val baseUrl: String,
val topic: String,
val instant: Boolean,
val mutedUntil: Long,
val totalCount: Int,
val newCount: Int,
val lastActive: Long
@ -44,7 +50,7 @@ data class Notification(
@ColumnInfo(name = "deleted") val deleted: Boolean,
)
@androidx.room.Database(entities = [Subscription::class, Notification::class], version = 2)
@androidx.room.Database(entities = [Subscription::class, Notification::class], version = 3)
abstract class Database : RoomDatabase() {
abstract fun subscriptionDao(): SubscriptionDao
abstract fun notificationDao(): NotificationDao
@ -79,6 +85,12 @@ abstract class Database : RoomDatabase() {
db.execSQL("ALTER TABLE Notification ADD COLUMN deleted INTEGER NOT NULL DEFAULT('0')")
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE Subscription ADD COLUMN mutedUntil INTEGER NOT NULL DEFAULT('0')")
}
}
}
}
@ -86,7 +98,7 @@ abstract class Database : RoomDatabase() {
interface SubscriptionDao {
@Query("""
SELECT
s.id, s.baseUrl, s.topic, s.instant,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
@ -99,7 +111,7 @@ interface SubscriptionDao {
@Query("""
SELECT
s.id, s.baseUrl, s.topic, s.instant,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
@ -112,7 +124,7 @@ interface SubscriptionDao {
@Query("""
SELECT
s.id, s.baseUrl, s.topic, s.instant,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
@ -125,7 +137,7 @@ interface SubscriptionDao {
@Query("""
SELECT
s.id, s.baseUrl, s.topic, s.instant,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive

View file

@ -113,6 +113,7 @@ class Repository(private val subscriptionDao: SubscriptionDao, private val notif
baseUrl = s.baseUrl,
topic = s.topic,
instant = s.instant,
mutedUntil = s.mutedUntil,
totalCount = s.totalCount,
newCount = s.newCount,
lastActive = s.lastActive,
@ -130,6 +131,7 @@ class Repository(private val subscriptionDao: SubscriptionDao, private val notif
baseUrl = s.baseUrl,
topic = s.topic,
instant = s.instant,
mutedUntil = s.mutedUntil,
totalCount = s.totalCount,
newCount = s.newCount,
lastActive = s.lastActive,

View file

@ -33,7 +33,7 @@ import kotlinx.coroutines.launch
import java.util.*
import java.util.concurrent.atomic.AtomicLong
class DetailActivity : AppCompatActivity(), ActionMode.Callback {
class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFragment.NotificationSettingsListener {
private val viewModel by viewModels<DetailViewModel> {
DetailViewModelFactory((application as Application).repository)
}
@ -187,6 +187,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback {
onTestClick()
true
}
R.id.detail_menu_notification -> {
onNotificationSettingsClick()
true
}
R.id.detail_menu_enable_instant -> {
onInstantEnableClick(enable = true)
true
@ -228,6 +232,38 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback {
}
}
private fun onNotificationSettingsClick() {
Log.d(TAG, "Showing notification settings dialog for ${topicShortUrl(subscriptionBaseUrl, subscriptionTopic)}")
val intent = Intent(this, SubscriptionSettingsActivity::class.java)
startActivityForResult(intent, /*XXXXXX*/MainActivity.REQUEST_CODE_DELETE_SUBSCRIPTION)
/*
val notificationFragment = NotificationFragment()
notificationFragment.show(supportFragmentManager, NotificationFragment.TAG)*/
}
override fun onNotificationSettingsChanged(mutedUntil: Long) {
lifecycleScope.launch(Dispatchers.IO) {
val subscription = repository.getSubscription(subscriptionId)
val newSubscription = subscription?.copy(mutedUntil = mutedUntil)
newSubscription?.let { repository.updateSubscription(newSubscription) }
runOnUiThread {
when (mutedUntil) {
0L -> Toast.makeText(this@DetailActivity, getString(R.string.notification_dialog_enabled_toast_message), Toast.LENGTH_SHORT).show()
1L -> Toast.makeText(this@DetailActivity, getString(R.string.notification_dialog_muted_forever_toast_message), Toast.LENGTH_SHORT).show()
else -> {
val mutedUntilDate = Date(mutedUntil).toString()
Toast.makeText(
this@DetailActivity,
getString(R.string.notification_dialog_muted_until_toast_message, mutedUntilDate),
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
private fun onCopyUrlClick() {
val url = topicUrl(subscriptionBaseUrl, subscriptionTopic)
Log.d(TAG, "Copying topic URL $url to clipboard ")
@ -478,6 +514,5 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback {
companion object {
const val TAG = "NtfyDetailActivity"
const val CANCEL_NOTIFICATION_DELAY_MILLIS = 20_000L
}
}

View file

@ -165,6 +165,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
baseUrl = baseUrl,
topic = topic,
instant = instant,
mutedUntil = 0,
totalCount = 0,
newCount = 0,
lastActive = Date().time/1000

View file

@ -0,0 +1,73 @@
package io.heckel.ntfy.ui
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.CheckBox
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputEditText
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Database
import io.heckel.ntfy.data.Repository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class NotificationFragment : DialogFragment() {
private lateinit var repository: Repository
private lateinit var settingsListener: NotificationSettingsListener
interface NotificationSettingsListener {
fun onNotificationSettingsChanged(mutedUntil: Long)
}
override fun onAttach(context: Context) {
super.onAttach(context)
settingsListener = activity as NotificationSettingsListener
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
if (activity == null) {
throw IllegalStateException("Activity cannot be null")
}
// Dependencies
val database = Database.getInstance(activity!!.applicationContext)
repository = Repository.getInstance(database.subscriptionDao(), database.notificationDao())
// Build root view
val view = requireActivity().layoutInflater.inflate(R.layout.notification_dialog_fragment, null)
// topicNameText = view.findViewById(R.id.add_dialog_topic_text) as TextInputEditText
// Build dialog
val alert = AlertDialog.Builder(activity)
.setView(view)
.setPositiveButton(R.string.notification_dialog_save) { _, _ ->
///
settingsListener.onNotificationSettingsChanged(0L)
}
.setNegativeButton(R.string.notification_dialog_cancel) { _, _ ->
dialog?.cancel()
}
.create()
// Add logic to disable "Subscribe" button on invalid input
alert.setOnShowListener {
val dialog = it as AlertDialog
///
}
return alert
}
companion object {
const val TAG = "NtfyNotificationFragment"
}
}

View file

@ -0,0 +1,5 @@
package io.heckel.ntfy.ui
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import io.heckel.ntfy.R

View file

@ -0,0 +1,25 @@
package io.heckel.ntfy.ui
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import io.heckel.ntfy.R
class SubscriptionSettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_subscription_settings)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportFragmentManager
.beginTransaction()
.replace(R.id.subscription_settings_content, SubscriptionSettingsFragment())
.commit()
}
class SubscriptionSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"
android:fillColor="#FFFFFF"/>
</vector>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.SubscriptionSettingsActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/subscription_settings_content" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
android:id="@+id/add_dialog_title_text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="3dp"
android:text="@string/notification_dialog_title"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp" android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:text="Pause notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/textView"
app:layout_constraintTop_toBottomOf="@+id/add_dialog_title_text2"
android:layout_marginTop="20dp" app:layout_constraintStart_toStartOf="@+id/add_dialog_title_text2"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/textView" app:layout_constraintTop_toBottomOf="@+id/textView"
android:layout_marginTop="10dp">
<RadioButton
android:text="Pause forever"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/radioButton"
/>
<RadioButton
android:text="Pause for 30 minutes"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/radioButton4"
/>
<RadioButton
android:text="Pause for 1 hour"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/radioButton5"
/>
<RadioButton
android:text="Pause for 2 hours"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/radioButton6"
/>
<RadioButton
android:text="Pause until tomorrow"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/radioButton3"
/>
</RadioGroup>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/switch1"
app:layout_constraintStart_toEndOf="@+id/textView" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="19dp" app:layout_constraintTop_toBottomOf="@+id/add_dialog_title_text2"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,4 +1,6 @@
<menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/detail_menu_notification" android:title="@string/detail_menu_enable_instant"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_white_24dp"/>
<item android:id="@+id/detail_menu_enable_instant" android:title="@string/detail_menu_enable_instant"
app:showAsAction="ifRoom" android:icon="@drawable/ic_bolt_outline_white_24dp"/>
<item android:id="@+id/detail_menu_disable_instant" android:title="@string/detail_menu_disable_instant"

View file

@ -0,0 +1,16 @@
<resources>
<!-- Reply Preference -->
<string-array name="reply_entries">
<item>Forever</item>
<item>30 minutes</item>
<item>1 hour</item>
<item>2 hours</item>
</string-array>
<string-array name="reply_values">
<item>forever</item>
<item>30min</item>
<item>1hr</item>
<item>2h</item>
</string-array>
</resources>

View file

@ -10,7 +10,8 @@
<string name="channel_subscriber_notification_text">You are subscribed to instant delivery topics</string>
<string name="channel_subscriber_notification_text_one">You are subscribed to one instant delivery topic</string>
<string name="channel_subscriber_notification_text_two">You are subscribed to two instant delivery topics</string>
<string name="channel_subscriber_notification_text_three">You are subscribed to three instant delivery topics</string>
<string name="channel_subscriber_notification_text_three">You are subscribed to three instant delivery topics
</string>
<string name="channel_subscriber_notification_text_four">You are subscribed to four instant delivery topics</string>
<string name="channel_subscriber_notification_text_more">You are subscribed to %1$d instant delivery topics</string>
@ -27,7 +28,9 @@
<!-- Main activity: Action mode -->
<string name="main_action_mode_menu_unsubscribe">Unsubscribe</string>
<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_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>
@ -38,12 +41,17 @@
<string name="main_item_date_yesterday">Yesterday</string>
<string name="main_add_button_description">Add subscription</string>
<string name="main_no_subscriptions_text">It looks like you don\'t have any subscriptions yet.</string>
<string name="main_how_to_intro">Click the button below to create or subscribe to a topic. After that, you can send messages via PUT or POST and you\'ll receive notifications on your phone.</string>
<string name="main_how_to_link">For more detailed instructions, check out the ntfy.sh website and documentation.</string>
<string name="main_how_to_intro">Click the button below to create or subscribe to a topic. After that, you can send
messages via PUT or POST and you\'ll receive notifications on your phone.
</string>
<string name="main_how_to_link">For more detailed instructions, check out the ntfy.sh website and documentation.
</string>
<!-- Add dialog -->
<string name="add_dialog_title">Subscribe to topic</string>
<string name="add_dialog_description_below">Topics are not password-protected, so choose a name that\'s not easy to guess. Once subscribed, you can PUT/POST to receive notifications on your phone.</string>
<string name="add_dialog_description_below">Topics are not password-protected, so choose a name that\'s not easy to
guess. Once subscribed, you can PUT/POST to receive notifications on your phone.
</string>
<string name="add_dialog_topic_name_hint">Topic name, e.g. phils_alerts</string>
<string name="add_dialog_use_another_server">Use another server</string>
<string name="add_dialog_use_another_server_description">
@ -60,13 +68,18 @@
<!-- Detail activity -->
<string name="detail_no_notifications_text">You haven\'t received any notifications for this topic yet.</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_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_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 unsubscribe from this topic and delete all of the messages you received?</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 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_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>
<string name="detail_test_message_error">Could not send test message: %1$s</string>
<string name="detail_copied_to_clipboard_message">Copied to clipboard</string>
<string name="detail_instant_delivery_enabled">Instant delivery enabled</string>
@ -74,17 +87,44 @@
<string name="detail_instant_info">Instant delivery cannot be disabled for subscriptions from other servers</string>
<!-- Detail activity: Action bar -->
<string name="detail_menu_test">Send test notification</string>
<string name="detail_menu_copy_url">Copy topic address</string>
<string name="detail_menu_notification">Notification</string>
<string name="detail_menu_enable_instant">Enable instant delivery</string>
<string name="detail_menu_disable_instant">Disable instant delivery</string>
<string name="detail_menu_test">Send test notification</string>
<string name="detail_menu_copy_url">Copy topic address</string>
<string name="detail_menu_instant_info">Instant delivery enabled</string>
<string name="detail_menu_unsubscribe">Unsubscribe</string>
<!-- Detail activity: Action mode -->
<string name="detail_action_mode_menu_copy">Copy</string>
<string name="detail_action_mode_menu_delete">Delete</string>
<string name="detail_action_mode_delete_dialog_message">Do you really want to permanently delete the selected message(s)?</string>
<string name="detail_action_mode_delete_dialog_message">Do you really want to permanently delete the selected
message(s)?
</string>
<string name="detail_action_mode_delete_dialog_permanently_delete">Permanently delete</string>
<string name="detail_action_mode_delete_dialog_cancel">Cancel</string>
<!-- Notification dialog -->
<string name="notification_dialog_title">Notification settings</string>
<string name="notification_dialog_cancel">Cancel</string>
<string name="notification_dialog_save">Save</string>
<string name="notification_dialog_enabled_toast_message">Notifications re-enabled</string>
<string name="notification_dialog_muted_forever_toast_message">Notifications are now paused</string>
<string name="notification_dialog_muted_until_toast_message">Notifications are now paused until %s</string>
<!-- Preference Titles -->
<string name="subscription_settings_notifications_header">Notifications</string>
<string name="subscription_settings_pause_title">Pause notifications</string>
<string name="subscription_settings_pause_for_title">Until …</string>
<string name="sync_header">Sync</string>
<!-- Messages Preferences -->
<string name="signature_title">Your signature</string>
<!-- Sync Preferences -->
<string name="attachment_title">Download incoming attachments</string>
<string name="attachment_summary_on">Automatically download attachments for incoming emails</string>
<string name="attachment_summary_off">Only download attachments when manually requested</string>
</resources>

View file

@ -0,0 +1,23 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:title="@string/subscription_settings_notifications_header">
<SwitchPreferenceCompat
app:key="pause"
app:title="@string/subscription_settings_pause_title"/>
<ListPreference
app:key="reply"
app:title="@string/subscription_settings_pause_for_title"
app:entries="@array/reply_entries"
app:entryValues="@array/reply_values"
app:defaultValue="false"
app:useSimpleSummaryProvider="true"
app:dependency="pause"
/>
</PreferenceCategory>
</PreferenceScreen>