Merge branch 'main' into 236-android-action-buttons
This commit is contained in:
commit
c6c525ca3d
34 changed files with 530 additions and 175 deletions
app
schemas/io.heckel.ntfy.db.Database
src/main
java/io/heckel/ntfy
backup
db
msg
service
ui
up
util
work
res
layout
values-bg
values-cs
values-de
values-es
values-fr
values-in
values-it
values-iw
values-ja
values-nb-rNO
values-nl
values-pt-rBR
values-ru
values-tr
values-zh-rCN
values
fastlane/metadata/android/en-US/changelog
326
app/schemas/io.heckel.ntfy.db.Database/12.json
Normal file
326
app/schemas/io.heckel.ntfy.db.Database/12.json
Normal file
|
@ -0,0 +1,326 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 12,
|
||||
"identityHash": "b439720b55cf5e6bfdec2b56dd46103d",
|
||||
"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, `minPriority` INTEGER NOT NULL, `autoDelete` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, 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
|
||||
},
|
||||
{
|
||||
"fieldPath": "minPriority",
|
||||
"columnName": "minPriority",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "autoDelete",
|
||||
"columnName": "autoDelete",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastNotificationId",
|
||||
"columnName": "lastNotificationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "upAppId",
|
||||
"columnName": "upAppId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "upConnectorToken",
|
||||
"columnName": "upConnectorToken",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_Subscription_baseUrl_topic",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"baseUrl",
|
||||
"topic"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)"
|
||||
},
|
||||
{
|
||||
"name": "index_Subscription_upConnectorToken",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"upConnectorToken"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_upConnectorToken` ON `${TABLE_NAME}` (`upConnectorToken`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Notification",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
|
||||
"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": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "encoding",
|
||||
"columnName": "encoding",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationId",
|
||||
"columnName": "notificationId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "priority",
|
||||
"columnName": "priority",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "3"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "click",
|
||||
"columnName": "click",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actions",
|
||||
"columnName": "actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "attachment.name",
|
||||
"columnName": "attachment_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "attachment.type",
|
||||
"columnName": "attachment_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "attachment.size",
|
||||
"columnName": "attachment_size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "attachment.expires",
|
||||
"columnName": "attachment_expires",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "attachment.url",
|
||||
"columnName": "attachment_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "attachment.contentUri",
|
||||
"columnName": "attachment_contentUri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "attachment.progress",
|
||||
"columnName": "attachment_progress",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id",
|
||||
"subscriptionId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "User",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`baseUrl`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "baseUrl",
|
||||
"columnName": "baseUrl",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"baseUrl"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `tag` TEXT NOT NULL, `level` INTEGER NOT NULL, `message` TEXT NOT NULL, `exception` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "level",
|
||||
"columnName": "level",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "exception",
|
||||
"columnName": "exception",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"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, 'b439720b55cf5e6bfdec2b56dd46103d')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -97,6 +97,7 @@ class Backuper(val context: Context) {
|
|||
mutedUntil = s.mutedUntil,
|
||||
minPriority = s.minPriority ?: Repository.MIN_PRIORITY_USE_GLOBAL,
|
||||
autoDelete = s.autoDelete ?: Repository.AUTO_DELETE_USE_GLOBAL,
|
||||
lastNotificationId = s.lastNotificationId,
|
||||
icon = s.icon,
|
||||
upAppId = s.upAppId,
|
||||
upConnectorToken = s.upConnectorToken
|
||||
|
@ -220,6 +221,7 @@ class Backuper(val context: Context) {
|
|||
mutedUntil = s.mutedUntil,
|
||||
minPriority = s.minPriority,
|
||||
autoDelete = s.autoDelete,
|
||||
lastNotificationId = s.lastNotificationId,
|
||||
icon = s.icon,
|
||||
upAppId = s.upAppId,
|
||||
upConnectorToken = s.upConnectorToken
|
||||
|
@ -326,6 +328,7 @@ data class Subscription(
|
|||
val mutedUntil: Long,
|
||||
val minPriority: Int?,
|
||||
val autoDelete: Long?,
|
||||
val lastNotificationId: String?,
|
||||
val icon: String?,
|
||||
val upAppId: String?,
|
||||
val upConnectorToken: String?
|
||||
|
|
|
@ -18,6 +18,7 @@ data class Subscription(
|
|||
@ColumnInfo(name = "mutedUntil") val mutedUntil: Long,
|
||||
@ColumnInfo(name = "minPriority") val minPriority: Int,
|
||||
@ColumnInfo(name = "autoDelete") val autoDelete: Long, // Seconds
|
||||
@ColumnInfo(name = "lastNotificationId") val lastNotificationId: String?, // Used for polling, with since=<id>
|
||||
@ColumnInfo(name = "icon") val icon: String?, // content://-URI (or later other identifier)
|
||||
@ColumnInfo(name = "upAppId") val upAppId: String?, // UnifiedPush application package name
|
||||
@ColumnInfo(name = "upConnectorToken") val upConnectorToken: String?, // UnifiedPush connector token
|
||||
|
@ -26,8 +27,8 @@ data class Subscription(
|
|||
@Ignore val lastActive: Long = 0, // Unix timestamp
|
||||
@Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE
|
||||
) {
|
||||
constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, minPriority: Int, autoDelete: Long, icon: String, upAppId: String, upConnectorToken: String) :
|
||||
this(id, baseUrl, topic, instant, mutedUntil, minPriority, autoDelete, icon, upAppId, upConnectorToken, 0, 0, 0, ConnectionState.NOT_APPLICABLE)
|
||||
constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, minPriority: Int, autoDelete: Long, lastNotificationId: String, icon: String, upAppId: String, upConnectorToken: String) :
|
||||
this(id, baseUrl, topic, instant, mutedUntil, minPriority, autoDelete, lastNotificationId, icon, upAppId, upConnectorToken, 0, 0, 0, ConnectionState.NOT_APPLICABLE)
|
||||
}
|
||||
|
||||
enum class ConnectionState {
|
||||
|
@ -42,6 +43,7 @@ data class SubscriptionWithMetadata(
|
|||
val mutedUntil: Long,
|
||||
val autoDelete: Long,
|
||||
val minPriority: Int,
|
||||
val lastNotificationId: String?,
|
||||
val icon: String?,
|
||||
val upAppId: String?,
|
||||
val upConnectorToken: String?,
|
||||
|
@ -144,7 +146,7 @@ data class LogEntry(
|
|||
this(0, timestamp, tag, level, message, exception)
|
||||
}
|
||||
|
||||
@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 11)
|
||||
@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 12)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class Database : RoomDatabase() {
|
||||
abstract fun subscriptionDao(): SubscriptionDao
|
||||
|
@ -170,6 +172,7 @@ abstract class Database : RoomDatabase() {
|
|||
.addMigrations(MIGRATION_8_9)
|
||||
.addMigrations(MIGRATION_9_10)
|
||||
.addMigrations(MIGRATION_10_11)
|
||||
.addMigrations(MIGRATION_11_12)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
this.instance = instance
|
||||
|
@ -259,6 +262,12 @@ abstract class Database : RoomDatabase() {
|
|||
db.execSQL("ALTER TABLE Subscription ADD COLUMN icon TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
private val MIGRATION_11_12 = object : Migration(11, 12) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE Subscription ADD COLUMN lastNotificationId TEXT")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +275,7 @@ abstract class Database : RoomDatabase() {
|
|||
interface SubscriptionDao {
|
||||
@Query("""
|
||||
SELECT
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken,
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken,
|
||||
COUNT(n.id) totalCount,
|
||||
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
|
||||
IFNULL(MAX(n.timestamp),0) AS lastActive
|
||||
|
@ -279,7 +288,7 @@ interface SubscriptionDao {
|
|||
|
||||
@Query("""
|
||||
SELECT
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken,
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken,
|
||||
COUNT(n.id) totalCount,
|
||||
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
|
||||
IFNULL(MAX(n.timestamp),0) AS lastActive
|
||||
|
@ -292,7 +301,7 @@ interface SubscriptionDao {
|
|||
|
||||
@Query("""
|
||||
SELECT
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken,
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken,
|
||||
COUNT(n.id) totalCount,
|
||||
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
|
||||
IFNULL(MAX(n.timestamp),0) AS lastActive
|
||||
|
@ -305,7 +314,7 @@ interface SubscriptionDao {
|
|||
|
||||
@Query("""
|
||||
SELECT
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken,
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken,
|
||||
COUNT(n.id) totalCount,
|
||||
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
|
||||
IFNULL(MAX(n.timestamp),0) AS lastActive
|
||||
|
@ -318,7 +327,7 @@ interface SubscriptionDao {
|
|||
|
||||
@Query("""
|
||||
SELECT
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken,
|
||||
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken,
|
||||
COUNT(n.id) totalCount,
|
||||
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
|
||||
IFNULL(MAX(n.timestamp),0) AS lastActive
|
||||
|
@ -335,6 +344,9 @@ interface SubscriptionDao {
|
|||
@Update
|
||||
fun update(subscription: Subscription)
|
||||
|
||||
@Query("UPDATE subscription SET lastNotificationId = :lastNotificationId WHERE id = :subscriptionId")
|
||||
fun updateLastNotificationId(subscriptionId: Long, lastNotificationId: String)
|
||||
|
||||
@Query("DELETE FROM subscription WHERE id = :subscriptionId")
|
||||
fun remove(subscriptionId: Long)
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
|||
if (maybeExistingNotification != null) {
|
||||
return false
|
||||
}
|
||||
subscriptionDao.updateLastNotificationId(notification.subscriptionId, notification.id)
|
||||
notificationDao.add(notification)
|
||||
return true
|
||||
}
|
||||
|
@ -299,13 +300,13 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
|||
.apply()
|
||||
}
|
||||
|
||||
fun getJsonStreamRemindTime(): Long {
|
||||
return sharedPrefs.getLong(SHARED_PREFS_JSON_STREAM_REMIND_TIME, JSON_STREAM_REMIND_TIME_ALWAYS)
|
||||
fun getWebSocketRemindTime(): Long {
|
||||
return sharedPrefs.getLong(SHARED_PREFS_WEBSOCKET_REMIND_TIME, WEBSOCKET_REMIND_TIME_ALWAYS)
|
||||
}
|
||||
|
||||
fun setJsonStreamRemindTime(timeMillis: Long) {
|
||||
fun setWebSocketRemindTime(timeMillis: Long) {
|
||||
sharedPrefs.edit()
|
||||
.putLong(SHARED_PREFS_JSON_STREAM_REMIND_TIME, timeMillis)
|
||||
.putLong(SHARED_PREFS_WEBSOCKET_REMIND_TIME, timeMillis)
|
||||
.apply()
|
||||
}
|
||||
|
||||
|
@ -379,6 +380,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
|||
mutedUntil = s.mutedUntil,
|
||||
minPriority = s.minPriority,
|
||||
autoDelete = s.autoDelete,
|
||||
lastNotificationId = s.lastNotificationId,
|
||||
icon = s.icon,
|
||||
upAppId = s.upAppId,
|
||||
upConnectorToken = s.upConnectorToken,
|
||||
|
@ -402,6 +404,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
|||
mutedUntil = s.mutedUntil,
|
||||
minPriority = s.minPriority,
|
||||
autoDelete = s.autoDelete,
|
||||
lastNotificationId = s.lastNotificationId,
|
||||
icon = s.icon,
|
||||
upAppId = s.upAppId,
|
||||
upConnectorToken = s.upConnectorToken,
|
||||
|
@ -448,7 +451,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
|||
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
|
||||
const val SHARED_PREFS_RECORD_LOGS_ENABLED = "RecordLogs"
|
||||
const val SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME = "BatteryOptimizationsRemindTime"
|
||||
const val SHARED_PREFS_JSON_STREAM_REMIND_TIME = "JsonStreamRemindTime" // Deprecation of JSON stream
|
||||
const val SHARED_PREFS_WEBSOCKET_REMIND_TIME = "JsonStreamRemindTime" // "Use WebSocket" banner (used to be JSON stream deprecation banner)
|
||||
const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL" // Legacy key required for migration to DefaultBaseURL
|
||||
const val SHARED_PREFS_DEFAULT_BASE_URL = "DefaultBaseURL"
|
||||
const val SHARED_PREFS_LAST_TOPICS = "LastTopics"
|
||||
|
@ -483,8 +486,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
|||
const val BATTERY_OPTIMIZATIONS_REMIND_TIME_ALWAYS = 1L
|
||||
const val BATTERY_OPTIMIZATIONS_REMIND_TIME_NEVER = Long.MAX_VALUE
|
||||
|
||||
const val JSON_STREAM_REMIND_TIME_ALWAYS = 1L
|
||||
const val JSON_STREAM_REMIND_TIME_NEVER = Long.MAX_VALUE
|
||||
const val WEBSOCKET_REMIND_TIME_ALWAYS = 1L
|
||||
const val WEBSOCKET_REMIND_TIME_NEVER = Long.MAX_VALUE
|
||||
|
||||
private const val TAG = "NtfyRepository"
|
||||
private var instance: Repository? = null
|
||||
|
|
|
@ -84,8 +84,8 @@ class ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
fun poll(subscriptionId: Long, baseUrl: String, topic: String, user: User?, since: Long = 0L): List<Notification> {
|
||||
val sinceVal = if (since == 0L) "all" else since.toString()
|
||||
fun poll(subscriptionId: Long, baseUrl: String, topic: String, user: User?, since: String? = null): List<Notification> {
|
||||
val sinceVal = since ?: "all"
|
||||
val url = topicUrlJsonPoll(baseUrl, topic, sinceVal)
|
||||
Log.d(TAG, "Polling topic $url")
|
||||
|
||||
|
@ -108,12 +108,12 @@ class ApiService {
|
|||
fun subscribe(
|
||||
baseUrl: String,
|
||||
topics: String,
|
||||
since: Long,
|
||||
since: String?,
|
||||
user: User?,
|
||||
notify: (topic: String, Notification) -> Unit,
|
||||
fail: (Exception) -> Unit
|
||||
): Call {
|
||||
val sinceVal = if (since == 0L) "all" else since.toString()
|
||||
val sinceVal = since ?: "all"
|
||||
val url = topicUrlJson(baseUrl, topics, sinceVal)
|
||||
Log.d(TAG, "Opening subscription connection to $url")
|
||||
val request = requestBuilder(url, user).build()
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.heckel.ntfy.service
|
|||
interface Connection {
|
||||
fun start()
|
||||
fun close()
|
||||
fun since(): Long
|
||||
fun since(): String?
|
||||
}
|
||||
|
||||
data class ConnectionId(
|
||||
|
|
|
@ -14,7 +14,7 @@ class JsonConnection(
|
|||
private val repository: Repository,
|
||||
private val api: ApiService,
|
||||
private val user: User?,
|
||||
private val sinceTime: Long,
|
||||
private val sinceId: String?,
|
||||
private val stateChangeListener: (Collection<Long>, ConnectionState) -> Unit,
|
||||
private val notificationListener: (Subscription, Notification) -> Unit,
|
||||
private val serviceActive: () -> Boolean
|
||||
|
@ -25,7 +25,7 @@ class JsonConnection(
|
|||
private val topicsStr = topicsToSubscriptionIds.keys.joinToString(separator = ",")
|
||||
private val url = topicUrl(baseUrl, topicsStr)
|
||||
|
||||
private var since: Long = sinceTime
|
||||
private var since: String? = sinceId
|
||||
private lateinit var call: Call
|
||||
private lateinit var job: Job
|
||||
|
||||
|
@ -39,7 +39,7 @@ class JsonConnection(
|
|||
Log.d(TAG, "[$url] (Re-)starting connection for subscriptions: $topicsToSubscriptionIds")
|
||||
val startTime = System.currentTimeMillis()
|
||||
val notify = notify@ { topic: String, notification: Notification ->
|
||||
since = notification.timestamp
|
||||
since = notification.id
|
||||
val subscriptionId = topicsToSubscriptionIds[topic] ?: return@notify
|
||||
val subscription = repository.getSubscription(subscriptionId) ?: return@notify
|
||||
val notificationWithSubscriptionId = notification.copy(subscriptionId = subscription.id)
|
||||
|
@ -81,7 +81,7 @@ class JsonConnection(
|
|||
}
|
||||
}
|
||||
|
||||
override fun since(): Long {
|
||||
override fun since(): String? {
|
||||
return since
|
||||
}
|
||||
|
||||
|
|
|
@ -178,14 +178,8 @@ class SubscriberService : Service() {
|
|||
val newConnectionIds = desiredConnectionIds.subtract(activeConnectionIds)
|
||||
val obsoleteConnectionIds = activeConnectionIds.subtract(desiredConnectionIds)
|
||||
val match = activeConnectionIds == desiredConnectionIds
|
||||
val newSinceByBaseUrl = connections
|
||||
.map { e ->
|
||||
// Get last message timestamp to determine new ?since= param; set to $last+1 if it
|
||||
// is defined to avoid retrieving old messages. See comment below too.
|
||||
val lastMessage = e.value.since()
|
||||
val newSince = if (lastMessage > 0) lastMessage+1 else 0
|
||||
e.key.baseUrl to newSince
|
||||
}
|
||||
val sinceByBaseUrl = connections
|
||||
.map { e -> e.key.baseUrl to e.value.since() } // Use since=<id>, avoid retrieving old messages (see comment below)
|
||||
.toMap()
|
||||
|
||||
Log.d(TAG, "Refreshing subscriptions")
|
||||
|
@ -205,7 +199,7 @@ class SubscriberService : Service() {
|
|||
// IMPORTANT: Do NOT request old messages for new connections; we call poll() in MainActivity to
|
||||
// retrieve old messages. This is important, so we don't download attachments from old messages.
|
||||
|
||||
val since = newSinceByBaseUrl[connectionId.baseUrl] ?: (System.currentTimeMillis() / 1000)
|
||||
val since = sinceByBaseUrl[connectionId.baseUrl] ?: "none"
|
||||
val serviceActive = { -> isServiceStarted }
|
||||
val user = repository.getUser(connectionId.baseUrl)
|
||||
val connection = if (repository.getConnectionProtocol() == Repository.CONNECTION_PROTOCOL_WS) {
|
||||
|
|
|
@ -5,15 +5,19 @@ import android.os.Build
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import io.heckel.ntfy.db.*
|
||||
import io.heckel.ntfy.util.Log
|
||||
import io.heckel.ntfy.msg.ApiService.Companion.requestBuilder
|
||||
import io.heckel.ntfy.msg.NotificationParser
|
||||
import io.heckel.ntfy.util.Log
|
||||
import io.heckel.ntfy.util.topicShortUrl
|
||||
import io.heckel.ntfy.util.topicUrlWs
|
||||
import okhttp3.*
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
|
@ -30,7 +34,7 @@ class WsConnection(
|
|||
private val connectionId: ConnectionId,
|
||||
private val repository: Repository,
|
||||
private val user: User?,
|
||||
private val sinceTime: Long,
|
||||
private val sinceId: String?,
|
||||
private val stateChangeListener: (Collection<Long>, ConnectionState) -> Unit,
|
||||
private val notificationListener: (Subscription, Notification) -> Unit,
|
||||
private val alarmManager: AlarmManager
|
||||
|
@ -49,7 +53,7 @@ class WsConnection(
|
|||
private val globalId = GLOBAL_ID.incrementAndGet()
|
||||
private val listenerId = AtomicLong(0)
|
||||
|
||||
private val since = AtomicLong(sinceTime)
|
||||
private val since = AtomicReference<String?>(sinceId)
|
||||
private val baseUrl = connectionId.baseUrl
|
||||
private val topicsToSubscriptionIds = connectionId.topicsToSubscriptionIds
|
||||
private val subscriptionIds = topicsToSubscriptionIds.values
|
||||
|
@ -71,7 +75,8 @@ class WsConnection(
|
|||
}
|
||||
state = State.Connecting
|
||||
val nextListenerId = listenerId.incrementAndGet()
|
||||
val sinceVal = if (since.get() == 0L) "all" else since.get().toString()
|
||||
val sinceId = since.get()
|
||||
val sinceVal = sinceId ?: "all"
|
||||
val urlWithSince = topicUrlWs(baseUrl, topicsStr, sinceVal)
|
||||
val request = requestBuilder(urlWithSince, user).build()
|
||||
Log.d(TAG, "$shortUrl (gid=$globalId): Opening $urlWithSince with listener ID $nextListenerId ...")
|
||||
|
@ -92,7 +97,7 @@ class WsConnection(
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
override fun since(): Long {
|
||||
override fun since(): String? {
|
||||
return since.get()
|
||||
}
|
||||
|
||||
|
@ -141,7 +146,7 @@ class WsConnection(
|
|||
val subscription = repository.getSubscription(subscriptionId) ?: return@synchronize
|
||||
val notificationWithSubscriptionId = notification.copy(subscriptionId = subscription.id)
|
||||
notificationListener(subscription, notificationWithSubscriptionId)
|
||||
since.set(notification.timestamp)
|
||||
since.set(notification.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,13 +9,17 @@ import io.heckel.ntfy.R
|
|||
|
||||
fun initBaseUrlDropdown(baseUrls: List<String>, textView: AutoCompleteTextView, layout: TextInputLayout) {
|
||||
// Base URL dropdown behavior; Oh my, why is this so complicated?!
|
||||
val context = layout.context
|
||||
val toggleEndIcon = {
|
||||
if (textView.text.isNotEmpty()) {
|
||||
layout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
||||
layout.endIconContentDescription = context.getString(R.string.add_dialog_base_urls_dropdown_clear)
|
||||
} else if (baseUrls.isEmpty()) {
|
||||
layout.setEndIconDrawable(0)
|
||||
layout.endIconContentDescription = ""
|
||||
} else {
|
||||
layout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
||||
layout.endIconContentDescription = context.getString(R.string.add_dialog_base_urls_dropdown_choose)
|
||||
}
|
||||
}
|
||||
layout.setEndIconOnClickListener {
|
||||
|
@ -23,11 +27,14 @@ fun initBaseUrlDropdown(baseUrls: List<String>, textView: AutoCompleteTextView,
|
|||
textView.text.clear()
|
||||
if (baseUrls.isEmpty()) {
|
||||
layout.setEndIconDrawable(0)
|
||||
layout.endIconContentDescription = ""
|
||||
} else {
|
||||
layout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
||||
layout.endIconContentDescription = context.getString(R.string.add_dialog_base_urls_dropdown_choose)
|
||||
}
|
||||
} else if (textView.text.isEmpty() && baseUrls.isNotEmpty()) {
|
||||
layout.setEndIconDrawable(R.drawable.ic_drop_up_gray_24dp)
|
||||
layout.endIconContentDescription = context.getString(R.string.add_dialog_base_urls_dropdown_choose)
|
||||
textView.showDropDown()
|
||||
}
|
||||
}
|
||||
|
@ -49,10 +56,13 @@ fun initBaseUrlDropdown(baseUrls: List<String>, textView: AutoCompleteTextView,
|
|||
textView.setAdapter(adapter)
|
||||
if (baseUrls.count() == 1) {
|
||||
layout.setEndIconDrawable(R.drawable.ic_cancel_gray_24dp)
|
||||
layout.endIconContentDescription = context.getString(R.string.add_dialog_base_urls_dropdown_clear)
|
||||
textView.setText(baseUrls.first())
|
||||
} else if (baseUrls.count() > 1) {
|
||||
layout.setEndIconDrawable(R.drawable.ic_drop_down_gray_24dp)
|
||||
layout.endIconContentDescription = context.getString(R.string.add_dialog_base_urls_dropdown_choose)
|
||||
} else {
|
||||
layout.setEndIconDrawable(0)
|
||||
layout.endIconContentDescription = ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,13 +105,14 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
if (subscription == null) {
|
||||
val instant = baseUrl != appBaseUrl
|
||||
subscription = Subscription(
|
||||
id = Random.nextLong(),
|
||||
id = randomSubscriptionId(),
|
||||
baseUrl = baseUrl,
|
||||
topic = topic,
|
||||
instant = instant,
|
||||
mutedUntil = 0,
|
||||
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
|
||||
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
|
||||
lastNotificationId = null,
|
||||
icon = null,
|
||||
upAppId = null,
|
||||
upConnectorToken = null,
|
||||
|
@ -457,8 +458,9 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val user = repository.getUser(subscriptionBaseUrl) // May be null
|
||||
val notifications = api.poll(subscriptionId, subscriptionBaseUrl, subscriptionTopic, user)
|
||||
val subscription = repository.getSubscription(subscriptionId) ?: return@launch
|
||||
val user = repository.getUser(subscription.baseUrl) // May be null
|
||||
val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user, subscription.lastNotificationId)
|
||||
val newNotifications = repository.onlyNewNotifications(subscriptionId, notifications)
|
||||
val toastMessage = if (newNotifications.isEmpty()) {
|
||||
getString(R.string.refresh_message_no_results)
|
||||
|
|
|
@ -9,11 +9,13 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
@ -41,6 +43,7 @@ import io.heckel.ntfy.work.PollWorker
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
|
@ -119,9 +122,9 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
|||
Log.addScrubTerm(s.topic)
|
||||
}
|
||||
|
||||
// Update banner + JSON stream banner
|
||||
// Update banner + WebSocket banner
|
||||
showHideBatteryBanner(subscriptions)
|
||||
showHideJsonStreamBanner(subscriptions)
|
||||
showHideWebSocketBanner(subscriptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,21 +172,25 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
|||
}
|
||||
}
|
||||
|
||||
// JSON stream banner
|
||||
val jsonStreamBanner = findViewById<View>(R.id.main_banner_json_stream) // Banner visibility is toggled in onResume()
|
||||
val jsonStreamDismissButton = findViewById<Button>(R.id.main_banner_json_stream_dontaskagain)
|
||||
val jsonStreamRemindButton = findViewById<Button>(R.id.main_banner_json_stream_remind_later)
|
||||
val jsonStreamLearnMoreButton = findViewById<Button>(R.id.main_banner_json_stream_learn_mode)
|
||||
jsonStreamDismissButton.setOnClickListener {
|
||||
jsonStreamBanner.visibility = View.GONE
|
||||
repository.setJsonStreamRemindTime(Repository.JSON_STREAM_REMIND_TIME_NEVER)
|
||||
// WebSocket banner
|
||||
val wsBanner = findViewById<View>(R.id.main_banner_websocket) // Banner visibility is toggled in onResume()
|
||||
val wsText = findViewById<TextView>(R.id.main_banner_websocket_text)
|
||||
val wsDismissButton = findViewById<Button>(R.id.main_banner_websocket_dontaskagain)
|
||||
val wsRemindButton = findViewById<Button>(R.id.main_banner_websocket_remind_later)
|
||||
val wsEnableButton = findViewById<Button>(R.id.main_banner_websocket_enable)
|
||||
wsText.movementMethod = LinkMovementMethod.getInstance() // Make links clickable
|
||||
wsDismissButton.setOnClickListener {
|
||||
wsBanner.visibility = View.GONE
|
||||
repository.setWebSocketRemindTime(Repository.WEBSOCKET_REMIND_TIME_NEVER)
|
||||
}
|
||||
jsonStreamRemindButton.setOnClickListener {
|
||||
jsonStreamBanner.visibility = View.GONE
|
||||
repository.setJsonStreamRemindTime(System.currentTimeMillis() + ONE_DAY_MILLIS)
|
||||
wsRemindButton.setOnClickListener {
|
||||
wsBanner.visibility = View.GONE
|
||||
repository.setWebSocketRemindTime(System.currentTimeMillis() + ONE_DAY_MILLIS)
|
||||
}
|
||||
jsonStreamLearnMoreButton.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_banner_json_stream_button_learn_more_url))))
|
||||
wsEnableButton.setOnClickListener {
|
||||
repository.setConnectionProtocol(Repository.CONNECTION_PROTOCOL_WS)
|
||||
SubscriberServiceManager(this).restart()
|
||||
wsBanner.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Create notification channels right away, so we can configure them immediately after installing the app
|
||||
|
@ -217,13 +224,13 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
|||
Log.d(TAG, "Battery: ignoring optimizations = $ignoringOptimizations (we want this to be true); instant subscriptions = $hasInstantSubscriptions; remind time reached = $batteryRemindTimeReached; banner = $showBanner")
|
||||
}
|
||||
|
||||
private fun showHideJsonStreamBanner(subscriptions: List<Subscription>) {
|
||||
val hasSelfhostedSubscriptions = subscriptions.count { it.baseUrl != appBaseUrl } > 0
|
||||
private fun showHideWebSocketBanner(subscriptions: List<Subscription>) {
|
||||
val hasSelfHostedSubscriptions = subscriptions.count { it.baseUrl != appBaseUrl } > 0
|
||||
val usingWebSockets = repository.getConnectionProtocol() == Repository.CONNECTION_PROTOCOL_WS
|
||||
val jsonStreamRemindTimeReached = repository.getJsonStreamRemindTime() < System.currentTimeMillis()
|
||||
val showBanner = hasSelfhostedSubscriptions && jsonStreamRemindTimeReached && !usingWebSockets
|
||||
val jsonStreamBanner = findViewById<View>(R.id.main_banner_json_stream)
|
||||
jsonStreamBanner.visibility = if (showBanner) View.VISIBLE else View.GONE
|
||||
val wsRemindTimeReached = repository.getWebSocketRemindTime() < System.currentTimeMillis()
|
||||
val showBanner = hasSelfHostedSubscriptions && wsRemindTimeReached && !usingWebSockets
|
||||
val wsBanner = findViewById<View>(R.id.main_banner_websocket)
|
||||
wsBanner.visibility = if (showBanner) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun schedulePeriodicPollWorker() {
|
||||
|
@ -420,13 +427,14 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
|||
|
||||
// Add subscription to database
|
||||
val subscription = Subscription(
|
||||
id = Random.nextLong(),
|
||||
id = randomSubscriptionId(),
|
||||
baseUrl = baseUrl,
|
||||
topic = topic,
|
||||
instant = instant,
|
||||
mutedUntil = 0,
|
||||
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
|
||||
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
|
||||
lastNotificationId = null,
|
||||
icon = null,
|
||||
upAppId = null,
|
||||
upConnectorToken = null,
|
||||
|
@ -488,9 +496,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
|||
var errorMessage = "" // First error
|
||||
var newNotificationsCount = 0
|
||||
repository.getSubscriptions().forEach { subscription ->
|
||||
Log.d(TAG, "subscription: ${subscription}")
|
||||
try {
|
||||
val user = repository.getUser(subscription.baseUrl) // May be null
|
||||
val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user)
|
||||
val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user, subscription.lastNotificationId)
|
||||
val newNotifications = repository.onlyNewNotifications(subscription.id, notifications)
|
||||
newNotifications.forEach { notification ->
|
||||
newNotificationsCount++
|
||||
|
|
|
@ -7,10 +7,7 @@ import io.heckel.ntfy.app.Application
|
|||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.db.Subscription
|
||||
import io.heckel.ntfy.service.SubscriberServiceManager
|
||||
import io.heckel.ntfy.util.Log
|
||||
import io.heckel.ntfy.util.randomString
|
||||
import io.heckel.ntfy.util.shortUrl
|
||||
import io.heckel.ntfy.util.topicUrlUp
|
||||
import io.heckel.ntfy.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -71,13 +68,14 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
|
|||
val topic = UP_PREFIX + randomString(TOPIC_RANDOM_ID_LENGTH)
|
||||
val endpoint = topicUrlUp(baseUrl, topic)
|
||||
val subscription = Subscription(
|
||||
id = Random.nextLong(),
|
||||
id = randomSubscriptionId(),
|
||||
baseUrl = baseUrl,
|
||||
topic = topic,
|
||||
instant = true, // No Firebase, always instant!
|
||||
mutedUntil = 0,
|
||||
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
|
||||
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
|
||||
lastNotificationId = null,
|
||||
icon = null,
|
||||
upAppId = appId,
|
||||
upConnectorToken = connectorToken,
|
||||
|
|
|
@ -43,6 +43,7 @@ import java.text.DateFormat
|
|||
import java.text.StringCharacterIterator
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
|
||||
fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush
|
||||
|
@ -276,6 +277,13 @@ fun randomString(len: Int): String {
|
|||
return (1..len).map { chars[random.nextInt(chars.size)] }.joinToString("")
|
||||
}
|
||||
|
||||
// Generates a random, positive subscription ID between 0-10M. This ensures that it doesn't have issues
|
||||
// when exported to JSON. It uses SecureRandom, because Random causes issues in the emulator (generating the
|
||||
// same value again and again), sometimes.
|
||||
fun randomSubscriptionId(): Long {
|
||||
return SecureRandom().nextLong().absoluteValue % 100_000_000
|
||||
}
|
||||
|
||||
// Allows letting multiple variables at once, see https://stackoverflow.com/a/35522422/1440785
|
||||
inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
|
||||
return if (p1 != null && p2 != null) block(p1, p2) else null
|
||||
|
|
|
@ -45,7 +45,7 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
|
|||
baseUrl = subscription.baseUrl,
|
||||
topic = subscription.topic,
|
||||
user = user,
|
||||
since = subscription.lastActive
|
||||
since = subscription.lastNotificationId
|
||||
)
|
||||
val newNotifications = repository
|
||||
.onlyNewNotifications(subscription.id, notifications)
|
||||
|
|
|
@ -86,64 +86,64 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:shapeAppearance="?shapeAppearanceLargeComponent" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/main_banner_battery"
|
||||
android:id="@+id/main_banner_json_stream" android:visibility="visible">
|
||||
android:id="@+id/main_banner_websocket" android:visibility="visible">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/main_banner_json_stream_constraint" android:elevation="5dp">
|
||||
android:id="@+id/main_banner_websocket_constraint" android:elevation="5dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp" app:srcCompat="@drawable/ic_announcement_orange_24dp"
|
||||
android:id="@+id/main_banner_json_stream_image"
|
||||
android:id="@+id/main_banner_websocket_image"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/main_banner_json_stream_text"
|
||||
app:layout_constraintEnd_toStartOf="@id/main_banner_json_stream_text"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/main_banner_json_stream_text"
|
||||
app:layout_constraintTop_toTopOf="@+id/main_banner_websocket_text"
|
||||
app:layout_constraintEnd_toStartOf="@id/main_banner_websocket_text"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/main_banner_websocket_text"
|
||||
android:layout_marginStart="15dp"/>
|
||||
<TextView
|
||||
android:id="@+id/main_banner_json_stream_text"
|
||||
android:id="@+id/main_banner_websocket_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/main_banner_json_stream_text"
|
||||
android:text="@string/main_banner_websocket_text"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
|
||||
app:layout_constraintStart_toEndOf="@+id/main_banner_json_stream_image"
|
||||
android:layout_marginStart="10dp" android:autoLink="web"/>
|
||||
app:layout_constraintStart_toEndOf="@+id/main_banner_websocket_image"
|
||||
android:layout_marginStart="10dp"
|
||||
/>
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:constraint_referenced_ids="main_banner_json_stream_remind_later,main_banner_json_stream_dontaskagain,main_banner_json_stream_learn_mode" app:layout_constraintTop_toBottomOf="@id/main_banner_json_stream_text" app:flow_horizontalAlign="end" app:flow_wrapMode="chain" app:flow_horizontalStyle="packed" android:layout_marginEnd="15dp" android:id="@+id/flow" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="15dp" app:flow_horizontalBias="1"
|
||||
app:constraint_referenced_ids="main_banner_websocket_remind_later,main_banner_websocket_dontaskagain,main_banner_websocket_enable" app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_text" app:flow_horizontalAlign="end" app:flow_wrapMode="chain" app:flow_horizontalStyle="packed" android:layout_marginEnd="15dp" android:id="@+id/flow" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="15dp" app:flow_horizontalBias="1"
|
||||
app:flow_verticalGap="0dp" app:flow_horizontalGap="0dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/main_banner_json_stream_remind_later"
|
||||
android:id="@+id/main_banner_websocket_remind_later"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/main_banner_json_stream_button_remind_later"
|
||||
android:text="@string/main_banner_websocket_button_remind_later"
|
||||
tools:layout_editor_absoluteX="86dp" tools:layout_editor_absoluteY="83dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/main_banner_json_stream_dontaskagain"
|
||||
android:id="@+id/main_banner_websocket_dontaskagain"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/main_banner_json_stream_button_dismiss"
|
||||
android:text="@string/main_banner_websocket_button_dismiss"
|
||||
tools:layout_editor_absoluteX="260dp" tools:layout_editor_absoluteY="83dp"/>
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/main_banner_json_stream_learn_mode"
|
||||
android:id="@+id/main_banner_websocket_enable"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/main_banner_json_stream_button_learn_more"
|
||||
android:text="@string/main_banner_websocket_button_enable_now"
|
||||
tools:layout_editor_absoluteX="253dp" tools:layout_editor_absoluteY="131dp"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
@ -155,7 +155,7 @@
|
|||
android:visibility="visible"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/main_banner_json_stream">
|
||||
app:layout_constraintTop_toBottomOf="@id/main_banner_websocket">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/main_subscriptions_list"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -82,7 +82,6 @@
|
|||
<string name="main_item_status_reconnecting">свързване…</string>
|
||||
<string name="main_no_subscriptions_text">В момента няма абонаменти</string>
|
||||
<string name="main_unified_push_toast">Този абонамент се управлява от %1$s чрез UnifiedPush</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Научете повече</string>
|
||||
<string name="add_dialog_button_cancel">Отказ</string>
|
||||
<string name="add_dialog_button_subscribe">Абониране</string>
|
||||
<string name="add_dialog_button_back">Назад</string>
|
||||
|
@ -99,9 +98,9 @@
|
|||
<string name="detail_clear_dialog_permanently_delete">Премахване</string>
|
||||
<string name="detail_clear_dialog_cancel">Отказ</string>
|
||||
<string name="main_banner_battery_button_remind_later">По-късно</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">По-късно</string>
|
||||
<string name="main_banner_websocket_button_remind_later">По-късно</string>
|
||||
<string name="main_banner_battery_button_dismiss">Отхвърляне</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Отхвърляне</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Отхвърляне</string>
|
||||
<string name="main_banner_battery_text">Оптимизирането на батерията трябва да е изключено, за да бъдат избегнати забавяния при получаване на известията.</string>
|
||||
<string name="main_banner_battery_button_fix_now">Настройки</string>
|
||||
<string name="add_dialog_instant_delivery">Незабавно доставяне в режим на сън</string>
|
||||
|
@ -184,7 +183,6 @@
|
|||
<string name="settings_notifications_auto_delete_three_months">След три месеца</string>
|
||||
<string name="settings_notifications_auto_delete_one_month">След един месец</string>
|
||||
<string name="settings_advanced_export_logs_entry_copy_scrubbed">Копиране в междинната памет (цензурирано)</string>
|
||||
<string name="main_banner_json_stream_text">От юни 2022 г. за връзка със сървърите на ntfy ще се използва WebSockets. Не забравяйте да настроите собствения сървър да го поддържа. За да проверите дали поддръжката на WebSocket работи, разрешете я в Настройки, в раздел Протокол за връзка.</string>
|
||||
<string name="settings_advanced_connection_protocol_summary_jsonhttp">За свързване със сървъра се използва поток от JSON през HTTP. Методът е остарял и ще бъде премахнат през месец юни 2022 год.</string>
|
||||
<string name="detail_test_message">Това е пробно известие от приложението ntfy за Android. То е с приоритет %1$d. Ако изпратите друго, то може да изглежда по различен начин.</string>
|
||||
<string name="detail_test_title">Проба: Ако желаете можете да сложите заглавие</string>
|
||||
|
@ -319,4 +317,6 @@
|
|||
<string name="detail_settings_notifications_instant_summary_off">Известията се доставят с помощта на Firebase. Доставката може да се забави, но изразходва по-малко батерия.</string>
|
||||
<string name="detail_settings_appearance_icon_error_saving">Значката не може да бъде запазена: %1$s</string>
|
||||
<string name="detail_settings_global_setting_suffix">от общите настройки</string>
|
||||
</resources>
|
||||
<string name="add_dialog_base_urls_dropdown_choose">Изберете адрес на услугата</string>
|
||||
<string name="add_dialog_base_urls_dropdown_clear">Изчистване на адреса на услугата</string>
|
||||
</resources>
|
||||
|
|
|
@ -42,9 +42,8 @@
|
|||
<string name="main_unified_push_toast">Tento odběr spravuje %1$s prostřednictvím UnifiedPush</string>
|
||||
<string name="main_banner_battery_text">Optimalizace baterie by měla být pro aplikaci vypnutá, aby se předešlo problémům s doručováním oznámení.</string>
|
||||
<string name="main_banner_battery_button_remind_later">Zeptat se později</string>
|
||||
<string name="main_banner_json_stream_text">Přepněte na WebSockets v Nastavení / \"Protokol připojení\" na , abyste zajistili, že budete moci komunikovat s vlastním serverem ntfy i po červnu 2022.</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Zeptat se později</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Zavřít</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Zeptat se později</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Zavřít</string>
|
||||
<string name="add_dialog_topic_name_hint">Název tématu, např. phils_alerts</string>
|
||||
<string name="add_dialog_use_another_server_description">Zadejte URL služby a přihlaste se k odběru témat z jiných serverů.</string>
|
||||
<string name="add_dialog_instant_delivery">Okamžité doručení v režimu spánku</string>
|
||||
|
@ -233,7 +232,6 @@
|
|||
<string name="refresh_message_error_one">Nepodařilo se obnovit odběr: %1$s</string>
|
||||
<string name="channel_subscriber_notification_instant_text_four">Přihlášeno k odběru čtyř témat okamžitého doručení</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_three">Odběr tří témat</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Zjistit více</string>
|
||||
<string name="main_menu_notifications_disabled_forever">Oznámení ztlumena</string>
|
||||
<string name="main_how_to_intro">Kliknutím na tlačítko + vytvoříte téma nebo se k němu přihlásíte. Poté obdržíte na svém zařízení oznámení při odesílání zpráv prostřednictvím PUT nebo POST.</string>
|
||||
<string name="main_banner_battery_button_dismiss">Zavřít</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="channel_subscriber_notification_instant_text_six">Přihlášeno k odběru šesti témat okamžitého doručení</string>
|
||||
<string name="detail_settings_notifications_instant_summary_off">Oznámení jsou doručena pomocí služby Firebase. Doručování může být zpožděné, ale spotřebovává méně baterie.</string>
|
||||
<string name="detail_settings_notifications_instant_title">Okamžité doručení</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -45,8 +45,7 @@
|
|||
<string name="main_unified_push_toast">Dieses Abo wird von %1$s per UnifiedPush verwaltet</string>
|
||||
<string name="main_banner_battery_text">Batterieoptimierungen sollten für die App deaktiviert sein, um Zustellungsprobleme bei Benachrichtigungen zu vermeiden.</string>
|
||||
<string name="main_banner_battery_button_fix_now">Jetzt beheben</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Später fragen</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Mehr erfahren</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Später fragen</string>
|
||||
<string name="add_dialog_title">Thema abonnieren</string>
|
||||
<string name="add_dialog_description_below">Themen sind evtl. nicht kennwort-geschützt, also wähle einen schwer zu erratenden Namen. Nach dem Abonnieren kannst Du Benachrichtigungen POSTen/PUTen.</string>
|
||||
<string name="add_dialog_topic_name_hint">Themenname, z.B. phils_alerts</string>
|
||||
|
@ -57,7 +56,7 @@
|
|||
<string name="add_dialog_instant_delivery_description">Stellt sicher, dass Benachrichtigungen sofort zugestellt werden, auch wenn das Gerät im Schlafmodus ist.</string>
|
||||
<string name="add_dialog_button_cancel">Abbrechen</string>
|
||||
<string name="main_banner_battery_button_dismiss">Verwerfen</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Verwerfen</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Verwerfen</string>
|
||||
<string name="add_dialog_login_title">Anmeldung erforderlich</string>
|
||||
<string name="add_dialog_login_description">Dieses Thema benötigt eine Anmeldung. Bitte gib Benutzernamen und Kennwort ein.</string>
|
||||
<string name="add_dialog_login_password_hint">Kennwort</string>
|
||||
|
@ -238,7 +237,6 @@
|
|||
<string name="main_banner_battery_button_remind_later">Später fragen</string>
|
||||
<string name="main_action_mode_delete_dialog_message">Vom gewählten Thema abmelden und alle Benachrichtigungen endgültig löschen\?</string>
|
||||
<string name="main_how_to_intro">Klicke den + Button zum Erstellen oder Abonnieren eines Themas. Dann erhältst Du Benachrichtigungen auf dem Gerät, wenn Nachrichten per PUT oder POST veröffentlicht werden.</string>
|
||||
<string name="main_banner_json_stream_text">Stelle in Einstellungen das Verbindungsprotokoll auf WebSockets um, damit die App sich auch nach Juni 2022 mit Deinem selbst betriebenen Server verbinden kann.</string>
|
||||
<string name="add_dialog_login_error_not_authorized">Anmeldung fehlgeschlagen. Keine Berechtigung für Benutzer %1$s.</string>
|
||||
<string name="add_dialog_login_username_hint">Benutzername</string>
|
||||
<string name="add_dialog_foreground_description">Sofortnachrichten sind für andere Server als %1$s immer aktiviert.</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="detail_settings_notifications_instant_title">Sofortnachrichten</string>
|
||||
<string name="detail_settings_appearance_icon_remove_summary">Icon, das in Benachrichtigungen zu diesem Thema angezeigt wird</string>
|
||||
<string name="detail_settings_notifications_instant_summary_on">Benachrichtigungen werden sofort zugestellt. Benötigt einen Vordergrund-Dienst und verbraucht mehr Akku.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<string name="channel_subscriber_notification_noinstant_text_more">Suscrito a %1$d tópicos</string>
|
||||
<string name="refresh_message_result">%1$d notificación(es) recibida(s)</string>
|
||||
<string name="refresh_message_no_results">Todo está actualizado</string>
|
||||
<string name="refresh_message_error">No se pudieron actualizar %1$d suscripciones
|
||||
<string name="refresh_message_error">No se pudieron actualizar %1$d suscripciones
|
||||
\n
|
||||
\n%2$s</string>
|
||||
<string name="refresh_message_error_one">No se pudo actualizar la suscripción: %1$s</string>
|
||||
|
@ -36,8 +36,7 @@
|
|||
<string name="main_banner_battery_text">La optimización de batería debe deshabilitarse para esta app para evitar problemas con la entrega de notificaciones.</string>
|
||||
<string name="main_banner_battery_button_remind_later">Preguntar más tarde</string>
|
||||
<string name="main_banner_battery_button_fix_now">Corregir ahora</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Descartar</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Más información</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Descartar</string>
|
||||
<string name="add_dialog_title">Suscribirse al tópico</string>
|
||||
<string name="add_dialog_description_below">Los tópicos podrían no estar protegidos por contraseña, así que elija un nombre que sea difícil de adivinar. Una vez suscrito, puede enviar notificaciones con PUT/POST.</string>
|
||||
<string name="add_dialog_topic_name_hint">Nombre del tópico, p. ej. phils_alerts</string>
|
||||
|
@ -277,7 +276,7 @@
|
|||
<string name="settings_general_dark_mode_summary_light">Modo claro activado</string>
|
||||
<string name="settings_backup_restore_restore_summary">Importar configuración, notificaciones y usuarios</string>
|
||||
<string name="user_dialog_description_edit">Puede editar el nombre de usuario / contraseña para el usuario seleccionado o eliminarlo.</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Preguntar más tarde</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Preguntar más tarde</string>
|
||||
<string name="add_dialog_foreground_description">La entrega instantánea siempre está habilitada para los hosts que no sean %1$s.</string>
|
||||
<string name="add_dialog_login_title">Se requiere inicio de sesión</string>
|
||||
<string name="main_action_mode_delete_dialog_permanently_delete">Eliminar permanentemente</string>
|
||||
|
@ -289,7 +288,6 @@
|
|||
<string name="main_menu_rate_title">Califica la aplicación ⭐</string>
|
||||
<string name="main_unified_push_toast">Esta suscripción es gestionada por %1$s a través de UnifiedPush</string>
|
||||
<string name="main_banner_battery_button_dismiss">Descartar</string>
|
||||
<string name="main_banner_json_stream_text">Cambie a WebSockets en Ajustes / \"Protocolo de conexión\" ahora para asegurarse de que puede comunicarse con su servidor autoalojado ntfy después de junio de 2022.</string>
|
||||
<string name="add_dialog_use_another_server">Usar otro servidor</string>
|
||||
<string name="detail_how_to_link">Instrucciones detalladas están disponibles en ntfy.sh y en la documentación.</string>
|
||||
<string name="main_menu_settings_title">Ajustes</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="detail_settings_notifications_instant_summary_off">Las notificaciones se entregan usando Firebase. La entrega puede retrasarse, pero consume menos batería.</string>
|
||||
<string name="detail_settings_notifications_instant_title">Entrega instantánea</string>
|
||||
<string name="detail_settings_appearance_icon_set_summary">Establecer un icono para que aparezca en las notificaciones</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -20,9 +20,8 @@
|
|||
<string name="main_add_button_description">Ajout d\'abonnement</string>
|
||||
<string name="main_how_to_link">Des instructions détaillées sont disponible sur ntfy.sh et dans la documentation.</string>
|
||||
<string name="main_banner_battery_text">L\'optimisation de la pile devrait être désactivée pour l\'application afin d\'éviter des problèmes de réception de notifications.</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Demander plus tard</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Ignorer</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">En apprendre plus</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Demander plus tard</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Ignorer</string>
|
||||
<string name="add_dialog_topic_name_hint">Nom de sujet, ex. : phils_alerts</string>
|
||||
<string name="add_dialog_instant_delivery">Livraison instantanée en mode somnolence</string>
|
||||
<string name="add_dialog_button_cancel">Annuler</string>
|
||||
|
@ -83,7 +82,6 @@
|
|||
<string name="main_how_to_intro">Cliquez le + pour créer ou vous abonner à un sujet. Ensuite, vous recevrez des notifications sur votre appareil lors de l\'envoi de messages par PUT ou POST.</string>
|
||||
<string name="main_unified_push_toast">Cet abonnement est géré par %1$s à l\'aide de UnifiedPush</string>
|
||||
<string name="main_banner_battery_button_remind_later">Demander plus tard</string>
|
||||
<string name="main_banner_json_stream_text">Changez pour les WebSockets dans Paramètres / «Protocole de connexion» maintenant pour assurer que vous puissiez communiquer avec votre serveur ntfy hébergé par vous-même après le mois de juin 2022.</string>
|
||||
<string name="add_dialog_description_below">Les sujets ne peuvent pas être protégés par mot de passe, choisissez donc un nom difficile à deviner. Une fois abonné, vous pourrez PUT/POST des notifcations.</string>
|
||||
<string name="add_dialog_title">Abonner au sujet</string>
|
||||
<string name="add_dialog_error_connection_failed">La connexion a échouée : %1$s</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="detail_settings_appearance_icon_set_summary">Choisir une icône à afficher pour les notifications</string>
|
||||
<string name="detail_settings_global_setting_title">Utiliser le paramètre global</string>
|
||||
<string name="detail_settings_global_setting_suffix">utilisation du paramètre global</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -39,9 +39,8 @@
|
|||
<string name="main_banner_battery_button_remind_later">Tanya nanti</string>
|
||||
<string name="main_banner_battery_button_dismiss">Abaikan</string>
|
||||
<string name="main_banner_battery_button_fix_now">Perbaiki sekarang</string>
|
||||
<string name="main_banner_json_stream_text">Ubah ke WebSockets dalam Pengaturan / \"Protokol koneksi\" sekarang untuk memastikan Anda dapat berkomunikasi dengan server ntfy Anda setelah Juni 2022.</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Tanya nanti</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Abaikan</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Tanya nanti</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Abaikan</string>
|
||||
<string name="add_dialog_title">Berlangganan ke sebuah topik</string>
|
||||
<string name="add_dialog_description_below">Topik mungkin tidak dilindungi oleh kata sandi, jadi pilih sebuah nama yang susah untuk ditebak. Setelah berlangganan, Anda dapat PUT/POST notifikasi.</string>
|
||||
<string name="add_dialog_topic_name_hint">Nama topik, mis. pemberitahuan_andi</string>
|
||||
|
@ -223,7 +222,6 @@
|
|||
<string name="channel_subscriber_notification_instant_text_more">Berlangganan ke %1$d topik pengiriman instan</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_four">Berlangganan ke empat topik</string>
|
||||
<string name="refresh_message_result">%1$d notifikasi diterima</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Pelajari lebih lanjut</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_three">Berlangganan ke tiga topik</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_more">Berlangganan ke %1$d topik</string>
|
||||
<string name="add_dialog_instant_delivery_description">Memastikan pesan dikirim dengan segera, bahkan jika perangkatnya tidak aktif.</string>
|
||||
|
@ -320,4 +318,6 @@
|
|||
<string name="detail_settings_notifications_instant_title">Pengiriman instan</string>
|
||||
<string name="detail_settings_notifications_instant_summary_off">Notifikasi dikirim menggunakan Firebase. Pengiriman mungkin telat, tetapi mengkonsumsi lebih sedikit baterai.</string>
|
||||
<string name="detail_settings_appearance_icon_set_title">Ikon langganan</string>
|
||||
</resources>
|
||||
<string name="add_dialog_base_urls_dropdown_choose">Pilih URL layanan</string>
|
||||
<string name="add_dialog_base_urls_dropdown_clear">Hapus URL layanan</string>
|
||||
</resources>
|
||||
|
|
|
@ -36,9 +36,8 @@
|
|||
<string name="main_banner_battery_button_remind_later">Chiedi in seguito</string>
|
||||
<string name="main_banner_battery_button_dismiss">Abbandona</string>
|
||||
<string name="main_banner_battery_button_fix_now">Correggi ora</string>
|
||||
<string name="main_banner_json_stream_text">Passa ora a WebSockets in Impostazioni/\"Protocollo di connessione\" per poter comunicare con il tuo server ntfy selfhosted dopo giugno 2022.</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Chiedi in seguito</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Abbandona</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Chiedi in seguito</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Abbandona</string>
|
||||
<string name="add_dialog_title">Iscriviti al topic</string>
|
||||
<string name="add_dialog_topic_name_hint">Nome del topic, es. phils_alerts</string>
|
||||
<string name="add_dialog_use_another_server">Usa un altro server</string>
|
||||
|
@ -193,7 +192,6 @@
|
|||
<string name="main_no_subscriptions_text">Sembra che non ci sia nessuna iscrizione al momento.</string>
|
||||
<string name="main_menu_report_bug_title">Segnala un bug</string>
|
||||
<string name="main_menu_rate_title">Valuta l\'app ⭐</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Scopri di più</string>
|
||||
<string name="add_dialog_description_below">I topic possono essere non protetti da password, per cui scegli un nome che è difficile da indovinare. Una volta iscritti, è possibile effettuare notifiche PUT/POST.</string>
|
||||
<string name="add_dialog_instant_delivery_description">Assicura che i messaggi siano consegnati immediatamente, anche se il device non è attivo.</string>
|
||||
<string name="add_dialog_login_description">Questo topic richiede il login. Per favore, inserire username e password.</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="detail_settings_appearance_icon_remove_summary">Icona visualizzata nelle notifiche di questo topic</string>
|
||||
<string name="detail_settings_appearance_icon_error_saving">Impossibile salvare l\'icona: %1$s</string>
|
||||
<string name="detail_settings_global_setting_suffix">utilizzando l\'impostazione globale</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<string name="main_banner_battery_button_remind_later">שאל אחר כך</string>
|
||||
<string name="main_banner_battery_button_dismiss">הסר</string>
|
||||
<string name="main_banner_battery_button_fix_now">תקן עכשיו</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">שאל אחר כך</string>
|
||||
<string name="main_banner_websocket_button_remind_later">שאל אחר כך</string>
|
||||
<string name="channel_notifications_min_name">התראות (עדיפות מינימאלית)</string>
|
||||
<string name="channel_subscriber_service_name">שירות רישום</string>
|
||||
<string name="channel_subscriber_notification_title">מאזין להתראות נכנסות</string>
|
||||
|
@ -39,5 +39,4 @@
|
|||
<string name="main_item_status_reconnecting">מתחבר מחדש…</string>
|
||||
<string name="main_how_to_intro">לחצ\\י על + על מנת ליצור או להירשם אל מול נושא מסוים. לאחר מכן תקבל\\י התראות במכשירך כשתשלח\\י התראות דרך PUT או POST.</string>
|
||||
<string name="main_how_to_link">הוראות מפורטות זמינות ב-ntfy.sh, ובדוקומנטציה.</string>
|
||||
<string name="main_banner_json_stream_text">החלפ\\י ל-WebSockets תחת הגדרות \\ ״פרוטוקול חיבור״ עכשיו על מנת לוודא שאת\\ה יכול\\ה לתקשר עם שרת ה-ntfy באחסון עצמי שלך גם לאחר יוני 2022.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -67,10 +67,8 @@
|
|||
<string name="add_dialog_title">トピックを購読</string>
|
||||
<string name="add_dialog_button_login">ログイン</string>
|
||||
<string name="add_dialog_login_error_not_authorized">ログインに失敗しました。ユーザー名 %1$s は許可されていません。</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">後で通知</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">無視</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">詳しく</string>
|
||||
<string name="main_banner_json_stream_text">2022年6月以降もセルフホストのntfyサーバーと通信できるように、設定の「接続プロトコル」をWebSocketsに変更してください。</string>
|
||||
<string name="main_banner_websocket_button_remind_later">後で通知</string>
|
||||
<string name="main_banner_websocket_button_dismiss">無視</string>
|
||||
<string name="add_dialog_description_below">トピックはパスワード保護されないので、推測されにくい名前にしてください。購読した後、PUT/POSTで通知を送信できます。</string>
|
||||
<string name="add_dialog_instant_delivery">Dozeモードでの即時配信</string>
|
||||
<string name="add_dialog_instant_delivery_description">デバイスが非アクティブの状態でもメッセージが即時配信されるようにします。</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="detail_settings_notifications_instant_summary_off">通知はFirebaseを用いて配信されます。配信が遅延する事がありますが、バッテリーの消費は抑えられます。</string>
|
||||
<string name="detail_settings_appearance_icon_set_summary">通知に表示されるアイコンを指定します</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_five">トピックを5件購読しています</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -29,8 +29,7 @@
|
|||
<string name="main_item_status_reconnecting">kobler til igjen …</string>
|
||||
<string name="main_item_date_yesterday">i går</string>
|
||||
<string name="main_add_button_description">Legg til abonnement</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Spør senere</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Lær mer</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Spør senere</string>
|
||||
<string name="add_dialog_title">Abonner på emnet</string>
|
||||
<string name="add_dialog_topic_name_hint">Emnenavn, f.eks. halgeirs_varsler</string>
|
||||
<string name="add_dialog_use_another_server">Bruk en annen tjener</string>
|
||||
|
@ -41,7 +40,7 @@
|
|||
<string name="main_no_subscriptions_text">Abonner på noe først</string>
|
||||
<string name="main_banner_battery_text">Batterioptimalisering bør skrus av for å unngå problemer med merknadsleveringen.</string>
|
||||
<string name="main_banner_battery_button_dismiss">Avfei</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Avfei</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Avfei</string>
|
||||
<string name="add_dialog_description_below">Det kan hende emner ikke er passordsbeskyttet, så velg et navn som ikke er enkelt å gjette. Når du har abonnert kan du utføre PUT/POST av merknader.</string>
|
||||
<string name="add_dialog_use_another_server_description">Du kan abonnere på emner fra en annen tjener. Skriv inn tjener-nettadressen nedenfor.</string>
|
||||
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
|
||||
|
@ -294,10 +293,9 @@
|
|||
<string name="main_how_to_link">Mer detaljert instruks er å finne på ntfy.sh-nettsiden og i dokumentasjonen.</string>
|
||||
<string name="main_unified_push_toast">Dette abonnementet håndteres av %1$s via UnifiedPush</string>
|
||||
<string name="add_dialog_foreground_description">Umiddelbar levering er alltid påskrudd for andre verter enn %1$s.</string>
|
||||
<string name="main_banner_json_stream_text">Fra Juni 2022 vil Vev-socketer bli brukt til å kommunisere med ntfy-tjenerne. Forsikre deg om at du har satt opp din selvtjente tjener for å støtte det. For å sjekke om vev-socket-støtte fungerer, kan du skru det på i innstillingene under «Tilkoblingsprotokoll».</string>
|
||||
<string name="settings_notifications_priority_min">min</string>
|
||||
<string name="settings_notifications_priority_low">lav</string>
|
||||
<string name="settings_notifications_priority_default">forvalg</string>
|
||||
<string name="settings_notifications_priority_high">høy</string>
|
||||
<string name="settings_notifications_priority_max">maks.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<string name="main_banner_battery_button_remind_later">Vraag later</string>
|
||||
<string name="main_banner_battery_button_dismiss">Afwijzen</string>
|
||||
<string name="main_banner_battery_button_fix_now">Nu oplossen</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Vraag later</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Vraag later</string>
|
||||
<string name="add_dialog_title">Abonneren op onderwerp</string>
|
||||
<string name="add_dialog_login_title">Aanmelden vereist</string>
|
||||
<string name="add_dialog_button_back">Terug</string>
|
||||
|
@ -70,7 +70,7 @@
|
|||
<string name="main_action_mode_delete_dialog_permanently_delete">Permanent verwijderen</string>
|
||||
<string name="main_unified_push_toast">Dit abonnement wordt beheerd door %1$s via UnifiedPush</string>
|
||||
<string name="main_banner_battery_text">Batterij optimalisatie zou uitgeschakeld moeten zijn voor deze app om problemen met de bezorging van meldingen te voorkomen.</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Afwijzen</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Afwijzen</string>
|
||||
<string name="user_dialog_button_delete">Gebruiker verwijderen</string>
|
||||
<string name="user_dialog_button_cancel">Annuleren</string>
|
||||
<string name="settings_advanced_connection_protocol_entry_ws">WebSockets</string>
|
||||
|
@ -141,10 +141,8 @@
|
|||
<string name="add_dialog_login_description">Dit onderwerp vereist dat je inlogt. Gelieve een gebruikersnaam en wachtwoord in te vullen.</string>
|
||||
<string name="detail_clear_dialog_message">Verwijder alle notificaties in dit onderwerp\?</string>
|
||||
<string name="add_dialog_instant_delivery">Onmiddellijke levering in \"doze\" modus</string>
|
||||
<string name="main_banner_json_stream_text">Schakel nu over naar WebSockets in Instellingen / \"Verbindingsprotocol\" om er zeker van te zijn dat je kunt communiceren met je zelf gehoste ntfy server na juni 2022.</string>
|
||||
<string name="add_dialog_description_below">Onderwerpen zijn mogelijk niet beveiligd met een wachtwoord, dus kies een naam die moeilijk te raden is. Eenmaal geabonneerd, kunt u berichten PUT/POST\'en.</string>
|
||||
<string name="add_dialog_use_another_server_description">Vul server URLS hieronder in om te abonneren op onderwerpen van andere servers.</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Leer meer</string>
|
||||
<string name="add_dialog_instant_delivery_description">Zorgt ervoor dat berichten onmiddellijk worden afgeleverd, zelfs als het toestel inactief is.</string>
|
||||
<string name="add_dialog_login_error_not_authorized">Login gefaald. Gebruiker %1$s is niet geauthoriseerd.</string>
|
||||
<string name="detail_no_notifications_text">Je hebt nog geen notificaties voor dit onderwerp ontvangen.</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="detail_settings_appearance_icon_set_summary">Stel een icoon in wat zal worden weergegeven in notificaties</string>
|
||||
<string name="detail_settings_appearance_icon_remove_title">Abonnementen icoon (tap om te verwijderen)</string>
|
||||
<string name="detail_settings_global_setting_suffix">Gebruikt globale instelling</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -41,9 +41,8 @@
|
|||
<string name="main_how_to_link">Instruções detalhadas disponíveis em ntfy.sh, e na documentação.</string>
|
||||
<string name="main_banner_battery_text">Otimização de bateria deve estar desligada para evitar problemas de entrega de notificação no aplicativo.</string>
|
||||
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Perguntar depois</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Dispensar</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Saiba mais</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Perguntar depois</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Dispensar</string>
|
||||
<string name="add_dialog_title">Inscrever-se no tema</string>
|
||||
<string name="add_dialog_description_below">Temas podem não ser protegidos por senha, escolha um nome difícil de adivinhar. Uma vez inscrito, você pode usar as notificações PUT/POST.</string>
|
||||
<string name="add_dialog_topic_name_hint">Nome do tema, ex: alertas_jose</string>
|
||||
|
@ -209,7 +208,6 @@
|
|||
<string name="main_menu_docs_title">Leia a documentação</string>
|
||||
<string name="main_action_mode_menu_unsubscribe">Cancelar inscrição</string>
|
||||
<string name="main_unified_push_toast">Essa inscrição é gerenciada por %1$s via UnifiedPush</string>
|
||||
<string name="main_banner_json_stream_text">Altere para WebSockets em Configurações / \"Protocolo de conexão\" para garantir a comunicação com seu servidor hospedado ntfy após Junho de 2022.</string>
|
||||
<string name="main_item_status_text_not_one">%1$d notificações</string>
|
||||
<string name="main_how_to_intro">Clique no + para criar ou inscrever-se em um tema. Você receberá notificações no seu dispositivo ao enviar mensagens via PUT ou POST.</string>
|
||||
<string name="main_banner_battery_button_remind_later">Perguntar depois</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="channel_subscriber_notification_instant_text_six">Inscrito em seis tópicos de entrega instantânea</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_five">Inscrito em cinco tópicos</string>
|
||||
<string name="detail_settings_global_setting_suffix">usando configurações globais</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -70,15 +70,14 @@
|
|||
<string name="main_banner_battery_button_remind_later">Спросить позже</string>
|
||||
<string name="main_banner_battery_button_fix_now">Исправить</string>
|
||||
<string name="main_banner_battery_button_dismiss">Закрыть</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Закрыть</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Закрыть</string>
|
||||
<string name="main_add_button_description">Добавить подписку</string>
|
||||
<string name="main_action_mode_menu_unsubscribe">Отписаться</string>
|
||||
<string name="main_item_status_text_one">%1$d уведомление</string>
|
||||
<string name="main_item_status_reconnecting">повторное подключение …</string>
|
||||
<string name="main_no_subscriptions_text">Похоже, у вас ещё нет подписок.</string>
|
||||
<string name="main_action_mode_delete_dialog_message">Отписаться от выбранных тем, и навсегда удалить все уведомления\?</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Спросить позже</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Узнать больше</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Спросить позже</string>
|
||||
<string name="add_dialog_title">Подписаться на тему</string>
|
||||
<string name="add_dialog_button_cancel">Отмена</string>
|
||||
<string name="main_unified_push_toast">Эта подписка управляется %1$s через UnifiedPush</string>
|
||||
|
@ -153,7 +152,6 @@
|
|||
<string name="refresh_message_no_results">Всё обновлено</string>
|
||||
<string name="add_dialog_instant_delivery">Мгновенная доставка в спящем режиме</string>
|
||||
<string name="main_banner_battery_text">Оптимизация батареи должна быть выключена, чтобы избежать проблем с доставкой уведомлений.</string>
|
||||
<string name="main_banner_json_stream_text">Переключитесь на WebSockets в Настройках / \"Connection protocol\" сейчас, чтобы вы могли пользоваться своим собственным ntfy сервером после июня 2022.</string>
|
||||
<string name="add_dialog_error_connection_failed">Ошибка связи: %1$s</string>
|
||||
<string name="add_dialog_login_title">Требуется вход в аккаунт</string>
|
||||
<string name="add_dialog_login_description">Эта тема требует авторизации. Пожалуйста, введите имя пользователя и пароль.</string>
|
||||
|
@ -308,4 +306,4 @@
|
|||
<string name="channel_subscriber_notification_instant_text_six">Подписан на шесть тем с мгновенной доставкой</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_five">Подписан на пять тем</string>
|
||||
<string name="channel_subscriber_notification_noinstant_text_six">Подписан на шесть тем</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -41,8 +41,8 @@
|
|||
<string name="main_how_to_link">Ayrıntılı talimatlar ntfy.sh adrsimde ve belgelerde bulunabilir.</string>
|
||||
<string name="main_unified_push_toast">Bu abonelik, %1$s tarafından UnifiedPush aracılığıyla yönetiliyor</string>
|
||||
<string name="main_banner_battery_text">Bildirim teslim sorunlarından kaçınmak için uygulama için pil iyileştirmesi kapalı olmalıdır.</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Daha sonra sor</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Kapat</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Daha sonra sor</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Kapat</string>
|
||||
<string name="add_dialog_title">Konuya abone ol</string>
|
||||
<string name="add_dialog_topic_name_hint">Konu adı, örn. benim_uyarilarim</string>
|
||||
<string name="add_dialog_use_another_server">Başka bir sunucu kullan</string>
|
||||
|
@ -209,8 +209,6 @@
|
|||
<string name="main_item_date_yesterday">dün</string>
|
||||
<string name="main_banner_battery_button_dismiss">Kapat</string>
|
||||
<string name="main_banner_battery_button_fix_now">Şimdi düzelt</string>
|
||||
<string name="main_banner_json_stream_text">Haziran 2022\'den sonra kendi barındırdığınız ntfy sunucunuzla iletişim kurabildiğinizden emin olmak için şimdi Ayarlar / \"Bağlantı protokolü\" bölümünde WebSockets\'e geçin.</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Daha fazla bilgi edin</string>
|
||||
<string name="detail_how_to_example">Örnek (curl kullanarak):<br/><tt>$ curl -d \"Merhaba\" %1$s</tt></string>
|
||||
<string name="detail_delete_dialog_permanently_delete">Kalıcı olarak sil</string>
|
||||
<string name="detail_test_message_error_unauthorized_user">Mesaj gönderilemiyor: \"%1$s\" kullanıcısı yetkilendirilmedi.</string>
|
||||
|
@ -319,4 +317,6 @@
|
|||
<string name="detail_settings_appearance_icon_remove_title">Abonelik simgesi (kaldırmak için dokunun)</string>
|
||||
<string name="detail_settings_appearance_icon_remove_summary">Bu konu için bildirimlerde görüntülenen simge</string>
|
||||
<string name="detail_settings_global_setting_title">Genel ayarı kullan</string>
|
||||
</resources>
|
||||
<string name="add_dialog_base_urls_dropdown_clear">Hizmet URL\'sini temizle</string>
|
||||
<string name="add_dialog_base_urls_dropdown_choose">Hizmet URL\'sini seç</string>
|
||||
</resources>
|
||||
|
|
|
@ -47,10 +47,8 @@
|
|||
<string name="add_dialog_login_error_not_authorized">登录失败。 %1$s用户无权访问。</string>
|
||||
<string name="add_dialog_login_new_user">新用户</string>
|
||||
<string name="detail_no_notifications_text">当前还没有关于此主题的通知。</string>
|
||||
<string name="main_banner_json_stream_text">请在“链接协议”中选择 WebSockets 以保证在 2022 年 6 月之后仍能收到来自自建 ntfy 服务器的推送。</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">稍后再问</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">暂时不管</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">详情</string>
|
||||
<string name="main_banner_websocket_button_remind_later">稍后再问</string>
|
||||
<string name="main_banner_websocket_button_dismiss">暂时不管</string>
|
||||
<string name="add_dialog_title">订阅主题</string>
|
||||
<string name="add_dialog_description_below">主题未必有密码保护,因此请选择难以猜测的名称。订阅之后,您可以通知 PUT/POST 请求发送通知。</string>
|
||||
<string name="add_dialog_topic_name_hint">主题名称,例如:phils_alerts</string>
|
||||
|
@ -319,4 +317,4 @@
|
|||
<string name="detail_settings_appearance_icon_error_saving">无法保存图标:%1$s</string>
|
||||
<string name="detail_settings_global_setting_title">使用全局设置</string>
|
||||
<string name="detail_settings_global_setting_suffix">使用全局设置</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="MissingTranslation">
|
||||
|
||||
<!-- Notification channels -->
|
||||
<string name="channel_notifications_min_name">Notifications (min priority)</string>
|
||||
<string name="channel_notifications_low_name">Notifications (low priority)</string>
|
||||
|
@ -72,11 +74,11 @@
|
|||
<string name="main_banner_battery_button_dismiss">Dismiss</string>
|
||||
<string name="main_banner_battery_button_fix_now">Fix now</string>
|
||||
|
||||
<!-- Main activity: JSON stream banner -->
|
||||
<string name="main_banner_json_stream_text">Switch to WebSockets in Settings / \"Connection protocol\" now to ensure you can communicate with your selfhosted ntfy server after June 2022.</string>
|
||||
<string name="main_banner_json_stream_button_remind_later">Ask later</string>
|
||||
<string name="main_banner_json_stream_button_dismiss">Dismiss</string>
|
||||
<string name="main_banner_json_stream_button_learn_more">Learn more</string>
|
||||
<!-- Main activity: WebSocket banner -->
|
||||
<string name="main_banner_websocket_text">Switching to WebSockets is the recommended way to connect to your server, and could improve battery life, but may require <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">additional config in your proxy</a>. This can be toggled in the Settings.</string>
|
||||
<string name="main_banner_websocket_button_remind_later">Ask later</string>
|
||||
<string name="main_banner_websocket_button_dismiss">Dismiss</string>
|
||||
<string name="main_banner_websocket_button_enable_now">Enable now</string>
|
||||
|
||||
<!-- Add dialog -->
|
||||
<string name="add_dialog_title">Subscribe to topic</string>
|
||||
|
@ -101,6 +103,8 @@
|
|||
<string name="add_dialog_login_password_hint">Password</string>
|
||||
<string name="add_dialog_login_error_not_authorized">Login failed. User %1$s not authorized.</string>
|
||||
<string name="add_dialog_login_new_user">New user</string>
|
||||
<string name="add_dialog_base_urls_dropdown_choose">Choose service URL</string>
|
||||
<string name="add_dialog_base_urls_dropdown_clear">Clear service URL</string>
|
||||
|
||||
<!-- Detail activity -->
|
||||
<string name="detail_no_notifications_text">You haven\'t received any notifications for this topic yet.</string>
|
||||
|
@ -330,8 +334,8 @@
|
|||
<string name="settings_advanced_clear_logs_summary">Delete previously recorded logs, and start over</string>
|
||||
<string name="settings_advanced_clear_logs_deleted_toast">Logs deleted</string>
|
||||
<string name="settings_advanced_connection_protocol_title">Connection protocol</string>
|
||||
<string name="settings_advanced_connection_protocol_summary_jsonhttp">Use a JSON stream over HTTP to connect to the server. This method is deprecated and will be removed in June 2022.</string>
|
||||
<string name="settings_advanced_connection_protocol_summary_ws">Use WebSockets to connect to the server. This will become the default in June 2022.</string>
|
||||
<string name="settings_advanced_connection_protocol_summary_jsonhttp">Use a JSON stream over HTTP to connect to the server. This method is battle-tested, but may consume more battery.</string>
|
||||
<string name="settings_advanced_connection_protocol_summary_ws">Use WebSockets to connect to the server. This is the recommended method, but may require additional config in your proxy.</string>
|
||||
<string name="settings_advanced_connection_protocol_entry_jsonhttp">JSON stream over HTTP</string>
|
||||
<string name="settings_advanced_connection_protocol_entry_ws">WebSockets</string>
|
||||
<string name="settings_about_header">About</string>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<!-- Main activity -->
|
||||
<string name="main_menu_report_bug_url" translatable="false">https://github.com/binwiederhier/ntfy/issues</string>
|
||||
<string name="main_menu_docs_url" translatable="false">https://ntfy.sh/docs</string>
|
||||
<string name="main_banner_json_stream_button_learn_more_url" translatable="false">https://ntfy.sh/docs/deprecations</string>
|
||||
|
||||
<!-- Settings constants -->
|
||||
<string name="settings_notifications_muted_until_key" translatable="false">MutedUntil</string>
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
Features:
|
||||
* Polling is now done with since=<id> API, which makes deduping easier (#165)
|
||||
* Turned JSON stream deprecation banner into "Use WebSockets" banner (no ticket)
|
||||
|
||||
Bugs:
|
||||
* Fixed: Long-click selecting of notifications scrolls to the top (#235, thanks to @wunter8)
|
||||
* Long-click selecting of notifications doesn't scoll to the top anymore (#235, thanks to @wunter8)
|
||||
* Add attachment and click URL extras to MESSAGE_RECEIVED broadcast (#329, thanks to @wunter8)
|
||||
* Accessibility: Clear/choose service URL button in base URL dropdown now has a label (#292, thanks to @mhameed for reporting)
|
||||
|
||||
Additional translations:
|
||||
* Italian (thanks to @Genio2003)
|
||||
|
|
Loading…
Add table
Reference in a new issue