Merge branch 'main' into custom_notification_channels
This commit is contained in:
commit
6f0bf4d112
41 changed files with 461 additions and 138 deletions
|
@ -14,8 +14,8 @@ android {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
|
|
||||||
versionCode 29
|
versionCode 32
|
||||||
versionName "1.15.0"
|
versionName "1.16.0"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
@ -44,10 +44,12 @@ android {
|
||||||
play {
|
play {
|
||||||
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true'
|
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true'
|
||||||
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true'
|
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true'
|
||||||
|
buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'false'
|
||||||
}
|
}
|
||||||
fdroid {
|
fdroid {
|
||||||
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false'
|
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false'
|
||||||
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'false'
|
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'false'
|
||||||
|
buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'true'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,12 +66,29 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disables GoogleServices tasks for F-Droid variant
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
def shouldProcessGoogleServices = variant.flavorName == "play"
|
def shouldProcessGoogleServices = variant.flavorName == "play"
|
||||||
def googleTask = tasks.findByName("process${variant.name.capitalize()}GoogleServices")
|
def googleTask = tasks.findByName("process${variant.name.capitalize()}GoogleServices")
|
||||||
googleTask.enabled = shouldProcessGoogleServices
|
googleTask.enabled = shouldProcessGoogleServices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strips out REQUEST_INSTALL_PACKAGES permission for Google Play variant
|
||||||
|
android.applicationVariants.all { variant ->
|
||||||
|
def shouldStripInstallPermission = variant.flavorName == "play"
|
||||||
|
if (shouldStripInstallPermission) {
|
||||||
|
variant.outputs.each { output ->
|
||||||
|
def processManifest = output.getProcessManifestProvider().get()
|
||||||
|
processManifest.doLast { task ->
|
||||||
|
def outputDir = task.getMultiApkManifestOutputDirectory().get().asFile
|
||||||
|
def manifestOutFile = file("$outputDir/AndroidManifest.xml")
|
||||||
|
def newFileContents = manifestOutFile.collect { s -> s.contains("android.permission.REQUEST_INSTALL_PACKAGES") ? "" : s }.join("\n")
|
||||||
|
manifestOutFile.write(newFileContents, 'UTF-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// AndroidX, The Basics
|
// AndroidX, The Basics
|
||||||
implementation "androidx.appcompat:appcompat:1.5.1"
|
implementation "androidx.appcompat:appcompat:1.5.1"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.heckel.ntfy.firebase
|
package io.heckel.ntfy.firebase
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
class FirebaseMessenger {
|
class FirebaseMessenger {
|
||||||
fun subscribe(topic: String) {
|
fun subscribe(topic: String) {
|
||||||
// Dummy to keep F-Droid flavor happy
|
// Dummy to keep F-Droid flavor happy
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="io.heckel.ntfy">
|
package="io.heckel.ntfy">
|
||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <!-- For instant delivery foregrounds service -->
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <!-- For instant delivery foregrounds service -->
|
||||||
|
@ -8,10 +9,17 @@
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <!-- To restart service on reboot -->
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <!-- To restart service on reboot -->
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/> <!-- Incoming notifications should be able to vibrate the phone -->
|
<uses-permission android:name="android.permission.VIBRATE"/> <!-- Incoming notifications should be able to vibrate the phone -->
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 -->
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 -->
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <!-- To install packages downloaded through ntfy; craazyy! -->
|
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry -->
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- As of Android 13, we need to ask for permission to post notifications -->
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- As of Android 13, we need to ask for permission to post notifications -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Permission REQUEST_INSTALL_PACKAGES (F-Droid only!):
|
||||||
|
- Permission is used to install .apk files that were received as attachments
|
||||||
|
- Google rejected the permission for ntfy, so this permission is STRIPPED OUT by the build process
|
||||||
|
for the Google Play variant of the app.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".app.Application"
|
android:name=".app.Application"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package io.heckel.ntfy.app
|
package io.heckel.ntfy.app
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
|
||||||
import io.heckel.ntfy.db.Database
|
import io.heckel.ntfy.db.Database
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
import io.heckel.ntfy.util.Log
|
import io.heckel.ntfy.util.Log
|
||||||
|
|
|
@ -89,7 +89,7 @@ class Backuper(val context: Context) {
|
||||||
|
|
||||||
private suspend fun applySubscriptions(subscriptions: List<Subscription>?) {
|
private suspend fun applySubscriptions(subscriptions: List<Subscription>?) {
|
||||||
if (subscriptions == null) {
|
if (subscriptions == null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
val appBaseUrl = context.getString(R.string.app_base_url)
|
val appBaseUrl = context.getString(R.string.app_base_url)
|
||||||
subscriptions.forEach { s ->
|
subscriptions.forEach { s ->
|
||||||
|
@ -120,7 +120,7 @@ class Backuper(val context: Context) {
|
||||||
|
|
||||||
private suspend fun applyNotifications(notifications: List<Notification>?) {
|
private suspend fun applyNotifications(notifications: List<Notification>?) {
|
||||||
if (notifications == null) {
|
if (notifications == null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
notifications.forEach { n ->
|
notifications.forEach { n ->
|
||||||
try {
|
try {
|
||||||
|
@ -189,7 +189,7 @@ class Backuper(val context: Context) {
|
||||||
|
|
||||||
private suspend fun applyUsers(users: List<User>?) {
|
private suspend fun applyUsers(users: List<User>?) {
|
||||||
if (users == null) {
|
if (users == null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
users.forEach { u ->
|
users.forEach { u ->
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -95,7 +95,7 @@ class ApiService {
|
||||||
throw Exception("Unexpected response ${response.code} when polling topic $url")
|
throw Exception("Unexpected response ${response.code} when polling topic $url")
|
||||||
}
|
}
|
||||||
val body = response.body?.string()?.trim()
|
val body = response.body?.string()?.trim()
|
||||||
if (body == null || body.isEmpty()) return emptyList()
|
if (body.isNullOrEmpty()) return emptyList()
|
||||||
val notifications = body.lines().mapNotNull { line ->
|
val notifications = body.lines().mapNotNull { line ->
|
||||||
parser.parse(line, subscriptionId = subscriptionId, notificationId = 0) // No notification when we poll
|
parser.parse(line, subscriptionId = subscriptionId, notificationId = 0) // No notification when we poll
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnauthorizedException(val user: User?) : Exception()
|
class UnauthorizedException(val user: User?) : Exception()
|
||||||
class EntityTooLargeException() : Exception()
|
class EntityTooLargeException : Exception()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})"
|
val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})"
|
||||||
|
|
|
@ -11,7 +11,7 @@ import io.heckel.ntfy.util.Log
|
||||||
* Download attachment in the background via WorkManager
|
* Download attachment in the background via WorkManager
|
||||||
*
|
*
|
||||||
* The indirection via WorkManager is required since this code may be executed
|
* The indirection via WorkManager is required since this code may be executed
|
||||||
* in a doze state and Internet may not be available. It's also best practice apparently.
|
* in a doze state and Internet may not be available. It's also best practice, apparently.
|
||||||
*/
|
*/
|
||||||
object DownloadManager {
|
object DownloadManager {
|
||||||
private const val TAG = "NtfyDownloadManager"
|
private const val TAG = "NtfyDownloadManager"
|
||||||
|
|
|
@ -206,6 +206,9 @@ class NotificationService(val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeAddOpenAction(builder: NotificationCompat.Builder, notification: Notification) {
|
private fun maybeAddOpenAction(builder: NotificationCompat.Builder, notification: Notification) {
|
||||||
|
if (!canOpenAttachment(notification.attachment)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (notification.attachment?.contentUri != null) {
|
if (notification.attachment?.contentUri != null) {
|
||||||
val contentUri = Uri.parse(notification.attachment.contentUri)
|
val contentUri = Uri.parse(notification.attachment.contentUri)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, contentUri).apply {
|
val intent = Intent(Intent.ACTION_VIEW, contentUri).apply {
|
||||||
|
|
|
@ -200,7 +200,7 @@ class SubscriberService : Service() {
|
||||||
// retrieve old messages. This is important, so we don't download attachments from old messages.
|
// retrieve old messages. This is important, so we don't download attachments from old messages.
|
||||||
|
|
||||||
val since = sinceByBaseUrl[connectionId.baseUrl] ?: "none"
|
val since = sinceByBaseUrl[connectionId.baseUrl] ?: "none"
|
||||||
val serviceActive = { -> isServiceStarted }
|
val serviceActive = { isServiceStarted }
|
||||||
val user = repository.getUser(connectionId.baseUrl)
|
val user = repository.getUser(connectionId.baseUrl)
|
||||||
val connection = if (repository.getConnectionProtocol() == Repository.CONNECTION_PROTOCOL_WS) {
|
val connection = if (repository.getConnectionProtocol() == Repository.CONNECTION_PROTOCOL_WS) {
|
||||||
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
|
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
|
||||||
|
@ -310,11 +310,11 @@ class SubscriberService : Service() {
|
||||||
override fun onTaskRemoved(rootIntent: Intent) {
|
override fun onTaskRemoved(rootIntent: Intent) {
|
||||||
val restartServiceIntent = Intent(applicationContext, SubscriberService::class.java).also {
|
val restartServiceIntent = Intent(applicationContext, SubscriberService::class.java).also {
|
||||||
it.setPackage(packageName)
|
it.setPackage(packageName)
|
||||||
};
|
}
|
||||||
val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE);
|
val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
applicationContext.getSystemService(Context.ALARM_SERVICE);
|
applicationContext.getSystemService(Context.ALARM_SERVICE)
|
||||||
val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager;
|
val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent);
|
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This re-starts the service on reboot; see manifest */
|
/* This re-starts the service on reboot; see manifest */
|
||||||
|
|
|
@ -5,8 +5,6 @@ import android.app.AlertDialog
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.Intent.ACTION_VIEW
|
import android.content.Intent.ACTION_VIEW
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.view.ActionMode
|
import android.view.ActionMode
|
||||||
|
@ -179,7 +180,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
howToExample.linksClickable = true
|
howToExample.linksClickable = true
|
||||||
|
|
||||||
val howToText = getString(R.string.detail_how_to_example, topicUrl)
|
val howToText = getString(R.string.detail_how_to_example, topicUrl)
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
howToExample.text = Html.fromHtml(howToText, Html.FROM_HTML_MODE_LEGACY)
|
howToExample.text = Html.fromHtml(howToText, Html.FROM_HTML_MODE_LEGACY)
|
||||||
} else {
|
} else {
|
||||||
howToExample.text = Html.fromHtml(howToText)
|
howToExample.text = Html.fromHtml(howToText)
|
||||||
|
@ -242,7 +243,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||||
if (positionStart == 0) {
|
if (positionStart == 0) {
|
||||||
Log.d(TAG, "$itemCount item(s) inserted at $positionStart, scrolling to the top")
|
Log.d(TAG, "$itemCount item(s) inserted at 0, scrolling to the top")
|
||||||
mainList.scrollToPosition(positionStart)
|
mainList.scrollToPosition(positionStart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -572,7 +573,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
dialog
|
dialog
|
||||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
.setTextAppearance(R.style.DangerText)
|
.dangerButton(this)
|
||||||
}
|
}
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
@ -610,7 +611,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
dialog
|
dialog
|
||||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
.setTextAppearance(R.style.DangerText)
|
.dangerButton(this)
|
||||||
}
|
}
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
@ -620,7 +621,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
handleActionModeClick(notification)
|
handleActionModeClick(notification)
|
||||||
} else if (notification.click != "") {
|
} else if (notification.click != "") {
|
||||||
try {
|
try {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(notification.click)))
|
startActivity(Intent(ACTION_VIEW, Uri.parse(notification.click)))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Cannot open click URL", e)
|
Log.w(TAG, "Cannot open click URL", e)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
@ -721,7 +722,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
dialog
|
dialog
|
||||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
.setTextAppearance(R.style.DangerText)
|
.dangerButton(this)
|
||||||
}
|
}
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,16 @@ import com.google.android.material.button.MaterialButton
|
||||||
import com.stfalcon.imageviewer.StfalconImageViewer
|
import com.stfalcon.imageviewer.StfalconImageViewer
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.*
|
import io.heckel.ntfy.db.*
|
||||||
import io.heckel.ntfy.msg.DownloadManager
|
|
||||||
import io.heckel.ntfy.msg.DownloadAttachmentWorker
|
import io.heckel.ntfy.msg.DownloadAttachmentWorker
|
||||||
|
import io.heckel.ntfy.msg.DownloadManager
|
||||||
import io.heckel.ntfy.msg.DownloadType
|
import io.heckel.ntfy.msg.DownloadType
|
||||||
import io.heckel.ntfy.msg.NotificationService
|
import io.heckel.ntfy.msg.NotificationService
|
||||||
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
|
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
|
||||||
import io.heckel.ntfy.util.*
|
import io.heckel.ntfy.util.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
class DetailAdapter(private val activity: Activity, private val lifecycleScope: CoroutineScope, private val repository: Repository, private val onClick: (Notification) -> Unit, private val onLongClick: (Notification) -> Unit) :
|
||||||
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
ListAdapter<Notification, DetailAdapter.DetailViewHolder>(TopicDiffCallback) {
|
||||||
|
@ -204,7 +206,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeRenderActions(context: Context, notification: Notification) {
|
private fun maybeRenderActions(context: Context, notification: Notification) {
|
||||||
if (notification.actions != null && notification.actions.isNotEmpty()) {
|
if (!notification.actions.isNullOrEmpty()) {
|
||||||
actionsWrapperView.visibility = View.VISIBLE
|
actionsWrapperView.visibility = View.VISIBLE
|
||||||
val actionsCount = Math.min(notification.actions.size, 3) // per documentation, only 3 actions are available
|
val actionsCount = Math.min(notification.actions.size, 3) // per documentation, only 3 actions are available
|
||||||
for (i in 0 until actionsCount) {
|
for (i in 0 until actionsCount) {
|
||||||
|
@ -220,7 +222,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
|
||||||
|
|
||||||
private fun resetCardButtons() {
|
private fun resetCardButtons() {
|
||||||
// clear any previously created dynamic buttons
|
// clear any previously created dynamic buttons
|
||||||
actionsFlow.allViews.forEach { it -> actionsFlow.removeView(it) }
|
actionsFlow.allViews.forEach { actionsFlow.removeView(it) }
|
||||||
actionsWrapperView.removeAllViews()
|
actionsWrapperView.removeAllViews()
|
||||||
actionsWrapperView.addView(actionsFlow)
|
actionsWrapperView.addView(actionsFlow)
|
||||||
}
|
}
|
||||||
|
@ -371,6 +373,12 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope:
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openFile(context: Context, attachment: Attachment): Boolean {
|
private fun openFile(context: Context, attachment: Attachment): Boolean {
|
||||||
|
if (!canOpenAttachment(attachment)) {
|
||||||
|
Toast
|
||||||
|
.makeText(context, context.getString(R.string.detail_item_cannot_open_apk), Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
Log.d(TAG, "Opening file ${attachment.contentUri}")
|
Log.d(TAG, "Opening file ${attachment.contentUri}")
|
||||||
try {
|
try {
|
||||||
val contentUri = Uri.parse(attachment.contentUri)
|
val contentUri = Uri.parse(attachment.contentUri)
|
||||||
|
|
|
@ -229,9 +229,8 @@ class DetailSettingsActivity : AppCompatActivity() {
|
||||||
return subscription.mutedUntil.toString()
|
return subscription.mutedUntil.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pref?.summaryProvider = Preference.SummaryProvider<ListPreference> { _ ->
|
pref?.summaryProvider = Preference.SummaryProvider<ListPreference> {
|
||||||
val mutedUntilValue = subscription.mutedUntil
|
when (val mutedUntilValue = subscription.mutedUntil) {
|
||||||
when (mutedUntilValue) {
|
|
||||||
Repository.MUTED_UNTIL_SHOW_ALL -> getString(R.string.settings_notifications_muted_until_show_all)
|
Repository.MUTED_UNTIL_SHOW_ALL -> getString(R.string.settings_notifications_muted_until_show_all)
|
||||||
Repository.MUTED_UNTIL_FOREVER -> getString(R.string.settings_notifications_muted_until_forever)
|
Repository.MUTED_UNTIL_FOREVER -> getString(R.string.settings_notifications_muted_until_forever)
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -312,7 +311,7 @@ class DetailSettingsActivity : AppCompatActivity() {
|
||||||
iconSetPref = findPreference(prefId) ?: return
|
iconSetPref = findPreference(prefId) ?: return
|
||||||
iconSetPref.isVisible = subscription.icon == null
|
iconSetPref.isVisible = subscription.icon == null
|
||||||
iconSetPref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
|
iconSetPref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
|
||||||
iconSetPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
|
iconSetPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
iconSetLauncher.launch("image/*")
|
iconSetLauncher.launch("image/*")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -323,7 +322,7 @@ class DetailSettingsActivity : AppCompatActivity() {
|
||||||
iconRemovePref = findPreference(prefId) ?: return
|
iconRemovePref = findPreference(prefId) ?: return
|
||||||
iconRemovePref.isVisible = subscription.icon != null
|
iconRemovePref.isVisible = subscription.icon != null
|
||||||
iconRemovePref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
|
iconRemovePref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting
|
||||||
iconRemovePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
|
iconRemovePref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
iconRemovePref.isVisible = false
|
iconRemovePref.isVisible = false
|
||||||
iconSetPref.isVisible = true
|
iconSetPref.isVisible = true
|
||||||
deleteIcon(subscription.icon)
|
deleteIcon(subscription.icon)
|
||||||
|
|
|
@ -35,7 +35,6 @@ import io.heckel.ntfy.app.Application
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
import io.heckel.ntfy.db.Subscription
|
import io.heckel.ntfy.db.Subscription
|
||||||
import io.heckel.ntfy.firebase.FirebaseMessenger
|
import io.heckel.ntfy.firebase.FirebaseMessenger
|
||||||
import io.heckel.ntfy.util.Log
|
|
||||||
import io.heckel.ntfy.msg.ApiService
|
import io.heckel.ntfy.msg.ApiService
|
||||||
import io.heckel.ntfy.msg.DownloadManager
|
import io.heckel.ntfy.msg.DownloadManager
|
||||||
import io.heckel.ntfy.msg.DownloadType
|
import io.heckel.ntfy.msg.DownloadType
|
||||||
|
@ -48,7 +47,6 @@ import io.heckel.ntfy.work.PollWorker
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
@ -623,7 +621,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
dialog
|
dialog
|
||||||
.getButton(AlertDialog.BUTTON_POSITIVE)
|
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
.setTextAppearance(R.style.DangerText)
|
.dangerButton(this)
|
||||||
}
|
}
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package io.heckel.ntfy.ui
|
package io.heckel.ntfy.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -17,10 +15,8 @@ import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.ConnectionState
|
import io.heckel.ntfy.db.ConnectionState
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
import io.heckel.ntfy.db.Subscription
|
import io.heckel.ntfy.db.Subscription
|
||||||
import io.heckel.ntfy.msg.NotificationService
|
|
||||||
import io.heckel.ntfy.util.Log
|
|
||||||
import io.heckel.ntfy.util.readBitmapFromUriOrNull
|
|
||||||
import io.heckel.ntfy.util.displayName
|
import io.heckel.ntfy.util.displayName
|
||||||
|
import io.heckel.ntfy.util.readBitmapFromUriOrNull
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -119,7 +115,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
|
||||||
if (selected.contains(subscription.id)) {
|
if (selected.contains(subscription.id)) {
|
||||||
itemView.setBackgroundResource(Colors.itemSelectedBackground(context))
|
itemView.setBackgroundResource(Colors.itemSelectedBackground(context))
|
||||||
} else {
|
} else {
|
||||||
itemView.setBackgroundColor(Color.TRANSPARENT);
|
itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,8 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.heckel.ntfy.db.*
|
import io.heckel.ntfy.db.*
|
||||||
import io.heckel.ntfy.up.Distributor
|
import io.heckel.ntfy.up.Distributor
|
||||||
import io.heckel.ntfy.util.Log
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.List
|
|
||||||
|
|
||||||
class SubscriptionsViewModel(private val repository: Repository) : ViewModel() {
|
class SubscriptionsViewModel(private val repository: Repository) : ViewModel() {
|
||||||
fun list(): LiveData<List<Subscription>> {
|
fun list(): LiveData<List<Subscription>> {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.widget.RadioButton
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.Database
|
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package io.heckel.ntfy.ui
|
package io.heckel.ntfy.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -295,7 +294,7 @@ class ShareActivity : AppCompatActivity() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val message = if (e is ApiService.UnauthorizedException) {
|
val errorMessage = if (e is ApiService.UnauthorizedException) {
|
||||||
if (e.user != null) {
|
if (e.user != null) {
|
||||||
getString(R.string.detail_test_message_error_unauthorized_user, e.user.username)
|
getString(R.string.detail_test_message_error_unauthorized_user, e.user.username)
|
||||||
} else {
|
} else {
|
||||||
|
@ -308,7 +307,7 @@ class ShareActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
progress.visibility = View.GONE
|
progress.visibility = View.GONE
|
||||||
errorText.text = message
|
errorText.text = errorMessage
|
||||||
errorImage.visibility = View.VISIBLE
|
errorImage.visibility = View.VISIBLE
|
||||||
errorText.visibility = View.VISIBLE
|
errorText.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,17 @@ package io.heckel.ntfy.ui
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.User
|
import io.heckel.ntfy.db.User
|
||||||
|
import io.heckel.ntfy.util.AfterChangedTextWatcher
|
||||||
|
import io.heckel.ntfy.util.dangerButton
|
||||||
import io.heckel.ntfy.util.validUrl
|
import io.heckel.ntfy.util.validUrl
|
||||||
|
|
||||||
class UserFragment : DialogFragment() {
|
class UserFragment : DialogFragment() {
|
||||||
|
@ -98,28 +96,14 @@ class UserFragment : DialogFragment() {
|
||||||
|
|
||||||
// Delete button should be red
|
// Delete button should be red
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
dialog
|
||||||
dialog
|
.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||||
.getButton(AlertDialog.BUTTON_NEUTRAL)
|
.dangerButton(requireContext())
|
||||||
.setTextAppearance(R.style.DangerText)
|
|
||||||
} else {
|
|
||||||
dialog
|
|
||||||
.getButton(AlertDialog.BUTTON_NEUTRAL)
|
|
||||||
.setTextColor(ContextCompat.getColor(requireContext(), Colors.dangerText(requireContext())))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate input when typing
|
// Validate input when typing
|
||||||
val textWatcher = object : TextWatcher {
|
val textWatcher = AfterChangedTextWatcher {
|
||||||
override fun afterTextChanged(s: Editable?) {
|
validateInput()
|
||||||
validateInput()
|
|
||||||
}
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
||||||
// Nothing
|
|
||||||
}
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
|
||||||
// Nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
baseUrlView.addTextChangedListener(textWatcher)
|
baseUrlView.addTextChangedListener(textWatcher)
|
||||||
usernameView.addTextChangedListener(textWatcher)
|
usernameView.addTextChangedListener(textWatcher)
|
||||||
|
@ -140,7 +124,7 @@ class UserFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show keyboard when the dialog is shown (see https://stackoverflow.com/a/19573049/1440785)
|
// Show keyboard when the dialog is shown (see https://stackoverflow.com/a/19573049/1440785)
|
||||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||||
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,10 @@ import io.heckel.ntfy.service.SubscriberServiceManager
|
||||||
import io.heckel.ntfy.util.*
|
import io.heckel.ntfy.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the UnifiedPush broadcast receiver to handle the distributor actions REGISTER and UNREGISTER.
|
* This is the UnifiedPush broadcast receiver to handle the distributor actions REGISTER and UNREGISTER.
|
||||||
|
|
|
@ -22,12 +22,16 @@ import android.util.Base64
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import io.heckel.ntfy.BuildConfig
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.*
|
import io.heckel.ntfy.db.*
|
||||||
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
|
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
|
||||||
|
import io.heckel.ntfy.ui.Colors
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -221,16 +225,6 @@ fun maybeAppendActionErrors(message: String, notification: Notification): String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks in the most horrible way if a content URI exists; I couldn't find a better way
|
|
||||||
fun fileExists(context: Context, contentUri: String?): Boolean {
|
|
||||||
return try {
|
|
||||||
fileStat(context, Uri.parse(contentUri)) // Throws if the file does not exist
|
|
||||||
true
|
|
||||||
} catch (_: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queries the filename of a content URI
|
// Queries the filename of a content URI
|
||||||
fun fileName(context: Context, contentUri: String?, fallbackName: String): String {
|
fun fileName(context: Context, contentUri: String?, fallbackName: String): String {
|
||||||
return try {
|
return try {
|
||||||
|
@ -325,6 +319,8 @@ fun formatBytes(bytes: Long, decimals: Int = 1): String {
|
||||||
return java.lang.String.format("%.${decimals}f %cB", value / 1024.0, ci.current())
|
return java.lang.String.format("%.${decimals}f %cB", value / 1024.0, ci.current())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const val androidAppMimeType = "application/vnd.android.package-archive"
|
||||||
|
|
||||||
fun mimeTypeToIconResource(mimeType: String?): Int {
|
fun mimeTypeToIconResource(mimeType: String?): Int {
|
||||||
return if (mimeType?.startsWith("image/") == true) {
|
return if (mimeType?.startsWith("image/") == true) {
|
||||||
R.drawable.ic_file_image_red_24dp
|
R.drawable.ic_file_image_red_24dp
|
||||||
|
@ -332,7 +328,7 @@ fun mimeTypeToIconResource(mimeType: String?): Int {
|
||||||
R.drawable.ic_file_video_orange_24dp
|
R.drawable.ic_file_video_orange_24dp
|
||||||
} else if (mimeType?.startsWith("audio/") == true) {
|
} else if (mimeType?.startsWith("audio/") == true) {
|
||||||
R.drawable.ic_file_audio_purple_24dp
|
R.drawable.ic_file_audio_purple_24dp
|
||||||
} else if (mimeType == "application/vnd.android.package-archive") {
|
} else if (mimeType == androidAppMimeType) {
|
||||||
R.drawable.ic_file_app_gray_24dp
|
R.drawable.ic_file_app_gray_24dp
|
||||||
} else {
|
} else {
|
||||||
R.drawable.ic_file_document_blue_24dp
|
R.drawable.ic_file_document_blue_24dp
|
||||||
|
@ -343,6 +339,15 @@ fun supportedImage(mimeType: String?): Boolean {
|
||||||
return listOf("image/jpeg", "image/png").contains(mimeType)
|
return listOf("image/jpeg", "image/png").contains(mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Google Play doesn't allow us to install received .apk files anymore.
|
||||||
|
// See https://github.com/binwiederhier/ntfy/issues/531
|
||||||
|
fun canOpenAttachment(attachment: Attachment?): Boolean {
|
||||||
|
if (attachment?.type == androidAppMimeType && !BuildConfig.INSTALL_PACKAGES_AVAILABLE) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Check if battery optimization is enabled, see https://stackoverflow.com/a/49098293/1440785
|
// Check if battery optimization is enabled, see https://stackoverflow.com/a/49098293/1440785
|
||||||
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
|
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
|
||||||
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
@ -494,3 +499,11 @@ fun String.sha256(): String {
|
||||||
val digest = md.digest(this.toByteArray())
|
val digest = md.digest(this.toByteArray())
|
||||||
return digest.fold("") { str, it -> str + "%02x".format(it) }
|
return digest.fold("") { str, it -> str + "%02x".format(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Button.dangerButton(context: Context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
setTextAppearance(R.style.DangerText)
|
||||||
|
} else {
|
||||||
|
setTextColor(ContextCompat.getColor(context, Colors.dangerText(context)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -327,4 +327,6 @@
|
||||||
<string name="detail_settings_about_header">Относно</string>
|
<string name="detail_settings_about_header">Относно</string>
|
||||||
<string name="detail_settings_about_topic_url_title">Адрес на темата</string>
|
<string name="detail_settings_about_topic_url_title">Адрес на темата</string>
|
||||||
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Копирано в междинната памет</string>
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Копирано в междинната памет</string>
|
||||||
|
<string name="main_menu_donate_title">Даряване 💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Ntfy не може да инсталира получени приложения. Вместо това изтеглете чрез браузъра. За подробности вижте дефект №531.</string>
|
||||||
</resources>
|
</resources>
|
|
@ -6,4 +6,43 @@
|
||||||
<string name="channel_notifications_high_name">Notificacions (alta prioritat)</string>
|
<string name="channel_notifications_high_name">Notificacions (alta prioritat)</string>
|
||||||
<string name="channel_notifications_max_name">Notificacions (prioritat màx)</string>
|
<string name="channel_notifications_max_name">Notificacions (prioritat màx)</string>
|
||||||
<string name="channel_subscriber_service_name">Servei Subscripció</string>
|
<string name="channel_subscriber_service_name">Servei Subscripció</string>
|
||||||
|
<string name="main_menu_donate_title">Donar 💸</string>
|
||||||
|
<string name="main_item_status_reconnecting">reconnectant…</string>
|
||||||
|
<string name="channel_subscriber_notification_title">Escoltant notificacions entrants</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text">Subscrit per entrega instantània de temes</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_one">Subscrit per entrega instantània d\'un tema</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_two">Subscrit per dues entregues instantànies de temes</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_three">Subscrit per tres entregues instantànies de temes</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_four">Subscrit per quatre entregues instantànies de temes</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_five">Subscrit per cinc entregues instantànies de temes</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_six">Subscrit per sis entregues instantànies de temes</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_more">Subscrit per %1$d entregues instantànies de temes</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text">Subscrit als temes</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_one">Subscrit a un tema</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_two">Subscrit a dos temes</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_three">Subscrit a tres temes</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_six">Subscrit a sis temes</string>
|
||||||
|
<string name="main_action_mode_delete_dialog_permanently_delete">Eliminat permanentment</string>
|
||||||
|
<string name="main_action_mode_delete_dialog_cancel">Cancel·lar</string>
|
||||||
|
<string name="main_item_status_text_one">%1$d notificació</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_more">Subscrit a %1$d temes</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_four">Subscrit a quatre temes</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_five">Subscrit a cinc temes</string>
|
||||||
|
<string name="refresh_message_result">%1$d notificacions rebudes</string>
|
||||||
|
<string name="refresh_message_no_results">Tot està actualitzat</string>
|
||||||
|
<string name="refresh_message_error">No es poden actualitzar %1$d subscripciones
|
||||||
|
\n
|
||||||
|
\n%2$s</string>
|
||||||
|
<string name="refresh_message_error_one">No s\'ha pogut actualitzar la subscripció: %1$s</string>
|
||||||
|
<string name="main_action_bar_title">Temes subscrits</string>
|
||||||
|
<string name="main_menu_notifications_enabled">Notificacions activades</string>
|
||||||
|
<string name="main_menu_notifications_disabled_forever">Notificacions silenciades</string>
|
||||||
|
<string name="main_menu_notifications_disabled_until">Notificacions silenciades fins: %1$s</string>
|
||||||
|
<string name="main_menu_settings_title">Ajustos</string>
|
||||||
|
<string name="main_menu_report_bug_title">Reportar un problema</string>
|
||||||
|
<string name="main_menu_docs_title">Llegir la documentació</string>
|
||||||
|
<string name="main_menu_rate_title">Valora la aplicació ⭐</string>
|
||||||
|
<string name="main_action_mode_menu_unsubscribe">Donar-se de baixa</string>
|
||||||
|
<string name="main_action_mode_delete_dialog_message">Donar-se de baixa del tema(es) seleccionat(s) permanentment i eliminar totes les notificacions\?</string>
|
||||||
|
<string name="main_item_status_text_not_one">%1$d notificacions</string>
|
||||||
</resources>
|
</resources>
|
|
@ -327,4 +327,6 @@
|
||||||
<string name="main_banner_websocket_button_enable_now">Povolit nyní</string>
|
<string name="main_banner_websocket_button_enable_now">Povolit nyní</string>
|
||||||
<string name="main_banner_websocket_text">WebSockets jsou doporučenou metodou připojení k vašemu serveru, která může zlepšit zvýšit výdrž baterie, ale může vyžadovat <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">další konfiguraci v proxy serveru</a>. Metodu připojení lze přepnout v Nastavení.</string>
|
<string name="main_banner_websocket_text">WebSockets jsou doporučenou metodou připojení k vašemu serveru, která může zlepšit zvýšit výdrž baterie, ale může vyžadovat <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">další konfiguraci v proxy serveru</a>. Metodu připojení lze přepnout v Nastavení.</string>
|
||||||
<string name="add_dialog_base_urls_dropdown_choose">Zvolit URL služby</string>
|
<string name="add_dialog_base_urls_dropdown_choose">Zvolit URL služby</string>
|
||||||
|
<string name="main_menu_donate_title">Přispět 💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Aplikace již nelze nainstalovat. Místo toho stahujte přes prohlížeč. Podrobnosti naleznete v issue #531.</string>
|
||||||
</resources>
|
</resources>
|
|
@ -309,7 +309,7 @@
|
||||||
<string name="detail_settings_appearance_header">Darstellung</string>
|
<string name="detail_settings_appearance_header">Darstellung</string>
|
||||||
<string name="detail_settings_appearance_icon_set_title">Abo-Icon</string>
|
<string name="detail_settings_appearance_icon_set_title">Abo-Icon</string>
|
||||||
<string name="detail_settings_appearance_icon_set_summary">Ein Icon zur Darstellung in Benachrichtigungen auswählen</string>
|
<string name="detail_settings_appearance_icon_set_summary">Ein Icon zur Darstellung in Benachrichtigungen auswählen</string>
|
||||||
<string name="detail_settings_appearance_icon_remove_title">Abo-Icon (entfernen durch antippen)</string>
|
<string name="detail_settings_appearance_icon_remove_title">Abo-Icon (entfernen durch Antippen)</string>
|
||||||
<string name="detail_settings_appearance_icon_error_saving">Kann Icon nicht speichern: %1$s</string>
|
<string name="detail_settings_appearance_icon_error_saving">Kann Icon nicht speichern: %1$s</string>
|
||||||
<string name="detail_settings_global_setting_title">Globale Einstellung verwenden</string>
|
<string name="detail_settings_global_setting_title">Globale Einstellung verwenden</string>
|
||||||
<string name="detail_settings_global_setting_suffix">globale Einstellung</string>
|
<string name="detail_settings_global_setting_suffix">globale Einstellung</string>
|
||||||
|
@ -323,8 +323,10 @@
|
||||||
<string name="add_dialog_base_urls_dropdown_clear">Service-URL löschen</string>
|
<string name="add_dialog_base_urls_dropdown_clear">Service-URL löschen</string>
|
||||||
<string name="detail_settings_appearance_display_name_default_summary">%1$s (Standard)</string>
|
<string name="detail_settings_appearance_display_name_default_summary">%1$s (Standard)</string>
|
||||||
<string name="detail_settings_appearance_display_name_title">Anzeigename</string>
|
<string name="detail_settings_appearance_display_name_title">Anzeigename</string>
|
||||||
<string name="detail_settings_appearance_display_name_message">Gib einen eigenen Anzeigenamen für diese Abo an. Leer lassen für den Standardwert (%1$s).</string>
|
<string name="detail_settings_appearance_display_name_message">Gib einen eigenen Anzeigenamen für dieses Abo an. Leer lassen für den Standardwert (%1$s).</string>
|
||||||
<string name="detail_settings_about_topic_url_title">Themen-URL</string>
|
<string name="detail_settings_about_topic_url_title">Themen-URL</string>
|
||||||
<string name="detail_settings_about_header">Über</string>
|
<string name="detail_settings_about_header">Über</string>
|
||||||
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">In Zwischenablage kopiert</string>
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">In Zwischenablage kopiert</string>
|
||||||
</resources>
|
<string name="main_menu_donate_title">Spenden 💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Apps können nicht mehr installiert werden. Bitte stattdessen über einen Browser herunterladen. Details siehe Issue #531.</string>
|
||||||
|
</resources>
|
||||||
|
|
|
@ -327,4 +327,6 @@
|
||||||
<string name="add_dialog_base_urls_dropdown_clear">Borrar la URL del servicio</string>
|
<string name="add_dialog_base_urls_dropdown_clear">Borrar la URL del servicio</string>
|
||||||
<string name="main_banner_websocket_text">Cambiar a WebSockets es la forma recomendada para conectarse a su servidor, y podría mejorar la vida de la batería, pero puede requerir <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">configuración adicional en su proxy</a>. Esto se puede cambiar en la Configuración.</string>
|
<string name="main_banner_websocket_text">Cambiar a WebSockets es la forma recomendada para conectarse a su servidor, y podría mejorar la vida de la batería, pero puede requerir <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">configuración adicional en su proxy</a>. Esto se puede cambiar en la Configuración.</string>
|
||||||
<string name="main_banner_websocket_button_enable_now">Habilitar ahora</string>
|
<string name="main_banner_websocket_button_enable_now">Habilitar ahora</string>
|
||||||
|
<string name="main_menu_donate_title">Donar 💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Las aplicaciones ya no se pueden instalar desde ntfy. Descárguelas a través del navegador. Consulte el issue #531 para obtener más información.</string>
|
||||||
</resources>
|
</resources>
|
|
@ -6,7 +6,7 @@
|
||||||
<string name="channel_notifications_min_name">Notifikasi (prioritas min)</string>
|
<string name="channel_notifications_min_name">Notifikasi (prioritas min)</string>
|
||||||
<string name="channel_notifications_low_name">Notifikasi (prioritas rendah)</string>
|
<string name="channel_notifications_low_name">Notifikasi (prioritas rendah)</string>
|
||||||
<string name="channel_subscriber_service_name">Layanan Langganan</string>
|
<string name="channel_subscriber_service_name">Layanan Langganan</string>
|
||||||
<string name="channel_subscriber_notification_title">Mendengarkan untuk notifikasi masuk</string>
|
<string name="channel_subscriber_notification_title">Mendengarkan notifikasi masuk</string>
|
||||||
<string name="channel_subscriber_notification_instant_text">Berlangganan ke topik pengiriman instan</string>
|
<string name="channel_subscriber_notification_instant_text">Berlangganan ke topik pengiriman instan</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_one">Berlangganan ke satu topik pengiriman instan</string>
|
<string name="channel_subscriber_notification_instant_text_one">Berlangganan ke satu topik pengiriman instan</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_two">Berlangganan ke dua topik pengiriman instan</string>
|
<string name="channel_subscriber_notification_instant_text_two">Berlangganan ke dua topik pengiriman instan</string>
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
<string name="main_menu_notifications_disabled_forever">Notifikasi dibisukan</string>
|
<string name="main_menu_notifications_disabled_forever">Notifikasi dibisukan</string>
|
||||||
<string name="main_menu_notifications_disabled_until">Notifikasi dibisukan sampai %1$s</string>
|
<string name="main_menu_notifications_disabled_until">Notifikasi dibisukan sampai %1$s</string>
|
||||||
<string name="main_menu_settings_title">Pengaturan</string>
|
<string name="main_menu_settings_title">Pengaturan</string>
|
||||||
<string name="main_menu_report_bug_title">Laporkan sebuah bug</string>
|
<string name="main_menu_report_bug_title">Laporkan kutu</string>
|
||||||
<string name="main_menu_docs_title">Baca dokumentasi</string>
|
<string name="main_menu_docs_title">Baca dokumentasi</string>
|
||||||
<string name="main_menu_rate_title">Beri nilai aplikasi ⭐</string>
|
<string name="main_menu_rate_title">Beri nilai aplikasi ⭐</string>
|
||||||
<string name="main_action_mode_menu_unsubscribe">Batalkan langganan</string>
|
<string name="main_action_mode_menu_unsubscribe">Batalkan langganan</string>
|
||||||
|
@ -327,4 +327,6 @@
|
||||||
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Disalin ke papan klip</string>
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Disalin ke papan klip</string>
|
||||||
<string name="detail_settings_about_header">Tentang</string>
|
<string name="detail_settings_about_header">Tentang</string>
|
||||||
<string name="detail_settings_about_topic_url_title">URL Topik</string>
|
<string name="detail_settings_about_topic_url_title">URL Topik</string>
|
||||||
|
<string name="main_menu_donate_title">Donasi 💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Aplikasi tidak dapat dipasang lagi. Unduh melalui peramban. Lihat masalah #531 untuk detail lebih lanjut.</string>
|
||||||
</resources>
|
</resources>
|
|
@ -15,7 +15,7 @@
|
||||||
<string name="main_action_mode_delete_dialog_permanently_delete">מחק/י לצמיתות</string>
|
<string name="main_action_mode_delete_dialog_permanently_delete">מחק/י לצמיתות</string>
|
||||||
<string name="main_action_mode_delete_dialog_cancel">ביטול</string>
|
<string name="main_action_mode_delete_dialog_cancel">ביטול</string>
|
||||||
<string name="main_item_status_text_one">התראת %1$d</string>
|
<string name="main_item_status_text_one">התראת %1$d</string>
|
||||||
<string name="main_item_status_text_not_one">התראות %1$d</string>
|
<string name="main_item_status_text_not_one">%1$d התראות</string>
|
||||||
<string name="main_item_date_yesterday">אתמול</string>
|
<string name="main_item_date_yesterday">אתמול</string>
|
||||||
<string name="main_add_button_description">הוספת רישום</string>
|
<string name="main_add_button_description">הוספת רישום</string>
|
||||||
<string name="main_no_subscriptions_text">נראה שלא נרשמת לאף נושא עדיין.</string>
|
<string name="main_no_subscriptions_text">נראה שלא נרשמת לאף נושא עדיין.</string>
|
||||||
|
@ -39,4 +39,20 @@
|
||||||
<string name="main_item_status_reconnecting">מתחבר מחדש…</string>
|
<string name="main_item_status_reconnecting">מתחבר מחדש…</string>
|
||||||
<string name="main_how_to_intro">לחצ\\י על + על מנת ליצור או להירשם אל מול נושא מסוים. לאחר מכן תקבל\\י התראות במכשירך כשתשלח\\י התראות דרך PUT או POST.</string>
|
<string name="main_how_to_intro">לחצ\\י על + על מנת ליצור או להירשם אל מול נושא מסוים. לאחר מכן תקבל\\י התראות במכשירך כשתשלח\\י התראות דרך PUT או POST.</string>
|
||||||
<string name="main_how_to_link">הוראות מפורטות זמינות ב-ntfy.sh, ובדוקומנטציה.</string>
|
<string name="main_how_to_link">הוראות מפורטות זמינות ב-ntfy.sh, ובדוקומנטציה.</string>
|
||||||
</resources>
|
<string name="main_menu_donate_title">תרום 💸</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_two">רשום לשני נושאים במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_three">רשום לשלושה נושאים במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text">רשום לנושאים במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_one">רשום לנושא אחד במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_four">רשום לארבעה נושאים במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_five">רשום לחמישה נושאים במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_six">רשום לשישה נושאים במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_instant_text_more">רשום ל%1$d נושאים במשלוח מהיר</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text">רשום לנושאים</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_one">רשום לנושא אחד</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_two">רשום לשני נושאים</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_three">רשום לשלושה נושאים</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_four">רשום לארבעה נושאים</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_five">רשום לחמישה נושאים</string>
|
||||||
|
<string name="channel_subscriber_notification_noinstant_text_six">רשום לשישה נושאים</string>
|
||||||
|
</resources>
|
|
@ -327,4 +327,6 @@
|
||||||
<string name="detail_settings_about_header">About</string>
|
<string name="detail_settings_about_header">About</string>
|
||||||
<string name="detail_settings_about_topic_url_title">トピックのURL</string>
|
<string name="detail_settings_about_topic_url_title">トピックのURL</string>
|
||||||
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">クリップボードにコピーしました</string>
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">クリップボードにコピーしました</string>
|
||||||
|
<string name="main_menu_donate_title">寄付する💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">アプリはインストールできなくなりました。代替手段としてブラウザからダウンロードしてください。詳細は issue #531 をご参照ください。</string>
|
||||||
</resources>
|
</resources>
|
|
@ -77,7 +77,7 @@
|
||||||
<string name="detail_item_tags">Tags: %1$s</string>
|
<string name="detail_item_tags">Tags: %1$s</string>
|
||||||
<string name="detail_item_snack_deleted">Notificação deletada</string>
|
<string name="detail_item_snack_deleted">Notificação deletada</string>
|
||||||
<string name="detail_item_snack_undo">Desfazer</string>
|
<string name="detail_item_snack_undo">Desfazer</string>
|
||||||
<string name="detail_item_menu_download">Fazer download do arquivo</string>
|
<string name="detail_item_menu_download">Baixar arquivo</string>
|
||||||
<string name="detail_item_menu_cancel">Cancelar o download</string>
|
<string name="detail_item_menu_cancel">Cancelar o download</string>
|
||||||
<string name="detail_item_cannot_open">Não foi possível abrir o anexo: %1$s</string>
|
<string name="detail_item_cannot_open">Não foi possível abrir o anexo: %1$s</string>
|
||||||
<string name="detail_item_cannot_open_not_found">Não foi possível abrir o anexo: O arquivo pode ter sido deletado, ou não existe app instalado que consiga abrir o arquivo.</string>
|
<string name="detail_item_cannot_open_not_found">Não foi possível abrir o anexo: O arquivo pode ter sido deletado, ou não existe app instalado que consiga abrir o arquivo.</string>
|
||||||
|
@ -258,4 +258,74 @@
|
||||||
<string name="settings_general_dark_mode_summary_light">Modo claro</string>
|
<string name="settings_general_dark_mode_summary_light">Modo claro</string>
|
||||||
<string name="settings_general_dark_mode_title">Modo escuro</string>
|
<string name="settings_general_dark_mode_title">Modo escuro</string>
|
||||||
<string name="settings_general_dark_mode_summary_system">Usar o padrão do sistema</string>
|
<string name="settings_general_dark_mode_summary_system">Usar o padrão do sistema</string>
|
||||||
|
<string name="main_menu_donate_title">Doar 💸</string>
|
||||||
|
<string name="settings_backup_restore_restore_failed">Recuperação falhou %1$s</string>
|
||||||
|
<string name="settings_advanced_header">Avançado</string>
|
||||||
|
<string name="settings_advanced_broadcast_title">Messagens de broadcast</string>
|
||||||
|
<string name="settings_advanced_broadcast_summary_disabled">Os aplicativos não podem receber notificações por broadcast</string>
|
||||||
|
<string name="settings_advanced_broadcast_summary_enabled">Os aplicativos já podem receber notificações por broadcast</string>
|
||||||
|
<string name="settings_advanced_export_logs_entry_copy_original">Copiar para área de transferência</string>
|
||||||
|
<string name="settings_advanced_export_logs_entry_copy_scrubbed">Copiar para área de transferência (censurado)</string>
|
||||||
|
<string name="settings_advanced_export_logs_entry_upload_original">Carregar e copiar link</string>
|
||||||
|
<string name="settings_advanced_export_logs_uploading">Carregando logs …</string>
|
||||||
|
<string name="settings_advanced_export_logs_copied_url">Logs enviados e URL copiada</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_summary_jsonhttp">Use um stream de JSON através de HTTP para se conectar ao servidor. Este método foi testado na pratica, mas pode consumir mais bateria.</string>
|
||||||
|
<string name="settings_about_version_format">ntfy %1$s (%2$s)</string>
|
||||||
|
<string name="detail_settings_notifications_instant_summary_off">As notificações são entregues utilizando o Firebase. A entrega pode atrasar, mas consome menos bateria.</string>
|
||||||
|
<string name="detail_settings_appearance_header">Aparência</string>
|
||||||
|
<string name="detail_settings_appearance_icon_set_title">Ícone de assinatura</string>
|
||||||
|
<string name="detail_settings_appearance_icon_set_summary">Defina um ícone para ser exibido nas notificações</string>
|
||||||
|
<string name="detail_settings_appearance_icon_remove_title">Ícone de assinatura (toque para remover)</string>
|
||||||
|
<string name="detail_settings_appearance_display_name_message">Defina um nome de exibição personalizado para esta assinatura. Deixe em branco para o padrão (%1$s).</string>
|
||||||
|
<string name="detail_settings_appearance_display_name_default_summary">%1$s (padrão)</string>
|
||||||
|
<string name="detail_settings_global_setting_title">Usar configuração global</string>
|
||||||
|
<string name="detail_settings_global_setting_suffix">usar configuração global</string>
|
||||||
|
<string name="detail_settings_about_header">Sobre</string>
|
||||||
|
<string name="detail_settings_about_topic_url_title">URL do tópico</string>
|
||||||
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Copiado para a área de transferência</string>
|
||||||
|
<string name="user_dialog_title_add">Adicionar usuário</string>
|
||||||
|
<string name="settings_backup_restore_restore_title">Recuperar do arquivo</string>
|
||||||
|
<string name="settings_backup_restore_restore_summary">Importar configurações, notificações e usuários</string>
|
||||||
|
<string name="settings_backup_restore_restore_successful">Restauração concluída</string>
|
||||||
|
<string name="settings_advanced_record_logs_summary_enabled">Registros (ate 1000 registros) para o dispositivo …</string>
|
||||||
|
<string name="settings_advanced_record_logs_summary_disabled">Ative o log para que seja possível compartilhar mais tarde os registros para diagnostico.</string>
|
||||||
|
<string name="settings_advanced_record_logs_title">Logs de registros</string>
|
||||||
|
<string name="settings_advanced_export_logs_title">Copiar/carregar logs</string>
|
||||||
|
<string name="settings_advanced_export_logs_summary">Copie os logs para a área de transferência ou faça o upload para nopaste.net (propriedade do autor do ntfy). Hostnames e tópicos podem ser censurados, as notificações não.</string>
|
||||||
|
<string name="settings_advanced_export_logs_entry_upload_scrubbed">Carregar e copiar link (censurado)</string>
|
||||||
|
<string name="settings_advanced_export_logs_copied_logs">Logs copiado para área de transferência</string>
|
||||||
|
<string name="settings_advanced_export_logs_scrub_dialog_text">Esses tópicos/hostnames foram substituídos por nomes de frutas, então você pode compartilhar o log sem se preocupar:
|
||||||
|
\n
|
||||||
|
\n%1$s
|
||||||
|
\n
|
||||||
|
\nAs senhas são retiradas e não são listadas aqui.</string>
|
||||||
|
<string name="settings_advanced_export_logs_error_uploading">Não foi possível carregar os logs: %1$s</string>
|
||||||
|
<string name="settings_advanced_export_logs_scrub_dialog_button_ok">OK</string>
|
||||||
|
<string name="settings_advanced_export_logs_scrub_dialog_empty">Nenhum tópico/hostname foi editado. Talvez você não tenha nenhuma assinatura\?</string>
|
||||||
|
<string name="settings_advanced_clear_logs_title">Limpar logs</string>
|
||||||
|
<string name="settings_advanced_clear_logs_deleted_toast">Logs excluídos</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_title">Protocolo de conexão</string>
|
||||||
|
<string name="settings_advanced_clear_logs_summary">Exclua os logs gravados anteriormente e comece de novo</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_summary_ws">Use WebSockets para se conectar ao servidor. Este é o método recomendado, mas pode exigir configuração adicional em seu proxy.</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_entry_jsonhttp">Stream JSON através de HTTP</string>
|
||||||
|
<string name="settings_about_header">Sobre</string>
|
||||||
|
<string name="settings_about_version_title">Versão</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_entry_ws">Web Sockets</string>
|
||||||
|
<string name="detail_settings_notifications_instant_title">Entrega instantânea</string>
|
||||||
|
<string name="detail_settings_notifications_instant_summary_on">As notificações são entregues instantaneamente. Requer um serviço que roda em primeiro plano e consome mais bateria.</string>
|
||||||
|
<string name="settings_about_version_copied_to_clipboard_message">Copiado para área de transferência</string>
|
||||||
|
<string name="detail_settings_appearance_icon_error_saving">Não foi possível salvar o ícone: %1$s</string>
|
||||||
|
<string name="detail_settings_appearance_icon_remove_summary">Ícone exibido nas notificações deste tópico</string>
|
||||||
|
<string name="detail_settings_appearance_display_name_title">Nome de exibição</string>
|
||||||
|
<string name="user_dialog_description_edit">Você pode editar o nome de usuário/senha do usuário selecionado ou excluí-lo.</string>
|
||||||
|
<string name="user_dialog_base_url_hint">URL do serviço</string>
|
||||||
|
<string name="user_dialog_title_edit">Editar usuário</string>
|
||||||
|
<string name="user_dialog_description_add">Você pode adicionar um usuário aqui. Todos os tópicos para o servidor fornecido usarão esse usuário.</string>
|
||||||
|
<string name="user_dialog_username_hint">Nome do usuário</string>
|
||||||
|
<string name="user_dialog_password_hint_add">Senha</string>
|
||||||
|
<string name="user_dialog_password_hint_edit">Senha (se deixada em branco não será alterada)</string>
|
||||||
|
<string name="user_dialog_button_cancel">Cancelar</string>
|
||||||
|
<string name="user_dialog_button_add">Adicionar usuário</string>
|
||||||
|
<string name="user_dialog_button_delete">Deletar usuário</string>
|
||||||
|
<string name="user_dialog_button_save">Salvar</string>
|
||||||
</resources>
|
</resources>
|
|
@ -99,4 +99,28 @@
|
||||||
<string name="add_dialog_login_username_hint">Användarnamn</string>
|
<string name="add_dialog_login_username_hint">Användarnamn</string>
|
||||||
<string name="add_dialog_login_password_hint">Lösenord</string>
|
<string name="add_dialog_login_password_hint">Lösenord</string>
|
||||||
<string name="detail_test_message_error_unauthorized_anon">Kan inte skicka meddelande: Anonym publicering är inte tillåten.</string>
|
<string name="detail_test_message_error_unauthorized_anon">Kan inte skicka meddelande: Anonym publicering är inte tillåten.</string>
|
||||||
|
<string name="detail_item_snack_deleted">Notifikation borttagen</string>
|
||||||
|
<string name="detail_item_menu_copy_url_copied">URL kopierad till urklipp</string>
|
||||||
|
<string name="detail_item_menu_copy_contents">Kopiera notifikation</string>
|
||||||
|
<string name="detail_item_menu_copy_contents_copied">Notifikation kopierad till urklipp</string>
|
||||||
|
<string name="detail_item_cannot_open_url">Kan inte öppna URL: %1$s</string>
|
||||||
|
<string name="detail_menu_clear">Rensa alla notifikationer</string>
|
||||||
|
<string name="detail_menu_test">Skicka testnotifikation</string>
|
||||||
|
<string name="detail_action_mode_menu_copy">Kopiera</string>
|
||||||
|
<string name="detail_action_mode_menu_delete">Ta bort</string>
|
||||||
|
<string name="detail_action_mode_delete_dialog_permanently_delete">Ta bort permanent</string>
|
||||||
|
<string name="detail_action_mode_delete_dialog_cancel">Avbryt</string>
|
||||||
|
<string name="detail_settings_title">Prenumerationsinställningar</string>
|
||||||
|
<string name="share_title">Dela</string>
|
||||||
|
<string name="share_menu_send">Dela</string>
|
||||||
|
<string name="main_menu_donate_title">Donera 💸</string>
|
||||||
|
<string name="detail_item_snack_undo">Ångra</string>
|
||||||
|
<string name="detail_item_menu_open">Öppna fil</string>
|
||||||
|
<string name="detail_item_menu_delete">Ta bort fil</string>
|
||||||
|
<string name="detail_item_menu_download">Ladda ner fil</string>
|
||||||
|
<string name="detail_item_menu_cancel">Avbryt nedladdning</string>
|
||||||
|
<string name="detail_item_menu_save_file">Spara fil</string>
|
||||||
|
<string name="detail_item_menu_copy_url">Kopiera URL</string>
|
||||||
|
<string name="detail_item_download_info_download_failed">nedladdning misslyckad</string>
|
||||||
|
<string name="detail_menu_settings">Prenumerationsinställningar</string>
|
||||||
</resources>
|
</resources>
|
|
@ -327,4 +327,6 @@
|
||||||
<string name="detail_settings_about_header">Hakkında</string>
|
<string name="detail_settings_about_header">Hakkında</string>
|
||||||
<string name="detail_settings_about_topic_url_title">Konu URL\'si</string>
|
<string name="detail_settings_about_topic_url_title">Konu URL\'si</string>
|
||||||
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Panoya kopyalandı</string>
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Panoya kopyalandı</string>
|
||||||
|
<string name="main_menu_donate_title">Bağış yap 💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Uygulamalar artık kurulamıyor. Bunun yerine tarayıcı üzerinden indirin. Ayrıntılar için sorun #531\'e bakın.</string>
|
||||||
</resources>
|
</resources>
|
|
@ -327,4 +327,6 @@
|
||||||
<string name="detail_settings_about_header">关于</string>
|
<string name="detail_settings_about_header">关于</string>
|
||||||
<string name="detail_settings_about_topic_url_title">话题 URL</string>
|
<string name="detail_settings_about_topic_url_title">话题 URL</string>
|
||||||
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">已复制到剪贴板</string>
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">已复制到剪贴板</string>
|
||||||
|
<string name="main_menu_donate_title">捐赠 💸</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">无法再安装应用。 请通过浏览器下载。 有关详细信息,请参阅问题 #531。</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1,23 +1,23 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="channel_subscriber_notification_noinstant_text_six">已訂閱6個主題</string>
|
<string name="channel_subscriber_notification_noinstant_text_six">已訂閱 6 個主題</string>
|
||||||
<string name="channel_notifications_default_name">通知(預設優先)</string>
|
<string name="channel_notifications_default_name">通知 (預設優先)</string>
|
||||||
<string name="main_menu_report_bug_title">回報bug</string>
|
<string name="main_menu_report_bug_title">問題回報</string>
|
||||||
<string name="channel_subscriber_notification_title">監聽傳入通知</string>
|
<string name="channel_subscriber_notification_title">正在接收通知</string>
|
||||||
<string name="channel_subscriber_notification_instant_text">已訂閱即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text">已訂閱即時推送主題</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_one">已訂閱1個即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text_one">已訂閱 1 個即時推送主題</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_two">已訂閱2個即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text_two">已訂閱 2 個即時推送主題</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_four">已訂閱4個即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text_four">已訂閱 4 個即時推送主題</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_five">已訂閱5個即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text_five">已訂閱 5 個即時推送主題</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_six">已訂閱6個即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text_six">已訂閱 6 個即時推送主題</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_more">已訂閱%1$d個即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text_more">已訂閱 %1$d 個即時推送主題</string>
|
||||||
<string name="channel_subscriber_notification_noinstant_text">已訂閱主題</string>
|
<string name="channel_subscriber_notification_noinstant_text">已訂閱主題</string>
|
||||||
<string name="refresh_message_result">收到%1$d個通知</string>
|
<string name="refresh_message_result">收到 %1$d 個通知</string>
|
||||||
<string name="refresh_message_no_results">已同步到最新</string>
|
<string name="refresh_message_no_results">已同步到最新</string>
|
||||||
<string name="refresh_message_error">有%1$d個訂閱無法更新
|
<string name="refresh_message_error">有 %1$d 個訂閱無法更新
|
||||||
\n
|
\n
|
||||||
\n%2$s</string>
|
\n%2$s</string>
|
||||||
<string name="refresh_message_error_one">訂閱無法更新:%1$s</string>
|
<string name="refresh_message_error_one">訂閱無法更新: %1$s</string>
|
||||||
<string name="main_action_bar_title">已訂閱主題</string>
|
<string name="main_action_bar_title">已訂閱主題</string>
|
||||||
<string name="main_menu_notifications_enabled">通知已開啟</string>
|
<string name="main_menu_notifications_enabled">通知已開啟</string>
|
||||||
<string name="main_menu_notifications_disabled_forever">通知已靜音</string>
|
<string name="main_menu_notifications_disabled_forever">通知已靜音</string>
|
||||||
|
@ -27,8 +27,8 @@
|
||||||
<string name="main_action_mode_delete_dialog_message">取消訂閱已選取的主題且永久刪除所有通知?</string>
|
<string name="main_action_mode_delete_dialog_message">取消訂閱已選取的主題且永久刪除所有通知?</string>
|
||||||
<string name="main_action_mode_delete_dialog_permanently_delete">永久刪除</string>
|
<string name="main_action_mode_delete_dialog_permanently_delete">永久刪除</string>
|
||||||
<string name="main_action_mode_delete_dialog_cancel">取消</string>
|
<string name="main_action_mode_delete_dialog_cancel">取消</string>
|
||||||
<string name="main_item_status_text_one">%1$d個通知</string>
|
<string name="main_item_status_text_one">%1$d 個通知</string>
|
||||||
<string name="main_item_status_text_not_one">%1$d個通知</string>
|
<string name="main_item_status_text_not_one">%1$d 個通知</string>
|
||||||
<string name="main_item_date_yesterday">昨天</string>
|
<string name="main_item_date_yesterday">昨天</string>
|
||||||
<string name="main_add_button_description">新增訂閱</string>
|
<string name="main_add_button_description">新增訂閱</string>
|
||||||
<string name="main_no_subscriptions_text">看來你還沒有訂閱任何主題。</string>
|
<string name="main_no_subscriptions_text">看來你還沒有訂閱任何主題。</string>
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
<string name="main_how_to_link">更多資訊請上 ntfy.sh,docs會有更多說明。</string>
|
<string name="main_how_to_link">更多資訊請上 ntfy.sh,docs會有更多說明。</string>
|
||||||
<string name="main_unified_push_toast">此訂閱已由 %1$s 透過 UnifiedPush 管理</string>
|
<string name="main_unified_push_toast">此訂閱已由 %1$s 透過 UnifiedPush 管理</string>
|
||||||
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
|
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
|
||||||
<string name="main_banner_battery_text">為了避免通知傳送問題,電池最佳化必須關閉。</string>
|
<string name="main_banner_battery_text">為了避免通知傳送問題,請務必關閉電池最佳化功能。</string>
|
||||||
<string name="main_banner_battery_button_remind_later">稍後詢問我</string>
|
<string name="main_banner_battery_button_remind_later">稍後詢問我</string>
|
||||||
<string name="main_banner_battery_button_dismiss">略過</string>
|
<string name="main_banner_battery_button_dismiss">略過</string>
|
||||||
<string name="main_banner_battery_button_fix_now">立即修正</string>
|
<string name="main_banner_battery_button_fix_now">立即修正</string>
|
||||||
|
@ -47,35 +47,147 @@
|
||||||
<string name="add_dialog_description_below">因為主題不能受密碼保護,請盡量取一個難以猜測的主題名稱。在訂閱之後你就可以使用 PUT/POST 來發送通知。</string>
|
<string name="add_dialog_description_below">因為主題不能受密碼保護,請盡量取一個難以猜測的主題名稱。在訂閱之後你就可以使用 PUT/POST 來發送通知。</string>
|
||||||
<string name="add_dialog_use_another_server">使用其他伺服器</string>
|
<string name="add_dialog_use_another_server">使用其他伺服器</string>
|
||||||
<string name="add_dialog_use_another_server_description">在下方輸入自訂的網址來訂閱主題。</string>
|
<string name="add_dialog_use_another_server_description">在下方輸入自訂的網址來訂閱主題。</string>
|
||||||
<string name="channel_notifications_min_name">通知(最低優先)</string>
|
<string name="channel_notifications_min_name">通知 (最低優先)</string>
|
||||||
<string name="channel_notifications_low_name">通知(低優先)</string>
|
<string name="channel_notifications_low_name">通知 (低優先)</string>
|
||||||
<string name="channel_notifications_high_name">通知(高優先)</string>
|
<string name="channel_notifications_high_name">通知 (高優先)</string>
|
||||||
<string name="channel_notifications_max_name">通知(最高優先)</string>
|
<string name="channel_notifications_max_name">通知 (最高優先)</string>
|
||||||
<string name="channel_subscriber_service_name">訂閱服務</string>
|
<string name="channel_subscriber_service_name">訂閱服務</string>
|
||||||
<string name="channel_subscriber_notification_instant_text_three">已訂閱3個即時推送主題</string>
|
<string name="channel_subscriber_notification_instant_text_three">已訂閱 3 個即時推送主題</string>
|
||||||
<string name="main_menu_docs_title">閱讀文件</string>
|
<string name="main_menu_docs_title">閱讀技術文件</string>
|
||||||
<string name="main_banner_websocket_text">建議使用 WebSockets 來連線你的伺服器,此動作可以有效增加電池續航,但需要<a href="https://ntfy.sh/docs/config/#nginxapache2caddy">對proxy進行更多設定</a>。這個動作可以在設定中進行。</string>
|
<string name="main_banner_websocket_text">建議使用 WebSocket 來接收通知。這個連接方式能有效改善電池續航,但可能需要<a href="https://ntfy.sh/docs/config/#nginxapache2caddy">在伺服器端進行額外設定</a>。你可以在設定頁面更改不同的連接方式。</string>
|
||||||
<string name="channel_subscriber_notification_noinstant_text_three">已訂閱3個主題</string>
|
<string name="channel_subscriber_notification_noinstant_text_three">已訂閱 3 個主題</string>
|
||||||
<string name="channel_subscriber_notification_noinstant_text_more">已訂閱%1$d個主題</string>
|
<string name="channel_subscriber_notification_noinstant_text_more">已訂閱 %1$d 個主題</string>
|
||||||
<string name="channel_subscriber_notification_noinstant_text_one">已訂閱1個主題</string>
|
<string name="channel_subscriber_notification_noinstant_text_one">已訂閱 1 個主題</string>
|
||||||
<string name="channel_subscriber_notification_noinstant_text_two">已訂閱2個主題</string>
|
<string name="channel_subscriber_notification_noinstant_text_two">已訂閱 2 個主題</string>
|
||||||
<string name="channel_subscriber_notification_noinstant_text_four">已訂閱4個主題</string>
|
<string name="channel_subscriber_notification_noinstant_text_four">已訂閱 4 個主題</string>
|
||||||
<string name="channel_subscriber_notification_noinstant_text_five">已訂閱5個主題</string>
|
<string name="channel_subscriber_notification_noinstant_text_five">已訂閱 5 個主題</string>
|
||||||
<string name="main_item_status_reconnecting">重新連線中 …</string>
|
<string name="main_item_status_reconnecting">重新連線中…</string>
|
||||||
<string name="main_menu_notifications_disabled_until">通知靜音到%1$s</string>
|
<string name="main_menu_notifications_disabled_until">通知靜音到 %1$s</string>
|
||||||
<string name="add_dialog_topic_name_hint">主題名稱(例如:phils_alerts)</string>
|
<string name="add_dialog_topic_name_hint">主題名稱 (例如:phils_alerts)</string>
|
||||||
<string name="add_dialog_button_cancel">取消</string>
|
<string name="add_dialog_button_cancel">取消</string>
|
||||||
<string name="add_dialog_button_subscribe">訂閱</string>
|
<string name="add_dialog_button_subscribe">訂閱</string>
|
||||||
<string name="add_dialog_button_back">退回</string>
|
<string name="add_dialog_button_back">返回</string>
|
||||||
<string name="add_dialog_error_connection_failed">連接失敗: %1$s</string>
|
<string name="add_dialog_error_connection_failed">連接失敗: %1$s</string>
|
||||||
<string name="add_dialog_login_title">需要登入</string>
|
<string name="add_dialog_login_title">需要登入</string>
|
||||||
<string name="add_dialog_login_username_hint">使用者名字</string>
|
<string name="add_dialog_login_username_hint">使用者名稱</string>
|
||||||
<string name="add_dialog_login_password_hint">密碼</string>
|
<string name="add_dialog_login_password_hint">密碼</string>
|
||||||
<string name="add_dialog_login_error_not_authorized">登入失敗. 使用者%1$s 未獲得授權.</string>
|
<string name="add_dialog_login_error_not_authorized">登入失敗,使用者 %1$s 並未授權訂閱這個主題。</string>
|
||||||
<string name="add_dialog_login_new_user">新使用者</string>
|
<string name="add_dialog_login_new_user">建立新使用者</string>
|
||||||
<string name="add_dialog_base_urls_dropdown_choose">選擇服務網址</string>
|
<string name="add_dialog_base_urls_dropdown_choose">選擇 ntfy 服務 URL</string>
|
||||||
<string name="add_dialog_base_urls_dropdown_clear">清除服務網址</string>
|
<string name="add_dialog_base_urls_dropdown_clear">清除 URL</string>
|
||||||
<string name="detail_copied_to_clipboard_message">複製到剪貼簿</string>
|
<string name="detail_copied_to_clipboard_message">已複製到剪貼簿</string>
|
||||||
<string name="add_dialog_button_login">登入</string>
|
<string name="add_dialog_button_login">登入</string>
|
||||||
<string name="add_dialog_login_description">這篇主題要求登入. 請輸入帳號密碼.</string>
|
<string name="add_dialog_login_description">這個主題需要登入,請先輸入帳號密碼。</string>
|
||||||
|
<string name="add_dialog_instant_delivery">在省電模式下依然接收即時通知</string>
|
||||||
|
<string name="add_dialog_instant_delivery_description">確保通知能在未使用裝置時都能接收。</string>
|
||||||
|
<string name="detail_clear_dialog_cancel">取消</string>
|
||||||
|
<string name="detail_delete_dialog_cancel">取消</string>
|
||||||
|
<string name="detail_action_mode_delete_dialog_cancel">取消</string>
|
||||||
|
<string name="share_title">分享</string>
|
||||||
|
<string name="share_topic_title">分享到</string>
|
||||||
|
<string name="settings_notifications_priority_low">低</string>
|
||||||
|
<string name="settings_notifications_priority_default">預設</string>
|
||||||
|
<string name="settings_notifications_priority_high">高</string>
|
||||||
|
<string name="settings_notifications_priority_max">最高</string>
|
||||||
|
<string name="settings_notifications_auto_delete_summary_one_month">自動刪除 1 個月前的通知</string>
|
||||||
|
<string name="settings_notifications_auto_delete_one_day">1 天後</string>
|
||||||
|
<string name="settings_notifications_auto_delete_three_days">3 天後</string>
|
||||||
|
<string name="settings_notifications_auto_delete_one_week">1 週後</string>
|
||||||
|
<string name="settings_notifications_auto_delete_one_month">1 個月後</string>
|
||||||
|
<string name="settings_notifications_auto_delete_three_months">3 個月後</string>
|
||||||
|
<string name="settings_general_header">一般</string>
|
||||||
|
<string name="settings_general_default_base_url_title">預設伺服器</string>
|
||||||
|
<string name="settings_general_default_base_url_default_summary">%1$s (預設)</string>
|
||||||
|
<string name="settings_general_users_title">管理使用者</string>
|
||||||
|
<string name="settings_backup_restore_header">備份與還原</string>
|
||||||
|
<string name="settings_about_version_copied_to_clipboard_message">已複製到剪貼簿</string>
|
||||||
|
<string name="settings_about_header">關於</string>
|
||||||
|
<string name="settings_about_version_title">版本號</string>
|
||||||
|
<string name="settings_about_version_format">ntfy %1$s (%2$s)</string>
|
||||||
|
<string name="detail_settings_appearance_header">主題</string>
|
||||||
|
<string name="detail_settings_notifications_instant_title">即時通知</string>
|
||||||
|
<string name="detail_settings_global_setting_suffix">使用全域設定</string>
|
||||||
|
<string name="user_dialog_title_add">新增使用者</string>
|
||||||
|
<string name="main_menu_donate_title">捐獻 💸</string>
|
||||||
|
<string name="detail_item_snack_undo">復原</string>
|
||||||
|
<string name="detail_item_download_info_downloading_x_percent">已下載 %1$d%%</string>
|
||||||
|
<string name="detail_menu_enable_instant">啓用即時通知</string>
|
||||||
|
<string name="detail_menu_disable_instant">關閉即時通知</string>
|
||||||
|
<string name="detail_menu_clear">清除所有通知</string>
|
||||||
|
<string name="detail_action_mode_menu_copy">複製</string>
|
||||||
|
<string name="detail_action_mode_menu_delete">刪除</string>
|
||||||
|
<string name="share_menu_send">傳送</string>
|
||||||
|
<string name="share_content_title">預覽</string>
|
||||||
|
<string name="notification_dialog_cancel">取消</string>
|
||||||
|
<string name="notification_dialog_save">儲存</string>
|
||||||
|
<string name="notification_dialog_30min">30 分鐘</string>
|
||||||
|
<string name="notification_dialog_1h">1 小時</string>
|
||||||
|
<string name="notification_dialog_2h">2 小時</string>
|
||||||
|
<string name="notification_dialog_8h">8 小時</string>
|
||||||
|
<string name="notification_dialog_tomorrow">直到明天</string>
|
||||||
|
<string name="notification_dialog_forever">直到我解除為止</string>
|
||||||
|
<string name="notification_popup_action_open">開啓</string>
|
||||||
|
<string name="notification_popup_action_browse">瀏覽</string>
|
||||||
|
<string name="notification_popup_action_download">下載</string>
|
||||||
|
<string name="notification_popup_action_cancel">取消</string>
|
||||||
|
<string name="notification_popup_file">%1$s
|
||||||
|
\n檔案: %2$s</string>
|
||||||
|
<string name="notification_popup_file_downloading">下載中 %1$s, %2$d%%
|
||||||
|
\n%3$s</string>
|
||||||
|
<string name="notification_popup_file_download_successful">%1$s
|
||||||
|
\n檔案下載已完成: %2$s</string>
|
||||||
|
<string name="notification_popup_file_download_failed">%1$s
|
||||||
|
\n檔案下載失敗: %2$s</string>
|
||||||
|
<string name="notification_popup_user_action_failed">%1$s 失敗: %2$s</string>
|
||||||
|
<string name="settings_title">設定</string>
|
||||||
|
<string name="settings_notifications_muted_until_show_all">顯示所有通知</string>
|
||||||
|
<string name="settings_notifications_priority_min">分鐘</string>
|
||||||
|
<string name="settings_notifications_auto_download_title">下載附件</string>
|
||||||
|
<string name="settings_notifications_auto_download_summary_always">自動下載所有附件</string>
|
||||||
|
<string name="settings_notifications_auto_download_summary_never">從不自動下載附件</string>
|
||||||
|
<string name="settings_notifications_auto_download_summary_smaller_than_x">自動下載小於 %1$s 的附件</string>
|
||||||
|
<string name="settings_notifications_auto_download_never">從不自動下載</string>
|
||||||
|
<string name="settings_notifications_auto_download_always">自動下載所有東西</string>
|
||||||
|
<string name="settings_notifications_auto_download_500k">小於 500 kB</string>
|
||||||
|
<string name="settings_notifications_auto_download_100k">小於 100 kB</string>
|
||||||
|
<string name="settings_notifications_auto_download_1m">小於 1 MB</string>
|
||||||
|
<string name="settings_notifications_auto_download_5m">小於 5 MB</string>
|
||||||
|
<string name="settings_notifications_auto_download_50m">小於 50 MB</string>
|
||||||
|
<string name="settings_notifications_auto_delete_title">自動刪除通知</string>
|
||||||
|
<string name="settings_notifications_auto_delete_summary_never">從不自動刪除通知</string>
|
||||||
|
<string name="settings_notifications_auto_delete_summary_one_day">自動刪除 1 天前的通知</string>
|
||||||
|
<string name="settings_notifications_auto_delete_summary_three_days">自動刪除 3 天前的通知</string>
|
||||||
|
<string name="settings_notifications_auto_delete_summary_one_week">自動刪除 1 週前的通知</string>
|
||||||
|
<string name="settings_notifications_auto_delete_summary_three_months">自動刪除 3 個月前的通知</string>
|
||||||
|
<string name="settings_notifications_auto_delete_never">從不</string>
|
||||||
|
<string name="settings_general_users_prefs_title">使用者</string>
|
||||||
|
<string name="settings_general_users_prefs_user_add">新增使用者</string>
|
||||||
|
<string name="settings_general_users_prefs_user_add_title">建立新使用者</string>
|
||||||
|
<string name="settings_general_dark_mode_title">黑暗模式</string>
|
||||||
|
<string name="settings_backup_restore_backup_entry_everything">全部</string>
|
||||||
|
<string name="settings_backup_restore_backup_entry_settings_only">僅備份設定</string>
|
||||||
|
<string name="settings_backup_restore_backup_entry_everything_no_users">全部備份 (使用者檔案除外)</string>
|
||||||
|
<string name="settings_backup_restore_backup_successful">已成功備份</string>
|
||||||
|
<string name="settings_backup_restore_restore_successful">還原成功</string>
|
||||||
|
<string name="settings_backup_restore_restore_failed">還原失敗: %1$s</string>
|
||||||
|
<string name="settings_advanced_header">進階</string>
|
||||||
|
<string name="settings_advanced_export_logs_scrub_dialog_button_ok">確定</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_title">連接方式</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_summary_jsonhttp">透過 HTTP 連接伺服器來接收 JSON 串流,但可能會持續消耗較大電量。</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_summary_ws">(建議) 透過 WebSocket 連接伺服器,但伺服器可能需要額外設定來啓用這種連接方式。</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_entry_jsonhttp">JSON 串流 (透過 HTTP 連接)</string>
|
||||||
|
<string name="settings_advanced_connection_protocol_entry_ws">WebSocket</string>
|
||||||
|
<string name="detail_settings_about_topic_url_title">主題 URL</string>
|
||||||
|
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">已複製到剪貼簿</string>
|
||||||
|
<string name="user_dialog_password_hint_add">密碼</string>
|
||||||
|
<string name="user_dialog_button_save">儲存</string>
|
||||||
|
<string name="user_dialog_button_add">新增使用者</string>
|
||||||
|
<string name="user_dialog_button_cancel">取消</string>
|
||||||
|
<string name="user_dialog_username_hint">使用者名稱</string>
|
||||||
|
<string name="settings_notifications_header">通知</string>
|
||||||
|
<string name="detail_menu_test">發送測試訊息</string>
|
||||||
|
<string name="settings_notifications_muted_until_title">暫停通知</string>
|
||||||
|
<string name="settings_notifications_auto_download_10m">小於 10 MB</string>
|
||||||
|
<string name="detail_settings_about_header">關於</string>
|
||||||
|
<string name="settings_backup_restore_backup_failed">備份失敗: %1$s</string>
|
||||||
</resources>
|
</resources>
|
|
@ -154,6 +154,7 @@
|
||||||
<string name="detail_item_cannot_open">Cannot open attachment: %1$s</string>
|
<string name="detail_item_cannot_open">Cannot open attachment: %1$s</string>
|
||||||
<string name="detail_item_cannot_open_not_found">Cannot open attachment: The file may have been deleted, or no installed app can open the file.</string>
|
<string name="detail_item_cannot_open_not_found">Cannot open attachment: The file may have been deleted, or no installed app can open the file.</string>
|
||||||
<string name="detail_item_cannot_open_url">Cannot open URL: %1$s</string>
|
<string name="detail_item_cannot_open_url">Cannot open URL: %1$s</string>
|
||||||
|
<string name="detail_item_cannot_open_apk">Apps cannot be installed anymore. Download via browser instead. See issue #531 for details.</string>
|
||||||
<string name="detail_item_cannot_save">Cannot save attachment: %1$s</string>
|
<string name="detail_item_cannot_save">Cannot save attachment: %1$s</string>
|
||||||
<string name="detail_item_cannot_delete">Cannot delete attachment: %1$s</string>
|
<string name="detail_item_cannot_delete">Cannot delete attachment: %1$s</string>
|
||||||
<string name="detail_item_download_failed">Could not download attachment: %1$s</string>
|
<string name="detail_item_download_failed">Could not download attachment: %1$s</string>
|
||||||
|
@ -318,7 +319,7 @@
|
||||||
<string name="settings_advanced_broadcast_summary_disabled">Apps cannot receive notifications as broadcasts</string>
|
<string name="settings_advanced_broadcast_summary_disabled">Apps cannot receive notifications as broadcasts</string>
|
||||||
<string name="settings_advanced_record_logs_title">Record logs</string>
|
<string name="settings_advanced_record_logs_title">Record logs</string>
|
||||||
<string name="settings_advanced_record_logs_summary_enabled">Logging (up to 1,000 entries) to device …</string>
|
<string name="settings_advanced_record_logs_summary_enabled">Logging (up to 1,000 entries) to device …</string>
|
||||||
<string name="settings_advanced_record_logs_summary_disabled">Turn on logging so you can share logs later to diagnose issues.</string>
|
<string name="settings_advanced_record_logs_summary_disabled">Turn on logging, so you can share logs later to diagnose issues.</string>
|
||||||
<string name="settings_advanced_export_logs_title">Copy/upload logs</string>
|
<string name="settings_advanced_export_logs_title">Copy/upload logs</string>
|
||||||
<string name="settings_advanced_export_logs_summary">Copy logs to the clipboard, or upload to nopaste.net (owned by the ntfy author). Hostnames and topics can be censored, notifications will never be.</string>
|
<string name="settings_advanced_export_logs_summary">Copy logs to the clipboard, or upload to nopaste.net (owned by the ntfy author). Hostnames and topics can be censored, notifications will never be.</string>
|
||||||
<string name="settings_advanced_export_logs_entry_copy_original">Copy to clipboard</string>
|
<string name="settings_advanced_export_logs_entry_copy_original">Copy to clipboard</string>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import io.heckel.ntfy.msg.ApiService
|
||||||
import io.heckel.ntfy.msg.NotificationDispatcher
|
import io.heckel.ntfy.msg.NotificationDispatcher
|
||||||
import io.heckel.ntfy.msg.NotificationParser
|
import io.heckel.ntfy.msg.NotificationParser
|
||||||
import io.heckel.ntfy.service.SubscriberService
|
import io.heckel.ntfy.service.SubscriberService
|
||||||
|
import io.heckel.ntfy.util.nullIfZero
|
||||||
import io.heckel.ntfy.util.toPriority
|
import io.heckel.ntfy.util.toPriority
|
||||||
import io.heckel.ntfy.util.topicShortUrl
|
import io.heckel.ntfy.util.topicShortUrl
|
||||||
import io.heckel.ntfy.work.PollWorker
|
import io.heckel.ntfy.work.PollWorker
|
||||||
|
@ -94,8 +95,8 @@ class FirebaseService : FirebaseMessagingService() {
|
||||||
val encoding = data["encoding"]
|
val encoding = data["encoding"]
|
||||||
val attachmentName = data["attachment_name"] ?: "attachment.bin"
|
val attachmentName = data["attachment_name"] ?: "attachment.bin"
|
||||||
val attachmentType = data["attachment_type"]
|
val attachmentType = data["attachment_type"]
|
||||||
val attachmentSize = data["attachment_size"]?.toLongOrNull()
|
val attachmentSize = data["attachment_size"]?.toLongOrNull()?.nullIfZero()
|
||||||
val attachmentExpires = data["attachment_expires"]?.toLongOrNull()
|
val attachmentExpires = data["attachment_expires"]?.toLongOrNull()?.nullIfZero()
|
||||||
val attachmentUrl = data["attachment_url"]
|
val attachmentUrl = data["attachment_url"]
|
||||||
val truncated = (data["truncated"] ?: "") == "1"
|
val truncated = (data["truncated"] ?: "") == "1"
|
||||||
if (id == null || topic == null || message == null || timestamp == null) {
|
if (id == null || topic == null || message == null || timestamp == null) {
|
||||||
|
|
|
@ -12,6 +12,8 @@ Bug fixes + maintenance:
|
||||||
* Fix topics do not re-subscribe to Firebase after restoring from backup (#511)
|
* Fix topics do not re-subscribe to Firebase after restoring from backup (#511)
|
||||||
* Fix crashes from large images (#474, thanks to @daedric7 for reporting)
|
* Fix crashes from large images (#474, thanks to @daedric7 for reporting)
|
||||||
* Fix notification click opens wrong subscription (#261, thanks to @SMAW for reporting)
|
* Fix notification click opens wrong subscription (#261, thanks to @SMAW for reporting)
|
||||||
|
* Fix Firebase-only "link expired" issue (#529)
|
||||||
|
* Remove "Install .apk" feature in Google Play variant due to policy change (#531)
|
||||||
* Add donate button (no ticket)
|
* Add donate button (no ticket)
|
||||||
|
|
||||||
Additional translations:
|
Additional translations:
|
2
fastlane/metadata/android/en-US/changelog/32.txt
Normal file
2
fastlane/metadata/android/en-US/changelog/32.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Bug fixes:
|
||||||
|
* Android 5 (SDK 21): Fix crash on unsubscribing (#528, thanks to Roger M.)
|
17
fastlane/metadata/android/zh-Hant/full_description.txt
Normal file
17
fastlane/metadata/android/zh-Hant/full_description.txt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
不論 Bash 還是 PowerShell,或者是你自己的應用程式、curl 或者 Invoke-WebRequest 等等,都可以透過 HTTP PUT/POST 向你的裝置傳送通知。
|
||||||
|
|
||||||
|
ntfy 是 https://ntfy.sh 的 Android APP,一個打造在 HTTP 標準之上的免費開源 pub-sub 服務。你可以透過訂閱主題來接收通過 HTTP API 發送的通知。
|
||||||
|
|
||||||
|
當中,你可能找到千變萬化的用途,例如:
|
||||||
|
* 在一個很長很長的程序完成後通知自己
|
||||||
|
* 在備份失敗後通知自己
|
||||||
|
* 當有人登入到伺服器的時候發送通知
|
||||||
|
|
||||||
|
發送通知可以簡單如此:
|
||||||
|
|
||||||
|
$ curl -d "備份完成了!" ntfy.sh/mytopic
|
||||||
|
|
||||||
|
你也可以在下面的連結閱讀更多資訊:
|
||||||
|
* 網站: https://ntfy.sh
|
||||||
|
* GitHub(伺服器端): https://github.com/binwiederhier/ntfy
|
||||||
|
* GitHub(Android APP): https://github.com/binwiederhier/ntfy-android
|
1
fastlane/metadata/android/zh-Hant/short_description.txt
Normal file
1
fastlane/metadata/android/zh-Hant/short_description.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
透過 HTTP PUT/POST 方式傳送任何通知到你的裝置
|
1
fastlane/metadata/android/zh-Hant/title.txt
Normal file
1
fastlane/metadata/android/zh-Hant/title.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ntfy - PUT/POST 到你的裝置
|
Loading…
Reference in a new issue