Merge pull request #2 from binwiederhier/main

merge upstream changes
This commit is contained in:
Tobias 2025-12-14 14:53:04 +01:00 committed by GitHub
commit cebd3d4f5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
102 changed files with 2397 additions and 1077 deletions

View file

@ -18,8 +18,8 @@ android {
minSdkVersion 21
targetSdkVersion 35
versionCode 41
versionName "1.17.8"
versionCode 48
versionName "1.19.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -53,10 +53,12 @@ android {
play {
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true'
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true'
buildConfigField 'boolean', 'PAYMENT_LINKS_AVAILABLE', 'false' // Google Play Payments Policy, see #1463
}
fdroid {
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false'
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'false'
buildConfigField 'boolean', 'PAYMENT_LINKS_AVAILABLE', 'true'
}
}
@ -112,7 +114,7 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
// Material design
implementation "com.google.android.material:material:1.9.0"
implementation "com.google.android.material:material:1.13.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"

View file

@ -87,6 +87,15 @@
android:exported="false">
</activity>
<!-- UnifiedPush link activity to facilitate distributor selection, see https://unifiedpush.org/developers/spec/android/#link-activity -->
<activity android:name=".up.LinkActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="unifiedpush" android:host="link" />
</intent-filter>
</activity>
<!-- Subscriber foreground service for hosts other than ntfy.sh -->
<service
android:name=".service.SubscriberService"

View file

@ -1,6 +1,7 @@
package io.heckel.ntfy.app
import android.app.Application
import com.google.android.material.color.DynamicColors
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.util.Log
@ -12,4 +13,11 @@ class Application : Application() {
}
repository
}
override fun onCreate() {
super.onCreate()
if (repository.getDynamicColorsEnabled()) {
DynamicColors.applyToActivitiesIfAvailable(this)
}
}
}

View file

@ -68,6 +68,9 @@ class Backuper(val context: Context) {
if (settings.darkMode != null) {
repository.setDarkMode(settings.darkMode)
}
if (settings.dynamicColors != null) {
repository.setDynamicColorsEnabled(settings.dynamicColors)
}
if (settings.connectionProtocol != null) {
repository.setConnectionProtocol(settings.connectionProtocol)
}
@ -234,6 +237,7 @@ class Backuper(val context: Context) {
autoDownloadMaxSize = repository.getAutoDownloadMaxSize(),
autoDeleteSeconds = repository.getAutoDeleteSeconds(),
darkMode = repository.getDarkMode(),
dynamicColors = repository.getDynamicColorsEnabled(),
connectionProtocol = repository.getConnectionProtocol(),
broadcastEnabled = repository.getBroadcastEnabled(),
recordLogs = repository.getRecordLogs(),
@ -357,6 +361,7 @@ data class Settings(
val autoDownloadMaxSize: Long?,
val autoDeleteSeconds: Long?,
val darkMode: Int?,
val dynamicColors: Boolean?,
val connectionProtocol: String?,
val broadcastEnabled: Boolean?,
val recordLogs: Boolean?,

View file

@ -274,6 +274,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
return sharedPrefs.getInt(SHARED_PREFS_DARK_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
fun setDynamicColorsEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_DYNAMIC_COLORS, enabled)
.commit()
}
fun getDynamicColorsEnabled(): Boolean {
return sharedPrefs.getBoolean(SHARED_PREFS_DYNAMIC_COLORS, false)
}
fun setConnectionProtocol(connectionProtocol: String) {
sharedPrefs.edit()
.putString(SHARED_PREFS_CONNECTION_PROTOCOL, connectionProtocol)
@ -527,6 +537,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
const val SHARED_PREFS_AUTO_DELETE_SECONDS = "AutoDelete"
const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol"
const val SHARED_PREFS_DARK_MODE = "DarkMode"
const val SHARED_PREFS_DYNAMIC_COLORS = "DynamicColors"
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
const val SHARED_PREFS_UNIFIEDPUSH_ENABLED = "UnifiedPushEnabled"
const val SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED = "InsistentMaxPriority"

View file

@ -15,7 +15,6 @@ import android.text.SpannedString
import android.text.style.CharacterStyle
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.db.*
import io.heckel.ntfy.db.Notification
@ -29,6 +28,7 @@ class NotificationService(val context: Context) {
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val repository = Repository.getInstance(context)
private val markwon = MarkwonFactory.createForNotification(context)
private val appBaseUrl = context.getString(R.string.app_base_url)
fun display(subscription: Subscription, notification: Notification) {
Log.d(TAG, "Displaying notification $notification")
@ -91,14 +91,14 @@ class NotificationService(val context: Context) {
}
private fun displayInternal(subscription: Subscription, notification: Notification, update: Boolean = false) {
val title = formatTitle(subscription, notification)
val title = formatTitle(appBaseUrl, subscription, notification)
val groupId = if (subscription.dedicatedChannels) subscriptionGroupId(subscription) else DEFAULT_GROUP
val channelId = toChannelId(groupId, notification.priority)
val insistent = notification.priority == PRIORITY_MAX &&
(repository.getInsistentMaxPriorityEnabled() || subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_ENABLED)
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, Colors.notificationIcon(context)))
.setColor(Colors.notificationIcon(context))
.setContentTitle(title)
.setOnlyAlertOnce(true) // Do not vibrate or play sound if already showing (updates!)
.setAutoCancel(true) // Cancel when notification is clicked
@ -365,7 +365,7 @@ class NotificationService(val context: Context) {
putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
}

View file

@ -10,7 +10,6 @@ import android.os.IBinder
import android.os.PowerManager
import android.os.SystemClock
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
@ -296,7 +295,7 @@ class SubscriberService : Service() {
}
return NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_instant)
.setColor(ContextCompat.getColor(this, Colors.notificationIcon(this)))
.setColor(Colors.notificationIcon(this))
.setContentTitle(title)
.setContentText(text)
.setContentIntent(pendingIntent)

View file

@ -1,16 +1,18 @@
package io.heckel.ntfy.ui
import android.app.Activity
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import io.heckel.ntfy.BuildConfig
@ -30,10 +32,10 @@ class AddFragment : DialogFragment() {
private lateinit var appBaseUrl: String
private var defaultBaseUrl: String? = null
private lateinit var toolbar: MaterialToolbar
private lateinit var actionMenuItem: MenuItem
private lateinit var subscribeView: View
private lateinit var loginView: View
private lateinit var positiveButton: Button
private lateinit var negativeButton: Button
// Subscribe page
private lateinit var subscribeTopicText: TextInputEditText
@ -78,6 +80,21 @@ class AddFragment : DialogFragment() {
// Build root view
val view = requireActivity().layoutInflater.inflate(R.layout.fragment_add_dialog, null)
// Setup toolbar
toolbar = view.findViewById(R.id.add_dialog_toolbar)
toolbar.setNavigationOnClickListener {
dismiss()
}
toolbar.setOnMenuItemClickListener { menuItem ->
if (menuItem.itemId == R.id.add_dialog_action_button) {
onActionButtonClick()
true
} else {
false
}
}
actionMenuItem = toolbar.menu.findItem(R.id.add_dialog_action_button)
// Main "pages"
subscribeView = view.findViewById(R.id.add_dialog_subscribe_view)
subscribeView.visibility = View.VISIBLE
@ -136,6 +153,19 @@ class AddFragment : DialogFragment() {
}
}
// Subscribe view validation
val subscribeTextWatcher = AfterChangedTextWatcher {
validateInputSubscribeView()
}
subscribeTopicText.addTextChangedListener(subscribeTextWatcher)
subscribeBaseUrlText.addTextChangedListener(subscribeTextWatcher)
subscribeInstantDeliveryCheckbox.setOnCheckedChangeListener { _, _ ->
validateInputSubscribeView()
}
subscribeUseAnotherServerCheckbox.setOnCheckedChangeListener { _, _ ->
validateInputSubscribeView()
}
// Username/password validation on type
val loginTextWatcher = AfterChangedTextWatcher {
validateInputLoginView()
@ -144,51 +174,36 @@ class AddFragment : DialogFragment() {
loginPasswordText.addTextChangedListener(loginTextWatcher)
// Build dialog
val dialog = AlertDialog.Builder(activity)
.setView(view)
.setPositiveButton(R.string.add_dialog_button_subscribe) { _, _ ->
// This will be overridden below to avoid closing the dialog immediately
}
.setNegativeButton(R.string.add_dialog_button_cancel) { _, _ ->
// This will be overridden below
}
.create()
val dialog = Dialog(requireContext(), R.style.Theme_App_FullScreenDialog)
dialog.setContentView(view)
// Show keyboard when the dialog is shown (see https://stackoverflow.com/a/19573049/1440785)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
// Add logic to disable "Subscribe" button on invalid input
dialog.setOnShowListener {
positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
positiveButton.isEnabled = false
positiveButton.setOnClickListener {
positiveButtonClick()
}
negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
negativeButtonClick()
}
val subscribeTextWatcher = AfterChangedTextWatcher {
validateInputSubscribeView()
}
subscribeTopicText.addTextChangedListener(subscribeTextWatcher)
subscribeBaseUrlText.addTextChangedListener(subscribeTextWatcher)
subscribeInstantDeliveryCheckbox.setOnCheckedChangeListener { _, _ ->
validateInputSubscribeView()
}
subscribeUseAnotherServerCheckbox.setOnCheckedChangeListener { _, _ ->
validateInputSubscribeView()
}
validateInputSubscribeView()
// Focus topic text (keyboard is shown too, see above)
subscribeTopicText.requestFocus()
}
// Initial validation
validateInputSubscribeView()
return dialog
}
private fun positiveButtonClick() {
override fun onStart() {
super.onStart()
dialog?.window?.apply {
setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
}
override fun onResume() {
super.onResume()
// Show keyboard after the dialog is fully visible
subscribeTopicText.postDelayed({
subscribeTopicText.requestFocus()
val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(subscribeTopicText, InputMethodManager.SHOW_FORCED)
}, 200)
}
private fun onActionButtonClick() {
val topic = subscribeTopicText.text.toString()
val baseUrl = getBaseUrl()
if (subscribeView.visibility == View.VISIBLE) {
@ -280,16 +295,8 @@ class AddFragment : DialogFragment() {
}
}
private fun negativeButtonClick() {
if (subscribeView.visibility == View.VISIBLE) {
dialog?.cancel()
} else if (loginView.visibility == View.VISIBLE) {
showSubscribeView()
}
}
private fun validateInputSubscribeView() {
if (!this::positiveButton.isInitialized) return // As per crash seen in Google Play
if (!this::actionMenuItem.isInitialized) return // As per crash seen in Google Play
// Show/hide things: This logic is intentionally kept simple. Do not simplify "just because it's pretty".
val instantToggleAllowed = if (!BuildConfig.FIREBASE_AVAILABLE) {
@ -327,11 +334,11 @@ class AddFragment : DialogFragment() {
activity?.let {
it.runOnUiThread {
if (subscription != null || DISALLOWED_TOPICS.contains(topic)) {
positiveButton.isEnabled = false
actionMenuItem.isEnabled = false
} else if (subscribeUseAnotherServerCheckbox.isChecked) {
positiveButton.isEnabled = validTopic(topic) && validUrl(baseUrl)
actionMenuItem.isEnabled = validTopic(topic) && validUrl(baseUrl)
} else {
positiveButton.isEnabled = validTopic(topic)
actionMenuItem.isEnabled = validTopic(topic)
}
}
}
@ -339,13 +346,13 @@ class AddFragment : DialogFragment() {
}
private fun validateInputLoginView() {
if (!this::positiveButton.isInitialized || !this::loginUsernameText.isInitialized || !this::loginPasswordText.isInitialized) {
if (!this::actionMenuItem.isInitialized || !this::loginUsernameText.isInitialized || !this::loginPasswordText.isInitialized) {
return // As per crash seen in Google Play
}
if (loginUsernameText.visibility == View.GONE) {
positiveButton.isEnabled = true
actionMenuItem.isEnabled = true
} else {
positiveButton.isEnabled = (loginUsernameText.text?.isNotEmpty() ?: false)
actionMenuItem.isEnabled = (loginUsernameText.text?.isNotEmpty() ?: false)
&& (loginPasswordText.text?.isNotEmpty() ?: false)
}
}
@ -372,8 +379,11 @@ class AddFragment : DialogFragment() {
private fun showSubscribeView() {
resetSubscribeView()
positiveButton.text = getString(R.string.add_dialog_button_subscribe)
negativeButton.text = getString(R.string.add_dialog_button_cancel)
toolbar.setTitle(R.string.add_dialog_title)
actionMenuItem.setTitle(R.string.add_dialog_button_subscribe)
toolbar.setNavigationOnClickListener {
dismiss()
}
loginView.visibility = View.GONE
subscribeView.visibility = View.VISIBLE
if (subscribeTopicText.requestFocus()) {
@ -385,8 +395,11 @@ class AddFragment : DialogFragment() {
private fun showLoginView(activity: Activity) {
resetLoginView()
loginProgress.visibility = View.INVISIBLE
positiveButton.text = getString(R.string.add_dialog_button_login)
negativeButton.text = getString(R.string.add_dialog_button_back)
toolbar.setTitle(R.string.add_dialog_login_title)
actionMenuItem.setTitle(R.string.add_dialog_button_login)
toolbar.setNavigationOnClickListener {
showSubscribeView()
}
subscribeView.visibility = View.GONE
loginView.visibility = View.VISIBLE
if (loginUsernameText.requestFocus()) {
@ -400,7 +413,7 @@ class AddFragment : DialogFragment() {
subscribeBaseUrlText.isEnabled = enable
subscribeInstantDeliveryCheckbox.isEnabled = enable
subscribeUseAnotherServerCheckbox.isEnabled = enable
positiveButton.isEnabled = enable
actionMenuItem.isEnabled = enable
}
private fun resetSubscribeView() {
@ -413,7 +426,7 @@ class AddFragment : DialogFragment() {
private fun enableLoginView(enable: Boolean) {
loginUsernameText.isEnabled = enable
loginPasswordText.isEnabled = enable
positiveButton.isEnabled = enable
actionMenuItem.isEnabled = enable
if (enable && loginUsernameText.requestFocus()) {
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(loginUsernameText, InputMethodManager.SHOW_IMPLICIT)

View file

@ -0,0 +1,62 @@
package io.heckel.ntfy.ui
import android.widget.TextView
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import io.heckel.ntfy.R
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
/**
* Show [ListPreference] and [EditTextPreference] dialog by [MaterialAlertDialogBuilder]
*/
override fun onDisplayPreferenceDialog(preference: Preference) {
when (preference) {
is ListPreference -> {
val prefIndex = preference.entryValues.indexOf(preference.value)
MaterialAlertDialogBuilder(requireContext())
.setTitle(preference.title)
.setSingleChoiceItems(preference.entries, prefIndex) { dialog, index ->
val newValue = preference.entryValues[index].toString()
if (preference.callChangeListener(newValue)) {
preference.value = newValue
}
dialog.dismiss()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
is EditTextPreference -> {
val view = layoutInflater.inflate(R.layout.preference_dialog_edittext_edited, null)
var message = ""
var hint = ""
if (preference.extras.getString("message") != null) {
message = preference.extras.getString("message")!!
}
if (preference.extras.getString("hint") != null) {
hint = preference.extras.getString("hint")!!
}
val messageView = view.findViewById<TextView>(android.R.id.message)
messageView.text = message
val editText = view.findViewById<TextInputEditText>(android.R.id.edit)
editText.setText(preference.text.toString())
editText.hint = hint
MaterialAlertDialogBuilder(requireContext())
.setTitle(preference.title)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
val newValue = editText.text.toString()
if (preference.callChangeListener(newValue)) {
preference.text = newValue
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
else -> super.onDisplayPreferenceDialog(preference)
}
}
}

View file

@ -1,48 +1,93 @@
package io.heckel.ntfy.ui
import android.content.Context
import android.graphics.Color
import android.os.Build
import androidx.core.content.ContextCompat
import com.google.android.material.color.MaterialColors
import io.heckel.ntfy.R
import io.heckel.ntfy.util.isDarkThemeOn
class Colors {
companion object {
val refreshProgressIndicator = R.color.teal
fun primary(context: Context): Int {
return MaterialColors.getColor(context, R.attr.colorPrimary, Color.GREEN)
}
fun onPrimary(context: Context): Int {
return MaterialColors.getColor(context, R.attr.colorOnPrimary, Color.GREEN)
}
fun notificationIcon(context: Context): Int {
return if (isDarkThemeOn(context)) R.color.teal_light else R.color.teal
return MaterialColors.getColor(context, R.attr.colorPrimary, Color.GREEN)
}
fun linkColor(context: Context): Int {
return MaterialColors.getColor(context, R.attr.colorPrimary, Color.GREEN)
}
fun itemSelectedBackground(context: Context): Int {
return if (isDarkThemeOn(context)) R.color.black_800b else R.color.gray_400
}
fun cardBackground(context: Context): Int {
return if (isDarkThemeOn(context)) R.color.black_800b else R.color.white
}
fun cardSelectedBackground(context: Context): Int {
return if (isDarkThemeOn(context)) R.color.black_700b else R.color.gray_500
return ContextCompat.getColor(context, R.color.md_theme_surfaceContainerHigh)
}
fun cardBackgroundColor(context: Context): Int {
return ContextCompat.getColor(context, cardBackground(context))
return if (isDarkThemeOn(context)) {
MaterialColors.getColor(context, R.attr.colorSurfaceContainer, Color.GRAY)
} else {
MaterialColors.getColor(context, R.attr.colorSurface, Color.WHITE)
}
}
fun cardSelectedBackgroundColor(context: Context): Int {
return ContextCompat.getColor(context, cardSelectedBackground(context))
return if (isDarkThemeOn(context)) {
MaterialColors.getColor(context, R.attr.colorSurfaceContainerHigh, Color.GRAY)
} else {
MaterialColors.getColor(context, R.attr.colorSurfaceContainerHighest, Color.GRAY)
}
}
fun statusBarNormal(context: Context): Int {
return if (isDarkThemeOn(context)) R.color.black_900 else R.color.teal
fun statusBarNormal(context: Context, dynamicColors: Boolean, darkMode: Boolean): Int {
val default = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getColor(R.color.action_bar, null)
} else {
@Suppress("DEPRECATION")
context.resources.getColor(R.color.action_bar)
}
return if (dynamicColors) {
// Use colorSurface for both light and dark mode when dynamic colors are enabled
MaterialColors.getColor(context, R.attr.colorSurface, default)
} else {
default
}
}
fun statusBarActionMode(context: Context): Int {
return if (isDarkThemeOn(context)) R.color.black_900 else R.color.teal_dark
fun shouldUseLightStatusBar(dynamicColors: Boolean, darkMode: Boolean): Boolean {
// Use light status bar (dark icons) when dynamic colors are enabled in light mode
return dynamicColors && !darkMode
}
fun toolbarTextColor(context: Context, dynamicColors: Boolean, darkMode: Boolean): Int {
return if (dynamicColors) {
// Use colorOnSurface (dark on light, light on dark) when dynamic colors are enabled
MaterialColors.getColor(context, R.attr.colorOnSurface, Color.BLACK)
} else {
if (darkMode) {
// In dark mode, toolbar is gray (surfaceContainer), so use light text
MaterialColors.getColor(context, R.attr.colorOnSurface, Color.WHITE)
} else {
// In light mode, toolbar is teal (primary), so use white text
MaterialColors.getColor(context, R.attr.colorOnPrimary, Color.WHITE)
}
}
}
fun dangerText(context: Context): Int {
return if (isDarkThemeOn(context)) R.color.red_light else R.color.red_dark
return MaterialColors.getColor(context, R.attr.colorError, Color.RED)
}
fun swipeToRefreshColor(context: Context): Int {
return MaterialColors.getColor(context, R.attr.colorPrimary, Color.GREEN)
}
}
}

View file

@ -10,7 +10,6 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -18,11 +17,15 @@ import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
@ -31,17 +34,30 @@ import io.heckel.ntfy.db.Notification
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.NotificationService
import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.*
import kotlinx.coroutines.*
import java.util.*
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.copyToClipboard
import io.heckel.ntfy.util.dangerButton
import io.heckel.ntfy.util.decodeMessage
import io.heckel.ntfy.util.displayName
import io.heckel.ntfy.util.formatDateShort
import io.heckel.ntfy.util.isDarkThemeOn
import io.heckel.ntfy.util.randomSubscriptionId
import io.heckel.ntfy.util.topicShortUrl
import io.heckel.ntfy.util.topicUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.util.Date
import kotlin.random.Random
import androidx.core.view.size
import androidx.core.view.get
class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFragment.NotificationSettingsListener {
class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSettingsListener {
private val viewModel by viewModels<DetailViewModel> {
DetailViewModelFactory((application as Application).repository)
}
@ -67,6 +83,36 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
// Action mode stuff
private var actionMode: ActionMode? = null
private val actionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
actionMode = mode
if (mode != null) {
mode.menuInflater.inflate(R.menu.menu_detail_action_mode, menu)
mode.title = "1" // One item selected
}
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.detail_action_mode_copy -> {
onMultiCopyClick()
true
}
R.id.detail_action_mode_delete -> {
onMultiDeleteClick()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
endActionModeAndRedraw()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -78,9 +124,47 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
notifier = NotificationService(this)
appBaseUrl = getString(R.string.app_base_url)
val toolbarLayout = findViewById<View>(R.id.app_bar_drawer)
val dynamicColors = repository.getDynamicColorsEnabled()
val darkMode = isDarkThemeOn(this)
val statusBarColor = Colors.statusBarNormal(this, dynamicColors, darkMode)
val toolbarTextColor = Colors.toolbarTextColor(this, dynamicColors, darkMode)
toolbarLayout.setBackgroundColor(statusBarColor)
val toolbar = toolbarLayout.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.toolbar)
toolbar.setTitleTextColor(toolbarTextColor)
toolbar.setNavigationIconTint(toolbarTextColor)
toolbar.overflowIcon?.setTint(toolbarTextColor)
setSupportActionBar(toolbar)
// Set system status bar color and appearance
window.statusBarColor = statusBarColor
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars =
Colors.shouldUseLightStatusBar(dynamicColors, darkMode)
// Set detail activity background: use theme background for dynamic colors, static gray for non-dynamic
val detailContentLayout = findViewById<View>(R.id.detail_content_layout)
if (repository.getDynamicColorsEnabled()) {
detailContentLayout.setBackgroundColor(
com.google.android.material.color.MaterialColors.getColor(
this,
android.R.attr.colorBackground,
ContextCompat.getColor(this, R.color.detail_activity_background)
)
)
} else {
detailContentLayout.setBackgroundColor(
ContextCompat.getColor(this, R.color.detail_activity_background)
)
}
// Show 'Back' button
supportActionBar?.setDisplayHomeAsUpEnabled(true)
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
val howToLink = findViewById<TextView>(R.id.detail_how_to_link)
howToLink.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE
// Handle direct deep links to topic "ntfy://..."
val url = intent?.data
if (intent?.action == ACTION_VIEW && url != null) {
@ -152,7 +236,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
@ -190,7 +274,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
// Swipe to refresh
mainListContainer = findViewById(R.id.detail_notification_list_container)
mainListContainer.setOnRefreshListener { refresh() }
mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator)
mainListContainer.setColorSchemeColors(Colors.swipeToRefreshColor(this))
// Update main list based on viewModel (& its datasource/livedata)
val noEntriesText: View = findViewById(R.id.detail_no_notifications)
@ -277,10 +361,11 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
val subscription = repository.getSubscription(subscriptionId) ?: return@launch
subscriptionInstant = subscription.instant
subscriptionMutedUntil = subscription.mutedUntil
subscriptionDisplayName = displayName(subscription)
subscriptionDisplayName = displayName(appBaseUrl, subscription)
showHideInstantMenuItems(subscriptionInstant)
showHideMutedUntilMenuItems(subscriptionMutedUntil)
showHideCopyMenuItems(subscription.baseUrl)
updateTitle(subscriptionDisplayName)
}
}
@ -312,10 +397,17 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_detail_action_bar, menu)
this.menu = menu
// Tint menu icons based on theme
val toolbarTextColor = Colors.toolbarTextColor(this, repository.getDynamicColorsEnabled(), isDarkThemeOn(this))
for (i in 0 until menu.size) {
menu[i].icon?.setTint(toolbarTextColor)
}
// Show and hide buttons
showHideInstantMenuItems(subscriptionInstant)
showHideMutedUntilMenuItems(subscriptionMutedUntil)
showHideCopyMenuItems(subscriptionBaseUrl)
// Regularly check if "notification muted" time has passed
// NOTE: This is done here, because then we know that we've initialized the menu items.
@ -559,6 +651,18 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
}
}
private fun showHideCopyMenuItems(subscriptionBaseUrl: String) {
if (!this::menu.isInitialized) {
return
}
runOnUiThread {
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
val copyUrlItem = menu.findItem(R.id.detail_menu_copy_url)
copyUrlItem?.isVisible = appBaseUrl != subscriptionBaseUrl || BuildConfig.PAYMENT_LINKS_AVAILABLE
}
}
private fun updateTitle(subscriptionDisplayName: String) {
runOnUiThread {
title = subscriptionDisplayName
@ -568,8 +672,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
private fun onClearClick() {
Log.d(TAG, "Clearing all notifications for ${topicShortUrl(subscriptionBaseUrl, subscriptionTopic)}")
val builder = AlertDialog.Builder(this)
val dialog = builder
val dialog = MaterialAlertDialogBuilder(this)
.setMessage(R.string.detail_clear_dialog_message)
.setPositiveButton(R.string.detail_clear_dialog_permanently_delete) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
@ -600,8 +703,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
private fun onDeleteClick() {
Log.d(TAG, "Deleting subscription ${topicShortUrl(subscriptionBaseUrl, subscriptionTopic)}")
val builder = AlertDialog.Builder(this)
val dialog = builder
val dialog = MaterialAlertDialogBuilder(this)
.setMessage(R.string.detail_delete_dialog_message)
.setPositiveButton(R.string.detail_delete_dialog_permanently_delete) { _, _ ->
Log.d(TAG, "Deleting subscription with subscription ID $subscriptionId (topic: $subscriptionTopic)")
@ -664,33 +766,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
}
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
this.actionMode = mode
if (mode != null) {
mode.menuInflater.inflate(R.menu.menu_detail_action_mode, menu)
mode.title = "1" // One item selected
}
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return when (item?.itemId) {
R.id.detail_action_mode_copy -> {
onMultiCopyClick()
true
}
R.id.detail_action_mode_delete -> {
onMultiDeleteClick()
true
}
else -> false
}
}
private fun onMultiCopyClick() {
Log.d(TAG, "Copying multiple notifications to clipboard")
@ -716,8 +791,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
private fun onMultiDeleteClick() {
Log.d(TAG, "Showing multi-delete dialog for selected items")
val builder = AlertDialog.Builder(this)
val dialog = builder
val dialog = MaterialAlertDialogBuilder(this)
.setMessage(R.string.detail_action_mode_delete_dialog_message)
.setPositiveButton(R.string.detail_action_mode_delete_dialog_permanently_delete) { _, _ ->
adapter.selected.map { notificationId -> viewModel.markAsDeleted(notificationId) }
@ -735,18 +809,9 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
dialog.show()
}
override fun onDestroyActionMode(mode: ActionMode?) {
endActionModeAndRedraw()
}
private fun beginActionMode(notification: Notification) {
actionMode = startActionMode(this)
actionMode = startSupportActionMode(actionModeCallback)
adapter.toggleSelection(notification.id)
// Fade status bar color
val fromColor = ContextCompat.getColor(this, Colors.statusBarNormal(this))
val toColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this))
fadeStatusBarColor(window, fromColor, toColor)
}
private fun finishActionMode() {
@ -758,11 +823,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
actionMode = null
adapter.selected.clear()
adapter.notifyItemRangeChanged(0, adapter.currentList.size)
// Fade status bar color
val fromColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this))
val toColor = ContextCompat.getColor(this, Colors.statusBarNormal(this))
fadeStatusBarColor(window, fromColor, toColor)
}
companion object {

View file

@ -15,9 +15,11 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.*
import androidx.preference.Preference.OnPreferenceClickListener
import com.google.android.material.appbar.AppBarLayout
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
import io.heckel.ntfy.db.Repository
@ -63,6 +65,24 @@ class DetailSettingsActivity : AppCompatActivity() {
.commit()
}
val toolbarLayout = findViewById<AppBarLayout>(R.id.app_bar_drawer)
val dynamicColors = repository.getDynamicColorsEnabled()
val darkMode = isDarkThemeOn(this)
val statusBarColor = Colors.statusBarNormal(this, dynamicColors, darkMode)
val toolbarTextColor = Colors.toolbarTextColor(this, dynamicColors, darkMode)
toolbarLayout.setBackgroundColor(statusBarColor)
val toolbar = toolbarLayout.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.toolbar)
toolbar.setTitleTextColor(toolbarTextColor)
toolbar.setNavigationIconTint(toolbarTextColor)
toolbar.overflowIcon?.setTint(toolbarTextColor)
setSupportActionBar(toolbar)
// Set system status bar color and appearance
window.statusBarColor = statusBarColor
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars =
Colors.shouldUseLightStatusBar(dynamicColors, darkMode)
// Title
val displayName = intent.getStringExtra(DetailActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME) ?: return
title = displayName
@ -87,6 +107,7 @@ class DetailSettingsActivity : AppCompatActivity() {
private lateinit var openChannelsPref: Preference
private lateinit var iconSetLauncher: ActivityResultLauncher<String>
private lateinit var iconRemovePref: Preference
private lateinit var appBaseUrl: String
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.detail_preferences, rootKey)
@ -96,6 +117,7 @@ class DetailSettingsActivity : AppCompatActivity() {
serviceManager = SubscriberServiceManager(requireActivity())
notificationService = NotificationService(requireActivity())
resolver = requireContext().applicationContext.contentResolver
appBaseUrl = requireContext().getString(R.string.app_base_url)
// Create result launcher for custom icon (must be created in onCreatePreferences() directly)
iconSetLauncher = createIconPickLauncher()
@ -137,7 +159,7 @@ class DetailSettingsActivity : AppCompatActivity() {
private fun loadInstantPref() {
val appBaseUrl = getString(R.string.app_base_url)
val prefId = context?.getString(R.string.detail_settings_notifications_instant_key) ?: return
val pref: SwitchPreference? = findPreference(prefId)
val pref: SwitchPreferenceCompat? = findPreference(prefId)
pref?.isVisible = BuildConfig.FIREBASE_AVAILABLE && subscription.baseUrl == appBaseUrl
pref?.isChecked = subscription.instant
pref?.preferenceDataStore = object : PreferenceDataStore() {
@ -148,7 +170,7 @@ class DetailSettingsActivity : AppCompatActivity() {
return subscription.instant
}
}
pref?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { preference ->
pref?.summaryProvider = Preference.SummaryProvider<SwitchPreferenceCompat> { preference ->
if (preference.isChecked) {
getString(R.string.detail_settings_notifications_instant_summary_on)
} else {
@ -159,7 +181,7 @@ class DetailSettingsActivity : AppCompatActivity() {
private fun loadDedicatedChannelsPrefs() {
val prefId = context?.getString(R.string.detail_settings_notifications_dedicated_channels_key) ?: return
val pref: SwitchPreference? = findPreference(prefId)
val pref: SwitchPreferenceCompat? = findPreference(prefId)
pref?.isVisible = true
pref?.isChecked = subscription.dedicatedChannels
pref?.preferenceDataStore = object : PreferenceDataStore() {
@ -176,7 +198,7 @@ class DetailSettingsActivity : AppCompatActivity() {
return subscription.dedicatedChannels
}
}
pref?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { preference ->
pref?.summaryProvider = Preference.SummaryProvider<SwitchPreferenceCompat> { preference ->
if (preference.isChecked) {
getString(R.string.detail_settings_notifications_dedicated_channels_summary_on)
} else {
@ -381,7 +403,7 @@ class DetailSettingsActivity : AppCompatActivity() {
save(newSubscription)
// Update activity title
activity?.runOnUiThread {
activity?.title = displayName(newSubscription)
activity?.title = displayName(appBaseUrl, newSubscription)
}
// Update dedicated notification channel
if (newSubscription.dedicatedChannels) {
@ -394,9 +416,10 @@ class DetailSettingsActivity : AppCompatActivity() {
}
pref?.summaryProvider = Preference.SummaryProvider<EditTextPreference> { provider ->
if (TextUtils.isEmpty(provider.text)) {
val appBaseUrl = context?.getString(R.string.app_base_url)
getString(
R.string.detail_settings_appearance_display_name_default_summary,
displayName(subscription)
displayName(appBaseUrl, subscription)
)
} else {
provider.text

View file

@ -14,7 +14,6 @@ import android.os.Bundle
import android.provider.Settings
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import android.text.method.LinkMovementMethod
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -24,12 +23,25 @@ import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.view.ActionMode
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.work.*
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
@ -43,17 +55,28 @@ import io.heckel.ntfy.msg.DownloadType
import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.*
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.dangerButton
import io.heckel.ntfy.util.displayName
import io.heckel.ntfy.util.formatDateShort
import io.heckel.ntfy.util.isDarkThemeOn
import io.heckel.ntfy.util.isIgnoringBatteryOptimizations
import io.heckel.ntfy.util.maybeSplitTopicUrl
import io.heckel.ntfy.util.randomSubscriptionId
import io.heckel.ntfy.util.shortUrl
import io.heckel.ntfy.util.topicShortUrl
import io.heckel.ntfy.work.DeleteWorker
import io.heckel.ntfy.work.PollWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlin.random.Random
import androidx.core.view.size
import androidx.core.view.get
class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.SubscribeListener, NotificationFragment.NotificationSettingsListener {
class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, NotificationFragment.NotificationSettingsListener {
private val viewModel by viewModels<SubscriptionsViewModel> {
SubscriptionsViewModelFactory((application as Application).repository)
}
@ -69,11 +92,39 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
private lateinit var fab: FloatingActionButton
// Other stuff
private var actionMode: ActionMode? = null
private var workManager: WorkManager? = null // Context-dependent
private var dispatcher: NotificationDispatcher? = null // Context-dependent
private var appBaseUrl: String? = null // Context-dependent
// Action mode stuff
private var actionMode: ActionMode? = null
private val actionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
actionMode = mode
if (mode != null) {
mode.menuInflater.inflate(R.menu.menu_main_action_mode, menu)
mode.title = "1" // One item selected
}
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.main_action_mode_delete -> {
onMultiDeleteClick()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
endActionModeAndRedraw()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@ -87,18 +138,44 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
appBaseUrl = getString(R.string.app_base_url)
// Action bar
val toolbarLayout = findViewById<AppBarLayout>(R.id.app_bar_drawer)
val dynamicColors = repository.getDynamicColorsEnabled()
val darkMode = isDarkThemeOn(this)
val statusBarColor = Colors.statusBarNormal(this, dynamicColors, darkMode)
val toolbarTextColor = Colors.toolbarTextColor(this, dynamicColors, darkMode)
toolbarLayout.setBackgroundColor(statusBarColor)
val toolbar = toolbarLayout.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.toolbar)
toolbar.setTitleTextColor(toolbarTextColor)
toolbar.setNavigationIconTint(toolbarTextColor)
toolbar.overflowIcon?.setTint(toolbarTextColor)
setSupportActionBar(toolbar)
title = getString(R.string.main_action_bar_title)
// Set system status bar color and appearance
window.statusBarColor = statusBarColor
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars =
Colors.shouldUseLightStatusBar(dynamicColors, darkMode)
// Floating action button ("+")
fab = findViewById(R.id.fab)
fab.setOnClickListener {
onSubscribeButtonClick()
}
// Add bottom padding to FAB to account for navigation bar
ViewCompat.setOnApplyWindowInsetsListener(fab) { view, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
val layoutParams = view.layoutParams as androidx.constraintlayout.widget.ConstraintLayout.LayoutParams
layoutParams.bottomMargin = systemBars.bottom
view.layoutParams = layoutParams
insets
}
// Swipe to refresh
mainListContainer = findViewById(R.id.main_subscriptions_list_container)
mainListContainer.setOnRefreshListener { refreshAllSubscriptions() }
mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator)
mainListContainer.setColorSchemeColors(Colors.swipeToRefreshColor(this))
// Update main list based on viewModel (& its datasource/livedata)
val noEntries: View = findViewById(R.id.main_no_subscriptions)
@ -106,7 +183,15 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
val onSubscriptionLongClick = { s: Subscription -> onSubscriptionItemLongClick(s) }
mainList = findViewById(R.id.main_subscriptions_list)
adapter = MainAdapter(repository, onSubscriptionClick, onSubscriptionLongClick)
adapter = MainAdapter(
repository,
onSubscriptionClick,
onSubscriptionLongClick,
ResourcesCompat.getDrawable(resources, R.drawable.ic_circle, theme)!!.apply {
setTint(Colors.primary(this@MainActivity))
},
Colors.onPrimary(this)
)
mainList.adapter = adapter
viewModel.list().observe(this) {
@ -244,6 +329,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
}
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
val howToLink = findViewById<TextView>(R.id.main_how_to_link)
howToLink.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE
// Create notification channels right away, so we can configure them immediately after installing the app
dispatcher?.init()
@ -293,7 +382,19 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
val wsRemindTimeReached = repository.getWebSocketRemindTime() < System.currentTimeMillis()
val showBanner = hasSelfHostedSubscriptions && wsRemindTimeReached && !usingWebSockets
val wsBanner = findViewById<View>(R.id.main_banner_websocket)
wsBanner.visibility = if (showBanner) View.VISIBLE else View.GONE
if (showBanner) {
wsBanner.visibility = View.VISIBLE
if (!BuildConfig.PAYMENT_LINKS_AVAILABLE) {
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
// This is a big fat hack, but I have to release this quickly ...
val wsBannerMainText = findViewById<TextView>(R.id.main_banner_websocket_text)
val raw = getString(R.string.main_banner_websocket_text)
val unlinked = raw.replace(Regex("</?a[^>]*>"), "")
wsBannerMainText.text = HtmlCompat.fromHtml(unlinked, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
} else {
wsBanner.visibility = View.GONE
}
}
private fun showHideWebSocketReconnectBanner(subscriptions: List<Subscription>) {
@ -372,6 +473,13 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main_action_bar, menu)
this.menu = menu
// Tint menu icons based on theme
val toolbarTextColor = Colors.toolbarTextColor(this, repository.getDynamicColorsEnabled(), isDarkThemeOn(this))
for (i in 0 until menu.size) {
menu[i].icon?.setTint(toolbarTextColor)
}
showHideNotificationMenuItems()
checkSubscriptionsMuted() // This is done here, because then we know that we've initialized the menu
return true
@ -412,9 +520,13 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
val mutedUntilSeconds = repository.getGlobalMutedUntil()
runOnUiThread {
// Show/hide in-app rate widget
// Show/hide menu items based on build config
val rateAppItem = menu.findItem(R.id.main_menu_rate)
val docsItem = menu.findItem(R.id.main_menu_docs)
val reportBugItem = menu.findItem(R.id.main_menu_report_bug)
rateAppItem.isVisible = BuildConfig.RATE_APP_AVAILABLE
docsItem.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE // Google Payments Policy, see https://github.com/binwiederhier/ntfy/issues/1463
reportBugItem.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE // Google Payments Policy, see https://github.com/binwiederhier/ntfy/issues/1463
// Pause notification icons
val notificationsEnabledItem = menu.findItem(R.id.main_menu_notifications_enabled)
@ -460,10 +572,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
true
}
R.id.main_menu_donate -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_donate_url))))
true
}
R.id.main_menu_docs -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_docs_url))))
true
@ -591,7 +699,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
}
} catch (e: Exception) {
val topic = displayName(subscription)
val topic = displayName(appBaseUrl, subscription)
if (errorMessage == "") errorMessage = "$topic: ${e.message}"
errors++
}
@ -618,7 +726,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
intent.putExtra(EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
intent.putExtra(EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
intent.putExtra(EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
intent.putExtra(EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
startActivity(intent)
@ -631,11 +739,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(subscription))
intent.putExtra(DetailActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription))
startActivity(intent)
}
private fun handleActionModeClick(subscription: Subscription) {
adapter.toggleSelection(subscription.id)
if (adapter.selected.size == 0) {
@ -645,34 +752,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
this.actionMode = mode
if (mode != null) {
mode.menuInflater.inflate(R.menu.menu_main_action_mode, menu)
mode.title = "1" // One item selected
}
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return when (item?.itemId) {
R.id.main_action_mode_delete -> {
onMultiDeleteClick()
true
}
else -> false
}
}
private fun onMultiDeleteClick() {
Log.d(DetailActivity.TAG, "Showing multi-delete dialog for selected items")
val builder = AlertDialog.Builder(this)
val dialog = builder
val dialog = MaterialAlertDialogBuilder(this)
.setMessage(R.string.main_action_mode_delete_dialog_message)
.setPositiveButton(R.string.main_action_mode_delete_dialog_permanently_delete) { _, _ ->
adapter.selected.map { subscriptionId -> viewModel.remove(this, subscriptionId) }
@ -690,15 +773,11 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
dialog.show()
}
override fun onDestroyActionMode(mode: ActionMode?) {
endActionModeAndRedraw()
}
private fun beginActionMode(subscription: Subscription) {
actionMode = startActionMode(this)
actionMode = startSupportActionMode(actionModeCallback)
adapter.toggleSelection(subscription.id)
// Fade out FAB
// Fade out FAB
fab.alpha = 1f
fab
.animate()
@ -709,11 +788,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
fab.visibility = View.GONE
}
})
// Fade status bar color
val fromColor = ContextCompat.getColor(this, Colors.statusBarNormal(this))
val toColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this))
fadeStatusBarColor(window, fromColor, toColor)
}
private fun finishActionMode() {
@ -738,11 +812,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
fab.visibility = View.VISIBLE // Required to replace the old listener
}
})
// Fade status bar color
val fromColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this))
val toColor = ContextCompat.getColor(this, Colors.statusBarNormal(this))
fadeStatusBarColor(window, fromColor, toColor)
}
private fun redrawList() {

View file

@ -2,6 +2,7 @@ package io.heckel.ntfy.ui
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -20,7 +21,13 @@ import io.heckel.ntfy.util.readBitmapFromUriOrNull
import java.text.DateFormat
import java.util.*
class MainAdapter(private val repository: Repository, private val onClick: (Subscription) -> Unit, private val onLongClick: (Subscription) -> Unit) :
class MainAdapter(
private val repository: Repository,
private val onClick: (Subscription) -> Unit,
private val onLongClick: (Subscription) -> Unit,
private val countDrawable: Drawable,
private val onPrimaryColor: Int
) :
ListAdapter<Subscription, MainAdapter.SubscriptionViewHolder>(TopicDiffCallback) {
val selected = mutableSetOf<Long>() // Subscription IDs
@ -28,7 +35,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_main_item, parent, false)
return SubscriptionViewHolder(view, repository, selected, onClick, onLongClick)
return SubscriptionViewHolder(view, repository, selected, onClick, onLongClick, countDrawable, onPrimaryColor)
}
/* Gets current topic and uses it to bind view. */
@ -52,7 +59,15 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
}
/* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
class SubscriptionViewHolder(itemView: View, private val repository: Repository, private val selected: Set<Long>, val onClick: (Subscription) -> Unit, val onLongClick: (Subscription) -> Unit) :
class SubscriptionViewHolder(
itemView: View,
private val repository: Repository,
private val selected: Set<Long>,
val onClick: (Subscription) -> Unit,
val onLongClick: (Subscription) -> Unit,
private val countDrawable: Drawable,
private val onPrimaryColor: Int
) :
RecyclerView.ViewHolder(itemView) {
private var subscription: Subscription? = null
private val context: Context = itemView.context
@ -64,6 +79,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
private val notificationDisabledForeverImageView: View = itemView.findViewById(R.id.main_item_notification_disabled_forever_image)
private val instantImageView: View = itemView.findViewById(R.id.main_item_instant_image)
private val newItemsView: TextView = itemView.findViewById(R.id.main_item_new)
private val appBaseUrl = context.getString(R.string.app_base_url)
fun bind(subscription: Subscription) {
this.subscription = subscription
@ -99,7 +115,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
} else {
imageView.setImageResource(R.drawable.ic_sms_gray_24dp)
}
nameView.text = displayName(subscription)
nameView.text = displayName(appBaseUrl, subscription)
statusView.text = statusMessage
dateView.text = dateText
dateView.visibility = if (isUnifiedPush) View.GONE else View.VISIBLE
@ -111,11 +127,13 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
} else {
newItemsView.visibility = View.VISIBLE
newItemsView.text = if (subscription.newCount <= 99) subscription.newCount.toString() else "99+"
newItemsView.setTextColor(onPrimaryColor)
newItemsView.background = countDrawable
}
itemView.setOnClickListener { onClick(subscription) }
itemView.setOnLongClickListener { onLongClick(subscription); true }
if (selected.contains(subscription.id)) {
itemView.setBackgroundResource(Colors.itemSelectedBackground(context))
itemView.setBackgroundColor(Colors.itemSelectedBackground(context))
} else {
itemView.setBackgroundColor(Color.TRANSPARENT)
}

View file

@ -7,6 +7,7 @@ import android.os.Bundle
import android.widget.RadioButton
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.heckel.ntfy.R
import io.heckel.ntfy.db.Repository
import kotlinx.coroutines.Dispatchers
@ -74,7 +75,7 @@ class NotificationFragment : DialogFragment() {
muteForeverButton = view.findViewById(R.id.notification_dialog_forever)
muteForeverButton.setOnClickListener{ onClick(Repository.MUTED_UNTIL_FOREVER) }
return AlertDialog.Builder(activity)
return MaterialAlertDialogBuilder(requireContext())
.setView(view)
.create()
}

View file

@ -2,7 +2,6 @@ package io.heckel.ntfy.ui
import android.Manifest
import android.app.AlarmManager
import android.app.AlertDialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
@ -13,6 +12,7 @@ import android.os.Bundle
import android.provider.Settings
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@ -21,10 +21,12 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.*
import androidx.preference.Preference.OnPreferenceClickListener
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
@ -65,6 +67,28 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
repository = Repository.getInstance(this)
serviceManager = SubscriberServiceManager(this)
val toolbarLayout = findViewById<View>(R.id.app_bar_drawer)
val dynamicColors = repository.getDynamicColorsEnabled()
val darkMode = isDarkThemeOn(this)
val statusBarColor = Colors.statusBarNormal(
this,
dynamicColors,
darkMode
)
val toolbarTextColor = Colors.toolbarTextColor(this, dynamicColors, darkMode)
toolbarLayout.setBackgroundColor(statusBarColor)
val toolbar = toolbarLayout.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.toolbar)
toolbar.setTitleTextColor(toolbarTextColor)
toolbar.setNavigationIconTint(toolbarTextColor)
toolbar.overflowIcon?.setTint(toolbarTextColor)
setSupportActionBar(toolbar)
// Set system status bar color and appearance
window.statusBarColor = statusBarColor
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars =
Colors.shouldUseLightStatusBar(dynamicColors, darkMode)
if (savedInstanceState == null) {
settingsFragment = SettingsFragment() // Empty constructor!
supportFragmentManager
@ -128,7 +152,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
}
}
class SettingsFragment : PreferenceFragmentCompat() {
class SettingsFragment : BasePreferenceFragment() {
private lateinit var repository: Repository
private lateinit var serviceManager: SubscriberServiceManager
private var autoDownloadSelection = AUTO_DOWNLOAD_SELECTION_NOT_SET
@ -211,7 +235,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
// Keep alerting for max priority
val insistentMaxPriorityPrefId = context?.getString(R.string.settings_notifications_insistent_max_priority_key) ?: return
val insistentMaxPriority: SwitchPreference? = findPreference(insistentMaxPriorityPrefId)
val insistentMaxPriority: SwitchPreferenceCompat? = findPreference(insistentMaxPriorityPrefId)
insistentMaxPriority?.isChecked = repository.getInsistentMaxPriorityEnabled()
insistentMaxPriority?.preferenceDataStore = object : PreferenceDataStore() {
override fun putBoolean(key: String?, value: Boolean) {
@ -221,7 +245,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
return repository.getInsistentMaxPriorityEnabled()
}
}
insistentMaxPriority?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { pref ->
insistentMaxPriority?.summaryProvider = Preference.SummaryProvider<SwitchPreferenceCompat> { pref ->
if (pref.isChecked) {
getString(R.string.settings_notifications_insistent_max_priority_summary_enabled)
} else {
@ -324,11 +348,45 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
}
}
// Dynamic colors
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val dynamicColorsEnabledPrefId = context?.getString(R.string.settings_general_dynamic_colors_key) ?: return
val dynamicColorsEnabled: SwitchPreferenceCompat? = findPreference(dynamicColorsEnabledPrefId)
dynamicColorsEnabled?.isChecked = repository.getDynamicColorsEnabled()
dynamicColorsEnabled?.preferenceDataStore = object : PreferenceDataStore() {
override fun putBoolean(key: String?, value: Boolean) {
repository.setDynamicColorsEnabled(value)
// Restart app
val packageManager = requireContext().packageManager
val packageName = requireContext().packageName
val intent = packageManager.getLaunchIntentForPackage(packageName)
val componentName = intent!!.component
val mainIntent = Intent.makeRestartActivityTask(componentName)
startActivity(mainIntent)
Runtime.getRuntime().exit(0)
}
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
return repository.getDynamicColorsEnabled()
}
}
dynamicColorsEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreferenceCompat> { pref ->
if (pref.isChecked) {
getString(R.string.settings_general_dynamic_colors_summary_enabled)
} else {
getString(R.string.settings_general_dynamic_colors_summary_disabled)
}
}
dynamicColorsEnabled?.isVisible = true
}
// Default Base URL
val appBaseUrl = getString(R.string.app_base_url)
val defaultBaseUrlPrefId = context?.getString(R.string.settings_general_default_base_url_key) ?: return
val defaultBaseUrl: EditTextPreference? = findPreference(defaultBaseUrlPrefId)
defaultBaseUrl?.text = repository.getDefaultBaseUrl() ?: ""
defaultBaseUrl?.extras?.putString("message", getString(R.string.settings_general_default_base_url_message))
defaultBaseUrl?.extras?.putString("hint", getString(R.string.app_base_url))
defaultBaseUrl?.preferenceDataStore = object : PreferenceDataStore() {
override fun putString(key: String, value: String?) {
val baseUrl = value ?: return
@ -355,7 +413,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
// Broadcast enabled
val broadcastEnabledPrefId = context?.getString(R.string.settings_advanced_broadcast_key) ?: return
val broadcastEnabled: SwitchPreference? = findPreference(broadcastEnabledPrefId)
val broadcastEnabled: SwitchPreferenceCompat? = findPreference(broadcastEnabledPrefId)
broadcastEnabled?.isChecked = repository.getBroadcastEnabled()
broadcastEnabled?.preferenceDataStore = object : PreferenceDataStore() {
override fun putBoolean(key: String?, value: Boolean) {
@ -365,7 +423,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
return repository.getBroadcastEnabled()
}
}
broadcastEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { pref ->
broadcastEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreferenceCompat> { pref ->
if (pref.isChecked) {
getString(R.string.settings_advanced_broadcast_summary_enabled)
} else {
@ -375,7 +433,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
// Enable UnifiedPush
val unifiedPushEnabledPrefId = context?.getString(R.string.settings_advanced_unifiedpush_key) ?: return
val unifiedPushEnabled: SwitchPreference? = findPreference(unifiedPushEnabledPrefId)
val unifiedPushEnabled: SwitchPreferenceCompat? = findPreference(unifiedPushEnabledPrefId)
unifiedPushEnabled?.isChecked = repository.getUnifiedPushEnabled()
unifiedPushEnabled?.preferenceDataStore = object : PreferenceDataStore() {
override fun putBoolean(key: String?, value: Boolean) {
@ -385,7 +443,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
return repository.getUnifiedPushEnabled()
}
}
unifiedPushEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { pref ->
unifiedPushEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreferenceCompat> { pref ->
if (pref.isChecked) {
getString(R.string.settings_advanced_unifiedpush_summary_enabled)
} else {
@ -420,7 +478,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
// Record logs
val recordLogsPrefId = context?.getString(R.string.settings_advanced_record_logs_key) ?: return
val recordLogsEnabled: SwitchPreference? = findPreference(recordLogsPrefId)
val recordLogsEnabled: SwitchPreferenceCompat? = findPreference(recordLogsPrefId)
recordLogsEnabled?.isChecked = Log.getRecord()
recordLogsEnabled?.preferenceDataStore = object : PreferenceDataStore() {
override fun putBoolean(key: String?, value: Boolean) {
@ -433,7 +491,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
return Log.getRecord()
}
}
recordLogsEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { pref ->
recordLogsEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreferenceCompat> { pref ->
if (pref.isChecked) {
getString(R.string.settings_advanced_record_logs_summary_enabled)
} else {
@ -670,7 +728,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
} else {
getString(R.string.settings_advanced_export_logs_scrub_dialog_empty)
}
val dialog = AlertDialog.Builder(activity)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setMessage(scrubbedText)
.setPositiveButton(R.string.settings_advanced_export_logs_scrub_dialog_button_ok) { _, _ -> /* Nothing */ }
@ -711,7 +769,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
data class NopasteResponse(val url: String)
}
class UserSettingsFragment : PreferenceFragmentCompat() {
class UserSettingsFragment : BasePreferenceFragment() {
private lateinit var repository: Repository
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View file

@ -9,6 +9,7 @@ import android.text.TextWatcher
import android.view.*
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputLayout
@ -18,6 +19,8 @@ import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import androidx.core.view.size
import androidx.core.view.get
class ShareActivity : AppCompatActivity() {
private val repository by lazy { (application as Application).repository }
@ -55,7 +58,24 @@ class ShareActivity : AppCompatActivity() {
Log.d(TAG, "Create $this with intent $intent")
// Action bar
val toolbarLayout = findViewById<View>(R.id.app_bar_drawer)
val dynamicColors = repository.getDynamicColorsEnabled()
val darkMode = isDarkThemeOn(this)
val statusBarColor = Colors.statusBarNormal(this, dynamicColors, darkMode)
val toolbarTextColor = Colors.toolbarTextColor(this, dynamicColors, darkMode)
toolbarLayout.setBackgroundColor(statusBarColor)
val toolbar = toolbarLayout.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.toolbar)
toolbar.setTitleTextColor(toolbarTextColor)
toolbar.setNavigationIconTint(toolbarTextColor)
toolbar.overflowIcon?.setTint(toolbarTextColor)
setSupportActionBar(toolbar)
title = getString(R.string.share_title)
// Set system status bar color and appearance
window.statusBarColor = statusBarColor
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars =
Colors.shouldUseLightStatusBar(dynamicColors, darkMode)
// Show 'Back' button
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -235,6 +255,13 @@ class ShareActivity : AppCompatActivity() {
menuInflater.inflate(R.menu.menu_share_action_bar, menu)
this.menu = menu
sendItem = menu.findItem(R.id.share_menu_send)
// Tint menu icons based on theme
val toolbarTextColor = Colors.toolbarTextColor(this, repository.getDynamicColorsEnabled(), isDarkThemeOn(this))
for (i in 0 until menu.size) {
menu[i].icon?.setTint(toolbarTextColor)
}
validateInput() // Disable icon
return true
}

View file

@ -9,7 +9,9 @@ import android.view.WindowManager
import android.widget.Button
import android.widget.TextView
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import io.heckel.ntfy.R
import io.heckel.ntfy.db.User
import io.heckel.ntfy.util.AfterChangedTextWatcher
@ -21,6 +23,7 @@ class UserFragment : DialogFragment() {
private lateinit var baseUrlsInUse: ArrayList<String>
private lateinit var listener: UserDialogListener
private lateinit var baseUrlViewLayout: TextInputLayout
private lateinit var baseUrlView: TextInputEditText
private lateinit var usernameView: TextInputEditText
private lateinit var passwordView: TextInputEditText
@ -54,9 +57,10 @@ class UserFragment : DialogFragment() {
val view = requireActivity().layoutInflater.inflate(R.layout.fragment_user_dialog, null)
val positiveButtonTextResId = if (user == null) R.string.user_dialog_button_add else R.string.user_dialog_button_save
val titleView = view.findViewById(R.id.user_dialog_title) as TextView
val descriptionView = view.findViewById(R.id.user_dialog_description) as TextView
val titleView = view.findViewById<TextView>(R.id.user_dialog_title)
val descriptionView = view.findViewById<TextView>(R.id.user_dialog_description)
baseUrlViewLayout = view.findViewById(R.id.user_dialog_base_url_layout)
baseUrlView = view.findViewById(R.id.user_dialog_base_url)
usernameView = view.findViewById(R.id.user_dialog_username)
passwordView = view.findViewById(R.id.user_dialog_password)
@ -64,18 +68,18 @@ class UserFragment : DialogFragment() {
if (user == null) {
titleView.text = getString(R.string.user_dialog_title_add)
descriptionView.text = getString(R.string.user_dialog_description_add)
baseUrlView.visibility = View.VISIBLE
baseUrlViewLayout.visibility = View.VISIBLE
passwordView.hint = getString(R.string.user_dialog_password_hint_add)
} else {
titleView.text = getString(R.string.user_dialog_title_edit)
descriptionView.text = getString(R.string.user_dialog_description_edit)
baseUrlView.visibility = View.GONE
baseUrlViewLayout.visibility = View.GONE
usernameView.setText(user!!.username)
passwordView.hint = getString(R.string.user_dialog_password_hint_edit)
}
// Build dialog
val builder = AlertDialog.Builder(activity)
val builder = MaterialAlertDialogBuilder(requireContext())
.setView(view)
.setPositiveButton(positiveButtonTextResId) { _, _ ->
saveClicked()

View file

@ -13,9 +13,8 @@ const val ACTION_MESSAGE = "org.unifiedpush.android.connector.MESSAGE"
const val ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER"
const val ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER"
const val FEATURE_BYTES_MESSAGE = "org.unifiedpush.android.distributor.feature.BYTES_MESSAGE"
const val EXTRA_APPLICATION = "application"
const val EXTRA_PI = "pi"
const val EXTRA_TOKEN = "token"
const val EXTRA_ENDPOINT = "endpoint"
const val EXTRA_MESSAGE = "message"

View file

@ -0,0 +1,36 @@
package io.heckel.ntfy.up
import android.app.Activity
import android.app.PendingIntent
import android.content.Intent
import android.os.Bundle
import android.util.Log
/**
* This implements the "Select default distributor" selection for UnifiedPush.
*
* To test, install ntfy and another distributor (e.g. SunUp) on the same phone.
* Install an app that uses UnifiedPush (e.g. UP Example) and click "Register".
*
* You should see a popup to select the default distributor.
* See https://unifiedpush.org/developers/spec/android/#link-activity
*/
class LinkActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent?.data?.run {
Log.d(TAG, "Received request for $callingPackage")
val intent = Intent("org.unifiedpush.register.dummy_app")
val pendingIntent = PendingIntent.getBroadcast(this@LinkActivity, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val result = Intent().apply {
putExtra(EXTRA_PI, pendingIntent)
}
setResult(RESULT_OK, result)
} ?: setResult(RESULT_CANCELED)
finish()
}
companion object {
private val TAG = LinkActivity::class.simpleName
}
}

View file

@ -1,12 +1,10 @@
package io.heckel.ntfy.util
import android.content.Context
import android.graphics.Paint
import android.graphics.Typeface
import android.text.style.*
import android.text.util.Linkify
import androidx.core.content.ContextCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.ui.Colors
import io.noties.markwon.*
import io.noties.markwon.core.CorePlugin
import io.noties.markwon.core.CoreProps
@ -36,7 +34,7 @@ internal object MarkwonFactory {
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureTheme(builder: MarkwonTheme.Builder) {
builder
.linkColor(ContextCompat.getColor(context, R.color.teal))
.linkColor(Colors.linkColor(context))
.isLinkUnderlined(true)
}

View file

@ -1,7 +1,5 @@
package io.heckel.ntfy.util
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ContentResolver
@ -20,11 +18,9 @@ import android.text.Editable
import android.text.TextWatcher
import android.util.Base64
import android.view.View
import android.view.Window
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.db.ACTION_PROGRESS_FAILED
import io.heckel.ntfy.db.ACTION_PROGRESS_ONGOING
@ -68,8 +64,13 @@ fun subscriptionTopicShortUrl(subscription: Subscription) : String {
return topicShortUrl(subscription.baseUrl, subscription.topic)
}
fun displayName(subscription: Subscription) : String {
return subscription.displayName ?: subscriptionTopicShortUrl(subscription)
fun displayName(appBaseUrl: String?, subscription: Subscription) : String {
if (subscription.displayName != null) {
return subscription.displayName
} else if (appBaseUrl == subscription.baseUrl) {
return subscription.topic
}
return subscriptionTopicShortUrl(subscription)
}
fun shortUrl(url: String) = url
@ -190,11 +191,11 @@ fun decodeBytesMessage(notification: Notification): ByteArray {
* See above; prepend emojis to title if the title is non-empty.
* Otherwise, they are prepended to the message.
*/
fun formatTitle(subscription: Subscription, notification: Notification): String {
fun formatTitle(appBaseUrl: String?, subscription: Subscription, notification: Notification): String {
return if (notification.title != "") {
formatTitle(notification)
} else {
displayName(subscription)
displayName(appBaseUrl, subscription)
}
}
@ -276,16 +277,6 @@ data class FileInfo(
val size: Long,
)
// Status bar color fading to match action bar, see https://stackoverflow.com/q/51150077/1440785
fun fadeStatusBarColor(window: Window, fromColor: Int, toColor: Int) {
val statusBarColorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), fromColor, toColor)
statusBarColorAnimation.addUpdateListener { animator ->
val color = animator.animatedValue as Int
window.statusBarColor = color
}
statusBarColorAnimation.start()
}
// Generates a (cryptographically secure) random string of a certain length
fun randomString(len: Int): String {
val random = SecureRandom()
@ -344,10 +335,7 @@ fun supportedImage(mimeType: String?): Boolean {
// Play didn't grant us the permission, and F-Droid users didn't want us to have it.
// See https://github.com/binwiederhier/ntfy/issues/531 & https://github.com/binwiederhier/ntfy/issues/684
fun canOpenAttachment(attachment: Attachment?): Boolean {
if (attachment?.type == ANDROID_APP_MIME_TYPE) {
return false
}
return true
return attachment?.type != ANDROID_APP_MIME_TYPE
}
// Check if battery optimization is enabled, see https://stackoverflow.com/a/49098293/1440785
@ -507,11 +495,10 @@ 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)))
setTextColor(Colors.dangerText(context))
}
}
fun Long.nullIfZero(): Long? {
return if (this == 0L) return null else this
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/decelerate_cubic">
<translate
android:duration="300"
android:fromYDelta="100%"
android:toYDelta="0" />
</set>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/accelerate_cubic">
<translate
android:duration="250"
android:fromYDelta="0"
android:toYDelta="100%" />
</set>

View file

@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View file

@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View file

@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View file

@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View file

@ -1,12 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.DetailActivity"
>
<include
android:id="@+id/app_bar_drawer"
layout="@layout/app_bar_drawer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/detail_content_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp"
android:background="@color/detail_activity_background"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
style="@style/CardViewBackground"
android:id="@+id/detail_notification_list_container"
@ -73,4 +88,6 @@
android:autoLink="web"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,290 +1,309 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shapeAppearance="?shapeAppearanceLargeComponent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/main_banner_battery"
android:visibility="visible"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_banner_battery_constraint" android:elevation="5dp">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp" app:srcCompat="@drawable/ic_battery_alert_red_24dp"
android:id="@+id/main_banner_battery_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/main_banner_battery_text"
app:layout_constraintEnd_toStartOf="@id/main_banner_battery_text"
app:layout_constraintBottom_toBottomOf="@+id/main_banner_battery_text"
android:layout_marginStart="15dp"/>
<TextView
android:id="@+id/main_banner_battery_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_text"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
app:layout_constraintStart_toEndOf="@+id/main_banner_battery_image"
android:layout_marginStart="10dp"/>
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:constraint_referenced_ids="main_banner_battery_ask_later,main_banner_battery_dontaskagain,main_banner_battery_fix_now"
app:layout_constraintTop_toBottomOf="@id/main_banner_battery_text"
app:flow_horizontalAlign="end"
app:flow_wrapMode="chain"
app:flow_horizontalStyle="packed"
android:layout_marginEnd="15dp"
android:id="@+id/main_banner_battery_flow"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="15dp"
app:flow_horizontalBias="1"
app:flow_verticalGap="0dp" app:flow_horizontalGap="0dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_battery_ask_later"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_button_remind_later"
tools:layout_editor_absoluteX="15dp" tools:layout_editor_absoluteY="67dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_battery_dontaskagain"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_button_dismiss"
tools:layout_editor_absoluteX="142dp" tools:layout_editor_absoluteY="71dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_battery_fix_now"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_button_fix_now"
tools:layout_editor_absoluteX="269dp" tools:layout_editor_absoluteY="67dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shapeAppearance="?shapeAppearanceLargeComponent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/main_banner_battery"
android:id="@+id/main_banner_websocket" android:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_banner_websocket_constraint" android:elevation="5dp">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp" app:srcCompat="@drawable/ic_announcement_orange_24dp"
android:id="@+id/main_banner_websocket_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/main_banner_websocket_text"
app:layout_constraintEnd_toStartOf="@id/main_banner_websocket_text"
app:layout_constraintBottom_toBottomOf="@+id/main_banner_websocket_text"
android:layout_marginStart="15dp"/>
<TextView
android:id="@+id/main_banner_websocket_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_text"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
app:layout_constraintStart_toEndOf="@+id/main_banner_websocket_image"
android:layout_marginStart="10dp"
/>
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:constraint_referenced_ids="main_banner_websocket_remind_later,main_banner_websocket_dontaskagain,main_banner_websocket_enable" app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_text" app:flow_horizontalAlign="end" app:flow_wrapMode="chain" app:flow_horizontalStyle="packed" android:layout_marginEnd="15dp" android:id="@+id/flow" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="15dp" app:flow_horizontalBias="1"
app:flow_verticalGap="0dp" app:flow_horizontalGap="0dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_remind_later"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_button_remind_later"
tools:layout_editor_absoluteX="86dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_dontaskagain"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_button_dismiss"
tools:layout_editor_absoluteX="260dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_enable"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_button_enable_now"
tools:layout_editor_absoluteX="253dp" tools:layout_editor_absoluteY="131dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
<include
android:id="@+id/app_bar_drawer"
layout="@layout/app_bar_drawer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shapeAppearance="?shapeAppearanceLargeComponent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/main_banner_websocket"
android:id="@+id/main_banner_websocket_reconnect" android:visibility="visible">
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_banner_websocket_reconnect_constraint" android:elevation="5dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp" app:srcCompat="@drawable/ic_announcement_orange_24dp"
android:id="@+id/main_banner_websocket_reconnect_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/main_banner_websocket_reconnect_text"
app:layout_constraintEnd_toStartOf="@id/main_banner_websocket_reconnect_text"
app:layout_constraintBottom_toBottomOf="@+id/main_banner_websocket_reconnect_text"
android:layout_marginStart="15dp"/>
<TextView
android:id="@+id/main_banner_websocket_reconnect_text"
android:layout_width="0dp"
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_text"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
style="@style/BannerCardStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
app:layout_constraintStart_toEndOf="@+id/main_banner_websocket_reconnect_image"
android:layout_marginStart="10dp"
android:id="@+id/main_banner_battery"
android:visibility="visible"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_banner_battery_constraint" android:elevation="5dp">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp" app:srcCompat="@drawable/ic_battery_alert_red_24dp"
android:id="@+id/main_banner_battery_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/main_banner_battery_text"
app:layout_constraintEnd_toStartOf="@id/main_banner_battery_text"
app:layout_constraintBottom_toBottomOf="@+id/main_banner_battery_text"
android:layout_marginStart="15dp"/>
<TextView
android:id="@+id/main_banner_battery_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_text"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
app:layout_constraintStart_toEndOf="@+id/main_banner_battery_image"
android:layout_marginStart="10dp"/>
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:constraint_referenced_ids="main_banner_battery_ask_later,main_banner_battery_dontaskagain,main_banner_battery_fix_now"
app:layout_constraintTop_toBottomOf="@id/main_banner_battery_text"
app:flow_horizontalAlign="end"
app:flow_wrapMode="chain"
app:flow_horizontalStyle="packed"
android:layout_marginEnd="15dp"
android:id="@+id/main_banner_battery_flow"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="15dp"
app:flow_horizontalBias="1"
app:flow_verticalGap="0dp" app:flow_horizontalGap="0dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_battery_ask_later"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_button_remind_later"
tools:layout_editor_absoluteX="15dp" tools:layout_editor_absoluteY="67dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_battery_dontaskagain"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_button_dismiss"
tools:layout_editor_absoluteX="142dp" tools:layout_editor_absoluteY="71dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_battery_fix_now"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_battery_button_fix_now"
tools:layout_editor_absoluteX="269dp" tools:layout_editor_absoluteY="67dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/BannerCardStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/main_banner_battery"
android:id="@+id/main_banner_websocket" android:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_banner_websocket_constraint" android:elevation="5dp">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp" app:srcCompat="@drawable/ic_announcement_orange_24dp"
android:id="@+id/main_banner_websocket_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/main_banner_websocket_text"
app:layout_constraintEnd_toStartOf="@id/main_banner_websocket_text"
app:layout_constraintBottom_toBottomOf="@+id/main_banner_websocket_text"
android:layout_marginStart="15dp"/>
<TextView
android:id="@+id/main_banner_websocket_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_text"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
app:layout_constraintStart_toEndOf="@+id/main_banner_websocket_image"
android:layout_marginStart="10dp"
/>
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:constraint_referenced_ids="main_banner_websocket_reconnect_remind_later,main_banner_websocket_reconnect_dontaskagain,main_banner_websocket_reconnect_enable" app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_reconnect_text" app:flow_horizontalAlign="end" app:flow_wrapMode="chain" app:flow_horizontalStyle="packed" android:layout_marginEnd="15dp" android:id="@+id/flow_reconnect" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="15dp" app:flow_horizontalBias="1"
app:flow_verticalGap="0dp" app:flow_horizontalGap="0dp"/>
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:constraint_referenced_ids="main_banner_websocket_remind_later,main_banner_websocket_dontaskagain,main_banner_websocket_enable" app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_text" app:flow_horizontalAlign="end" app:flow_wrapMode="chain" app:flow_horizontalStyle="packed" android:layout_marginEnd="15dp" android:id="@+id/flow" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="15dp" app:flow_horizontalBias="1"
app:flow_verticalGap="0dp" app:flow_horizontalGap="0dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_reconnect_remind_later"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_button_remind_later"
tools:layout_editor_absoluteX="86dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_remind_later"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_button_remind_later"
tools:layout_editor_absoluteX="86dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_reconnect_dontaskagain"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_button_dismiss"
tools:layout_editor_absoluteX="260dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_dontaskagain"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_button_dismiss"
tools:layout_editor_absoluteX="260dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_reconnect_enable"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_button_enable_now"
tools:layout_editor_absoluteX="253dp" tools:layout_editor_absoluteY="131dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_enable"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_button_enable_now"
tools:layout_editor_absoluteX="253dp" tools:layout_editor_absoluteY="131dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/main_subscriptions_list_container"
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_reconnect">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_subscriptions_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:clipToPadding="false"
android:background="?android:attr/selectableItemBackground"
app:layoutManager="LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/fab" app:layout_constraintStart_toStartOf="parent"
android:id="@+id/main_no_subscriptions" android:visibility="gone">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_sms_gray_48dp"
android:id="@+id/main_no_subscriptions_image"/>
<TextView
android:id="@+id/main_no_subscriptions_text"
android:text="@string/main_no_subscriptions_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:padding="10dp" android:gravity="center_horizontal"
android:paddingStart="50dp" android:paddingEnd="50dp"/>
<TextView
android:text="@string/main_how_to_intro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_how_to_intro"
android:layout_marginTop="20dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"/>
<TextView
android:text="@string/main_how_to_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_how_to_link"
android:layout_marginTop="7dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
android:linksClickable="true"
android:autoLink="web"/>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:contentDescription="@string/main_add_button_description"
android:src="@drawable/ic_add_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/FloatingActionButton"
/>
style="@style/BannerCardStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/main_banner_websocket"
android:id="@+id/main_banner_websocket_reconnect" android:visibility="visible">
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_banner_websocket_reconnect_constraint" android:elevation="5dp">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp" app:srcCompat="@drawable/ic_announcement_orange_24dp"
android:id="@+id/main_banner_websocket_reconnect_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/main_banner_websocket_reconnect_text"
app:layout_constraintEnd_toStartOf="@id/main_banner_websocket_reconnect_text"
app:layout_constraintBottom_toBottomOf="@+id/main_banner_websocket_reconnect_text"
android:layout_marginStart="15dp"/>
<TextView
android:id="@+id/main_banner_websocket_reconnect_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_text"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
app:layout_constraintStart_toEndOf="@+id/main_banner_websocket_reconnect_image"
android:layout_marginStart="10dp"
/>
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:constraint_referenced_ids="main_banner_websocket_reconnect_remind_later,main_banner_websocket_reconnect_dontaskagain,main_banner_websocket_reconnect_enable" app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_reconnect_text" app:flow_horizontalAlign="end" app:flow_wrapMode="chain" app:flow_horizontalStyle="packed" android:layout_marginEnd="15dp" android:id="@+id/flow_reconnect" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="15dp" app:flow_horizontalBias="1"
app:flow_verticalGap="0dp" app:flow_horizontalGap="0dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_reconnect_remind_later"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_button_remind_later"
tools:layout_editor_absoluteX="86dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_reconnect_dontaskagain"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_button_dismiss"
tools:layout_editor_absoluteX="260dp" tools:layout_editor_absoluteY="83dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/main_banner_websocket_reconnect_enable"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_banner_websocket_reconnect_button_enable_now"
tools:layout_editor_absoluteX="253dp" tools:layout_editor_absoluteY="131dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/main_subscriptions_list_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/main_banner_websocket_reconnect">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_subscriptions_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:clipToPadding="false"
android:background="?android:attr/selectableItemBackground"
app:layoutManager="LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/fab" app:layout_constraintStart_toStartOf="parent"
android:id="@+id/main_no_subscriptions" android:visibility="gone">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_sms_gray_48dp"
android:id="@+id/main_no_subscriptions_image"/>
<TextView
android:id="@+id/main_no_subscriptions_text"
android:text="@string/main_no_subscriptions_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:padding="10dp" android:gravity="center_horizontal"
android:paddingStart="50dp" android:paddingEnd="50dp"/>
<TextView
android:text="@string/main_how_to_intro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_how_to_intro"
android:layout_marginTop="20dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"/>
<TextView
android:text="@string/main_how_to_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_how_to_link"
android:layout_marginTop="7dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
android:linksClickable="true"
android:autoLink="web"/>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:contentDescription="@string/main_add_button_description"
android:src="@drawable/ic_add_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/FloatingActionButton"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,9 +1,27 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include
android:id="@+id/app_bar_drawer"
layout="@layout/app_bar_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="@+id/settings_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
android:layout_height="match_parent" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,9 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include
android:id="@+id/app_bar_drawer"
layout="@layout/app_bar_drawer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -40,6 +54,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/share_content_text_hint"
android:importantForAutofill="no"
android:backgroundTint="?attr/colorPrimary"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:lines="10" android:gravity="start|top" app:layout_constraintTop_toBottomOf="@id/share_content_image" android:minLines="1" android:layout_marginTop="5dp"/>
<androidx.constraintlayout.widget.ConstraintLayout
@ -86,6 +101,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/add_dialog_topic_name_hint"
android:importantForAutofill="no"
android:backgroundTint="?attr/colorPrimary"
android:maxLines="1" android:inputType="text|textNoSuggestions" android:maxLength="64"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/share_topic_title" android:layout_marginStart="-3dp"/>
@ -163,4 +179,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/share_error_text" android:layout_marginTop="2dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:fitsSystemWindows="true"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>

View file

@ -1,73 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/add_dialog_subscribe_view">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/add_dialog_subscribe_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="3dp"
android:text="@string/add_dialog_title"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:fitsSystemWindows="true">
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/add_dialog_subscribe_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/add_dialog_subscribe_description"
android:indeterminate="true" android:layout_marginBottom="5dp" android:visibility="gone"/>
<TextView
android:text="@string/add_dialog_description_below"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/add_dialog_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:elevation="0dp"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/add_dialog_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorSurface"
app:navigationIcon="@drawable/ic_close_white_24dp"
app:navigationIconTint="?attr/colorOnSurface"
app:title="@string/add_dialog_title"
app:titleTextColor="?attr/colorOnSurface"
app:menu="@menu/menu_add_dialog" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="?dialogPreferredPadding"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<ScrollView
android:id="@+id/add_dialog_subscribe_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_description"
android:paddingStart="4dp" android:paddingTop="3dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_title"/>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/add_dialog_subscribe_topic_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/add_dialog_topic_name_hint"
android:importantForAutofill="no"
android:maxLines="1" android:inputType="text" android:maxLength="64"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_description"/>
<CheckBox
android:text="@string/add_dialog_use_another_server"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_use_another_server_checkbox"
android:layout_marginStart="-3dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_topic_text"
android:layout_marginTop="-3dp"/>
<TextView
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/add_dialog_subscribe_description"
android:text="@string/add_dialog_description_below"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:paddingTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/add_dialog_subscribe_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:indeterminate="true"
android:layout_marginTop="16dp"
android:visibility="gone"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/add_dialog_subscribe_topic_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/add_dialog_subscribe_topic_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/add_dialog_topic_name_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="text"
android:maxLength="64"/>
</com.google.android.material.textfield.TextInputLayout>
<CheckBox
android:id="@+id/add_dialog_subscribe_use_another_server_checkbox"
android:text="@string/add_dialog_use_another_server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-3dp"
android:layout_marginTop="-3dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_topic_layout"/>
<TextView
android:id="@+id/add_dialog_subscribe_use_another_server_description"
android:text="@string/add_dialog_use_another_server_description"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_use_another_server_description"
android:paddingStart="4dp" android:paddingTop="0dp"
android:visibility="gone" app:layout_constraintStart_toStartOf="parent"
android:layout_height="wrap_content"
android:layout_marginTop="-5dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:paddingTop="0dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_checkbox"
android:layout_marginTop="-5dp"/>
<com.google.android.material.textfield.TextInputLayout
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_checkbox" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu"
android:id="@+id/add_dialog_subscribe_base_url_layout"
android:layout_width="match_parent"
@ -81,13 +129,14 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_description">
<AutoCompleteTextView
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/add_dialog_subscribe_base_url_text"
android:hint="@string/app_base_url"
android:maxLines="1"
android:layout_marginTop="0dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="10dp"
android:inputType="textNoSuggestions"
android:paddingStart="0dp"
@ -96,132 +145,196 @@
android:paddingBottom="5dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
</com.google.android.material.textfield.TextInputLayout>
android:textAppearance="?android:attr/textAppearanceMedium"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_instant_delivery_box"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_base_url_layout" android:layout_marginTop="-3dp">
<CheckBox
android:text="@string/add_dialog_instant_delivery"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_instant_delivery_checkbox"
android:layout_marginTop="-8dp" android:layout_marginBottom="-5dp"
android:layout_marginStart="-3dp"/>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp" app:srcCompat="@drawable/ic_bolt_gray_24dp"
android:id="@+id/add_dialog_subscribe_instant_image"
app:layout_constraintTop_toTopOf="@+id/main_item_text"
app:layout_constraintEnd_toStartOf="@+id/main_item_date" android:paddingTop="3dp"
android:layout_marginTop="3dp"/>
</LinearLayout>
<TextView
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/add_dialog_subscribe_instant_delivery_box"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-3dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_base_url_layout">
<CheckBox
android:id="@+id/add_dialog_subscribe_instant_delivery_checkbox"
android:text="@string/add_dialog_instant_delivery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-8dp"
android:layout_marginBottom="-5dp"
android:layout_marginStart="-3dp"/>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="3dp"
android:paddingTop="3dp"
android:id="@+id/add_dialog_subscribe_instant_image"
app:srcCompat="@drawable/ic_bolt_gray_24dp"
app:layout_constraintTop_toTopOf="@+id/main_item_text"
app:layout_constraintEnd_toStartOf="@+id/main_item_date"/>
</LinearLayout>
<TextView
android:id="@+id/add_dialog_subscribe_instant_delivery_description"
android:text="@string/add_dialog_instant_delivery_description"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_instant_delivery_description"
android:paddingStart="4dp" android:paddingTop="0dp"
android:visibility="gone" app:layout_constraintStart_toStartOf="parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:paddingTop="0dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_box"/>
<TextView
<TextView
android:id="@+id/add_dialog_subscribe_foreground_description"
android:text="@string/add_dialog_foreground_description"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_foreground_description"
android:paddingStart="4dp" app:layout_constraintStart_toStartOf="parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingEnd="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_description"/>
<ImageView
android:layout_width="20dp"
android:layout_height="20dp" app:srcCompat="@drawable/ic_error_red_24dp"
android:id="@+id/add_dialog_subscribe_error_text_image"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/add_dialog_subscribe_error_text" android:layout_marginTop="1dp"/>
<TextView
<ImageView
android:id="@+id/add_dialog_subscribe_error_text_image"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="1dp"
android:visibility="gone"
app:srcCompat="@drawable/ic_error_red_24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/add_dialog_subscribe_error_text"/>
<TextView
android:id="@+id/add_dialog_subscribe_error_text"
android:text="Unable to resolve host example.com"
android:layout_width="0dp"
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_error_text"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:paddingStart="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_foreground_description"
android:paddingEnd="4dp"
android:textAppearance="@style/DangerText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_foreground_description"
app:layout_constraintStart_toEndOf="@id/add_dialog_subscribe_error_text_image"
android:layout_marginTop="5dp"
tools:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<ScrollView
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/add_dialog_login_view">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/add_dialog_login_title"
android:layout_width="0dp"
android:id="@+id/add_dialog_login_view"
android:visibility="gone">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="3dp"
android:text="@string/add_dialog_login_title"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:text="@string/add_dialog_login_description"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/add_dialog_login_description"
android:paddingStart="4dp" android:paddingTop="3dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_login_title" android:paddingEnd="4dp"/>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/add_dialog_login_username"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/add_dialog_login_username_hint"
android:importantForAutofill="no"
android:maxLines="1" android:inputType="text" android:maxLength="64"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="10dp" app:layout_constraintTop_toBottomOf="@+id/add_dialog_login_description"/>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/add_dialog_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/add_dialog_login_password_hint"
android:importantForAutofill="no"
android:maxLines="1" android:inputType="textPassword" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_login_username"/>
<ImageView
android:layout_width="20dp"
android:layout_height="20dp" app:srcCompat="@drawable/ic_error_red_24dp"
android:id="@+id/add_dialog_login_error_text_image"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="@+id/add_dialog_login_error_text" app:layout_constraintTop_toTopOf="@+id/add_dialog_login_error_text"/>
<TextView
android:text="Login failed. User not authorized."
android:layout_width="0dp"
android:layout_height="wrap_content" android:id="@+id/add_dialog_login_error_text"
android:paddingStart="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_login_password"
android:paddingEnd="4dp"
android:textAppearance="@style/DangerText"
app:layout_constraintStart_toEndOf="@id/add_dialog_login_error_text_image"/>
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="25dp"
android:layout_height="25dp"
android:id="@+id/add_dialog_login_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/add_dialog_login_description"
android:indeterminate="true" android:layout_marginBottom="5dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</LinearLayout>
android:orientation="horizontal">
<TextView
android:text="@string/add_dialog_login_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/add_dialog_login_description"
android:paddingStart="4dp"
android:paddingTop="16dp"
android:paddingEnd="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="25dp"
android:layout_height="25dp"
android:id="@+id/add_dialog_login_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:indeterminate="true"
android:layout_marginTop="16dp"
android:visibility="gone"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/add_dialog_login_username_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/add_dialog_login_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/add_dialog_login_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/add_dialog_login_username_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="text"
android:maxLength="64"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/add_dialog_login_password_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/add_dialog_login_username_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/add_dialog_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/add_dialog_login_password_hint"
android:importantForAutofill="no"
android:maxLines="1"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:id="@+id/add_dialog_login_error_text_image"
android:visibility="gone"
app:srcCompat="@drawable/ic_error_red_24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="@+id/add_dialog_login_error_text"
app:layout_constraintTop_toTopOf="@+id/add_dialog_login_error_text"/>
<TextView
android:id="@+id/add_dialog_login_error_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Login failed. User not authorized."
android:paddingStart="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/add_dialog_login_password_layout"
android:paddingEnd="4dp"
android:visibility="gone"
android:textAppearance="@style/DangerText"
app:layout_constraintStart_toEndOf="@id/add_dialog_login_error_text_image"
tools:visibility="visible"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,55 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:visibility="visible" android:paddingBottom="10dp">
<TextView
android:text="This topic requires you to login. Please pick an existing user or type in a username and password."
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/user_dialog_description"
android:paddingStart="4dp" android:paddingTop="3dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_dialog_title"/>
<TextView
android:id="@+id/user_dialog_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="3dp"
android:text="@string/user_dialog_title_add"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="?dialogPreferredPadding"
android:visibility="visible">
<com.google.android.material.textfield.TextInputEditText
<TextView
android:id="@+id/user_dialog_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="3dp"
android:text="@string/user_dialog_title_add"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/user_dialog_description"
android:text="@string/add_dialog_login_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="3dp"
android:paddingBottom="?dialogPreferredPadding"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_dialog_title"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/user_dialog_base_url_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/user_dialog_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/user_dialog_base_url"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/user_dialog_base_url_hint"
android:layout_height="wrap_content"
android:hint="@string/user_dialog_base_url_hint"
android:importantForAutofill="no"
android:maxLines="1" android:inputType="text"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@id/user_dialog_description"/>
<com.google.android.material.textfield.TextInputEditText
android:maxLines="1"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/user_dialog_username_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/user_dialog_base_url_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/user_dialog_username"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/user_dialog_username_hint"
android:layout_height="wrap_content"
android:hint="@string/user_dialog_username_hint"
android:importantForAutofill="no"
android:maxLines="1" android:inputType="text"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@id/user_dialog_base_url"/>
<com.google.android.material.textfield.TextInputEditText
android:maxLines="1"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/user_dialog_username_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/user_dialog_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:hint="@string/user_dialog_password_hint_add"
android:layout_height="wrap_content"
android:hint="@string/user_dialog_password_hint_add"
android:importantForAutofill="no"
android:maxLines="1" android:inputType="textPassword"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@id/user_dialog_username"/>
android:maxLines="1"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -45,15 +45,18 @@
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"/>
<EditText
android:id="@android:id/edit"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight = "48dp" />
android:layout_marginHorizontal="?dialogPreferredPadding"
android:paddingTop="?dialogPreferredPadding">
<com.google.android.material.textfield.TextInputEditText
android:id="@android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_dialog_action_button"
android:title="@string/add_dialog_button_subscribe"
android:enabled="false"
app:showAsAction="always" />
</menu>

View file

@ -1,17 +1,44 @@
<menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/detail_menu_notifications_enabled" android:title="@string/detail_menu_notifications_enabled"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_white_24dp"/>
<item android:id="@+id/detail_menu_notifications_disabled_until" android:title="@string/detail_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_off_time_white_outline_24dp"/>
<item android:id="@+id/detail_menu_notifications_disabled_forever" android:title="@string/detail_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_off_white_outline_24dp"/>
<item android:id="@+id/detail_menu_enable_instant" android:title="@string/detail_menu_enable_instant"
app:showAsAction="ifRoom" android:icon="@drawable/ic_bolt_outline_white_24dp"/>
<item android:id="@+id/detail_menu_disable_instant" android:title="@string/detail_menu_disable_instant"
android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom"/>
<item android:id="@+id/detail_menu_settings" android:title="@string/detail_menu_settings"/>
<item android:id="@+id/detail_menu_copy_url" android:title="@string/detail_menu_copy_url"/>
<item android:id="@+id/detail_menu_clear" android:title="@string/detail_menu_clear"/>
<item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/>
<item android:id="@+id/detail_menu_unsubscribe" android:title="@string/detail_menu_unsubscribe"/>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/detail_menu_notifications_enabled"
android:icon="@drawable/ic_notifications_white_24dp"
android:title="@string/detail_menu_notifications_enabled"
app:showAsAction="ifRoom" />
<item
android:id="@+id/detail_menu_notifications_disabled_until"
android:icon="@drawable/ic_notifications_off_time_white_outline_24dp"
android:title="@string/detail_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" />
<item
android:id="@+id/detail_menu_notifications_disabled_forever"
android:icon="@drawable/ic_notifications_off_white_outline_24dp"
android:title="@string/detail_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" />
<item
android:id="@+id/detail_menu_enable_instant"
android:icon="@drawable/ic_bolt_outline_white_24dp"
android:title="@string/detail_menu_enable_instant"
app:showAsAction="ifRoom" />
<item
android:id="@+id/detail_menu_disable_instant"
android:icon="@drawable/ic_bolt_white_24dp"
android:title="@string/detail_menu_disable_instant"
app:showAsAction="ifRoom" />
<item
android:id="@+id/detail_menu_settings"
android:title="@string/detail_menu_settings" />
<item
android:id="@+id/detail_menu_copy_url"
android:title="@string/detail_menu_copy_url" />
<item
android:id="@+id/detail_menu_clear"
android:title="@string/detail_menu_clear" />
<item
android:id="@+id/detail_menu_test"
android:title="@string/detail_menu_test" />
<item
android:id="@+id/detail_menu_unsubscribe"
android:title="@string/detail_menu_unsubscribe" />
</menu>

View file

@ -1,6 +1,14 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/detail_action_mode_copy" android:title="@string/detail_action_mode_menu_copy"
android:icon="@drawable/ic_content_copy_white_24dp"/>
<item android:id="@+id/detail_action_mode_delete" android:title="@string/detail_action_mode_menu_delete"
android:icon="@drawable/ic_delete_white_20dp"/>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/detail_action_mode_copy"
android:icon="@drawable/ic_content_copy_white_24dp"
android:title="@string/detail_action_mode_menu_copy"
app:iconTint="@android:color/white" />
<item
android:id="@+id/detail_action_mode_delete"
android:icon="@drawable/ic_delete_white_20dp"
android:title="@string/detail_action_mode_menu_delete"
app:iconTint="@android:color/white" />
</menu>

View file

@ -1,13 +1,31 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/main_menu_notifications_enabled" android:title="@string/main_menu_notifications_enabled"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_white_24dp"/>
<item android:id="@+id/main_menu_notifications_disabled_until" android:title="@string/main_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_off_time_white_outline_24dp"/>
<item android:id="@+id/main_menu_notifications_disabled_forever" android:title="@string/detail_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_off_white_outline_24dp"/>
<item android:id="@+id/main_menu_settings" android:title="@string/main_menu_settings_title"/>
<item android:id="@+id/main_menu_docs" android:title="@string/main_menu_docs_title"/>
<item android:id="@+id/main_menu_rate" android:title="@string/main_menu_rate_title"/>
<item android:id="@+id/main_menu_donate" android:title="@string/main_menu_donate_title"/>
<item android:id="@+id/main_menu_report_bug" android:title="@string/main_menu_report_bug_title"/>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/main_menu_notifications_enabled"
android:icon="@drawable/ic_notifications_white_24dp"
android:title="@string/main_menu_notifications_enabled"
app:showAsAction="ifRoom" />
<item
android:id="@+id/main_menu_notifications_disabled_until"
android:icon="@drawable/ic_notifications_off_time_white_outline_24dp"
android:title="@string/main_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" />
<item
android:id="@+id/main_menu_notifications_disabled_forever"
android:icon="@drawable/ic_notifications_off_white_outline_24dp"
android:title="@string/detail_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" />
<item
android:id="@+id/main_menu_settings"
android:title="@string/main_menu_settings_title" />
<item
android:id="@+id/main_menu_docs"
android:title="@string/main_menu_docs_title" />
<item
android:id="@+id/main_menu_rate"
android:title="@string/main_menu_rate_title" />
<item
android:id="@+id/main_menu_report_bug"
android:title="@string/main_menu_report_bug_title" />
</menu>

View file

@ -1,4 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/main_action_mode_delete" android:title="@string/main_action_mode_menu_unsubscribe"
android:icon="@drawable/ic_delete_white_20dp"/>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/main_action_mode_delete"
android:icon="@drawable/ic_delete_white_20dp"
android:title="@string/main_action_mode_menu_unsubscribe"
app:iconTint="@android:color/white" />
</menu>

View file

@ -1,4 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/share_menu_send" android:title="@string/share_menu_send"
app:showAsAction="always" android:icon="@drawable/ic_send_white_24dp"/>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/share_menu_send"
android:icon="@drawable/ic_send_white_24dp"
android:title="@string/share_menu_send"
app:showAsAction="always" />
</menu>

View file

@ -66,7 +66,6 @@
<string name="refresh_message_no_results">كل شئ محدث لاخر تحديث</string>
<string name="channel_subscriber_notification_instant_text_one">تم الاشتراك في 1 موضوع فوري</string>
<string name="channel_subscriber_notification_instant_text_four">تم الاشتراك في 4 مواضيع فورية</string>
<string name="main_menu_donate_title">تبرع 💸</string>
<string name="settings_title">اﻹعدادات</string>
<string name="refresh_message_error">تعذر تحديث %1$d اشتراكات
\n

View file

@ -327,7 +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>
<string name="channel_notifications_group_default_name">Подразбирани</string>
<string name="detail_settings_notifications_dedicated_channels_title">Потребителски настройки за известия</string>

View file

@ -6,7 +6,6 @@
<string name="channel_notifications_high_name">Prioritat alta</string>
<string name="channel_notifications_max_name">Prioritat màxima</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>

View file

@ -327,7 +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>
<string name="channel_notifications_group_default_name">Výchozí</string>
<string name="settings_notifications_insistent_max_priority_summary_disabled">Upozornění s nejvyšší prioritou pouze jednou</string>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="channel_notifications_low_name">Низкое преимѹ́щество</string>
<string name="channel_notifications_min_name">Мин преимѹ́щество</string>
<string name="channel_subscriber_service_name">Слꙋ́жба подписки</string>
<string name="channel_notifications_default_name">Ѻбыденное преимѹ́щество</string>
<string name="channel_notifications_high_name">Высо́кое преимѹ́щество</string>
<string name="channel_notifications_max_name">Макс преимѹ́щество</string>
</resources>

View file

@ -17,4 +17,81 @@
<string name="channel_subscriber_notification_instant_text_one">Abonnerer på et emne med øjeblikkelig levering</string>
<string name="channel_subscriber_notification_instant_text_four">Abonnerer på fire emner med øjeblikkelig levering</string>
<string name="channel_notifications_high_name">Høj prioritet</string>
</resources>
<string name="channel_subscriber_notification_noinstant_text_one">Abonnerer på et emne</string>
<string name="channel_subscriber_notification_noinstant_text_two">Abonnerer på to emner</string>
<string name="channel_subscriber_notification_noinstant_text_three">Abonnerer på tre emner</string>
<string name="channel_subscriber_notification_noinstant_text_four">Abonnerer på fire emner</string>
<string name="channel_subscriber_notification_noinstant_text_five">Abonnerer på fem emner</string>
<string name="channel_subscriber_notification_noinstant_text_six">Abonnerer på seks emner</string>
<string name="channel_subscriber_notification_noinstant_text_more">Abonnerer på %1$d emner</string>
<string name="refresh_message_result">%1$d notifikation(er) modtaget</string>
<string name="refresh_message_no_results">Alt er opdateret</string>
<string name="refresh_message_error">Kunne ikke opdaterer %1$d abonnementer\n\n%2$s</string>
<string name="refresh_message_error_one">Kunne ikke genopfriske abonnement %1$s</string>
<string name="main_menu_notifications_enabled">Notifikationer aktiveret</string>
<string name="main_menu_notifications_disabled_forever">Notifikationer slået fra</string>
<string name="main_menu_notifications_disabled_until">Notifikationer slået fra indtil %1$s</string>
<string name="main_menu_settings_title">Indstillinger</string>
<string name="main_menu_report_bug_title">Anmeld fejl</string>
<string name="main_menu_docs_title">Læs manualen</string>
<string name="main_menu_rate_title">Anmeld appen ⭐</string>
<string name="main_action_mode_menu_unsubscribe">Afmeld</string>
<string name="main_action_mode_delete_dialog_message">Afmeld valgte emne(r) og slet alle notifikationer permanent?</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Slet permanent</string>
<string name="main_action_mode_delete_dialog_cancel">Annuller</string>
<string name="main_item_status_text_one">%1$d notifikationer</string>
<string name="main_item_status_text_not_one">%1$d notifikationer</string>
<string name="main_item_status_reconnecting">Tilslutter …</string>
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
<string name="main_item_date_yesterday">i går</string>
<string name="main_add_button_description">Tilføj abonomment</string>
<string name="main_no_subscriptions_text">Det ser ud til at du ikke abonnere på noget endnu.</string>
<string name="main_how_to_intro">Klik + for at oprette eller abonnere på et emne. Derefter kan du modtage notifikationer på din enhed vha. PUT og POST.</string>
<string name="main_how_to_link">Detaljeret instruktioner tilgængelige på ntfy.sh, og i manualen.</string>
<string name="main_unified_push_toast">Dette abonnement er styret af %1$s vha. UnifiedPush</string>
<string name="main_banner_battery_text">Batterioptimering bør være slået fra for appen for at undgå problemer med at modtage notifikationer.</string>
<string name="main_banner_battery_button_remind_later">Spørg senere</string>
<string name="main_banner_battery_button_dismiss">Afvis</string>
<string name="main_banner_battery_button_fix_now">Løs nu</string>
<string name="main_banner_websocket_text">Anvendelse af WebSockets er den anbefalede måde tilslutte dig din server, og kan forbedre batterilevetiden, men det kan kræve <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">yderligere konfigurationer i din proxy</a>. Dette kan ændres i indstillingerne.</string>
<string name="main_banner_websocket_button_remind_later">Spørg senere</string>
<string name="main_banner_websocket_button_dismiss">Afvis</string>
<string name="main_banner_websocket_button_enable_now">Aktiver nu</string>
<string name="main_banner_websocket_reconnect_text">For at kunne garantere at WebSockets genopretter forbindelsen i baggrunden, skal du give Alarm &amp; Påmindelses tilladelser til ntfy</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Spørg senere</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Afvis</string>
<string name="main_banner_websocket_reconnect_button_enable_now">Tildel nu</string>
<string name="add_dialog_title">Abonner på emne</string>
<string name="add_dialog_description_below">Emner er ikke password-beskyttet, så vælg et navn der er svært at gætte. Når først du er abonnere, kan du PUT/POST notifikationer.</string>
<string name="add_dialog_topic_name_hint">Emne navn, f.eks. jørns_alarmer</string>
<string name="add_dialog_use_another_server">Brug anden server</string>
<string name="add_dialog_use_another_server_description">Skriv URLs herunder for at abonnere på emner fra andre servere.</string>
<string name="add_dialog_instant_delivery">Øjeblikkelig levering i dvale</string>
<string name="add_dialog_instant_delivery_description">Garanter at meldingerne bliver leveret med det samme, selv hvis enheden er inaktiv.</string>
<string name="add_dialog_foreground_description">Øjeblikkelig levering er altid aktiveret for andre værter end %1$s.</string>
<string name="add_dialog_button_cancel">Annuller</string>
<string name="add_dialog_button_subscribe">Abonner</string>
<string name="add_dialog_button_back">Tilbage</string>
<string name="add_dialog_button_login">Log ind</string>
<string name="add_dialog_error_connection_failed">Tilslutning fejlede: %1$s</string>
<string name="add_dialog_login_title">Login krævet</string>
<string name="add_dialog_login_description">Dette emne kræver at du logger ind. Skriv venligst dit brugernavn og password.</string>
<string name="add_dialog_login_username_hint">Brugernavn</string>
<string name="add_dialog_login_password_hint">Password</string>
<string name="add_dialog_login_error_not_authorized">Login fejlede. Bruger %1$s er ikke autoriseret.</string>
<string name="add_dialog_login_new_user">Ny bruger</string>
<string name="add_dialog_base_urls_dropdown_choose">Vælg service URL</string>
<string name="add_dialog_base_urls_dropdown_clear">Fjern service URL</string>
<string name="detail_no_notifications_text">Du har ikke modtaget nogen notifikationer for dette emne endnu.</string>
<string name="detail_how_to_intro">For at sende notifikationer for dette emne, PUT eller POST til emne URLen.</string>
<string name="detail_how_to_example"><![CDATA[ Example (using curl):<br/><tt>$ curl -d \"Hej\"%1$s</tt>]]></string>
<string name="detail_how_to_link">Detaljeret instruktioner tilgængelig på ntfy.sh, and in the docs.</string>
<string name="detail_clear_dialog_message">Slet alle notifikationer for emnet?</string>
<string name="detail_clear_dialog_permanently_delete">Slet permanent</string>
<string name="detail_clear_dialog_cancel">Annuller</string>
<string name="detail_delete_dialog_message">Afmeld abonnementet for dette emne og slet alle modtagende notifikationer?</string>
<string name="detail_delete_dialog_permanently_delete">Slet permanent</string>
<string name="detail_delete_dialog_cancel">Annuller</string>
<string name="detail_test_title">Test: Ændre titlen til det du vil.</string>
<string name="detail_test_message">Dette er en test notifikation fra ntfy Android app. Den har prioritets niveau %1$d. Hvis du sender en anden, kan den se anderledes ud.</string>
</resources>

View file

@ -327,7 +327,6 @@
<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>
<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>
<string name="detail_settings_notifications_dedicated_channels_summary_on">Eigene Einstellungen für dieses Abo verwenden</string>
<string name="detail_settings_notifications_open_channels_title">Beanchrichtigungseinstellungen konfigurieren</string>

View file

@ -60,7 +60,6 @@
<string name="add_dialog_topic_name_hint">Όνομα θέματος</string>
<string name="add_dialog_use_another_server">Χρήση άλλου server/εξυπηρετητή</string>
<string name="add_dialog_title">Εγγραφή σε ειδοποίηση</string>
<string name="main_menu_donate_title">Κάντε δωρεά</string>
<string name="main_banner_websocket_text">Η μετάβαση σε WebSockets είναι ο συνιστώμενος τρόπος σύνδεσης με τον διακομιστή σας και μπορεί να βελτιώσει τη διάρκεια ζωής της μπαταρίας, αλλά μπορεί να απαιτεί <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">πρόσθετες ρυθμίσεις στο διακομιστή μεσολάβησης</a>. Αυτό μπορεί να ενεργοποιηθεί στις Ρυθμίσεις.</string>
<string name="add_dialog_login_password_hint">Κωδικός πρόσβασης</string>
<string name="add_dialog_login_error_not_authorized">Η σύνδεση απέτυχε. Ο χρήστης %1$s δεν είναι εξουσιοδοτημένος.</string>

View file

@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="channel_notifications_min_name">Min prioritato</string>
<string name="channel_notifications_low_name">Malalta prioritato</string>
<string name="channel_notifications_default_name">Defaŭlta prioritato</string>
</resources>

View file

@ -327,7 +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>
<string name="channel_notifications_group_default_name">Predeterminado</string>
<string name="settings_notifications_insistent_max_priority_title">Mantener alertas para la máxima prioridad</string>

View file

@ -6,7 +6,6 @@
<string name="main_menu_report_bug_title">Teata vigadest</string>
<string name="main_menu_docs_title">Loe dokumentatsiooni</string>
<string name="main_menu_rate_title">Hinda rakendust ⭐</string>
<string name="main_menu_donate_title">Toeta arendajat 💸</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Kustuta jäädavalt</string>
<string name="main_item_status_text_not_one">%1$d teavitust</string>
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
@ -274,4 +273,70 @@
<string name="settings_backup_restore_restore_successful">Taastamine õnnestus</string>
<string name="settings_backup_restore_restore_failed">Taastamine ei õnnestunud: %1$s</string>
<string name="settings_advanced_header">Täiendavad seadistused</string>
<string name="settings_advanced_broadcast_title">Leviedasta sõnumeid</string>
<string name="settings_advanced_broadcast_summary_enabled">Rakendused saavad teavitusi leviedastusena (kõik saavad sama sõnumi)</string>
<string name="settings_advanced_broadcast_summary_disabled">Rakendused ei saa teavitusi leviedastusena</string>
<string name="settings_advanced_unifiedpush_title">Kasuta UnifiedPushi</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy toimib UnifiedPushi levitajana</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy ei toimi UnifiedPushi levitajana</string>
<string name="settings_advanced_record_logs_title">Salvesta logisid</string>
<string name="settings_advanced_record_logs_summary_enabled">Login (kuni 1,000 kirjet) seadmesse…</string>
<string name="settings_advanced_record_logs_summary_disabled">Lülita logimine sisse ja sa saad neid vigade otsimisel jagada.</string>
<string name="settings_advanced_export_logs_title">Kopeeri logid või laadi nad üles</string>
<string name="settings_advanced_export_logs_summary">Kopeeri logid lõikelaule või laadi nad nopaste.net teenusesse (mille omanik on ntfy autor). Seadmete nimed ja aadressid on võimalik välja jätta, kui teavituste sisusid mitte.</string>
<string name="settings_advanced_export_logs_entry_copy_original">Kopeeri lõikelauale</string>
<string name="settings_advanced_export_logs_entry_copy_scrubbed">Kopeeri lõikelauale (tsenseerituna)</string>
<string name="settings_advanced_export_logs_entry_upload_original">Laadi üles ja kopeeri link</string>
<string name="settings_advanced_export_logs_entry_upload_scrubbed">Laadi üles ja kopeeri link (tsenseerituna)</string>
<string name="settings_advanced_export_logs_copied_logs">Logid on kopeeritud lõikelauale</string>
<string name="settings_advanced_export_logs_uploading">Logid on üleslaadimisel…</string>
<string name="settings_advanced_export_logs_copied_url">Logid on laaditud üles ja võrguaadress on kopeeritud</string>
<string name="settings_advanced_export_logs_error_uploading">Logide üleslaadimine ei õnnestunud: %1$s</string>
<string name="settings_advanced_export_logs_scrub_dialog_text">Need teemad ja seadmete nimed on asendatud puuviljade nimedega ja seega saad ohutumalt logi jagada:\n\n%1$s\n\nKa salasõnad on korjatud välja, kuid neid pole siin näidatud.</string>
<string name="settings_advanced_export_logs_scrub_dialog_empty">Ühtegi teemat ega seadme nime polnud asendatud. Kas sul üldse on teemade tellimusi?</string>
<string name="settings_advanced_export_logs_scrub_dialog_button_ok">Sobib</string>
<string name="settings_advanced_clear_logs_title">Kustuta logid</string>
<string name="settings_advanced_clear_logs_summary">Kustuta varasemad logid ja alusta nullist</string>
<string name="settings_advanced_clear_logs_deleted_toast">Logid on kustutatud</string>
<string name="settings_advanced_connection_protocol_title">Ühendusprotokoll</string>
<string name="settings_advanced_connection_protocol_summary_jsonhttp">Ühenduseks serveriga kasuta JSON-i voogedastust üle HTTP. See meetod on korralikult testitud, aga võib suurendada akukasutust.</string>
<string name="settings_advanced_connection_protocol_summary_ws">Ühenduseks serveriga kasuta WebSocketsi protokolli. Selle meetodi kasutamine on esimene soovitus, aga see võib eeldada sinu proksiserveri täiendavat seadistamist.</string>
<string name="settings_advanced_connection_protocol_entry_jsonhttp">JSON-i voogedastus üle HTTP</string>
<string name="settings_advanced_connection_protocol_entry_ws">WebSockets</string>
<string name="detail_settings_global_setting_title">Kasuta üldist seadistust</string>
<string name="detail_settings_global_setting_suffix">kasutan üldist seadistust</string>
<string name="detail_settings_about_header">Rakenduse teave</string>
<string name="user_dialog_title_add">Lisa kasutaja</string>
<string name="user_dialog_title_edit">Muuda kasutajat</string>
<string name="user_dialog_description_edit">Sa võid muuta valitud kasutaja kasutajanime või salasõna, aga ta ka sootuks kustutada.</string>
<string name="settings_about_version_format">ntfy %1$s (%2$s)</string>
<string name="settings_about_version_copied_to_clipboard_message">Kopeeritud lõikelauale</string>
<string name="user_dialog_base_url_hint">Teenuse võrguaadress</string>
<string name="user_dialog_username_hint">Kasutajanimi</string>
<string name="user_dialog_password_hint_add">Salasõna</string>
<string name="user_dialog_password_hint_edit">Salasõna (kui jääb tühjaks, siis ei muutu)</string>
<string name="user_dialog_button_add">Lisa kasutaja</string>
<string name="user_dialog_button_cancel">Katkesta</string>
<string name="user_dialog_button_delete">Kustuta kasutaja</string>
<string name="user_dialog_button_save">Salvesta</string>
<string name="settings_advanced_exact_alarms_title">Täpsed äratused</string>
<string name="settings_advanced_exact_alarms_true">ntfy võib ajastada täpseid äratusi. Need on vajalikud WebSocketsi toimimiseks taustal. Klõpsa selle õiguse keelamiseks.</string>
<string name="settings_advanced_exact_alarms_false">ntfy ei või ajastada täpseid äratusi. Need on vajalikud WebSocketsi toimimiseks taustal. Klõpsa selle õiguse lubamiseks.</string>
<string name="settings_about_header">Rakenduse teave</string>
<string name="settings_about_version_title">Versioon</string>
<string name="detail_settings_appearance_icon_error_saving">Ikooni salvestamine ei õnnestu: %1$s</string>
<string name="detail_settings_appearance_display_name_title">Kuvatav nimi</string>
<string name="detail_settings_appearance_display_name_message">Määra selle tellimuse jaoks eraldi kuvatav nimi. Vaikimisi nime jaoks jäta tühjaks (%1$s).</string>
<string name="detail_settings_appearance_display_name_default_summary">%1$s (vaikimisi)</string>
<string name="detail_settings_appearance_header">Välimus</string>
<string name="detail_settings_appearance_icon_set_title">Tellimuse ikoon</string>
<string name="detail_settings_appearance_icon_set_summary">Vali teavitustes kuvatav ikoon</string>
<string name="detail_settings_appearance_icon_remove_title">Tellimuste ikoon (eemaldamiseks klõpsa)</string>
<string name="detail_settings_notifications_dedicated_channels_title">Teavituste kohanadatud seadistused</string>
<string name="detail_settings_notifications_dedicated_channels_summary_on">Kasutan selle tellimuse jaoks kohandatud teavitusi</string>
<string name="detail_settings_notifications_dedicated_channels_summary_off">Kasutan vaikimisi teavitusi (helimärguanded, „Ära sega“ olekuga mittearvestamine, jne)</string>
<string name="detail_settings_notifications_open_channels_title">Kohenda teavituste seadistusi</string>
<string name="detail_settings_notifications_open_channels_summary">Helimärguanded, „Ära sega“ olekuga mittearvestamine, jne.</string>
<string name="detail_settings_notifications_insistent_max_priority_list_item_enabled">Jätka pidevate märguannetega</string>
<string name="detail_settings_notifications_insistent_max_priority_list_item_disabled">Anna märku vaid üks kord</string>
</resources>

View file

@ -14,7 +14,6 @@
<string name="main_menu_report_bug_title">گزارش یک نقص فنی</string>
<string name="main_menu_docs_title">مطالعه مستندات</string>
<string name="main_menu_rate_title">رتبه دهی به اپ ⭐</string>
<string name="main_menu_donate_title">حمایت مالی 💸</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_reconnecting">در حال اتصال دوباره …</string>

View file

@ -60,7 +60,7 @@
<string name="channel_subscriber_notification_noinstant_text_two">Tilattu kahteen topikkiin</string>
<string name="detail_settings_title">Tilausasetukset</string>
<string name="detail_item_saved_successfully">Tallennettu nimellä %1$s lataukset kansioon</string>
<string name="settings_notifications_auto_delete_one_month">jälkeen kolmenkymmenen päivän</string>
<string name="settings_notifications_auto_delete_one_month">Kuukauden jälkeen</string>
<string name="channel_notifications_group_default_name">Oletus</string>
<string name="detail_menu_test">Lähetä testi ilmoitus</string>
<string name="settings_notifications_priority_high">korkea</string>
@ -91,14 +91,14 @@
<string name="channel_subscriber_service_name">Tilaus palvelu</string>
<string name="channel_subscriber_notification_instant_text_five">Tilattu viiteen välittömään topikkiin</string>
<string name="settings_general_users_prefs_user_add">Lisää käyttäjiä</string>
<string name="settings_notifications_auto_delete_three_months">jälkeen kolmen kuukauden</string>
<string name="settings_notifications_auto_delete_three_months">Kolmen kuukauden jälkeen</string>
<string name="main_add_button_description">Lisää tilaus</string>
<string name="notification_dialog_muted_forever_toast_message">Ilmoitukset hiljennetty</string>
<string name="settings_notifications_muted_until_show_all">Näytä kaikki ilmoitukset</string>
<string name="notification_popup_file_download_failed">%1$s
\ntiedosto: %2$s, lataus virhe</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Poista pysyvästi</string>
<string name="settings_notifications_auto_delete_three_days">Jälkeen kolmen päivän</string>
<string name="settings_notifications_auto_delete_three_days">Kolmen päivän jälkeen</string>
<string name="settings_general_dark_mode_summary_light">Vaalea tila päälle</string>
<string name="detail_item_cannot_open">Ei voida avata liitettä %1$s</string>
<string name="settings_notifications_auto_download_5m">Jos tiedoston koko on alle 5 MB</string>
@ -147,7 +147,7 @@
<string name="settings_backup_restore_backup_entry_everything">Kaikki</string>
<string name="settings_general_users_summary">Lisää/poista käyttäjiä suojatuille topikeille</string>
<string name="add_dialog_use_another_server_description">Syötä palvelun URL-osoitte alle tilataksesi topikkeja muilta palvelimilta.</string>
<string name="settings_notifications_auto_delete_one_day">Jälkeen yhden päivän</string>
<string name="settings_notifications_auto_delete_one_day">Päivän jälkeen</string>
<string name="settings_general_dark_mode_summary_dark">Tumma tila päällä. Oletko vampyyri \?</string>
<string name="settings_general_users_prefs_user_add_summary">Lisää uusi käyttäjä uudelle palvelimelle</string>
<string name="settings_general_dark_mode_entry_system">Käytä oletusasetusta</string>
@ -291,7 +291,7 @@
<string name="notification_popup_file">%1$s
\nTiedosto: %2$s</string>
<string name="settings_notifications_muted_until_forever">Ilmoitukset mykistetty, kunnes niitä jatketaan</string>
<string name="detail_how_to_example">Esimerkki (käytä curl):<br></br><tt>$ curl -d \"Hei\" %1$s</tt></string>
<string name="detail_how_to_example"><![CDATA[ Esimerkki (käytä curl):<br/><tt>$ curl -d "Hei" %1$s</tt> ]]></string>
<string name="detail_settings_appearance_icon_set_title">Kuvake</string>
<string name="detail_instant_delivery_disabled">Välitön lähetys pois</string>
<string name="channel_subscriber_notification_instant_text_four">Tilattu neljään välittömään topikkiin</string>
@ -314,7 +314,7 @@
<string name="settings_notifications_auto_download_summary_always">Lataa automaattisesti liitteet</string>
<string name="user_dialog_title_edit">Muokkaa käyttäjää</string>
<string name="settings_backup_restore_restore_successful">Palautus onnistui</string>
<string name="settings_notifications_auto_delete_one_week">jälkeen seitsemän päivän</string>
<string name="settings_notifications_auto_delete_one_week">Viikon jälkeen</string>
<string name="settings_backup_restore_restore_failed">Palautus epäonnistui: %1$s</string>
<string name="settings_notifications_header">Ilmoitukset</string>
<string name="detail_test_message">Tämä on testi-ilmoitus ntfy Android -sovelluksesta. Sillä on prioriteettitaso %1$d. Jos lähetät toisen, se voi näyttää erilaiselta.</string>
@ -328,8 +328,7 @@
<string name="settings_advanced_record_logs_title">Nauhoitetut logit</string>
<string name="settings_notifications_insistent_max_priority_title">Pidä hälytykset korkeimmalla tasolla</string>
<string name="settings_notifications_min_priority_max">Vain maksimi ja ylittävät</string>
<string name="settings_about_header">About</string>
<string name="main_menu_donate_title">Lahjoita 💸</string>
<string name="settings_about_header">Tietoja</string>
<string name="notification_dialog_8h">8 tuntia</string>
<string name="detail_deep_link_subscribed_toast_message">Topikki %1$s tilattu</string>
<string name="user_dialog_username_hint">Käyttäjätunnus</string>
@ -340,4 +339,7 @@
<string name="main_item_status_unified_push">%1$s (Yleis Push)</string>
<string name="detail_item_tags">Tagit %1$s</string>
<string name="main_item_status_text_one">%1$d Huomautus</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Kysy myöhemmin</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Sulje</string>
<string name="main_banner_websocket_reconnect_button_enable_now">Salli nyt</string>
</resources>

View file

@ -329,7 +329,6 @@
<string name="detail_settings_about_topic_url_title">URL du sujet</string>
<string name="channel_notifications_group_default_name">Défaut</string>
<string name="detail_settings_notifications_insistent_max_priority_list_item_enabled">Conserver les notifications</string>
<string name="main_menu_donate_title">Faire un don 💸</string>
<string name="detail_item_cannot_open_apk">Les applications ne peuvent plus être installées. Veuillez les télécharger via un navigateur. Voir le ticket #531 pour plus de détails.</string>
<string name="settings_notifications_insistent_max_priority_title">Conserver les notifications avec une priorité maximale</string>
<string name="detail_settings_notifications_dedicated_channels_title">Paramètres personnalisés de la notification</string>

View file

@ -31,7 +31,6 @@
<string name="main_menu_report_bug_title">Informar dun fallo</string>
<string name="main_menu_docs_title">Ler documentación</string>
<string name="main_menu_rate_title">Valorar a app ⭐</string>
<string name="main_menu_donate_title">Doar 💸</string>
<string name="main_action_mode_menu_unsubscribe">Retirar subscrición</string>
<string name="main_action_mode_delete_dialog_message">Retirar a subscrición ao(s) asunto(s) seleccionado(s) e eliminar definitivamente tódalas notificacións\?</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Eliminar definitivamente</string>

View file

@ -79,7 +79,6 @@
<string name="add_dialog_title">विषय की सदस्यता लें</string>
<string name="add_dialog_button_back">पीछे जाएं</string>
<string name="channel_subscriber_notification_noinstant_text_one">एक विषय की सदस्यता</string>
<string name="main_menu_donate_title">दान करें</string>
<string name="main_item_status_text_one">%1$d सूचना</string>
<string name="detail_clear_dialog_cancel">रद्द करें</string>
<string name="detail_delete_dialog_cancel">रद्द करें</string>

View file

@ -80,7 +80,6 @@
<string name="main_item_status_reconnecting">ponovno povezivanje…</string>
<string name="main_banner_websocket_button_enable_now">Uključi sada</string>
<string name="main_banner_websocket_text">Prebacivanje na WebSockete je preporučen način povezivanja sa serverom, i moglo bi poboljšati životni vijek baterije, ali može zahtjevati <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">dodatnu konfiguraciju u tvojem proxy-u</a>. Ovo možeš promijeniti u postavkama.</string>
<string name="main_menu_donate_title">Doniraj 💸</string>
<string name="main_menu_report_bug_title">Prijavu grešku</string>
<string name="main_how_to_link">Opširnije upute dostupne su na ntfy.sh i u dokumentaciji.</string>
<string name="main_how_to_intro">Klikni + za kreiranje ili pretplaćivanje na temu. Nakon toga primaš obavijesti na uređaj kad pošalješ poruke preko PUT ili POST.</string>

View file

@ -50,7 +50,6 @@
<string name="main_item_status_text_one">%1$d értesítés</string>
<string name="main_action_mode_delete_dialog_cancel">Mégse</string>
<string name="channel_notifications_group_default_name">Alapértelmezett</string>
<string name="main_menu_donate_title">Adomány 💸</string>
<string name="main_banner_websocket_button_dismiss">Bezár</string>
<string name="add_dialog_instant_delivery_description">Garantálja az azonnali üzenetküldést, akkor is, ha az eszköz inaktív.</string>
<string name="add_dialog_foreground_description">Az azonnali üzenetküldés mindig bekapcsolva a %1$s címen kívül.</string>

View file

@ -55,7 +55,7 @@
<string name="add_dialog_login_new_user">Pengguna baru</string>
<string name="detail_no_notifications_text">Anda belum menerima notifikasi apa pun.</string>
<string name="detail_how_to_intro">Untuk mengirimkan notifikasi ke topik ini, lakukan PUT atau POST ke URL topik.</string>
<string name="detail_how_to_example">Contoh (menggunakan curl):<br></br><tt>$ curl -d \"Hai\" %1$s</tt></string>
<string name="detail_how_to_example"><![CDATA[ Contoh (menggunakan curl):<br/><tt>$ curl -d \"Hi\" %1$s</tt> ]]></string>
<string name="detail_how_to_link">Instruksi rinci tersedia di ntfy.sh, dan dalam dokumentasi.</string>
<string name="detail_clear_dialog_message">Hapus semua notifikasi di topik ini\?</string>
<string name="detail_clear_dialog_permanently_delete">Hapus secara permanen</string>
@ -327,7 +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>
<string name="settings_notifications_insistent_max_priority_summary_disabled">Notifikasi prioritas maks hanya memperingati sekali</string>
<string name="detail_settings_notifications_open_channels_title">Atur pengaturan notifikasi</string>
@ -343,4 +342,11 @@
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy akan menjadi sebagai distributor UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy tidak akan menjadi sebagai distributor UnifiedPush</string>
<string name="settings_advanced_unifiedpush_title">Aktifkan UnifiedPush</string>
<string name="main_banner_websocket_reconnect_text">Untuk memastikan WebSockets tersambung kembali di latar belakang, berikan izin Alarm &amp; Pengingat untuk ntfy</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Tanyakan nanti</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Singkirkan</string>
<string name="main_banner_websocket_reconnect_button_enable_now">Berikan sekarang</string>
<string name="settings_advanced_exact_alarms_title">Alarm akurat</string>
<string name="settings_advanced_exact_alarms_true">ntfy dapat menjadwalkan alarm yang tepat. Alarm yang tepat diperlukan untuk menyambungkan kembali WebSockets di latar belakang. Klik untuk mencabut izin.</string>
<string name="settings_advanced_exact_alarms_false">ntfy tidak dapat menjadwalkan alarm yang tepat. Alarm yang tepat diperlukan untuk menyambungkan kembali WebSockets di latar belakang. Klik untuk memberikan izin.</string>
</resources>

View file

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="channel_notifications_low_name">Priorità bassa</string>
<string name="channel_subscriber_notification_noinstant_text_three">Iscritto a tre topic</string>
<string name="channel_subscriber_notification_noinstant_text_three">Iscritto a tre argomenti</string>
<string name="channel_notifications_high_name">Priorità alta</string>
<string name="channel_notifications_max_name">Priorità massima</string>
<string name="channel_subscriber_service_name">Servizio di iscrizione</string>
<string name="channel_subscriber_notification_instant_text">Iscritto ai topic a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_two">Iscritto a due topic a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_three">Iscritto a tre topic a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_four">Iscritto a quattro topic a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_more">Iscritto a %1$d topic a consegna istantanea</string>
<string name="channel_subscriber_notification_noinstant_text">Iscritto ai topic</string>
<string name="channel_subscriber_notification_noinstant_text_one">Iscritto a un topic</string>
<string name="channel_subscriber_notification_noinstant_text_two">Iscritto a due topic</string>
<string name="channel_subscriber_notification_instant_text">Iscritto ad argomenti a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_two">Iscritto a due argomenti a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_three">Iscritto a tre argomenti a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_four">Iscritto a quattro argomenti a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_more">Iscritto a %1$d argomenti a consegna istantanea</string>
<string name="channel_subscriber_notification_noinstant_text">Iscritto agli argomenti</string>
<string name="channel_subscriber_notification_noinstant_text_one">Iscritto ad un argomento</string>
<string name="channel_subscriber_notification_noinstant_text_two">Iscritto a due argomenti</string>
<string name="refresh_message_result">%1$d notifiche ricevute</string>
<string name="refresh_message_no_results">Tutto è aggiornato</string>
<string name="refresh_message_error">Impossibile aggiornare %1$d iscrizioni
@ -23,73 +23,73 @@
<string name="main_menu_notifications_disabled_forever">Notifiche disattivate</string>
<string name="main_menu_notifications_disabled_until">Notiche disattivate fino a %1$s</string>
<string name="main_menu_settings_title">Impostazioni</string>
<string name="main_action_bar_title">Topic iscritti</string>
<string name="main_action_mode_delete_dialog_message">Disiscriversi dai topic selezionati ed eliminare definitivamente tutte le notifiche\?</string>
<string name="main_action_bar_title">Argomenti sottoscritti</string>
<string name="main_action_mode_delete_dialog_message">Disiscriversi dagli argomenti selezionati ed eliminare definitivamente tutte le notifiche?</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Elimina definitivamente</string>
<string name="main_item_status_text_one">%1$d notifiche</string>
<string name="main_item_status_text_not_one">%1$d notifiche</string>
<string name="main_item_status_reconnecting">riconnessione…</string>
<string name="main_item_date_yesterday">ieri</string>
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
<string name="main_how_to_link">Istruzioni dettagliate disponibili su ntfy.sh, e nella documentazione.</string>
<string name="main_how_to_link">Istruzioni dettagliate disponibili su ntfy.sh e nella documentazione.</string>
<string name="main_unified_push_toast">Questa iscrizione è gestita da %1$s via UnifiedPush</string>
<string name="main_banner_battery_button_remind_later">Chiedi in seguito</string>
<string name="main_banner_battery_button_dismiss">Abbandona</string>
<string name="main_banner_battery_button_fix_now">Correggi ora</string>
<string name="main_banner_websocket_button_remind_later">Chiedi in seguito</string>
<string name="main_banner_websocket_button_dismiss">Abbandona</string>
<string name="add_dialog_title">Iscriviti al topic</string>
<string name="add_dialog_topic_name_hint">Nome del topic, es. phils_alerts</string>
<string name="add_dialog_title">Iscriviti all\'argomento</string>
<string name="add_dialog_topic_name_hint">Nome dell\'argomento, es. phils_alerts</string>
<string name="add_dialog_use_another_server">Usa un altro server</string>
<string name="add_dialog_use_another_server_description">Immettere gli URL dei servizi qui sotto per iscriversi ai topic di altri server.</string>
<string name="add_dialog_use_another_server_description">Inserisci gli URL dei servizi qui sotto per iscriversi agli argomenti di altri server.</string>
<string name="add_dialog_instant_delivery">Consegna istantanea in modalità doze</string>
<string name="add_dialog_foreground_description">La consegna istantanea è sempre attiva per gli host diversi da %1$s.</string>
<string name="add_dialog_button_cancel">Cancella</string>
<string name="add_dialog_button_login">Log in</string>
<string name="add_dialog_foreground_description">La consegna istantanea è sempre attiva per i sistemi diversi da %1$s.</string>
<string name="add_dialog_button_cancel">Annulla</string>
<string name="add_dialog_button_login">Accesso</string>
<string name="add_dialog_error_connection_failed">Connessione fallita: %1$s</string>
<string name="add_dialog_login_title">Login richiesto</string>
<string name="add_dialog_login_username_hint">Username</string>
<string name="add_dialog_login_title">Accesso richiesto</string>
<string name="add_dialog_login_username_hint">Nome utente</string>
<string name="add_dialog_login_password_hint">Password</string>
<string name="add_dialog_login_error_not_authorized">Login fallito. Utente %1$s non autorizzato.</string>
<string name="add_dialog_login_error_not_authorized">Accesso fallito. Utente %1$s non autorizzato.</string>
<string name="add_dialog_login_new_user">Nuovo utente</string>
<string name="detail_how_to_intro">Per inviare notifiche a questo topic, usa PUT o POST all\'URL del topic.</string>
<string name="detail_how_to_link">Istruzioni dettagliate disponibili su ntfy.sh, e nella documentazione.</string>
<string name="detail_clear_dialog_message">Eliminare tutte le notifiche in questo topic\?</string>
<string name="detail_how_to_intro">Per inviare notifiche a questo argomento, usa PUT o POST all\'URL dell\'argomento.</string>
<string name="detail_how_to_link">Istruzioni dettagliate disponibili su ntfy.sh e nella documentazione.</string>
<string name="detail_clear_dialog_message">Eliminare tutte le notifiche in questo argomento?</string>
<string name="detail_clear_dialog_permanently_delete">Elimina definitivamente</string>
<string name="detail_clear_dialog_cancel">Cancella</string>
<string name="detail_delete_dialog_message">Disiscriversi da questo topic e cancellare tutte le notifiche ricevute\?</string>
<string name="detail_clear_dialog_cancel">Annulla</string>
<string name="detail_delete_dialog_message">Disiscriversi da questo argomento ed eliminare tutte le notifiche ricevute?</string>
<string name="detail_delete_dialog_permanently_delete">Elimina definitivamente</string>
<string name="detail_delete_dialog_cancel">Cancella</string>
<string name="detail_delete_dialog_cancel">Annulla</string>
<string name="detail_test_title">Test: Puoi impostare un titolo, se vuoi.</string>
<string name="detail_test_message_error_unauthorized_anon">Impossibile inviare il messaggio: Pubblicazione anonima non permessa.</string>
<string name="detail_test_message_error_unauthorized_user">Impossibile inviare il messaggio: L\'utente \"%1$s\" non è autorizzato.</string>
<string name="detail_copied_to_clipboard_message">Copiato negli appunti</string>
<string name="detail_instant_delivery_enabled">Consegna istantanea ON</string>
<string name="detail_item_tags">Tags: %1$s</string>
<string name="detail_instant_delivery_enabled">Consegna istantanea ATTIVATA</string>
<string name="detail_item_tags">Etichette: %1$s</string>
<string name="detail_test_message_error_too_large">Impossibile inviare il messaggio: L\'allegato è troppo grande.</string>
<string name="detail_item_snack_deleted">Notifica eliminata</string>
<string name="detail_item_snack_undo">Annulla</string>
<string name="detail_item_menu_copy_contents_copied">Notifica copiata negli appunti</string>
<string name="detail_item_cannot_download">Impossibile aprire o scaricare l\'allegato. Il link è scaduto e nessun file locale è stato trovato.</string>
<string name="detail_item_cannot_download">Impossibile aprire o scaricare l\'allegato. Il collegamento è scaduto e nessun file locale è stato trovato.</string>
<string name="detail_item_cannot_open">Impossibile aprire l\'allegato: %1$s</string>
<string name="detail_item_cannot_open_url">Impossibile aprire URL: %1$s</string>
<string name="detail_item_cannot_delete">Impossibile eliminare l\'allegato: %1$s</string>
<string name="detail_item_download_failed">Impossibile scaricare l\'allegato: %1$s</string>
<string name="detail_item_download_info_not_downloaded">non scaricato</string>
<string name="detail_item_download_info_not_downloaded_expired">non scaricato, link scaduto</string>
<string name="detail_item_download_info_not_downloaded_expired">non scaricato, collegamento scaduto</string>
<string name="detail_item_download_info_not_downloaded_expires_x">non scaricato, scadenza %1$s</string>
<string name="detail_item_download_info_downloading_x_percent">%1$d%% scaricato</string>
<string name="detail_item_download_info_deleted">eliminato</string>
<string name="detail_item_download_info_deleted_expired">eliminato, link scaduto</string>
<string name="detail_item_download_info_deleted_expires_x">eliminato, scadenza link %1$s</string>
<string name="detail_item_download_info_deleted_expired">eliminato, collegamento scaduto</string>
<string name="detail_item_download_info_deleted_expires_x">eliminato, scadenza collegamento %1$s</string>
<string name="detail_item_download_info_download_failed">download fallito</string>
<string name="detail_item_download_info_download_failed_expired">download fallito, link scaduto</string>
<string name="detail_item_download_info_download_failed_expired">download fallito, collegamento scaduto</string>
<string name="detail_menu_notifications_disabled_forever">Notifiche disattivate</string>
<string name="detail_menu_notifications_disabled_until">Notifiche disattivate fino a %1$s</string>
<string name="detail_menu_enable_instant">Abilita consegna istantanea</string>
<string name="detail_menu_disable_instant">Disabilita consegna istantanea</string>
<string name="detail_menu_test">Invia notifica di test</string>
<string name="detail_item_download_info_download_failed_expires_x">download fallito, scadenza link %1$s</string>
<string name="detail_item_download_info_download_failed_expires_x">download fallito, scadenza collegamento %1$s</string>
<string name="detail_menu_clear">Cancella tutte le notifiche</string>
<string name="detail_action_mode_menu_delete">Elimina</string>
<string name="detail_action_mode_delete_dialog_message">Eliminare definitivamente le notifiche selezionate\?</string>
@ -103,7 +103,7 @@
<string name="share_content_image_error">Impossibile leggere l\'immagine: %1$s</string>
<string name="share_topic_title">Condividi con</string>
<string name="share_successful">Messaggio pubblicato</string>
<string name="notification_dialog_cancel">Cancella</string>
<string name="notification_dialog_cancel">Annulla</string>
<string name="notification_dialog_show_all">Mostra tutte le notifiche</string>
<string name="notification_dialog_1h">1 ora</string>
<string name="notification_dialog_tomorrow">Fino a domani</string>
@ -127,7 +127,7 @@
<string name="settings_notifications_min_priority_summary_max">Mostra notifiche se la priorità è 5 (max)</string>
<string name="settings_notifications_min_priority_min">Tutte le priorità</string>
<string name="settings_notifications_min_priority_low">Priorità bassa e superiori</string>
<string name="settings_notifications_min_priority_default">Priorità di default e superiori</string>
<string name="settings_notifications_min_priority_default">Priorità predefinita e superiori</string>
<string name="settings_notifications_min_priority_high">Priorità alta e superiori</string>
<string name="settings_notifications_min_priority_max">Solo priorità massima</string>
<string name="settings_notifications_auto_download_summary_always">Scarica automaticamente tutti gli allegati</string>
@ -137,78 +137,74 @@
<string name="settings_notifications_auto_download_100k">Se sotto 100kB</string>
<string name="settings_notifications_auto_download_500k">Se sotto 500 kB</string>
<string name="settings_notifications_auto_delete_title">Elimina tutte le notifiche</string>
<string name="settings_general_default_base_url_title">Server di default</string>
<string name="settings_general_default_base_url_title">Server predefinito</string>
<string name="settings_general_header">Generale</string>
<string name="settings_general_default_base_url_default_summary">%1$s (default)</string>
<string name="settings_general_default_base_url_default_summary">%1$s (predefinito)</string>
<string name="settings_general_users_title">Gestisci utenti</string>
<string name="settings_general_users_summary">Aggiungi/rimuovi utenti per topic protetti</string>
<string name="settings_general_users_summary">Aggiungi/rimuovi utenti per argomenti protetti</string>
<string name="settings_general_users_prefs_title">Utenti</string>
<string name="settings_general_users_prefs_user_not_used">Non utilizzato da nessun topic</string>
<string name="settings_general_users_prefs_user_used_by_one">Utilizzato dal topic %1$s</string>
<string name="settings_general_users_prefs_user_not_used">Non utilizzato da nessun argomento</string>
<string name="settings_general_users_prefs_user_used_by_one">Utilizzato dall\'argomento %1$s</string>
<string name="settings_general_users_prefs_user_add_title">Aggiungi nuovo utente</string>
<string name="settings_general_users_prefs_user_add_summary">Crea un nuovo utente per un nuovo server</string>
<string name="settings_general_dark_mode_title">Modalità dark</string>
<string name="settings_general_dark_mode_summary_system">Utilizzando il default di sistema</string>
<string name="settings_general_dark_mode_summary_light">Modalità light ON</string>
<string name="settings_general_dark_mode_entry_system">Usa il default di sistema</string>
<string name="settings_general_dark_mode_entry_light">Modalità light</string>
<string name="settings_general_dark_mode_title">Modalità scura</string>
<string name="settings_general_dark_mode_summary_system">Utilizzo impostazione predefinita di sistema</string>
<string name="settings_general_dark_mode_summary_light">Modalità chiara attiva</string>
<string name="settings_general_dark_mode_entry_system">Usa impostazione predefinita di sistema</string>
<string name="settings_general_dark_mode_entry_light">Modalità chiara</string>
<string name="settings_backup_restore_backup_entry_settings_only">Solo le impostazioni</string>
<string name="settings_advanced_broadcast_title">Messaggi broadcast</string>
<string name="settings_advanced_broadcast_summary_enabled">Le app possono ricevere le notifiche in ingresso come broadcast</string>
<string name="settings_advanced_record_logs_title">Abilita registrazione dei log</string>
<string name="settings_advanced_export_logs_copied_url">Log caricati e URL copiato</string>
<string name="settings_advanced_export_logs_error_uploading">Impossibile caricare i log: %1$s</string>
<string name="settings_advanced_export_logs_scrub_dialog_empty">Nessun topic/hostname è stato redatto. Forse non hai iscrizioni\?</string>
<string name="settings_advanced_export_logs_scrub_dialog_text">Questi topic/hostnames sono stati sostituiti con nomi di frutta, così puoi condividere i log senza preoccupazioni:
\n
\n%1$s
\n
\nLe password sono state ripulite, ma non sono elencate qui.</string>
<string name="settings_advanced_export_logs_scrub_dialog_empty">Nessun argomento/nome di sistema è stato redatto. Forse non hai iscrizioni?</string>
<string name="settings_advanced_export_logs_scrub_dialog_text">Questi argomenti/nomi di sistema sono stati sostituiti con nomi di frutta, così puoi condividere i log senza preoccupazioni: \n \n%1$s \n \nLe password sono state ripulite, ma non sono elencate qui.</string>
<string name="settings_advanced_export_logs_scrub_dialog_button_ok">Ok</string>
<string name="settings_advanced_clear_logs_title">Cancella i log</string>
<string name="settings_advanced_clear_logs_title">Cancella i registri</string>
<string name="settings_advanced_connection_protocol_summary_jsonhttp">Usa stream JSON over HTTP per collegarti al server. Questo metodo è collaudato, ma può consumare più batteria.</string>
<string name="settings_advanced_connection_protocol_entry_jsonhttp">Stream JSON over HTTP</string>
<string name="user_dialog_description_add">Puoi aggiungere un utente qui. Tutti i topic per il dato server utilizzeranno questo utente.</string>
<string name="user_dialog_description_edit">Puoi modificare username/password per l\'utente selezionato, oppure eliminarlo.</string>
<string name="user_dialog_description_add">Puoi aggiungere un utente qui. Tutti gli argomenti per il server specificato utilizzeranno questo utente.</string>
<string name="user_dialog_description_edit">Puoi modificare nome utente/password per l\'utente selezionato, oppure eliminarlo.</string>
<string name="user_dialog_base_url_hint">URL del servizio</string>
<string name="user_dialog_username_hint">Username</string>
<string name="user_dialog_username_hint">Nome utente</string>
<string name="user_dialog_password_hint_add">Password</string>
<string name="user_dialog_password_hint_edit">Password (non modificata se il campo viene lasciato vuoto)</string>
<string name="user_dialog_button_add">Aggiungi utente</string>
<string name="user_dialog_button_cancel">Cancella</string>
<string name="user_dialog_button_cancel">Annulla</string>
<string name="user_dialog_button_delete">Elimina utente</string>
<string name="user_dialog_button_save">Salva</string>
<string name="channel_subscriber_notification_noinstant_text_more">Iscritto a %1$d topic</string>
<string name="channel_notifications_default_name">Priorità di default</string>
<string name="channel_subscriber_notification_noinstant_text_more">Iscritto a %1$d argomenti</string>
<string name="channel_notifications_default_name">Priorità predefinita</string>
<string name="channel_notifications_min_name">Priorità minima</string>
<string name="channel_subscriber_notification_instant_text_one">Iscritto a un topic a consegna istantanea</string>
<string name="channel_subscriber_notification_noinstant_text_four">Iscritto a quattro topic</string>
<string name="channel_subscriber_notification_instant_text_one">Iscritto ad un argomento a consegna istantanea</string>
<string name="channel_subscriber_notification_noinstant_text_four">Iscritto a quattro argomenti</string>
<string name="main_menu_docs_title">Leggi la documentazione</string>
<string name="main_action_mode_menu_unsubscribe">Disiscriviti</string>
<string name="main_how_to_intro">Clicca + per creare o iscriversi ad un topic. In seguito, riceverai notifiche sul tuo device quando invierai messaggi via PUT o POST.</string>
<string name="main_how_to_intro">Clicca + per creare o iscriversi ad un argomento. In seguito, riceverai notifiche sul tuo dispositivo quando invierai messaggi via PUT o POST.</string>
<string name="main_banner_battery_text">L\'ottimizzazione della batteria deve essere disabilitata per l\'app per evitare problemi di consegna delle notifiche.</string>
<string name="main_add_button_description">Aggiungi iscrizione</string>
<string name="main_action_mode_delete_dialog_cancel">Cancella</string>
<string name="main_action_mode_delete_dialog_cancel">Annulla</string>
<string name="main_no_subscriptions_text">Sembra che non ci sia nessuna iscrizione al momento.</string>
<string name="main_menu_report_bug_title">Segnala un bug</string>
<string name="main_menu_report_bug_title">Segnala un problema</string>
<string name="main_menu_rate_title">Valuta l\'app ⭐</string>
<string name="add_dialog_description_below">I topic possono essere non protetti da password, per cui scegli un nome che è difficile da indovinare. Una volta iscritti, è possibile effettuare notifiche PUT/POST.</string>
<string name="add_dialog_description_below">Gli argomenti possono essere non protetti da password, per cui scegli un nome che sia difficile da indovinare. Una volta iscritto, è possibile effettuare notifiche PUT/POST.</string>
<string name="add_dialog_instant_delivery_description">Assicura che i messaggi siano consegnati immediatamente, anche se il device non è attivo.</string>
<string name="add_dialog_login_description">Questo topic richiede il login. Per favore, inserire username e password.</string>
<string name="add_dialog_login_description">Questo argomento richiede l\'accesso. Per favore, inserisci nome utente e password.</string>
<string name="add_dialog_button_subscribe">Iscriviti</string>
<string name="add_dialog_button_back">Indietro</string>
<string name="detail_no_notifications_text">Non hai ancora ricevuto notifiche su questo topic.</string>
<string name="detail_no_notifications_text">Non hai ancora ricevuto notifiche su questo argomento.</string>
<string name="detail_item_menu_open">Apri file</string>
<string name="detail_how_to_example"><![CDATA[ Esempio (utilizzando curl):<br/><tt>$ curl -d \"Hi\" %1$s</tt> ]]></string>
<string name="detail_instant_delivery_disabled">Consegna istantanea OFF</string>
<string name="detail_instant_delivery_disabled">Consegna istantanea DISATTIVATA</string>
<string name="detail_item_menu_delete">Elimina file</string>
<string name="detail_item_menu_save_file">Salva il file</string>
<string name="detail_item_menu_copy_url">Copia URL</string>
<string name="detail_menu_copy_url">Copia l\'indirizzo del topic</string>
<string name="detail_menu_copy_url">Copia l\'indirizzo dell\'argomento</string>
<string name="detail_item_menu_copy_url_copied">URL copiato negli appunti</string>
<string name="detail_menu_notifications_enabled">Notifiche ON</string>
<string name="detail_test_message_error">Impossibile inviare il messaggio: %1$s</string>
<string name="detail_item_menu_cancel">Interrompi il download</string>
<string name="detail_item_menu_cancel">Annulla download</string>
<string name="detail_item_menu_copy_contents">Copia notifica</string>
<string name="detail_item_saved_successfully">Salvato con nome \"%1$s\" nella cartella \"Downloads\"</string>
<string name="detail_item_cannot_save">Impossibile salvare l\'allegato: %1$s</string>
@ -230,21 +226,21 @@
<string name="settings_notifications_auto_download_title">Scarica allegati</string>
<string name="detail_item_menu_download">Scarica file</string>
<string name="notification_popup_action_download">Scarica</string>
<string name="notification_popup_action_cancel">Cancella</string>
<string name="notification_popup_action_cancel">Annulla</string>
<string name="settings_notifications_auto_download_always">Scarica tutto automaticamente</string>
<string name="settings_notifications_auto_download_1m">Se sotto 1 MB</string>
<string name="settings_notifications_auto_download_10m">Se sotto 10 MB</string>
<string name="settings_notifications_auto_download_50m">Se sotto 50 MB</string>
<string name="detail_item_cannot_open_not_found">Impossibile aprire l\'allegato: Il file può essere stato cancellato oppure nessuna app installata è in grado di aprire il file.</string>
<string name="detail_item_cannot_open_not_found">Impossibile aprire l\'allegato: Il file può essere stato eliminato oppure nessuna app installata è in grado di aprire il file.</string>
<string name="detail_menu_unsubscribe">Disiscriviti</string>
<string name="detail_action_mode_delete_dialog_cancel">Cancella</string>
<string name="detail_action_mode_delete_dialog_cancel">Annulla</string>
<string name="settings_notifications_header">Notifiche</string>
<string name="share_content_file_error">Impossibile leggere le informazioni del file: %1$s</string>
<string name="notification_popup_action_open">Apri</string>
<string name="settings_notifications_muted_until_forever">Notifiche disattivate fino al ripristino</string>
<string name="settings_notifications_auto_download_5m">Se sotto 5 MB</string>
<string name="share_content_file_text">Un file è stato condiviso con te</string>
<string name="share_suggested_topics">Topic suggeriti</string>
<string name="share_suggested_topics">Argomenti suggeriti</string>
<string name="notification_dialog_title">Disattivare le notifiche</string>
<string name="notification_dialog_save">Salva</string>
<string name="notification_dialog_enabled_toast_message">Notifiche ripristinate</string>
@ -255,34 +251,34 @@
<string name="notification_dialog_8h">8 ore</string>
<string name="settings_backup_restore_backup_entry_everything_no_users">Tutto, eccetto utenti</string>
<string name="settings_advanced_header">Avanzate</string>
<string name="settings_general_default_base_url_message">Inserisci il root URL del tuo server per utilizzarlo come default durante l\'iscrizione a nuovi topic e/o durante la condivisione ai topic.</string>
<string name="settings_general_users_prefs_user_used_by_many">Utilizzato dai topic %1$s</string>
<string name="settings_general_default_base_url_message">Inserisci l\'URL radice del tuo server per utilizzare il tuo server come predefinito quando ti iscrivi a nuovi argomenti e/o condividi argomenti.</string>
<string name="settings_general_users_prefs_user_used_by_many">Utilizzato dagli argomenti %1$s</string>
<string name="settings_general_users_prefs_user_add">Aggiungi utente</string>
<string name="settings_general_dark_mode_entry_dark">Modalità dark</string>
<string name="settings_general_dark_mode_entry_dark">Modalità scura</string>
<string name="settings_backup_restore_backup_title">Backup su file</string>
<string name="settings_general_dark_mode_summary_dark">Modalità dark ON. Sei un vampiro\?</string>
<string name="settings_general_dark_mode_summary_dark">Modalità scura attiva. Sei un vampiro?</string>
<string name="settings_backup_restore_header">Backup &amp; Ripristino</string>
<string name="settings_backup_restore_restore_failed">Ripristino fallito: %1$s</string>
<string name="settings_advanced_export_logs_copied_logs">Log copiati negli appunti</string>
<string name="settings_about_header">Informazioni</string>
<string name="settings_backup_restore_backup_summary">Esporta config, notifiche e utenti</string>
<string name="settings_backup_restore_backup_summary">Esporta configurazione, notifiche e utenti</string>
<string name="settings_backup_restore_backup_entry_everything">Tutto</string>
<string name="settings_backup_restore_backup_successful">Backup creato</string>
<string name="settings_backup_restore_backup_failed">Backup fallito: %1$s</string>
<string name="settings_backup_restore_restore_title">Ripristina da file</string>
<string name="settings_backup_restore_restore_summary">Importa config, notifiche e utenti</string>
<string name="settings_advanced_record_logs_summary_enabled">Logging (fino a 1,000 elementi) nel device</string>
<string name="settings_backup_restore_restore_summary">Importa configurazione, notifiche e utenti</string>
<string name="settings_advanced_record_logs_summary_enabled">Registrazione (fino a 1.000 voci) sul dispositivo</string>
<string name="settings_backup_restore_restore_successful">Ripristino riuscito</string>
<string name="settings_advanced_broadcast_summary_disabled">Le app non possono ricevere le notifiche come broadcast</string>
<string name="settings_advanced_export_logs_entry_upload_original">Carica e copia link</string>
<string name="settings_advanced_record_logs_summary_disabled">Attiva la trascrizione dei log per condividere file di log in seguito per diagnosticare problemi.</string>
<string name="settings_advanced_export_logs_entry_upload_original">Carica e copia collegamento</string>
<string name="settings_advanced_record_logs_summary_disabled">Attiva la registrazione, così potrai condividere i registri in un secondo momento per diagnosticare i problemi.</string>
<string name="settings_advanced_export_logs_entry_copy_scrubbed">Copia negli appunti (censurato)</string>
<string name="settings_about_version_title">Versione</string>
<string name="settings_about_version_format">ntfy %1$s (%2$s)</string>
<string name="settings_advanced_export_logs_title">Copia/upload file di log</string>
<string name="settings_advanced_export_logs_entry_copy_original">Copia negli appunti</string>
<string name="settings_advanced_export_logs_entry_upload_scrubbed">Carica e copia link (censurato)</string>
<string name="settings_advanced_export_logs_uploading">Log in upload</string>
<string name="settings_advanced_export_logs_entry_upload_scrubbed">Carica e copia collegamento (censurato)</string>
<string name="settings_advanced_export_logs_uploading">Caricando registri</string>
<string name="settings_advanced_clear_logs_summary">Elimina i log precedentemente salvati, e ricomincia</string>
<string name="settings_advanced_connection_protocol_title">Protocollo di connessione</string>
<string name="settings_advanced_connection_protocol_summary_ws">Usa WebSockets per collegarti al server. Questo è il metodo consigliato, ma potrebbe richiedere una configurazione aggiuntiva del proxy.</string>
@ -292,19 +288,19 @@
<string name="user_dialog_title_edit">Modifica utente</string>
<string name="channel_subscriber_notification_title">In attesa di notifiche in ingresso</string>
<string name="detail_test_message">Questa è una notifica test dall\'app Android ntfy. Ha livello di priorità %1$d. Se ne invii un\'altra, potrebbe avere contenuti differenti.</string>
<string name="settings_advanced_export_logs_summary">Copia i log negli appunti, o carica su nopaste.net (in possesso dell\'autore di ntfy). Hostname e topic possono essere censurati, le notifiche non lo saranno mai.</string>
<string name="settings_notifications_priority_default">default</string>
<string name="settings_advanced_export_logs_summary">Copia i log negli appunti, o carica su nopaste.net (in possesso dell\'autore di ntfy). I nomi di sistema e gli argomenti possono essere censurati, le notifiche non lo saranno mai.</string>
<string name="settings_notifications_priority_default">predefinita</string>
<string name="settings_notifications_priority_max">massima</string>
<string name="settings_notifications_priority_high">alta</string>
<string name="settings_notifications_priority_min">minima</string>
<string name="settings_notifications_priority_low">bassa</string>
<string name="detail_deep_link_subscribed_toast_message">Iscritto al topic %1$s</string>
<string name="detail_deep_link_subscribed_toast_message">Iscritto all\'argomento %1$s</string>
<string name="detail_settings_global_setting_title">Utilizzare l\'impostazione globale</string>
<string name="settings_notifications_channel_prefs_summary">Esclusione del DND (Do Not Disturb), suoni, ecc.</string>
<string name="channel_subscriber_notification_instant_text_five">Iscritto a cinque topic a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_six">Iscritto a sei topic a consegna istantanea</string>
<string name="channel_subscriber_notification_noinstant_text_five">Iscritto a cinque topic</string>
<string name="channel_subscriber_notification_noinstant_text_six">Iscritto a sei topic</string>
<string name="settings_notifications_channel_prefs_summary">Disattivazione funzione Non disturbare (DND), suoni, ecc.</string>
<string name="channel_subscriber_notification_instant_text_five">Iscritto a cinque argomenti a consegna istantanea</string>
<string name="channel_subscriber_notification_instant_text_six">Iscritto a sei argomenti a consegna istantanea</string>
<string name="channel_subscriber_notification_noinstant_text_five">Iscritto a cinque argomenti</string>
<string name="channel_subscriber_notification_noinstant_text_six">Iscritto a sei argomenti</string>
<string name="notification_popup_user_action_failed">%1$s fallito: %2$s</string>
<string name="settings_notifications_channel_prefs_title">Impostazioni del canale</string>
<string name="detail_settings_notifications_instant_title">Consegna istantanea</string>
@ -314,7 +310,7 @@
<string name="detail_settings_appearance_icon_set_title">Icona della sottoscrizione</string>
<string name="detail_settings_appearance_icon_set_summary">Impostare un\'icona da visualizzare nelle notifiche</string>
<string name="detail_settings_appearance_icon_remove_title">Icona della sottoscrizione (toccare per rimuovere)</string>
<string name="detail_settings_appearance_icon_remove_summary">Icona visualizzata nelle notifiche di questo topic</string>
<string name="detail_settings_appearance_icon_remove_summary">Icona visualizzata nelle notifiche di questo argomento</string>
<string name="detail_settings_appearance_icon_error_saving">Impossibile salvare l\'icona: %1$s</string>
<string name="detail_settings_global_setting_suffix">utilizzando l\'impostazione globale</string>
<string name="detail_settings_appearance_display_name_title">Nome visualizzato</string>
@ -327,18 +323,17 @@
<string name="add_dialog_base_urls_dropdown_choose">Scegli il servizio URL</string>
<string name="add_dialog_base_urls_dropdown_clear">Pulisci il servizio URL</string>
<string name="main_banner_websocket_button_enable_now">Attiva ora</string>
<string name="channel_notifications_group_default_name">Default</string>
<string name="main_menu_donate_title">Dona 💸</string>
<string name="detail_item_cannot_open_apk">Le app non possono più essere installate: devono essere scaricate via browser. Vedi l\'issue #531 per dettagli.</string>
<string name="channel_notifications_group_default_name">Predefinita</string>
<string name="detail_item_cannot_open_apk">Le app non possono più essere installate: devono essere scaricate tramite browser. Vedi la segnalazione #531 per dettagli.</string>
<string name="settings_notifications_insistent_max_priority_title">Mantieni l\'alert per le notifiche a priorità massima</string>
<string name="settings_advanced_unifiedpush_title">Attiva UnifiedPush</string>
<string name="detail_settings_notifications_open_channels_summary">Bypass Do-Not-Disturb, suoni, etc.</string>
<string name="detail_settings_notifications_open_channels_summary">Esclusione Non disturbare (DND), suoni, ecc.</string>
<string name="detail_settings_notifications_insistent_max_priority_list_item_enabled">Continua ad inviare notifiche</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy non si comporterà come un distributore UnifiedPus</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy si comporterà come un distributore UnifiedPush</string>
<string name="detail_settings_notifications_dedicated_channels_title">Impostazioni di notifica personalizzate</string>
<string name="detail_settings_notifications_dedicated_channels_summary_on">Usa impostazioni personalizzate per questa iscrizione</string>
<string name="detail_settings_notifications_dedicated_channels_summary_off">Impostazioni predefinite sono in uso (suoni, bypass Do-Not-Disturb, etc.)</string>
<string name="detail_settings_notifications_dedicated_channels_summary_off">Utilizzo delle impostazioni predefinite (suoni, esclusione della funzione Non disturbare, ecc.)</string>
<string name="detail_settings_notifications_open_channels_title">Configura le impostazioni di notifica</string>
<string name="detail_settings_notifications_insistent_max_priority_list_item_disabled">Invia notifiche solo una volta</string>
<string name="settings_notifications_insistent_max_priority_summary_enabled">Le notifiche a massima priorità continuano ad allertare fino a che non vengono rimosse</string>

View file

@ -39,7 +39,6 @@
<string name="main_item_status_reconnecting">התחברות מחדש…</string>
<string name="main_how_to_intro">לחיצה על + תאפשר ליצור או להירשם לנושא. לאחר מכן התראות תגענה למכשיר שלך בעת שליחת הודעות עם PUT או POST.</string>
<string name="main_how_to_link">הוראות מפורטות זמינות ב־ntfy.sh, ובתיעוד.</string>
<string name="main_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>

View file

@ -327,7 +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>
<string name="settings_notifications_insistent_max_priority_summary_enabled">優先度最高は非表示になるまで通知継続</string>
<string name="channel_notifications_group_default_name">デフォルト</string>

View file

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="detail_how_to_link">자세한 설명은 ntfy.sh와 docs 페이지에서 찾으실 수 있습니다.</string>
<string name="channel_notifications_high_name">알림 (우선순위 높음)</string>
<string name="channel_notifications_high_name">우선순위 높음</string>
<string name="main_menu_notifications_disabled_forever">알림 음소거됨</string>
<string name="channel_subscriber_notification_instant_text_three">즉시 전달 주제 3개 구독중</string>
<string name="add_dialog_use_another_server">다른 서버 사용</string>
<string name="main_menu_notifications_enabled">알림 켜짐</string>
<string name="channel_notifications_default_name">알림 (우선순위 기본)</string>
<string name="channel_notifications_max_name">알림 (우선순위 최상)</string>
<string name="channel_notifications_default_name">우선순위 기본</string>
<string name="channel_notifications_max_name">우선순위 최상</string>
<string name="channel_subscriber_service_name">구독 서비스</string>
<string name="channel_subscriber_notification_title">알림 수신중</string>
<string name="channel_subscriber_notification_instant_text">즉시 전달 주제를 구독함</string>
@ -165,9 +165,9 @@
<string name="settings_advanced_connection_protocol_title">연결 프로토콜</string>
<string name="settings_advanced_connection_protocol_entry_jsonhttp">JSON stream over HTTP</string>
<string name="detail_settings_appearance_header">표시 설정</string>
<string name="channel_notifications_low_name">알림 (우선순위 낮음)</string>
<string name="channel_notifications_low_name">우선순위 낮음</string>
<string name="user_dialog_button_save">저장</string>
<string name="channel_notifications_min_name">알림 (우선순위 최하)</string>
<string name="channel_notifications_min_name">우선순위 최하</string>
<string name="channel_subscriber_notification_instant_text_two">즉시 전달 주제 2개 구독중</string>
<string name="channel_subscriber_notification_instant_text_five">즉시 전달 주제 5개 구독중</string>
<string name="channel_subscriber_notification_instant_text_four">즉시 전달 주제 4개 구독중</string>
@ -214,7 +214,7 @@
<string name="detail_item_menu_open">파일 열기</string>
<string name="detail_item_menu_copy_url_copied">URL이 클립보드에 복사됨</string>
<string name="add_dialog_base_urls_dropdown_choose">서비스 URL 선택</string>
<string name="detail_how_to_example">예제 (curl 사용):<br></br><tt>$ curl -d \\\"Hi\\\" %1$s</tt></string>
<string name="detail_how_to_example">예제 (curl 사용):<br/><tt>$ curl -d \\\"Hi\\\" %1$s</tt></string>
<string name="share_content_file_error">파일 정보를 읽을 수 없습니다: %1$s</string>
<string name="detail_test_title">테스트: 원한다면 제목을 설정할 수 있습니다.</string>
<string name="detail_instant_delivery_disabled">즉시 전달 꺼짐</string>
@ -328,4 +328,5 @@
<string name="user_dialog_button_add">사용자 추가</string>
<string name="user_dialog_button_delete">사용자 삭제</string>
<string name="detail_settings_notifications_dedicated_channels_summary_on">이 구독에 사용자 설정 사용</string>
<string name="channel_notifications_group_default_name">기본 그룹</string>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="channel_notifications_low_name">Низок приоритет</string>
<string name="channel_notifications_min_name">Мин приоритет</string>
<string name="channel_notifications_default_name">Стандарден приоритет</string>
<string name="channel_notifications_high_name">Висок приоритет</string>
<string name="channel_notifications_max_name">Највисок приоритет</string>
</resources>

View file

@ -34,7 +34,6 @@
<string name="main_menu_notifications_disabled_until">Notifikasi disenyapkan hingga %1$s</string>
<string name="main_menu_settings_title">Tetapan</string>
<string name="main_menu_report_bug_title">Aduan Kerosakan</string>
<string name="main_menu_donate_title">Sumbangan 💸</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Padamkan secara kekal</string>
<string name="main_action_mode_delete_dialog_cancel">Batal</string>
<string name="main_item_status_text_one">%1$d notifikasi</string>

View file

@ -324,7 +324,6 @@
<string name="detail_settings_appearance_display_name_default_summary">%1$s (standard)</string>
<string name="detail_settings_about_topic_url_copied_to_clipboard_message">Kopier til utklippstavlen</string>
<string name="detail_settings_appearance_display_name_message">Angi et tilpasset visningsnavn for dette abonnementet. La stå tomt for standard (%1$s).</string>
<string name="main_menu_donate_title">Doner 💸</string>
<string name="main_banner_websocket_text">Å bytte til WebSockets er den anbefalte måten å koble til serveren på, og kan forbedre batterilevetiden, men kan kreve <a href="https://ntfy.sh/docs/config/#nginxapache2caddy">ytterligere konfigurasjon i proxy-serveren</a>. Dette kan endres i innstillingene.</string>
<string name="main_banner_websocket_button_enable_now">Aktiver nå</string>
<string name="detail_deep_link_subscribed_toast_message">Abonnerte på emnet %1$s</string>

View file

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="md_theme_primary">#84D6C2</color>
<color name="md_theme_onPrimary">#00382E</color>
<color name="md_theme_primaryContainer">#005144</color>
<color name="md_theme_onPrimaryContainer">#A0F2DD</color>
<color name="md_theme_secondary">#B1CCC4</color>
<color name="md_theme_onSecondary">#1D352F</color>
<color name="md_theme_secondaryContainer">#334B45</color>
<color name="md_theme_onSecondaryContainer">#CDE8DF</color>
<color name="md_theme_tertiary">#AACBE4</color>
<color name="md_theme_onTertiary">#113447</color>
<color name="md_theme_tertiaryContainer">#2A4A5F</color>
<color name="md_theme_onTertiaryContainer">#C8E6FF</color>
<color name="md_theme_error">#FFB4AB</color>
<color name="md_theme_onError">#690005</color>
<color name="md_theme_errorContainer">#93000A</color>
<color name="md_theme_onErrorContainer">#FFDAD6</color>
<color name="md_theme_background">#121212</color>
<color name="md_theme_onBackground">#E0E0E0</color>
<color name="md_theme_surface">#121212</color>
<color name="md_theme_onSurface">#E0E0E0</color>
<color name="md_theme_surfaceVariant">#3F4946</color>
<color name="md_theme_onSurfaceVariant">#C0C0C0</color>
<color name="md_theme_outline">#89938F</color>
<color name="md_theme_outlineVariant">#3F4946</color>
<color name="md_theme_scrim">#000000</color>
<color name="md_theme_inverseSurface">#E0E0E0</color>
<color name="md_theme_inverseOnSurface">#2B3230</color>
<color name="md_theme_inversePrimary">#076B5B</color>
<color name="md_theme_primaryFixed">#A0F2DD</color>
<color name="md_theme_onPrimaryFixed">#00201A</color>
<color name="md_theme_primaryFixedDim">#84D6C2</color>
<color name="md_theme_onPrimaryFixedVariant">#005144</color>
<color name="md_theme_secondaryFixed">#CDE8DF</color>
<color name="md_theme_onSecondaryFixed">#06201A</color>
<color name="md_theme_secondaryFixedDim">#B1CCC4</color>
<color name="md_theme_onSecondaryFixedVariant">#334B45</color>
<color name="md_theme_tertiaryFixed">#C8E6FF</color>
<color name="md_theme_onTertiaryFixed">#001E2E</color>
<color name="md_theme_tertiaryFixedDim">#AACBE4</color>
<color name="md_theme_onTertiaryFixedVariant">#2A4A5F</color>
<color name="md_theme_surfaceDim">#121212</color>
<color name="md_theme_surfaceBright">#383838</color>
<color name="md_theme_surfaceContainerLowest">#0D0D0D</color>
<color name="md_theme_surfaceContainerLow">#1B1B1B</color>
<color name="md_theme_surfaceContainer">#1B2023</color>
<color name="md_theme_surfaceContainerHigh">#282F33</color>
<color name="md_theme_surfaceContainerHighest">#333333</color>
<color name="md_theme_primary_mediumContrast">#9AECD7</color>
<color name="md_theme_onPrimary_mediumContrast">#002C24</color>
<color name="md_theme_primaryContainer_mediumContrast">#4D9F8C</color>
<color name="md_theme_onPrimaryContainer_mediumContrast">#000000</color>
<color name="md_theme_secondary_mediumContrast">#C7E2D9</color>
<color name="md_theme_onSecondary_mediumContrast">#112A24</color>
<color name="md_theme_secondaryContainer_mediumContrast">#7C968E</color>
<color name="md_theme_onSecondaryContainer_mediumContrast">#000000</color>
<color name="md_theme_tertiary_mediumContrast">#C0E1FA</color>
<color name="md_theme_onTertiary_mediumContrast">#02293C</color>
<color name="md_theme_tertiaryContainer_mediumContrast">#7595AC</color>
<color name="md_theme_onTertiaryContainer_mediumContrast">#000000</color>
<color name="md_theme_error_mediumContrast">#FFD2CC</color>
<color name="md_theme_onError_mediumContrast">#540003</color>
<color name="md_theme_errorContainer_mediumContrast">#FF5449</color>
<color name="md_theme_onErrorContainer_mediumContrast">#000000</color>
<color name="md_theme_background_mediumContrast">#0E1513</color>
<color name="md_theme_onBackground_mediumContrast">#DEE4E0</color>
<color name="md_theme_surface_mediumContrast">#0E1513</color>
<color name="md_theme_onSurface_mediumContrast">#FFFFFF</color>
<color name="md_theme_surfaceVariant_mediumContrast">#3F4946</color>
<color name="md_theme_onSurfaceVariant_mediumContrast">#D4DFDA</color>
<color name="md_theme_outline_mediumContrast">#AAB4B0</color>
<color name="md_theme_outlineVariant_mediumContrast">#88938F</color>
<color name="md_theme_scrim_mediumContrast">#000000</color>
<color name="md_theme_inverseSurface_mediumContrast">#DEE4E0</color>
<color name="md_theme_inverseOnSurface_mediumContrast">#252B29</color>
<color name="md_theme_inversePrimary_mediumContrast">#005245</color>
<color name="md_theme_primaryFixed_mediumContrast">#A0F2DD</color>
<color name="md_theme_onPrimaryFixed_mediumContrast">#001510</color>
<color name="md_theme_primaryFixedDim_mediumContrast">#84D6C2</color>
<color name="md_theme_onPrimaryFixedVariant_mediumContrast">#003E34</color>
<color name="md_theme_secondaryFixed_mediumContrast">#CDE8DF</color>
<color name="md_theme_onSecondaryFixed_mediumContrast">#001510</color>
<color name="md_theme_secondaryFixedDim_mediumContrast">#B1CCC4</color>
<color name="md_theme_onSecondaryFixedVariant_mediumContrast">#233B35</color>
<color name="md_theme_tertiaryFixed_mediumContrast">#C8E6FF</color>
<color name="md_theme_onTertiaryFixed_mediumContrast">#00131F</color>
<color name="md_theme_tertiaryFixedDim_mediumContrast">#AACBE4</color>
<color name="md_theme_onTertiaryFixedVariant_mediumContrast">#18394E</color>
<color name="md_theme_surfaceDim_mediumContrast">#0E1513</color>
<color name="md_theme_surfaceBright_mediumContrast">#3F4643</color>
<color name="md_theme_surfaceContainerLowest_mediumContrast">#040807</color>
<color name="md_theme_surfaceContainerLow_mediumContrast">#191F1D</color>
<color name="md_theme_surfaceContainer_mediumContrast">#232927</color>
<color name="md_theme_surfaceContainerHigh_mediumContrast">#2D3432</color>
<color name="md_theme_surfaceContainerHighest_mediumContrast">#393F3D</color>
<color name="md_theme_primary_highContrast">#B2FFEB</color>
<color name="md_theme_onPrimary_highContrast">#000000</color>
<color name="md_theme_primaryContainer_highContrast">#81D2BE</color>
<color name="md_theme_onPrimaryContainer_highContrast">#000E0A</color>
<color name="md_theme_secondary_highContrast">#DAF6ED</color>
<color name="md_theme_onSecondary_highContrast">#000000</color>
<color name="md_theme_secondaryContainer_highContrast">#ADC8C0</color>
<color name="md_theme_onSecondaryContainer_highContrast">#000E0A</color>
<color name="md_theme_tertiary_highContrast">#E3F2FF</color>
<color name="md_theme_onTertiary_highContrast">#000000</color>
<color name="md_theme_tertiaryContainer_highContrast">#A6C7E0</color>
<color name="md_theme_onTertiaryContainer_highContrast">#000D17</color>
<color name="md_theme_error_highContrast">#FFECE9</color>
<color name="md_theme_onError_highContrast">#000000</color>
<color name="md_theme_errorContainer_highContrast">#FFAEA4</color>
<color name="md_theme_onErrorContainer_highContrast">#220001</color>
<color name="md_theme_background_highContrast">#0E1513</color>
<color name="md_theme_onBackground_highContrast">#DEE4E0</color>
<color name="md_theme_surface_highContrast">#0E1513</color>
<color name="md_theme_onSurface_highContrast">#FFFFFF</color>
<color name="md_theme_surfaceVariant_highContrast">#3F4946</color>
<color name="md_theme_onSurfaceVariant_highContrast">#FFFFFF</color>
<color name="md_theme_outline_highContrast">#E8F2EE</color>
<color name="md_theme_outlineVariant_highContrast">#BBC5C1</color>
<color name="md_theme_scrim_highContrast">#000000</color>
<color name="md_theme_inverseSurface_highContrast">#DEE4E0</color>
<color name="md_theme_inverseOnSurface_highContrast">#000000</color>
<color name="md_theme_inversePrimary_highContrast">#005245</color>
<color name="md_theme_primaryFixed_highContrast">#A0F2DD</color>
<color name="md_theme_onPrimaryFixed_highContrast">#000000</color>
<color name="md_theme_primaryFixedDim_highContrast">#84D6C2</color>
<color name="md_theme_onPrimaryFixedVariant_highContrast">#001510</color>
<color name="md_theme_secondaryFixed_highContrast">#CDE8DF</color>
<color name="md_theme_onSecondaryFixed_highContrast">#000000</color>
<color name="md_theme_secondaryFixedDim_highContrast">#B1CCC4</color>
<color name="md_theme_onSecondaryFixedVariant_highContrast">#001510</color>
<color name="md_theme_tertiaryFixed_highContrast">#C8E6FF</color>
<color name="md_theme_onTertiaryFixed_highContrast">#000000</color>
<color name="md_theme_tertiaryFixedDim_highContrast">#AACBE4</color>
<color name="md_theme_onTertiaryFixedVariant_highContrast">#00131F</color>
<color name="md_theme_surfaceDim_highContrast">#0E1513</color>
<color name="md_theme_surfaceBright_highContrast">#4B514F</color>
<color name="md_theme_surfaceContainerLowest_highContrast">#000000</color>
<color name="md_theme_surfaceContainerLow_highContrast">#1B211F</color>
<color name="md_theme_surfaceContainer_highContrast">#2B3230</color>
<color name="md_theme_surfaceContainerHigh_highContrast">#363D3A</color>
<color name="md_theme_surfaceContainerHighest_highContrast">#424846</color>
<color name="action_bar">#1B2023</color> <!-- md_theme_surfaceContainer (dark) - matches card color -->
<color name="detail_activity_background">#121212</color> <!-- Black for detail activity in dark mode -->
</resources>

View file

@ -1,42 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!--
This file contains only overrides for the dark theme.
Also see "ui/Colors.kt" for colors that have to be defined in code.
Resources:
- https://material.io/design/color/dark-theme.html
- https://material.io/develop/android/theming/dark
- https://developer.android.com/codelabs/basic-android-kotlin-training-change-app-theme#4
- https://developer.android.com/guide/topics/ui/look-and-feel/themes
-->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/teal_light</item>
<item name="colorAccent">@color/teal_light</item> <!-- checkboxes, text fields -->
<item name="android:colorBackground">@color/black_900</item> <!-- background -->
<item name="android:statusBarColor">@color/black_900</item>
<item name="actionModeBackground">@color/black_900</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
<!-- Action bar background & text color -->
<item name="colorSurface">@color/black_800b</item>
<item name="colorOnSurface">@color/white</item>
</style>
<style name="DangerText" parent="@android:style/TextAppearance">
<item name="android:textColor">@color/red_light</item>
</style>
<style name="FloatingActionButton" parent="@style/Widget.MaterialComponents.FloatingActionButton">
<item name="tint">@color/black_900</item>
<item name="backgroundTint">@color/teal_light</item>
</style>
<style name="CardView" parent="@style/Widget.MaterialComponents.CardView">
<item name="cardBackgroundColor">@color/black_800b</item>
</style>
<style name="CardViewBackground">
<item name="android:background">@color/black_900</item>
</style>
</resources>

View file

@ -0,0 +1,12 @@
<resources>
<!-- Action mode overrides for dark mode -->
<style name="ActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">?attr/colorSurface</item>
<item name="titleTextStyle">@style/ActionModeTitle</item>
</style>
<style name="ActionModeTitle" parent="@style/TextAppearance.AppCompat.Widget.ActionMode.Title">
<item name="android:textColor">?attr/colorOnSurface</item>
</style>
</resources>

View file

@ -328,7 +328,6 @@
<string name="main_banner_websocket_button_enable_now">Nu inschakelen</string>
<string name="main_banner_websocket_text">WebSockets is de aangeraden manier om te verbinden met uw server en kan batterij verbruik verminderen. Het kan <a href="https://ntfy.sh/docs/config/#nginxapache2caddy"> extra configuratie in uw proxy</a> vereisen. Dit kan omgeschakeld worden in de instellingen.</string>
<string name="channel_notifications_group_default_name">Standaard</string>
<string name="main_menu_donate_title">Doneer 💸</string>
<string name="detail_item_cannot_open_apk">Apps kunnen niet meer worden geïnstalleerd. Download deze via de browser. Raadpleeg issue #531 voor meer details.</string>
<string name="settings_notifications_insistent_max_priority_title">Behoud meldingen voor hoogste prioriteit</string>
<string name="settings_notifications_insistent_max_priority_summary_enabled">Max prioriteit berichten geven continue een melding totdat deze worden gesloten</string>

View file

@ -327,7 +327,6 @@
<string name="main_banner_websocket_button_dismiss">Odrzuć</string>
<string name="main_banner_websocket_button_enable_now">Aktywuj teraz</string>
<string name="settings_advanced_connection_protocol_summary_jsonhttp">Użyj strumienia JSON przez HTTP, aby połączyć się z serwerem. Ta metoda jest sprawdzona, ale może zużywać więcej baterii.</string>
<string name="main_menu_donate_title">Wspomóż💸</string>
<string name="detail_item_cannot_open_apk">Aplikacja nie może zostać zainstalowana. Pobierz ją poprzez przeglądarkę. Sprawdź problem #531 po więcej informacji.</string>
<string name="settings_notifications_insistent_max_priority_title">Nadal wysyłaj powiadomienia dla najwyższych priorytetów</string>
<string name="detail_settings_notifications_dedicated_channels_title">Własne ustawienia powiadomień</string>

View file

@ -324,7 +324,6 @@
<string name="add_dialog_base_urls_dropdown_clear">Limpar URL do serviço</string>
<string name="main_banner_websocket_button_enable_now">Habilitar agora</string>
<string name="channel_notifications_group_default_name">Padrão</string>
<string name="main_menu_donate_title">Doar 💸</string>
<string name="settings_notifications_insistent_max_priority_summary_disabled">As notificações de prioridade máxima alertam apenas uma vez</string>
<string name="detail_settings_notifications_open_channels_title">Configurar configurações de notificação</string>
<string name="detail_settings_notifications_open_channels_summary">Ignorar o Não Perturbe, sons, etc.</string>

View file

@ -219,7 +219,7 @@
<string name="add_dialog_login_description">Esse tópico necessita autenticação. Por favor, insira um nome de utilizador e palavra-passe.</string>
<string name="add_dialog_login_new_user">Novo utilizador</string>
<string name="add_dialog_base_urls_dropdown_choose">Escolha URL de serviço</string>
<string name="detail_how_to_example">Exemplo (utilizando curl): <br></br><tt>$ curl -d \"Olá\" %1$s</tt></string>
<string name="detail_how_to_example"><![CDATA[ Exemplo (usando curl):<br/><tt>$ curl -d \"Olá\" %1$s</tt> ]]></string>
<string name="detail_delete_dialog_message">Deseja anular a subscrição deste tópico e eliminar todas as notificações recebidas\?</string>
<string name="detail_test_message">Esta é uma notificação de teste da aplicação Android do ntfy. Tem uma prioridade de nível %1$d. Se enviar outra notificação, poderá ser diferente.</string>
<string name="detail_test_message_error_too_large">Não foi possível enviar a mensagem: O anexo é grande demais.</string>
@ -258,7 +258,6 @@
<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 a predefinição do sistema</string>
<string name="main_menu_donate_title">Doar 💸</string>
<string name="settings_backup_restore_restore_failed">Falha ao restaurar %1$s</string>
<string name="settings_advanced_header">Avançado</string>
<string name="settings_advanced_broadcast_title">Messagens de transmissão</string>
@ -343,4 +342,11 @@
<string name="settings_advanced_unifiedpush_title">Habilitar UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy atuará como distribuidora UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy não atuará como distribuidora UnifiedPush</string>
<string name="main_banner_websocket_reconnect_text">Para garantir que os WebSockets se reconectem em segundo plano, conceda a permissão de Alarmes e Lembretes à aplicação ntfy</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Lembrar mais tarde</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Ignorar</string>
<string name="main_banner_websocket_reconnect_button_enable_now">Autorizar agora</string>
<string name="settings_advanced_exact_alarms_title">Alarmes exactos</string>
<string name="settings_advanced_exact_alarms_true">O ntfy pode agendar alarmes exatos. Alarmes exatos são necessários para reconectar os WebSockets em segundo plano. Clique para revogar a permissão.</string>
<string name="settings_advanced_exact_alarms_false">O ntfy não pode agendar alarmes exatos. Alarmes exatos são necessários para reconectar os WebSockets em segundo plano. Clique para conceder a permissão.</string>
</resources>

View file

@ -162,7 +162,6 @@
<string name="main_menu_report_bug_title">Raportează o problemă</string>
<string name="main_menu_docs_title">Citește documentația</string>
<string name="main_action_mode_delete_dialog_message">Dezabonează-te de la topicele selectate și șterge notificările permanent\?</string>
<string name="main_menu_donate_title">Donează 💸</string>
<string name="main_item_status_unified_push">%1$s (UnifiedPush)</string>
<string name="main_add_button_description">Adaugă abonament</string>
<string name="main_item_status_text_not_one">%1$d notificări</string>
@ -313,7 +312,7 @@
<string name="add_dialog_login_error_not_authorized">Logare eșuată. Utilizatorul %1$s nu este autorizat.</string>
<string name="add_dialog_login_new_user">Utilizator nou</string>
<string name="detail_no_notifications_text">Nu ai primit încă notificări pentru acest topic.</string>
<string name="detail_how_to_example">Exemplu (folosing curl):<br></br><tt>$ curl -d \"Salut\" %1$s</tt></string>
<string name="detail_how_to_example">Exemplu (folosing curl):<br/><tt>$ curl -d \"Salut\" %1$s</tt></string>
<string name="detail_delete_dialog_cancel">Anulează</string>
<string name="detail_test_title">Test: Poți specifica un titlu dacă dorești.</string>
<string name="detail_test_message">Această este o notificare de test de la aplicația ntfy pentru Android. Are nivelul priorității %1$d. Dacă trimiți altă notificare, s-ar putea să arate altfel.</string>
@ -343,4 +342,9 @@
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy va avea rol de distribuitor UnifiedPush</string>
<string name="settings_advanced_unifiedpush_title">Activează UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy nu va avea rol de distribuitor UnifiedPush</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Întreabă mai târziu</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Respinge</string>
<string name="settings_advanced_exact_alarms_title">Alarme exacte</string>
<string name="settings_advanced_exact_alarms_true">ntfy poate programa alarme exacte. Alarmele exacte sunt necesare pentru a reconecta WebSocket-urile în fundal. Faceți clic pentru a revoca permisiunea.</string>
<string name="settings_advanced_exact_alarms_false">ntfy nu poate programa alarme exacte. Alarmele exacte sunt necesare pentru a reconecta WebSockets în fundal. Faceți clic pentru a acorda permisiunea.</string>
</resources>

View file

@ -18,7 +18,7 @@
<string name="detail_delete_dialog_permanently_delete">Удалить навсегда</string>
<string name="detail_delete_dialog_cancel">Отмена</string>
<string name="detail_test_title">Тест: Вы можете установить заголовок, если хотите.</string>
<string name="detail_how_to_example">Пример (используя curl):<br></br><tt>$ curl -d \"Привет\" %1$s</tt></string>
<string name="detail_how_to_example"><![CDATA[ Пример (используя curl):<br/><tt>$ curl -d \"Привет\" %1$s</tt> ]]></string>
<string name="detail_clear_dialog_message">Удалить все уведомления в этой теме\?</string>
<string name="detail_clear_dialog_permanently_delete">Удалить навсегда</string>
<string name="detail_test_message_error">Не получилось отправить сообщение: %1$s</string>
@ -313,7 +313,6 @@
<string name="detail_item_cannot_open_apk">Установка приложений через уведомления больше не поддерживается. Скачайте приложение через браузер. Подробности смотрите в отчёте ntfy #531.</string>
<string name="detail_settings_appearance_icon_set_title">Иконка подписки</string>
<string name="detail_settings_appearance_icon_set_summary">Использовать иконку для отображения в уведомлениях</string>
<string name="main_menu_donate_title">Пожертвовать 💸</string>
<string name="settings_notifications_insistent_max_priority_summary_disabled">Уведомления с наивысшим приоритетом будут давать о себе знать только один раз</string>
<string name="detail_settings_notifications_instant_summary_off">Уведомления будут доставляться с помощью Firebase. Могут быть задержки с доставкой, но потребляется меньше энергии.</string>
<string name="add_dialog_base_urls_dropdown_clear">Очистить URL-адрес сервера</string>
@ -343,4 +342,11 @@
<string name="detail_settings_notifications_dedicated_channels_summary_on">Используются пользовательские настройки для этой подписки</string>
<string name="detail_settings_notifications_insistent_max_priority_list_item_disabled">Уведомлять только один раз</string>
<string name="settings_notifications_insistent_max_priority_title">Продолжать уведомлять при наивысшем приоритете</string>
<string name="main_banner_websocket_reconnect_text">Чтобы WebSockets работал в фоновом режиме, проставьте ntfy права «Будильники и напоминания»</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Спроси потом</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Закрыть</string>
<string name="main_banner_websocket_reconnect_button_enable_now">Предоставить</string>
<string name="settings_advanced_exact_alarms_title">Точные оповещения</string>
<string name="settings_advanced_exact_alarms_true">ntfy может выставлять точные оповещения. Точные оповещения необходимы для переподключения WebSockets в фоновом режиме. Нажмите, чтобы отозвать разрешение.</string>
<string name="settings_advanced_exact_alarms_false">ntfy может выставлять точные оповещения. Точные оповещения необходимы для переподключения WebSockets в фоновом режиме. Нажмите, чтобы дать разрешение.</string>
</resources>

View file

@ -282,7 +282,6 @@
<string name="main_menu_rate_title">Ohodnotiť aplikáciu ⭐</string>
<string name="main_menu_docs_title">Prečítať dokumentáciu</string>
<string name="main_menu_notifications_disabled_until">Oznámenia stlmené do %1$s</string>
<string name="main_menu_donate_title">Prispieť 💸</string>
<string name="main_action_mode_delete_dialog_message">Odhlásiť odber z vybraných tém a natrvalo vymazať všetky oznámenia\?</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Vymazať natrvalo</string>
<string name="main_action_mode_menu_unsubscribe">Odhlásiť odber</string>

View file

@ -70,7 +70,7 @@
<string name="add_dialog_base_urls_dropdown_clear">Rensa tjänstens URL</string>
<string name="detail_no_notifications_text">Du har inte fått några meddelanden för detta ämne ännu.</string>
<string name="detail_how_to_intro">För att skicka meddelanden till det här ämnet, PUT eller POST till ämnesadressen.</string>
<string name="detail_how_to_example">Exempel (med curl):<br></br><tt>$ curl -d \"Hej\" %1$s</tt></string>
<string name="detail_how_to_example"><![CDATA[ Exempel (med curl):<br/><tt>$ curl -d \"Hej\" %1$s</tt> ]]></string>
<string name="detail_how_to_link">Detaljerade instruktioner finns på ntfy.sh och i dokumentationen.</string>
<string name="detail_clear_dialog_message">Ta bort alla meddelanden i det här ämnet\?</string>
<string name="detail_clear_dialog_permanently_delete">Ta bort permanent</string>
@ -113,7 +113,6 @@
<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>
@ -343,4 +342,11 @@
<string name="detail_settings_notifications_insistent_max_priority_list_item_disabled">Meddela endast en gång</string>
<string name="detail_settings_global_setting_title">Använd de globala inställningarna</string>
<string name="detail_settings_appearance_icon_remove_summary">Ikon som visas i meddelanden för detta ämne</string>
<string name="main_banner_websocket_reconnect_text">För att säkerställa att WebSockets återansluter i bakgrunden, bevilja behörigheten Alarm &amp; Påminnelser till ntfy</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Fråga senare</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Avfärda</string>
<string name="main_banner_websocket_reconnect_button_enable_now">Bevilja nu</string>
<string name="settings_advanced_exact_alarms_title">Exakta larm</string>
<string name="settings_advanced_exact_alarms_true">ntfy kan schemalägga exakta larm. Exakta larm krävs för att återansluta WebSockets i bakgrunden. Klicka för att återkalla behörigheten.</string>
<string name="settings_advanced_exact_alarms_false">ntfy kan inte schemalägga exakta larm. Exakta larm krävs för att återansluta WebSockets i bakgrunden. Klicka för att bevilja behörigheten.</string>
</resources>

View file

@ -40,7 +40,6 @@
<string name="main_menu_report_bug_title">ஒரு பிழையைப் புகாரளிக்கவும்</string>
<string name="main_menu_docs_title">ஆவணத்தைப் படியுங்கள்</string>
<string name="main_menu_rate_title">பயன்பாட்டை மதிப்பிடுங்கள்</string>
<string name="main_menu_donate_title">நன்கொடை</string>
<string name="main_action_mode_menu_unsubscribe">குழுவிலகவும்</string>
<string name="main_action_mode_delete_dialog_message">தேர்ந்தெடுக்கப்பட்ட தலைப்பு (கள்) இலிருந்து குழுவிலகவும், அனைத்து அறிவிப்புகளையும் நிரந்தரமாக நீக்கவா?</string>
<string name="main_action_mode_delete_dialog_cancel">ரத்துசெய்</string>

View file

@ -35,7 +35,6 @@
<string name="main_menu_report_bug_title">รายงานปัญหา(Bug)</string>
<string name="main_menu_docs_title">อ่านเอกสาร</string>
<string name="main_menu_rate_title">ให้คะแนนแอป ⭐</string>
<string name="main_menu_donate_title">บริจาค 💸</string>
<string name="main_action_mode_menu_unsubscribe">ยกเลิกการสมัครรับ</string>
<string name="main_action_mode_delete_dialog_message">ต้องการยกเลิกการสมัครจากหัวข้อที่เลือกและลบการแจ้งเตือนทั้งหมดอย่างถาวรใช่ไหม</string>
<string name="main_action_mode_delete_dialog_permanently_delete">ลบถาวร</string>

View file

@ -19,9 +19,7 @@
<string name="refresh_message_no_results">Her şey güncel</string>
<string name="main_menu_notifications_disabled_until">Bildirimler şu zamana kadar sessize alındı: %1$s</string>
<string name="channel_subscriber_notification_noinstant_text_more">%1$d konuya abone olundu</string>
<string name="refresh_message_error">%1$d abonelik yenilenemedi
\n
\n%2$s</string>
<string name="refresh_message_error">%1$d abonelik yenilenemedi\n\n%2$s</string>
<string name="refresh_message_error_one">Abonelik yenilenemedi: %1$s</string>
<string name="main_action_bar_title">Abone olunan konular</string>
<string name="main_menu_notifications_enabled">Bildirimler açık</string>
@ -209,7 +207,7 @@
<string name="main_item_date_yesterday">dün</string>
<string name="main_banner_battery_button_dismiss">Kapat</string>
<string name="main_banner_battery_button_fix_now">Şimdi düzelt</string>
<string name="detail_how_to_example">Örnek (curl kullanarak):<br></br><tt>$ curl -d \"Merhaba\" %1$s</tt></string>
<string name="detail_how_to_example"><![CDATA[ Örnek (curl kullanarak):<br/><tt>$ curl -d "Merhaba" %1$s</tt> ]]></string>
<string name="detail_delete_dialog_permanently_delete">Kalıcı olarak sil</string>
<string name="detail_test_message_error_unauthorized_user">Mesaj gönderilemiyor: \"%1$s\" kullanıcısı yetkilendirilmedi.</string>
<string name="detail_how_to_link">Ayrıntılı talimatlar ntfy.sh adrsimde ve belgelerde bulunabilir.</string>
@ -327,7 +325,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>
<string name="settings_notifications_insistent_max_priority_summary_disabled">En yüksek öncelikli bildirimler yalnızca bir kez uyarı verir</string>
<string name="detail_settings_notifications_dedicated_channels_summary_on">Bu abonelik için özel ayarları kullan</string>
@ -343,4 +340,11 @@
<string name="settings_advanced_unifiedpush_title">UnifiedPush\'u etkinleştir</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy bir UnifiedPush dağıtıcısı olarak davranmayacaktır</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy bir UnifiedPush dağıtıcısı olarak davranacaktır</string>
<string name="main_banner_websocket_reconnect_text">WebSocket bağlantılarının arka planda yeniden bağlanmasını sağlamak için ntfy uygulamasına Alarm ve Hatırlatıcılar iznini verin</string>
<string name="main_banner_websocket_reconnect_button_remind_later">Daha Sonra Hatırlat</string>
<string name="main_banner_websocket_reconnect_button_dismiss">Yoksay</string>
<string name="main_banner_websocket_reconnect_button_enable_now">Şimdi izin ver</string>
<string name="settings_advanced_exact_alarms_title">Kesin alarmlar</string>
<string name="settings_advanced_exact_alarms_true">ntfy, kesin alarmlar planlayabilir. Kesin alarmlar, WebSocketlerin arka planda yeniden bağlanması için gereklidir. İzni geri almak için tıklayın.</string>
<string name="settings_advanced_exact_alarms_false">ntfy, kesin alarmlar planlayamaz. Kesin alarmlar, WebSocketlerin arka planda yeniden bağlanması için gereklidir. İzni vermek için tıklayın.</string>
</resources>

View file

@ -331,7 +331,6 @@
<string name="detail_settings_notifications_dedicated_channels_summary_off">Використання налаштувань за замовчуванням (звуки, \"Не турбувати\" тощо)</string>
<string name="detail_settings_notifications_open_channels_summary">Перевизначення режиму \"Не турбувати\" (DND), звуки тощо.</string>
<string name="detail_settings_notifications_insistent_max_priority_list_item_enabled">Продовжувати сповіщати</string>
<string name="main_menu_donate_title">Пожертвувати 💸</string>
<string name="settings_notifications_insistent_max_priority_title">Безперервне сповіщення для найвищого пріоритету</string>
<string name="settings_notifications_insistent_max_priority_summary_enabled">Сповіщення з максимальним пріоритетом безперервно сповіщають, доки не будуть закриті</string>
<string name="settings_notifications_insistent_max_priority_summary_disabled">Сповіщення з максимальним пріоритетом сповіщають лише один раз</string>

View file

@ -248,7 +248,6 @@
<string name="channel_subscriber_notification_instant_text_three">Uchta darhol yuborish mavzulariga obuna boldik</string>
<string name="main_menu_notifications_disabled_until">Bildirishnomalar %1$s gacha ochirilgan</string>
<string name="channel_subscriber_notification_noinstant_text_five">Beshta mavzuga obuna boldik</string>
<string name="main_menu_donate_title">Xayriya qiling 💸</string>
<string name="main_action_mode_delete_dialog_cancel">Bekor qilish</string>
<string name="add_dialog_use_another_server_description">Boshqa serverlardan mavzularga obuna bolish uchun quyida URL manzillarini kiriting.</string>
<string name="add_dialog_login_description">Ushbu mavzu tizimga kirishni talab qiladi. Iltimos, foydalanuvchi nomi va parolni kiriting.</string>

View file

@ -25,7 +25,6 @@
<string name="channel_subscriber_notification_title">Chờ thông báo</string>
<string name="refresh_message_result">Đã nhận %1$d thông báo</string>
<string name="main_action_mode_delete_dialog_permanently_delete">Xóa vĩnh viễn</string>
<string name="main_menu_donate_title">Quyên góp 💸</string>
<string name="main_action_mode_delete_dialog_cancel">Hủy</string>
<string name="main_action_mode_delete_dialog_message">Hủy đăng kí các chủ đề đã chọn và xóa tất cả thông báo?</string>
<string name="main_banner_websocket_button_remind_later">Hỏi sau</string>

View file

@ -327,7 +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>
<string name="channel_notifications_group_default_name">默认</string>
<string name="settings_notifications_insistent_max_priority_summary_enabled">持续以最高优先级通知进行提醒,直至取消</string>

View file

@ -108,7 +108,6 @@
<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>
@ -213,7 +212,7 @@
<string name="detail_item_saved_successfully">儲存為 \"Downloads\" 資料中的 \"%1$s\"</string>
<string name="detail_test_message_error_unauthorized_user">不能夠發布信息:用戶 %1$s 不被授權。</string>
<string name="detail_how_to_intro">PUT 或 POST 主題網址以傳送通訊。</string>
<string name="detail_how_to_example">例如(使用 curl): <br></br><tt>$ curl -d \"Hi\" %1$s</tt></string>
<string name="detail_how_to_example"><![CDATA[ 例如(使用 curl): <br/><tt>$ curl -d "Hi" %1$s</tt>]]></string>
<string name="detail_test_message_error">不能夠傳送訊息:%1$s</string>
<string name="detail_item_tags">標籤:%1$s</string>
<string name="detail_instant_delivery_enabled">啟動即時傳送</string>
@ -343,4 +342,11 @@
<string name="detail_settings_appearance_icon_error_saving">無法保存圖標:%1$s</string>
<string name="detail_settings_global_setting_title">使用全局設置</string>
<string name="user_dialog_description_edit">您可以編輯該用戶的用戶名和密碼,或刪除該用戶。</string>
<string name="main_banner_websocket_reconnect_text">為確保 WebSocket 能在背景重新連線,請授予 ntfy「鬧鐘與提醒」權限</string>
<string name="main_banner_websocket_reconnect_button_dismiss">關閉</string>
<string name="main_banner_websocket_reconnect_button_remind_later">稍後再問</string>
<string name="main_banner_websocket_reconnect_button_enable_now">立即授權</string>
<string name="settings_advanced_exact_alarms_title">精準提醒</string>
<string name="settings_advanced_exact_alarms_true">ntfy 可以排程精準提醒。精準提醒是讓 WebSocket 能在背景重新連線的必要條件。點擊以撤銷此權限。</string>
<string name="settings_advanced_exact_alarms_false">ntfy 無法排程精準提醒。精準提醒是讓 WebSocket 能在背景重新連線的必要條件。點擊以授予此權限。</string>
</resources>

View file

@ -1,17 +1,151 @@
<!--?xml version="1.0" encoding="UTF-8"?-->
<resources>
<color name="black">#ff000000</color>
<color name="black_900">#121212</color> <!-- Main dark mode surface color, as per style guide -->
<color name="black_800b">#1b2023</color> <!-- Action bar & item selection (dark mode); this has a touch of blue! -->
<color name="black_700b">#282F33</color> <!-- Card selection (dark mode); this has a touch of blue! -->
<color name="gray_500">#dddddd</color> <!-- Card selection (light mode) -->
<color name="gray_400">#eeeeee</color> <!-- Item selection (light mode) -->
<color name="white">#ffffffff</color>
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="md_theme_primary">#338574</color>
<color name="md_theme_onPrimary">#FFFFFF</color>
<color name="md_theme_primaryContainer">#A0F2DD</color>
<color name="md_theme_onPrimaryContainer">#005144</color>
<color name="md_theme_secondary">#4B635C</color>
<color name="md_theme_onSecondary">#FFFFFF</color>
<color name="md_theme_secondaryContainer">#CDE8DF</color>
<color name="md_theme_onSecondaryContainer">#334B45</color>
<color name="md_theme_tertiary">#436278</color>
<color name="md_theme_onTertiary">#FFFFFF</color>
<color name="md_theme_tertiaryContainer">#C8E6FF</color>
<color name="md_theme_onTertiaryContainer">#2A4A5F</color>
<color name="md_theme_error">#BA1A1A</color>
<color name="md_theme_onError">#FFFFFF</color>
<color name="md_theme_errorContainer">#FFDAD6</color>
<color name="md_theme_onErrorContainer">#93000A</color>
<color name="md_theme_background">#FFFFFF</color>
<color name="md_theme_onBackground">#171D1B</color>
<color name="md_theme_surface">#FFFFFF</color>
<color name="md_theme_onSurface">#171D1B</color>
<color name="md_theme_surfaceVariant">#E0E0E0</color>
<color name="md_theme_onSurfaceVariant">#3F4946</color>
<color name="md_theme_outline">#6F7976</color>
<color name="md_theme_outlineVariant">#C0C0C0</color>
<color name="md_theme_scrim">#000000</color>
<color name="md_theme_inverseSurface">#2B3230</color>
<color name="md_theme_inverseOnSurface">#F0F0F0</color>
<color name="md_theme_inversePrimary">#84D6C2</color>
<color name="md_theme_primaryFixed">#A0F2DD</color>
<color name="md_theme_onPrimaryFixed">#00201A</color>
<color name="md_theme_primaryFixedDim">#84D6C2</color>
<color name="md_theme_onPrimaryFixedVariant">#005144</color>
<color name="md_theme_secondaryFixed">#CDE8DF</color>
<color name="md_theme_onSecondaryFixed">#06201A</color>
<color name="md_theme_secondaryFixedDim">#B1CCC4</color>
<color name="md_theme_onSecondaryFixedVariant">#334B45</color>
<color name="md_theme_tertiaryFixed">#C8E6FF</color>
<color name="md_theme_onTertiaryFixed">#001E2E</color>
<color name="md_theme_tertiaryFixedDim">#AACBE4</color>
<color name="md_theme_onTertiaryFixedVariant">#2A4A5F</color>
<color name="md_theme_surfaceDim">#E0E0E0</color>
<color name="md_theme_surfaceBright">#FFFFFF</color>
<color name="md_theme_surfaceContainerLowest">#FFFFFF</color>
<color name="md_theme_surfaceContainerLow">#F5F5F5</color>
<color name="md_theme_surfaceContainer">#F0F0F0</color>
<color name="md_theme_surfaceContainerHigh">#EEEEEE</color>
<color name="md_theme_surfaceContainerHighest">#E0E0E0</color>
<color name="md_theme_primary_mediumContrast">#003E34</color>
<color name="md_theme_onPrimary_mediumContrast">#FFFFFF</color>
<color name="md_theme_primaryContainer_mediumContrast">#237A69</color>
<color name="md_theme_onPrimaryContainer_mediumContrast">#FFFFFF</color>
<color name="md_theme_secondary_mediumContrast">#233B35</color>
<color name="md_theme_onSecondary_mediumContrast">#FFFFFF</color>
<color name="md_theme_secondaryContainer_mediumContrast">#59726B</color>
<color name="md_theme_onSecondaryContainer_mediumContrast">#FFFFFF</color>
<color name="md_theme_tertiary_mediumContrast">#18394E</color>
<color name="md_theme_onTertiary_mediumContrast">#FFFFFF</color>
<color name="md_theme_tertiaryContainer_mediumContrast">#517187</color>
<color name="md_theme_onTertiaryContainer_mediumContrast">#FFFFFF</color>
<color name="md_theme_error_mediumContrast">#740006</color>
<color name="md_theme_onError_mediumContrast">#FFFFFF</color>
<color name="md_theme_errorContainer_mediumContrast">#CF2C27</color>
<color name="md_theme_onErrorContainer_mediumContrast">#FFFFFF</color>
<color name="md_theme_background_mediumContrast">#F5FBF7</color>
<color name="md_theme_onBackground_mediumContrast">#171D1B</color>
<color name="md_theme_surface_mediumContrast">#F5FBF7</color>
<color name="md_theme_onSurface_mediumContrast">#0C1211</color>
<color name="md_theme_surfaceVariant_mediumContrast">#DBE5E0</color>
<color name="md_theme_onSurfaceVariant_mediumContrast">#2F3835</color>
<color name="md_theme_outline_mediumContrast">#4B5551</color>
<color name="md_theme_outlineVariant_mediumContrast">#656F6C</color>
<color name="md_theme_scrim_mediumContrast">#000000</color>
<color name="md_theme_inverseSurface_mediumContrast">#2B3230</color>
<color name="md_theme_inverseOnSurface_mediumContrast">#ECF2EF</color>
<color name="md_theme_inversePrimary_mediumContrast">#84D6C2</color>
<color name="md_theme_primaryFixed_mediumContrast">#237A69</color>
<color name="md_theme_onPrimaryFixed_mediumContrast">#FFFFFF</color>
<color name="md_theme_primaryFixedDim_mediumContrast">#006051</color>
<color name="md_theme_onPrimaryFixedVariant_mediumContrast">#FFFFFF</color>
<color name="md_theme_secondaryFixed_mediumContrast">#59726B</color>
<color name="md_theme_onSecondaryFixed_mediumContrast">#FFFFFF</color>
<color name="md_theme_secondaryFixedDim_mediumContrast">#415A53</color>
<color name="md_theme_onSecondaryFixedVariant_mediumContrast">#FFFFFF</color>
<color name="md_theme_tertiaryFixed_mediumContrast">#517187</color>
<color name="md_theme_onTertiaryFixed_mediumContrast">#FFFFFF</color>
<color name="md_theme_tertiaryFixedDim_mediumContrast">#39586E</color>
<color name="md_theme_onTertiaryFixedVariant_mediumContrast">#FFFFFF</color>
<color name="md_theme_surfaceDim_mediumContrast">#C1C8C5</color>
<color name="md_theme_surfaceBright_mediumContrast">#F5FBF7</color>
<color name="md_theme_surfaceContainerLowest_mediumContrast">#FFFFFF</color>
<color name="md_theme_surfaceContainerLow_mediumContrast">#EFF5F1</color>
<color name="md_theme_surfaceContainer_mediumContrast">#E3EAE6</color>
<color name="md_theme_surfaceContainerHigh_mediumContrast">#D8DEDB</color>
<color name="md_theme_surfaceContainerHighest_mediumContrast">#CDD3D0</color>
<color name="md_theme_primary_highContrast">#00332A</color>
<color name="md_theme_onPrimary_highContrast">#FFFFFF</color>
<color name="md_theme_primaryContainer_highContrast">#005346</color>
<color name="md_theme_onPrimaryContainer_highContrast">#FFFFFF</color>
<color name="md_theme_secondary_highContrast">#18302B</color>
<color name="md_theme_onSecondary_highContrast">#FFFFFF</color>
<color name="md_theme_secondaryContainer_highContrast">#364E47</color>
<color name="md_theme_onSecondaryContainer_highContrast">#FFFFFF</color>
<color name="md_theme_tertiary_highContrast">#0B2F43</color>
<color name="md_theme_onTertiary_highContrast">#FFFFFF</color>
<color name="md_theme_tertiaryContainer_highContrast">#2D4D62</color>
<color name="md_theme_onTertiaryContainer_highContrast">#FFFFFF</color>
<color name="md_theme_error_highContrast">#600004</color>
<color name="md_theme_onError_highContrast">#FFFFFF</color>
<color name="md_theme_errorContainer_highContrast">#98000A</color>
<color name="md_theme_onErrorContainer_highContrast">#FFFFFF</color>
<color name="md_theme_background_highContrast">#F5FBF7</color>
<color name="md_theme_onBackground_highContrast">#171D1B</color>
<color name="md_theme_surface_highContrast">#F5FBF7</color>
<color name="md_theme_onSurface_highContrast">#000000</color>
<color name="md_theme_surfaceVariant_highContrast">#DBE5E0</color>
<color name="md_theme_onSurfaceVariant_highContrast">#000000</color>
<color name="md_theme_outline_highContrast">#252E2B</color>
<color name="md_theme_outlineVariant_highContrast">#424B48</color>
<color name="md_theme_scrim_highContrast">#000000</color>
<color name="md_theme_inverseSurface_highContrast">#2B3230</color>
<color name="md_theme_inverseOnSurface_highContrast">#FFFFFF</color>
<color name="md_theme_inversePrimary_highContrast">#84D6C2</color>
<color name="md_theme_primaryFixed_highContrast">#005346</color>
<color name="md_theme_onPrimaryFixed_highContrast">#FFFFFF</color>
<color name="md_theme_primaryFixedDim_highContrast">#003A30</color>
<color name="md_theme_onPrimaryFixedVariant_highContrast">#FFFFFF</color>
<color name="md_theme_secondaryFixed_highContrast">#364E47</color>
<color name="md_theme_onSecondaryFixed_highContrast">#FFFFFF</color>
<color name="md_theme_secondaryFixedDim_highContrast">#1F3731</color>
<color name="md_theme_onSecondaryFixedVariant_highContrast">#FFFFFF</color>
<color name="md_theme_tertiaryFixed_highContrast">#2D4D62</color>
<color name="md_theme_onTertiaryFixed_highContrast">#FFFFFF</color>
<color name="md_theme_tertiaryFixedDim_highContrast">#14364A</color>
<color name="md_theme_onTertiaryFixedVariant_highContrast">#FFFFFF</color>
<color name="md_theme_surfaceDim_highContrast">#B4BAB7</color>
<color name="md_theme_surfaceBright_highContrast">#F5FBF7</color>
<color name="md_theme_surfaceContainerLowest_highContrast">#FFFFFF</color>
<color name="md_theme_surfaceContainerLow_highContrast">#ECF2EF</color>
<color name="md_theme_surfaceContainer_highContrast">#DEE4E0</color>
<color name="md_theme_surfaceContainerHigh_highContrast">#CFD6D2</color>
<color name="md_theme_surfaceContainerHighest_highContrast">#C1C8C5</color>
<color name="teal">#338574</color> <!-- Primary color (light mode) -->
<color name="teal_light">#65b5a3</color> <!-- Primary color (dark mode) -->
<color name="teal_dark">#2a6e60</color> <!-- Action bar background in action mode (light mode) -->
<color name="red_light">#fe4d2e</color> <!-- Danger text (dark mode) -->
<color name="red_dark">#c30000</color> <!-- Danger text (light mode) -->
<color name="action_bar">#338574</color> <!-- md_theme_primary (light) -->
<color name="detail_activity_background">#EEEEEE</color> <!-- Light gray for detail activity in light mode -->
<!-- Remove black status bar in Action mode: https://stackoverflow.com/a/79456725 -->
<color name="abc_decor_view_status_guard" tools:override="true">@android:color/transparent</color>
<color name="abc_decor_view_status_guard_light" tools:override="true">@android:color/transparent</color>
</resources>

View file

@ -42,7 +42,6 @@
<string name="main_menu_report_bug_title">Report a bug</string>
<string name="main_menu_docs_title">Read the docs</string>
<string name="main_menu_rate_title">Rate the app ⭐</string>
<string name="main_menu_donate_title">Donate 💸</string>
<!-- Main activity: Action mode -->
<string name="main_action_mode_menu_unsubscribe">Unsubscribe</string>
@ -310,6 +309,9 @@
<string name="settings_general_dark_mode_entry_system">Use system default</string>
<string name="settings_general_dark_mode_entry_light">Light mode</string>
<string name="settings_general_dark_mode_entry_dark">Dark mode</string>
<string name="settings_general_dynamic_colors_title">Dynamic colors</string>
<string name="settings_general_dynamic_colors_summary_enabled">Using the dynamic system colors</string>
<string name="settings_general_dynamic_colors_summary_disabled">Using the ntfy theme colors</string>
<string name="settings_backup_restore_header">Backup &amp; Restore</string>
<string name="settings_backup_restore_backup_title">Back up to file</string>
<string name="settings_backup_restore_backup_summary">Export config, notifications, and users</string>

View file

@ -1,34 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Main app theme; dark theme styles see values-night/styles.xml -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/teal</item>
<item name="colorAccent">@color/teal</item> <!-- checkboxes, text fields -->
<item name="android:colorBackground">@color/white</item> <!-- background -->
<item name="android:statusBarColor">@color/teal</item>
<item name="actionModeBackground">@color/teal_dark</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
</style>
<style name="DangerText" parent="@android:style/TextAppearance">
<item name="android:textColor">@color/red_dark</item>
</style>
<style name="FloatingActionButton" parent="@style/Widget.MaterialComponents.FloatingActionButton">
<item name="tint">@color/white</item>
<item name="backgroundTint">@color/teal</item>
</style>
<style name="CardView" parent="@style/Widget.MaterialComponents.CardView">
<item name="cardBackgroundColor">@color/white</item>
</style>
<style name="CardViewBackground">
<item name="android:background">@color/gray_400</item>
</style>
<!-- Rounded corners in images, see https://stackoverflow.com/a/61960983/1440785 -->
<style name="roundedCornersImageView" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">5dp</item>
</style>
</resources>

View file

@ -0,0 +1,122 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/md_theme_primary</item>
<item name="colorOnPrimary">@color/md_theme_onPrimary</item>
<item name="colorPrimaryContainer">@color/md_theme_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer</item>
<item name="colorSecondary">@color/md_theme_secondary</item>
<item name="colorOnSecondary">@color/md_theme_onSecondary</item>
<item name="colorSecondaryContainer">@color/md_theme_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer</item>
<item name="colorTertiary">@color/md_theme_tertiary</item>
<item name="colorOnTertiary">@color/md_theme_onTertiary</item>
<item name="colorTertiaryContainer">@color/md_theme_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/md_theme_onTertiaryContainer</item>
<item name="colorError">@color/md_theme_error</item>
<item name="colorOnError">@color/md_theme_onError</item>
<item name="colorErrorContainer">@color/md_theme_errorContainer</item>
<item name="colorOnErrorContainer">@color/md_theme_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_background</item>
<item name="colorOnBackground">@color/md_theme_onBackground</item>
<item name="colorSurface">@color/md_theme_surface</item>
<item name="colorOnSurface">@color/md_theme_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_outline</item>
<item name="colorOutlineVariant">@color/md_theme_outlineVariant</item>
<item name="colorSurfaceInverse">@color/md_theme_inverseSurface</item>
<item name="colorOnSurfaceInverse">@color/md_theme_inverseOnSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_inversePrimary</item>
<item name="colorPrimaryFixed">@color/md_theme_primaryFixed</item>
<item name="colorOnPrimaryFixed">@color/md_theme_onPrimaryFixed</item>
<item name="colorPrimaryFixedDim">@color/md_theme_primaryFixedDim</item>
<item name="colorOnPrimaryFixedVariant">@color/md_theme_onPrimaryFixedVariant</item>
<item name="colorSecondaryFixed">@color/md_theme_secondaryFixed</item>
<item name="colorOnSecondaryFixed">@color/md_theme_onSecondaryFixed</item>
<item name="colorSecondaryFixedDim">@color/md_theme_secondaryFixedDim</item>
<item name="colorOnSecondaryFixedVariant">@color/md_theme_onSecondaryFixedVariant</item>
<item name="colorTertiaryFixed">@color/md_theme_tertiaryFixed</item>
<item name="colorOnTertiaryFixed">@color/md_theme_onTertiaryFixed</item>
<item name="colorTertiaryFixedDim">@color/md_theme_tertiaryFixedDim</item>
<item name="colorOnTertiaryFixedVariant">@color/md_theme_onTertiaryFixedVariant</item>
<item name="colorSurfaceDim">@color/md_theme_surfaceDim</item>
<item name="colorSurfaceBright">@color/md_theme_surfaceBright</item>
<item name="colorSurfaceContainerLowest">@color/md_theme_surfaceContainerLowest</item>
<item name="colorSurfaceContainerLow">@color/md_theme_surfaceContainerLow</item>
<item name="colorSurfaceContainer">@color/md_theme_surfaceContainer</item>
<item name="colorSurfaceContainerHigh">@color/md_theme_surfaceContainerHigh</item>
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest</item>
<item name="switchPreferenceCompatStyle">@style/MaterialSwitch</item>
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">@style/ActionMode</item>
<item name="actionModeCloseButtonStyle">@style/ActionModeCloseButtonStyle</item>
<item name="actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
</style>
<style name="OverflowButtonStyle" parent="Widget.AppCompat.ActionButton.Overflow" />
<style name="DangerText" parent="@android:style/TextAppearance">
<item name="android:textColor">?attr/colorError</item>
</style>
<style name="FloatingActionButton" parent="@style/Widget.Material3.FloatingActionButton.Primary">
<item name="backgroundTint">?attr/colorPrimary</item>
<item name="tint">?attr/colorOnPrimary</item>
</style>
<style name="CardView" parent="@style/Widget.Material3.CardView.Elevated" />
<style name="CardViewBackground" />
<!-- Rounded corners in images, see https://stackoverflow.com/a/61960983/1440785 -->
<style name="roundedCornersImageView" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">5dp</item>
</style>
<!-- Material Design 3 switches in the preferences -->
<style name="MaterialSwitch" parent="@style/Preference.SwitchPreferenceCompat.Material">
<item name="widgetLayout">@layout/view_preference_switch</item>
</style>
<!-- Action mode colors -->
<style name="ActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">?attr/colorPrimary</item>
<item name="titleTextStyle">@style/ActionModeTitle</item>
</style>
<style name="ActionModeTitle" parent="@style/TextAppearance.AppCompat.Widget.ActionMode.Title">
<item name="android:textColor">?attr/colorOnPrimary</item>
</style>
<style name="ActionModeCloseButtonStyle" parent="Widget.AppCompat.ActionButton.CloseMode">
<item name="android:tint">@android:color/white</item>
</style>
<!-- Banner style with reduced corner radius -->
<style name="BannerCardStyle" parent="Widget.Material3.CardView.Elevated">
<item name="shapeAppearanceOverlay">@style/BannerShapeAppearance</item>
</style>
<style name="BannerShapeAppearance">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">8dp</item>
</style>
<!-- Full-screen dialog style for Material 3 compliance -->
<style name="Theme.App.FullScreenDialog" parent="AppTheme">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">?attr/colorSurface</item>
<item name="android:windowBackground">?attr/colorSurface</item>
<item name="android:windowAnimationStyle">@style/Animation.App.FullScreenDialog</item>
</style>
<!-- Slide animation for full-screen dialog -->
<style name="Animation.App.FullScreenDialog" parent="Animation.AppCompat.Dialog">
<item name="android:windowEnterAnimation">@anim/slide_in_bottom</item>
<item name="android:windowExitAnimation">@anim/slide_out_bottom</item>
</style>
</resources>

View file

@ -22,6 +22,7 @@
<string name="settings_general_default_base_url_key" translatable="false">DefaultBaseURL</string>
<string name="settings_general_users_key" translatable="false">ManageUsers</string>
<string name="settings_general_dark_mode_key" translatable="false">DarkMode</string>
<string name="settings_general_dynamic_colors_key" translatable="false">DynamicColors</string>
<string name="settings_backup_restore_backup_key" translatable="false">Backup</string>
<string name="settings_backup_restore_restore_key" translatable="false">Restore</string>
<string name="settings_advanced_broadcast_key" translatable="false">BroadcastEnabled</string>

View file

@ -3,7 +3,7 @@
<PreferenceCategory
app:key="@string/detail_settings_notifications_header_key"
app:title="@string/settings_notifications_header">
<SwitchPreference
<SwitchPreferenceCompat
app:key="@string/detail_settings_notifications_instant_key"
app:title="@string/detail_settings_notifications_instant_title"
app:isPreferenceVisible="false"/>
@ -35,7 +35,7 @@
app:entryValues="@array/detail_settings_notifications_insistent_max_priority_values"
app:defaultValue="-1"
app:isPreferenceVisible="false"/> <!-- Same as Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL -->
<SwitchPreference
<SwitchPreferenceCompat
app:key="@string/detail_settings_notifications_dedicated_channels_key"
app:title="@string/detail_settings_notifications_dedicated_channels_title"
app:isPreferenceVisible="false"/>

View file

@ -25,7 +25,7 @@
app:entries="@array/settings_notifications_auto_delete_entries"
app:entryValues="@array/settings_notifications_auto_delete_values"
app:defaultValue="2592000"/>
<SwitchPreference
<SwitchPreferenceCompat
app:key="@string/settings_notifications_insistent_max_priority_key"
app:title="@string/settings_notifications_insistent_max_priority_title"
app:defaultValue="false"/>
@ -37,9 +37,7 @@
<PreferenceCategory app:title="@string/settings_general_header">
<EditTextPreference
app:key="@string/settings_general_default_base_url_key"
app:title="@string/settings_general_default_base_url_title"
app:dialogLayout="@layout/preference_dialog_edittext_edited"
app:dialogMessage="@string/settings_general_default_base_url_message"/>
app:title="@string/settings_general_default_base_url_title" />
<Preference
app:key="@string/settings_general_users_key"
app:title="@string/settings_general_users_title"
@ -51,6 +49,10 @@
app:entries="@array/settings_general_dark_mode_entries"
app:entryValues="@array/settings_general_dark_mode_values"
app:defaultValue="-1"/>
<SwitchPreferenceCompat
app:key="@string/settings_general_dynamic_colors_key"
app:title="@string/settings_general_dynamic_colors_title"
app:isPreferenceVisible="false"/>
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_backup_restore_header">
<ListPreference
@ -75,15 +77,15 @@
<Preference
app:key="@string/settings_advanced_exact_alarms_key"
app:title="@string/settings_advanced_exact_alarms_title"/>
<SwitchPreference
<SwitchPreferenceCompat
app:key="@string/settings_advanced_broadcast_key"
app:title="@string/settings_advanced_broadcast_title"
app:enabled="true"/>
<SwitchPreference
<SwitchPreferenceCompat
app:key="@string/settings_advanced_unifiedpush_key"
app:title="@string/settings_advanced_unifiedpush_title"
app:enabled="true"/>
<SwitchPreference
<SwitchPreferenceCompat
app:key="@string/settings_advanced_record_logs_key"
app:title="@string/settings_advanced_record_logs_title"
app:enabled="true"/>

View file

@ -0,0 +1,9 @@
This release makes changes to comply with the Google Play policies.
See https://github.com/binwiederhier/ntfy/issues/1463 for details.
Changes:
- Remove the "Donate" button from menu (all variants)
- Change default display name from "ntfy.sh/mytopic" to "mytopic" (all variants)
- Remove links to ntfy docs and issue tracker (Play variant only)
- Remove how-to links to ntfy.sh in a few places (Play variant only)
- Remove "Copy topic address" from subscription menu (Play variant only)

View file

@ -1,6 +1,7 @@
Features:
* Added GIF support for preview images (ntfy-android#76, thanks to @MichaelArkh)
* Added WebP support for preview images (ntfy-android#81, thanks to @jokakilla)
* Added UnifiedPush distributor selection support (ntfy-android#137, thanks to @p1gp1g)
Bug fixes + maintenance:
* Remove REQUEST_INSTALL_PACKAGES permission (#684)

View file

@ -0,0 +1,2 @@
Features:
* Moved the user interface to Material 3 and added dynamic color support (#580, ntfy-android#56, ntfy-android#126, ntfy-android#135, thanks to @Bnyro and @cyb3rko for the implementation, and to @RokeJulianLockhart for reporting)

Some files were not shown because too many files have changed in this diff Show more