diff options
author | Philipp Heckel <pheckel@datto.com> | 2022-12-06 20:45:12 -0500 |
---|---|---|
committer | Philipp Heckel <pheckel@datto.com> | 2022-12-06 20:45:12 -0500 |
commit | 6f0bf4d11236bae33a2ec446b3ecd54e9a7863b1 (patch) | |
tree | cad6d0b33a06e3473d1c97088404c85453fd290c | |
parent | 209cf852bb74f4f44da637682381db3dc44a3a2f (diff) | |
parent | 1e70ae0d18e1e799b63ebd2cab8f77aa018b1242 (diff) | |
download | ponypush-6f0bf4d11236bae33a2ec446b3ecd54e9a7863b1.tar.gz ponypush-6f0bf4d11236bae33a2ec446b3ecd54e9a7863b1.tar.bz2 ponypush-6f0bf4d11236bae33a2ec446b3ecd54e9a7863b1.zip |
Merge branch 'main' into custom_notification_channels
41 files changed, 461 insertions, 138 deletions
diff --git a/app/build.gradle b/app/build.gradle index d34de50..b79295a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { minSdkVersion 21 targetSdkVersion 33 - versionCode 29 - versionName "1.15.0" + versionCode 32 + versionName "1.16.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -44,10 +44,12 @@ android { play { buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true' buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true' + buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'false' } fdroid { buildConfigField 'boolean', 'FIREBASE_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 -> def shouldProcessGoogleServices = variant.flavorName == "play" def googleTask = tasks.findByName("process${variant.name.capitalize()}GoogleServices") 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 { // AndroidX, The Basics implementation "androidx.appcompat:appcompat:1.5.1" diff --git a/app/src/fdroid/java/io/heckel/ntfy/firebase/FirebaseMessenger.kt b/app/src/fdroid/java/io/heckel/ntfy/firebase/FirebaseMessenger.kt index e121d2f..d12f90e 100644 --- a/app/src/fdroid/java/io/heckel/ntfy/firebase/FirebaseMessenger.kt +++ b/app/src/fdroid/java/io/heckel/ntfy/firebase/FirebaseMessenger.kt @@ -1,5 +1,6 @@ package io.heckel.ntfy.firebase +@Suppress("UNUSED_PARAMETER") class FirebaseMessenger { fun subscribe(topic: String) { // Dummy to keep F-Droid flavor happy diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4cbdf60..0580856 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.heckel.ntfy"> + <!-- Permissions --> <uses-permission android:name="android.permission.INTERNET"/> <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.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.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.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 android:name=".app.Application" android:allowBackup="true" diff --git a/app/src/main/java/io/heckel/ntfy/app/Application.kt b/app/src/main/java/io/heckel/ntfy/app/Application.kt index b4b104c..6e4e760 100644 --- a/app/src/main/java/io/heckel/ntfy/app/Application.kt +++ b/app/src/main/java/io/heckel/ntfy/app/Application.kt @@ -1,7 +1,6 @@ package io.heckel.ntfy.app import android.app.Application -import android.content.Context import io.heckel.ntfy.db.Database import io.heckel.ntfy.db.Repository import io.heckel.ntfy.util.Log diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index f730c20..58dd2d4 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -89,7 +89,7 @@ class Backuper(val context: Context) { private suspend fun applySubscriptions(subscriptions: List<Subscription>?) { if (subscriptions == null) { - return; + return } val appBaseUrl = context.getString(R.string.app_base_url) subscriptions.forEach { s -> @@ -120,7 +120,7 @@ class Backuper(val context: Context) { private suspend fun applyNotifications(notifications: List<Notification>?) { if (notifications == null) { - return; + return } notifications.forEach { n -> try { @@ -189,7 +189,7 @@ class Backuper(val context: Context) { private suspend fun applyUsers(users: List<User>?) { if (users == null) { - return; + return } users.forEach { u -> try { diff --git a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt index 64baa45..bdd8b01 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt @@ -95,7 +95,7 @@ class ApiService { throw Exception("Unexpected response ${response.code} when polling topic $url") } val body = response.body?.string()?.trim() - if (body == null || body.isEmpty()) return emptyList() + if (body.isNullOrEmpty()) return emptyList() val notifications = body.lines().mapNotNull { line -> 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 EntityTooLargeException() : Exception() + class EntityTooLargeException : Exception() companion object { val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})" diff --git a/app/src/main/java/io/heckel/ntfy/msg/DownloadManager.kt b/app/src/main/java/io/heckel/ntfy/msg/DownloadManager.kt index ab0dd2e..83cfb1a 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/DownloadManager.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/DownloadManager.kt @@ -11,7 +11,7 @@ import io.heckel.ntfy.util.Log * Download attachment in the background via WorkManager * * 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 { private const val TAG = "NtfyDownloadManager" diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index dcaad0a..319a4bd 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -206,6 +206,9 @@ class NotificationService(val context: Context) { } private fun maybeAddOpenAction(builder: NotificationCompat.Builder, notification: Notification) { + if (!canOpenAttachment(notification.attachment)) { + return + } if (notification.attachment?.contentUri != null) { val contentUri = Uri.parse(notification.attachment.contentUri) val intent = Intent(Intent.ACTION_VIEW, contentUri).apply { diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt index 103ff1c..2f59aa6 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -200,7 +200,7 @@ class SubscriberService : Service() { // retrieve old messages. This is important, so we don't download attachments from old messages. val since = sinceByBaseUrl[connectionId.baseUrl] ?: "none" - val serviceActive = { -> isServiceStarted } + val serviceActive = { isServiceStarted } val user = repository.getUser(connectionId.baseUrl) val connection = if (repository.getConnectionProtocol() == Repository.CONNECTION_PROTOCOL_WS) { val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager @@ -310,11 +310,11 @@ class SubscriberService : Service() { override fun onTaskRemoved(rootIntent: Intent) { val restartServiceIntent = Intent(applicationContext, SubscriberService::class.java).also { it.setPackage(packageName) - }; - val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE); - applicationContext.getSystemService(Context.ALARM_SERVICE); - val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager; - alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent); + } + val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE) + applicationContext.getSystemService(Context.ALARM_SERVICE) + val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent) } /* This re-starts the service on reboot; see manifest */ diff --git a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt index b9770fa..8e56bab 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt @@ -5,8 +5,6 @@ import android.app.AlertDialog import android.app.Dialog import android.content.Context import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 0837770..1df09eb 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -7,6 +7,7 @@ import android.content.Context import android.content.Intent import android.content.Intent.ACTION_VIEW import android.net.Uri +import android.os.Build import android.os.Bundle import android.text.Html import android.view.ActionMode @@ -179,7 +180,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra howToExample.linksClickable = true 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) } else { howToExample.text = Html.fromHtml(howToText) @@ -242,7 +243,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 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) } } @@ -572,7 +573,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextAppearance(R.style.DangerText) + .dangerButton(this) } dialog.show() } @@ -610,7 +611,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextAppearance(R.style.DangerText) + .dangerButton(this) } dialog.show() } @@ -620,7 +621,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra handleActionModeClick(notification) } else if (notification.click != "") { try { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(notification.click))) + startActivity(Intent(ACTION_VIEW, Uri.parse(notification.click))) } catch (e: Exception) { Log.w(TAG, "Cannot open click URL", e) runOnUiThread { @@ -721,7 +722,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextAppearance(R.style.DangerText) + .dangerButton(this) } dialog.show() } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt index 19f6f3c..c3a08fe 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt @@ -27,14 +27,16 @@ import com.google.android.material.button.MaterialButton import com.stfalcon.imageviewer.StfalconImageViewer import io.heckel.ntfy.R import io.heckel.ntfy.db.* -import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.msg.DownloadAttachmentWorker +import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.msg.DownloadType import io.heckel.ntfy.msg.NotificationService import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW 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) : 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) { - if (notification.actions != null && notification.actions.isNotEmpty()) { + if (!notification.actions.isNullOrEmpty()) { actionsWrapperView.visibility = View.VISIBLE val actionsCount = Math.min(notification.actions.size, 3) // per documentation, only 3 actions are available for (i in 0 until actionsCount) { @@ -220,7 +222,7 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: private fun resetCardButtons() { // clear any previously created dynamic buttons - actionsFlow.allViews.forEach { it -> actionsFlow.removeView(it) } + actionsFlow.allViews.forEach { actionsFlow.removeView(it) } actionsWrapperView.removeAllViews() actionsWrapperView.addView(actionsFlow) } @@ -371,6 +373,12 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: } 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}") try { val contentUri = Uri.parse(attachment.contentUri) diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt index 5f31686..75c7164 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -229,9 +229,8 @@ class DetailSettingsActivity : AppCompatActivity() { return subscription.mutedUntil.toString() } } - pref?.summaryProvider = Preference.SummaryProvider<ListPreference> { _ -> - val mutedUntilValue = subscription.mutedUntil - when (mutedUntilValue) { + pref?.summaryProvider = Preference.SummaryProvider<ListPreference> { + when (val mutedUntilValue = subscription.mutedUntil) { 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) else -> { @@ -312,7 +311,7 @@ class DetailSettingsActivity : AppCompatActivity() { iconSetPref = findPreference(prefId) ?: return iconSetPref.isVisible = subscription.icon == null iconSetPref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting - iconSetPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ -> + iconSetPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { iconSetLauncher.launch("image/*") true } @@ -323,7 +322,7 @@ class DetailSettingsActivity : AppCompatActivity() { iconRemovePref = findPreference(prefId) ?: return iconRemovePref.isVisible = subscription.icon != null iconRemovePref.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting - iconRemovePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ -> + iconRemovePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { iconRemovePref.isVisible = false iconSetPref.isVisible = true deleteIcon(subscription.icon) diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index 9a8f40a..3e65656 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -35,7 +35,6 @@ import io.heckel.ntfy.app.Application import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.firebase.FirebaseMessenger -import io.heckel.ntfy.util.Log import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.msg.DownloadType @@ -48,7 +47,6 @@ import io.heckel.ntfy.work.PollWorker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import java.security.SecureRandom import java.util.* import java.util.concurrent.TimeUnit import kotlin.random.Random @@ -623,7 +621,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextAppearance(R.style.DangerText) + .dangerButton(this) } dialog.show() } diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt index bb78468..f2a9ea6 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt @@ -1,9 +1,7 @@ package io.heckel.ntfy.ui import android.content.Context -import android.graphics.BitmapFactory import android.graphics.Color -import android.net.Uri import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -17,10 +15,8 @@ import io.heckel.ntfy.R import io.heckel.ntfy.db.ConnectionState import io.heckel.ntfy.db.Repository 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.readBitmapFromUriOrNull import java.text.DateFormat import java.util.* @@ -119,7 +115,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs if (selected.contains(subscription.id)) { itemView.setBackgroundResource(Colors.itemSelectedBackground(context)) } else { - itemView.setBackgroundColor(Color.TRANSPARENT); + itemView.setBackgroundColor(Color.TRANSPARENT) } } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt b/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt index 84857db..19e384c 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainViewModel.kt @@ -8,10 +8,8 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import io.heckel.ntfy.db.* import io.heckel.ntfy.up.Distributor -import io.heckel.ntfy.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlin.collections.List class SubscriptionsViewModel(private val repository: Repository) : ViewModel() { fun list(): LiveData<List<Subscription>> { diff --git a/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt index 8509e8e..2d84879 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt @@ -8,7 +8,6 @@ import android.widget.RadioButton import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import io.heckel.ntfy.R -import io.heckel.ntfy.db.Database import io.heckel.ntfy.db.Repository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay diff --git a/app/src/main/java/io/heckel/ntfy/ui/ShareActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/ShareActivity.kt index 1db1c33..984f6af 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/ShareActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/ShareActivity.kt @@ -1,7 +1,6 @@ package io.heckel.ntfy.ui import android.content.Intent -import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle import android.os.Parcelable @@ -295,7 +294,7 @@ class ShareActivity : AppCompatActivity() { .show() } } catch (e: Exception) { - val message = if (e is ApiService.UnauthorizedException) { + val errorMessage = if (e is ApiService.UnauthorizedException) { if (e.user != null) { getString(R.string.detail_test_message_error_unauthorized_user, e.user.username) } else { @@ -308,7 +307,7 @@ class ShareActivity : AppCompatActivity() { } runOnUiThread { progress.visibility = View.GONE - errorText.text = message + errorText.text = errorMessage errorImage.visibility = View.VISIBLE errorText.visibility = View.VISIBLE } diff --git a/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt index 7dd8bb9..6da8304 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt @@ -3,19 +3,17 @@ package io.heckel.ntfy.ui import android.app.AlertDialog import android.app.Dialog import android.content.Context -import android.os.Build import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher import android.view.View import android.view.WindowManager import android.widget.Button import android.widget.TextView -import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.google.android.material.textfield.TextInputEditText import io.heckel.ntfy.R import io.heckel.ntfy.db.User +import io.heckel.ntfy.util.AfterChangedTextWatcher +import io.heckel.ntfy.util.dangerButton import io.heckel.ntfy.util.validUrl class UserFragment : DialogFragment() { @@ -98,28 +96,14 @@ class UserFragment : DialogFragment() { // Delete button should be red if (user != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - dialog - .getButton(AlertDialog.BUTTON_NEUTRAL) - .setTextAppearance(R.style.DangerText) - } else { - dialog - .getButton(AlertDialog.BUTTON_NEUTRAL) - .setTextColor(ContextCompat.getColor(requireContext(), Colors.dangerText(requireContext()))) - } + dialog + .getButton(AlertDialog.BUTTON_NEUTRAL) + .dangerButton(requireContext()) } // Validate input when typing - val textWatcher = object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - 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 - } + val textWatcher = AfterChangedTextWatcher { + validateInput() } baseUrlView.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) - dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) return dialog } diff --git a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt index 5ddcd57..093d51f 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -10,12 +10,10 @@ import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.util.* -import kotlin.random.Random /** * This is the UnifiedPush broadcast receiver to handle the distributor actions REGISTER and UNREGISTER. diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index bd57516..8524dc0 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -22,12 +22,16 @@ import android.util.Base64 import android.util.TypedValue import android.view.View import android.view.Window +import android.widget.Button import android.widget.ImageView import android.widget.Toast import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.ContextCompat +import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R import io.heckel.ntfy.db.* import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64 +import io.heckel.ntfy.ui.Colors import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers 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 fun fileName(context: Context, contentUri: String?, fallbackName: String): String { 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()) } +const val androidAppMimeType = "application/vnd.android.package-archive" + fun mimeTypeToIconResource(mimeType: String?): Int { return if (mimeType?.startsWith("image/") == true) { R.drawable.ic_file_image_red_24dp @@ -332,7 +328,7 @@ fun mimeTypeToIconResource(mimeType: String?): Int { R.drawable.ic_file_video_orange_24dp } else if (mimeType?.startsWith("audio/") == true) { 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 } else { R.drawable.ic_file_document_blue_24dp @@ -343,6 +339,15 @@ fun supportedImage(mimeType: String?): Boolean { 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 fun isIgnoringBatteryOptimizations(context: Context): Boolean { val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager @@ -494,3 +499,11 @@ fun String.sha256(): String { val digest = md.digest(this.toByteArray()) 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))) + } +} diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 197ea54..7b8bb8c 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -327,4 +327,6 @@ <string name="detail_settings_about_header">Относно</string> <string name="detail_settings_about_topic_url_title">Адрес на темата</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>
\ No newline at end of file diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 6564bca..d70279f 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -6,4 +6,43 @@ <string name="channel_notifications_high_name">Notificacions (alta prioritat)</string> <string name="channel_notifications_max_name">Notificacions (prioritat màx)</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>
\ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 646024f..35c596d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -327,4 +327,6 @@ <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="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>
\ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 029f961..2a16331 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -309,7 +309,7 @@ <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_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_global_setting_title">Globale Einstellung verwenden</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="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_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_header">Über</string> <string name="detail_settings_about_topic_url_copied_to_clipboard_message">In Zwischenablage kopiert</string> -</resources>
\ No newline at end of file + <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> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 436b898..4615748 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -327,4 +327,6 @@ <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_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>
\ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 4e2b23c..9718910 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -6,7 +6,7 @@ <string name="channel_notifications_min_name">Notifikasi (prioritas min)</string> <string name="channel_notifications_low_name">Notifikasi (prioritas rendah)</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_one">Berlangganan ke satu 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_until">Notifikasi dibisukan sampai %1$s</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_rate_title">Beri nilai aplikasi ⭐</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_header">Tentang</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>
\ No newline at end of file diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 8fb0788..dabff91 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -15,7 +15,7 @@ <string name="main_action_mode_delete_dialog_permanently_delete">מחק/י לצמיתות</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_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_add_button_description">הוספת רישום</string> <string name="main_no_subscriptions_text">נראה שלא נרשמת לאף נושא עדיין.</string> @@ -39,4 +39,20 @@ <string name="main_item_status_reconnecting">מתחבר מחדש…</string> <string name="main_how_to_intro">לחצ\\י על + על מנת ליצור או להירשם אל מול נושא מסוים. לאחר מכן תקבל\\י התראות במכשירך כשתשלח\\י התראות דרך PUT או POST.</string> <string name="main_how_to_link">הוראות מפורטות זמינות ב-ntfy.sh, ובדוקומנטציה.</string> -</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>
\ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 0731eb9..e06e252 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -327,4 +327,6 @@ <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_copied_to_clipboard_message">クリップボードにコピーしました</string> + <string name="main_menu_donate_title">寄付する💸</string> + <string name="detail_item_cannot_open_apk">アプリはインストールできなくなりました。代替手段としてブラウザからダウンロードしてください。詳細は issue #531 をご参照ください。</string> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bc95a63..10983c4 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -77,7 +77,7 @@ <string name="detail_item_tags">Tags: %1$s</string> <string name="detail_item_snack_deleted">Notificação deletada</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_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> @@ -258,4 +258,74 @@ <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_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>
\ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index c9e4078..24ede8f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -99,4 +99,28 @@ <string name="add_dialog_login_username_hint">Användarnamn</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_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>
\ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index dbf2453..ce45f6f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -327,4 +327,6 @@ <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_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>
\ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index f6efd9c..fe61fe6 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -327,4 +327,6 @@ <string name="detail_settings_about_header">关于</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="main_menu_donate_title">捐赠 💸</string> + <string name="detail_item_cannot_open_apk">无法再安装应用。 请通过浏览器下载。 有关详细信息,请参阅问题 #531。</string> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index da86ce1..c6f7aa6 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,23 +1,23 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="channel_subscriber_notification_noinstant_text_six">已訂閱6個主題</string> - <string name="channel_notifications_default_name">通知(預設優先)</string> - <string name="main_menu_report_bug_title">回報bug</string> - <string name="channel_subscriber_notification_title">監聽傳入通知</string> + <string name="channel_subscriber_notification_noinstant_text_six">已訂閱 6 個主題</string> + <string name="channel_notifications_default_name">通知 (預設優先)</string> + <string name="main_menu_report_bug_title">問題回報</string> + <string name="channel_subscriber_notification_title">正在接收通知</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_two">已訂閱2個即時推送主題</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_six">已訂閱6個即時推送主題</string> - <string name="channel_subscriber_notification_instant_text_more">已訂閱%1$d個即時推送主題</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_four">已訂閱 4 個即時推送主題</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_more">已訂閱 %1$d 個即時推送主題</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_error">有%1$d個訂閱無法更新 + <string name="refresh_message_error">有 %1$d 個訂閱無法更新 \n \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_menu_notifications_enabled">通知已開啟</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_permanently_delete">永久刪除</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_not_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_date_yesterday">昨天</string> <string name="main_add_button_description">新增訂閱</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_unified_push_toast">此訂閱已由 %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_dismiss">略過</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_use_another_server">使用其他伺服器</string> <string name="add_dialog_use_another_server_description">在下方輸入自訂的網址來訂閱主題。</string> - <string name="channel_notifications_min_name">通知(最低優先)</string> - <string name="channel_notifications_low_name">通知(低優先)</string> - <string name="channel_notifications_high_name">通知(高優先)</string> - <string name="channel_notifications_max_name">通知(最高優先)</string> + <string name="channel_notifications_min_name">通知 (最低優先)</string> + <string name="channel_notifications_low_name">通知 (低優先)</string> + <string name="channel_notifications_high_name">通知 (高優先)</string> + <string name="channel_notifications_max_name">通知 (最高優先)</string> <string name="channel_subscriber_service_name">訂閱服務</string> - <string name="channel_subscriber_notification_instant_text_three">已訂閱3個即時推送主題</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="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_one">已訂閱1個主題</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_five">已訂閱5個主題</string> - <string name="main_item_status_reconnecting">重新連線中 …</string> - <string name="main_menu_notifications_disabled_until">通知靜音到%1$s</string> - <string name="add_dialog_topic_name_hint">主題名稱(例如:phils_alerts)</string> + <string name="channel_subscriber_notification_instant_text_three">已訂閱 3 個即時推送主題</string> + <string name="main_menu_docs_title">閱讀技術文件</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_more">已訂閱 %1$d 個主題</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_four">已訂閱 4 個主題</string> + <string name="channel_subscriber_notification_noinstant_text_five">已訂閱 5 個主題</string> + <string name="main_item_status_reconnecting">重新連線中…</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_button_cancel">取消</string> <string name="add_dialog_button_subscribe">訂閱</string> - <string name="add_dialog_button_back">退回</string> - <string name="add_dialog_error_connection_failed">連接失敗: %1$s</string> + <string name="add_dialog_button_back">返回</string> + <string name="add_dialog_error_connection_failed">連接失敗: %1$s</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_error_not_authorized">登入失敗. 使用者%1$s 未獲得授權.</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_clear">清除服務網址</string> - <string name="detail_copied_to_clipboard_message">複製到剪貼簿</string> + <string name="add_dialog_login_error_not_authorized">登入失敗,使用者 %1$s 並未授權訂閱這個主題。</string> + <string name="add_dialog_login_new_user">建立新使用者</string> + <string name="add_dialog_base_urls_dropdown_choose">選擇 ntfy 服務 URL</string> + <string name="add_dialog_base_urls_dropdown_clear">清除 URL</string> + <string name="detail_copied_to_clipboard_message">已複製到剪貼簿</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>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17fd9ae..33353a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,6 +154,7 @@ <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_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_delete">Cannot delete 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_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_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_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> diff --git a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt index 1a367ab..830b92d 100644 --- a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt +++ b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt @@ -14,6 +14,7 @@ import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.msg.NotificationParser import io.heckel.ntfy.service.SubscriberService +import io.heckel.ntfy.util.nullIfZero import io.heckel.ntfy.util.toPriority import io.heckel.ntfy.util.topicShortUrl import io.heckel.ntfy.work.PollWorker @@ -94,8 +95,8 @@ class FirebaseService : FirebaseMessagingService() { val encoding = data["encoding"] val attachmentName = data["attachment_name"] ?: "attachment.bin" val attachmentType = data["attachment_type"] - val attachmentSize = data["attachment_size"]?.toLongOrNull() - val attachmentExpires = data["attachment_expires"]?.toLongOrNull() + val attachmentSize = data["attachment_size"]?.toLongOrNull()?.nullIfZero() + val attachmentExpires = data["attachment_expires"]?.toLongOrNull()?.nullIfZero() val attachmentUrl = data["attachment_url"] val truncated = (data["truncated"] ?: "") == "1" if (id == null || topic == null || message == null || timestamp == null) { diff --git a/fastlane/metadata/android/en-US/changelog/29.txt b/fastlane/metadata/android/en-US/changelog/31.txt index 805d904..c807459 100644 --- a/fastlane/metadata/android/en-US/changelog/29.txt +++ b/fastlane/metadata/android/en-US/changelog/31.txt @@ -12,6 +12,8 @@ Bug fixes + maintenance: * 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 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) Additional translations: diff --git a/fastlane/metadata/android/en-US/changelog/32.txt b/fastlane/metadata/android/en-US/changelog/32.txt new file mode 100644 index 0000000..27617b8 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelog/32.txt @@ -0,0 +1,2 @@ +Bug fixes: +* Android 5 (SDK 21): Fix crash on unsubscribing (#528, thanks to Roger M.) diff --git a/fastlane/metadata/android/zh-Hant/full_description.txt b/fastlane/metadata/android/zh-Hant/full_description.txt new file mode 100644 index 0000000..50bd036 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/full_description.txt @@ -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 diff --git a/fastlane/metadata/android/zh-Hant/short_description.txt b/fastlane/metadata/android/zh-Hant/short_description.txt new file mode 100644 index 0000000..71f9a34 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/short_description.txt @@ -0,0 +1 @@ +透過 HTTP PUT/POST 方式傳送任何通知到你的裝置 diff --git a/fastlane/metadata/android/zh-Hant/title.txt b/fastlane/metadata/android/zh-Hant/title.txt new file mode 100644 index 0000000..80dc0ad --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/title.txt @@ -0,0 +1 @@ +ntfy - PUT/POST 到你的裝置 |