fix: Improve push and pull notification reliability (#880)

Clean up the notification handling code and fix a lot of bugs, hopefully
without introducing new ones in the process.

Specific bugs discovered and fixed:

- The code that tried to sync notification filtering state between the
server and Pachli could fail, leaving things in an inconsistent state,
resulting in dropped notifications. Remove that code, do filtering
client-side.

- Logging out of an account would disable push notifications for all
accounts.

- If any account did not support push notifications then push
notifications were disabled for all accounts.

- If any account did not support push notifications the user was
prompted to log out of all accounts. Drop that entirely.

- The UnifiedPush library could get to a state where configuring the
notification mechanism would silently fail,

The preferences UI now has a section for notifications, showing:

- The Unified Push distributor in use (if any)
- A mechanism to change the distributor
- Per-account configuration and notification fetch details
- Battery optimisation state

General changes:

- Update to UnifiedPush library 2.4.0.

- NotificationFetcher.fetchAndShow() can now fetch a single account's
notifications, or all accounts, depending on data passed to the worker.

- Use ApiResult for `push/subscription` responses.

- Drop the "needs migration" terminology for the more specific "has push
scope", to make it clear what the issue with the account is.
This commit is contained in:
Nik Clayton 2024-08-18 15:17:57 +02:00 committed by GitHub
parent fcbcb4073e
commit 8b9fe6d5ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 795 additions and 603 deletions

View File

@ -36,7 +36,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="17"
line="18"
column="9"/>
</issue>
@ -47,7 +47,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="9"/>
</issue>
@ -58,7 +58,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="25"
line="26"
column="9"/>
</issue>
@ -69,7 +69,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="26"
line="27"
column="9"/>
</issue>
@ -694,17 +694,6 @@
column="86"/>
</issue>
<issue
id="Typos"
message="Repeated word &quot;tek&quot; in message: possible typo"
errorLine1=" &lt;string name=&quot;dialog_push_notification_migration_other_accounts&quot;>Pachli\&apos;ye bildirim aboneliği izni vermek için mevcut hesabınıza yeniden giriş yaptınız. Ancak, yine de bu şekilde geçirilmemiş başka hesaplarınız var. UnifiedPush bildirimleri desteğini etkinleştirmek için bunlara geçin ve tek tek yeniden giriş yapın.&lt;/string>"
errorLine2=" ^">
<location
file="src/main/res/values-tr/strings.xml"
line="497"
column="294"/>
</issue>
<issue
id="Typos"
message="&quot;Media&quot; is a common misspelling; did you mean &quot;Medier&quot;?"
@ -712,7 +701,7 @@
errorLine2=" ^">
<location
file="src/main/res/values-nb-rNO/strings.xml"
line="519"
line="514"
column="51"/>
</issue>
@ -723,7 +712,7 @@
errorLine2=" ^">
<location
file="src/main/res/values-nb-rNO/strings.xml"
line="649"
line="644"
column="43"/>
</issue>
@ -756,7 +745,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="99"
line="101"
column="5"/>
</issue>
@ -800,7 +789,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="629"
line="626"
column="5"/>
</issue>
@ -811,7 +800,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -822,7 +811,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -833,7 +822,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -844,7 +833,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -855,7 +844,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -866,7 +855,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -877,7 +866,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -888,7 +877,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -899,7 +888,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="24"
line="25"
column="31"/>
</issue>
@ -910,7 +899,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="18"
line="19"
column="30"/>
</issue>
@ -1207,7 +1196,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="19"
line="21"
column="13"/>
</issue>
@ -1218,7 +1207,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="22"
line="24"
column="13"/>
</issue>
@ -1229,7 +1218,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="32"
line="34"
column="13"/>
</issue>
@ -1240,7 +1229,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="74"
line="76"
column="13"/>
</issue>
@ -1251,7 +1240,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="80"
line="82"
column="13"/>
</issue>
@ -1262,7 +1251,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="101"
line="103"
column="13"/>
</issue>
@ -1273,7 +1262,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="109"
line="111"
column="13"/>
</issue>
@ -1284,7 +1273,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="143"
line="145"
column="13"/>
</issue>
@ -1409,17 +1398,6 @@
column="13"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.string.restart` appears to be unused"
errorLine1=" &lt;string name=&quot;restart&quot;>Restart&lt;/string>"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="426"
column="13"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.string.download_failed` appears to be unused"
@ -1559,7 +1537,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="621"
line="618"
column="13"/>
</issue>
@ -1570,7 +1548,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="647"
line="644"
column="13"/>
</issue>

View File

@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name=".PachliApplication"
@ -177,16 +178,6 @@
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED"/>
</intent-filter>
</receiver>
<receiver
android:exported="true"
android:enabled="true"
android:name=".receiver.NotificationBlockStateBroadcastReceiver"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"/>
<action android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"/>
</intent-filter>
</receiver>
<service
android:name=".service.PachliTileService"
@ -221,5 +212,4 @@
tools:node="remove" />
</application>
</manifest>

View File

@ -64,9 +64,7 @@ import app.pachli.appstore.ProfileEditedEvent
import app.pachli.components.compose.ComposeActivity.Companion.canHandleMimeType
import app.pachli.components.notifications.androidNotificationsAreEnabled
import app.pachli.components.notifications.createNotificationChannelsForAccount
import app.pachli.components.notifications.disableAllNotifications
import app.pachli.components.notifications.enablePushNotificationsWithFallback
import app.pachli.components.notifications.showMigrationNoticeIfNecessary
import app.pachli.components.notifications.enableAllNotifications
import app.pachli.core.activity.AccountSelectionListener
import app.pachli.core.activity.BottomSheetActivity
import app.pachli.core.activity.PostLookupFallbackBehavior
@ -1065,21 +1063,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
accountManager.updateActiveAccount(me)
createNotificationChannelsForAccount(accountManager.activeAccount!!, this)
// Setup push notifications
showMigrationNoticeIfNecessary(
this,
binding.mainCoordinatorLayout,
binding.composeButton,
accountManager,
sharedPreferencesRepository,
)
if (androidNotificationsAreEnabled(this, accountManager)) {
lifecycleScope.launch {
enablePushNotificationsWithFallback(this@MainActivity, mastodonApi, accountManager)
}
} else {
disableAllNotifications(this, accountManager)
}
// Setup notifications
// TODO: Continue to call this, as it sets properties in NotificationConfig
androidNotificationsAreEnabled(this, accountManager)
lifecycleScope.launch { enableAllNotifications(this@MainActivity, mastodonApi, accountManager) }
updateProfiles()

View File

@ -28,6 +28,7 @@ import app.pachli.core.network.model.Links
import app.pachli.core.network.model.Marker
import app.pachli.core.network.model.Notification
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.worker.NotificationWorker
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import dagger.hilt.android.qualifiers.ApplicationContext
@ -52,9 +53,18 @@ class NotificationFetcher @Inject constructor(
private val accountManager: AccountManager,
@ApplicationContext private val context: Context,
) {
suspend fun fetchAndShow() {
suspend fun fetchAndShow(accountId: Long) {
Timber.d("NotificationFetcher.fetchAndShow() started")
for (account in accountManager.getAllAccountsOrderedByActive()) {
val accounts = buildList {
if (accountId == NotificationWorker.ALL_ACCOUNTS) {
addAll(accountManager.getAllAccountsOrderedByActive())
} else {
accountManager.getAccountById(accountId)?.let { add(it) }
}
}
for (account in accounts) {
Timber.d(
"Checking %s$, notificationsEnabled = %s",
account.fullName,
@ -66,7 +76,7 @@ class NotificationFetcher @Inject constructor(
// Create sorted list of new notifications
val notifications = fetchNewNotifications(account)
.filter { filterNotification(notificationManager, account, it) }
.filter { filterNotification(notificationManager, account, it.type) }
.sortedWith(compareBy({ it.id.length }, { it.id })) // oldest notifications first
.toMutableList()

View File

@ -662,14 +662,10 @@ fun clearNotificationsForAccount(context: Context, account: AccountEntity) {
}
}
fun filterNotification(
notificationManager: NotificationManager,
account: AccountEntity,
notification: Notification,
): Boolean {
return filterNotification(notificationManager, account, notification.type)
}
/**
* Returns true if [account] is **not** filtering notifications of [type],
* otherwise false.
*/
fun filterNotification(
notificationManager: NotificationManager,
account: AccountEntity,
@ -775,52 +771,34 @@ private fun titleForType(
account: AccountEntity,
): String? {
val accountName = notification.account.name.unicodeWrap()
when (notification.type) {
return when (notification.type) {
Notification.Type.MENTION -> {
return String.format(
context.getString(R.string.notification_mention_format),
accountName,
)
context.getString(R.string.notification_mention_format, accountName)
}
Notification.Type.STATUS -> {
return String.format(
context.getString(R.string.notification_subscription_format),
accountName,
)
context.getString(R.string.notification_subscription_format, accountName)
}
Notification.Type.FOLLOW -> {
return String.format(
context.getString(R.string.notification_follow_format),
accountName,
)
context.getString(R.string.notification_follow_format, accountName)
}
Notification.Type.FOLLOW_REQUEST -> {
return String.format(
context.getString(R.string.notification_follow_request_format),
accountName,
)
context.getString(R.string.notification_follow_request_format, accountName)
}
Notification.Type.FAVOURITE -> {
return String.format(
context.getString(R.string.notification_favourite_format),
accountName,
)
context.getString(R.string.notification_favourite_format, accountName)
}
Notification.Type.REBLOG -> {
return String.format(
context.getString(R.string.notification_reblog_format),
accountName,
)
context.getString(R.string.notification_reblog_format, accountName)
}
Notification.Type.POLL -> {
val status = notification.status!!
return if (status.account.id == account.accountId) {
if (status.account.id == account.accountId) {
context.getString(R.string.poll_ended_created)
} else {
context.getString(R.string.poll_ended_voted)
@ -828,31 +806,25 @@ private fun titleForType(
}
Notification.Type.SIGN_UP -> {
return String.format(
context.getString(R.string.notification_sign_up_format),
accountName,
)
context.getString(R.string.notification_sign_up_format, accountName)
}
Notification.Type.UPDATE -> {
return String.format(
context.getString(R.string.notification_update_format),
accountName,
)
context.getString(R.string.notification_update_format, accountName)
}
Notification.Type.REPORT -> {
return context.getString(R.string.notification_report_format, account.domain)
context.getString(R.string.notification_report_format, account.domain)
}
Notification.Type.SEVERED_RELATIONSHIPS -> {
return context.getString(
context.getString(
R.string.notification_severed_relationships_format,
notification.relationshipSeveranceEvent?.targetName,
)
}
Notification.Type.UNKNOWN -> return null
Notification.Type.UNKNOWN -> null
}
}

View File

@ -627,8 +627,7 @@ class NotificationsFragment :
if (viewModel.uiState.value.activeFilter != filter) {
viewModel.accept(InfallibleUiAction.ApplyFilter(filter))
}
}
.show(parentFragmentManager, "dialogFilter")
}.show(parentFragmentManager, "dialogFilter")
}
override fun onViewTag(tag: String) {

View File

@ -16,187 +16,319 @@
package app.pachli.components.notifications
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.view.View
import androidx.appcompat.app.AlertDialog
import app.pachli.R
import androidx.preference.PreferenceManager
import app.pachli.core.accounts.AccountManager
import app.pachli.core.activity.NotificationConfig
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.navigation.LoginActivityIntent.LoginMode
import app.pachli.core.network.model.Notification
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.core.preferences.SharedPreferencesRepository
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.ui.extensions.awaitSingleChoiceItem
import app.pachli.util.CryptoUtil
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure
import at.connyduck.calladapter.networkresult.onSuccess
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.google.android.material.snackbar.Snackbar
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.onSuccess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.unifiedpush.android.connector.PREF_MASTER
import org.unifiedpush.android.connector.PREF_MASTER_DISTRIBUTOR
import org.unifiedpush.android.connector.PREF_MASTER_DISTRIBUTOR_ACK
import org.unifiedpush.android.connector.PREF_MASTER_INSTANCE
import org.unifiedpush.android.connector.PREF_MASTER_TOKEN
import org.unifiedpush.android.connector.UnifiedPush
import timber.log.Timber
private const val KEY_MIGRATION_NOTICE_DISMISSED = "migration_notice_dismissed"
/** The notification method an account is using. */
enum class AccountNotificationMethod {
/** Notifications are pushed. */
PUSH,
private fun anyAccountNeedsMigration(accountManager: AccountManager): Boolean =
accountManager.accounts.any(::accountNeedsMigration)
/** @return True if the account does not have the `push` OAuth scope, false otherwise */
private fun accountNeedsMigration(account: AccountEntity): Boolean =
!account.oauthScopes.contains("push")
fun currentAccountNeedsMigration(accountManager: AccountManager): Boolean =
accountManager.activeAccount?.let(::accountNeedsMigration) ?: false
fun showMigrationNoticeIfNecessary(
context: Context,
parent: View,
anchorView: View?,
accountManager: AccountManager,
sharedPreferencesRepository: SharedPreferencesRepository,
) {
// No point showing anything if we cannot enable it
if (!isUnifiedPushAvailable(context)) return
if (!anyAccountNeedsMigration(accountManager)) return
if (sharedPreferencesRepository.getBoolean(KEY_MIGRATION_NOTICE_DISMISSED, false)) return
Snackbar.make(parent, R.string.tips_push_notification_migration, Snackbar.LENGTH_INDEFINITE)
.setAnchorView(anchorView)
.setAction(R.string.action_details) {
showMigrationExplanationDialog(context, accountManager, sharedPreferencesRepository)
}
.show()
/** Notifications are pulled. */
PULL,
}
private fun showMigrationExplanationDialog(
context: Context,
accountManager: AccountManager,
sharedPreferencesRepository: SharedPreferencesRepository,
) {
AlertDialog.Builder(context).apply {
if (currentAccountNeedsMigration(accountManager)) {
setMessage(R.string.dialog_push_notification_migration)
setPositiveButton(R.string.title_migration_relogin) { _, _ ->
context.startActivity(
LoginActivityIntent(
context,
LoginMode.MIGRATION,
),
)
/** The overall app notification method. */
enum class AppNotificationMethod {
/** All accounts are configured to use UnifiedPush, and are registered with the distributor. */
ALL_PUSH,
/**
* Some accounts are configured to use UnifiedPush, and are registered with the distributor.
* For other accounts either registration failed, or their server does not support push, and
* notifications are pulled.
*/
MIXED,
/** All accounts are configured to pull notifications. */
ALL_PULL,
}
/** The account's [AccountNotificationMethod]. */
val AccountEntity.notificationMethod: AccountNotificationMethod
get() {
if (unifiedPushUrl.isBlank()) return AccountNotificationMethod.PULL
return AccountNotificationMethod.PUSH
}
/** True if the account has the `push` OAuth scope, false otherwise. */
val AccountEntity.hasPushScope: Boolean
get() = oauthScopes.contains("push")
/**
* Logs the current state of UnifiedPush preferences for debugging.
*
* @param context
* @param msg Optional message to log before the preferences.
*/
fun logUnifiedPushPreferences(context: Context, msg: String? = null) {
msg?.let { Timber.d(it) }
context.getSharedPreferences(PREF_MASTER, Context.MODE_PRIVATE).all.entries.forEach {
Timber.d(" ${it.key} -> ${it.value}")
}
}
/** @return The app level [AppNotificationMethod]. */
fun notificationMethod(context: Context, accountManager: AccountManager): AppNotificationMethod {
UnifiedPush.getAckDistributor(context) ?: return AppNotificationMethod.ALL_PULL
val notificationMethods = accountManager.accounts.map { it.notificationMethod }
// All pull?
if (notificationMethods.all { it == AccountNotificationMethod.PULL }) return AppNotificationMethod.ALL_PULL
// At least one is PUSH. If any are PULL then it's mixed, otherwise all must be push
if (notificationMethods.any { it == AccountNotificationMethod.PULL }) return AppNotificationMethod.MIXED
return AppNotificationMethod.ALL_PUSH
}
/** @return True if the active account does not have the `push` Oauth scope, false otherwise. */
fun activeAccountNeedsPushScope(accountManager: AccountManager) = accountManager.activeAccount?.hasPushScope == false
/**
* Attempts to enable notifications for all accounts.
*
* - Cancels any existing notification workers
* - Starts a periodic worker for pull notifications
*
* If a UnifiedPush distributor is available then use it, and register each account as an
* instance.
*/
suspend fun enableAllNotifications(context: Context, api: MastodonApi, accountManager: AccountManager) {
// Start from a clean slate.
disableAllNotifications(context, api, accountManager)
// Launch a single pull worker to periodically get notifications from all accounts,
// irrespective of whether or not UnifiedPush is configured.
enablePullNotifications(context)
// If no accounts have push scope there's nothing to do.
val accountsWithPushScope = accountManager.accounts.filter { it.hasPushScope }
if (accountsWithPushScope.isEmpty()) {
Timber.d("No accounts have push scope, skipping UnifiedPush reconfiguration")
return
}
// If no UnifiedPush distributors are installed then there's nothing more to do.
NotificationConfig.unifiedPushAvailable = false
// Get the UnifiedPush distributor to use, possibly falling back to the user's previous
// choice if it's still on the device.
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val usePreviousDistributor = prefs.getBoolean(PrefKeys.USE_PREVIOUS_UNIFIED_PUSH_DISTRIBUTOR, true)
if (!usePreviousDistributor) {
prefs.edit().apply {
putBoolean(PrefKeys.USE_PREVIOUS_UNIFIED_PUSH_DISTRIBUTOR, true)
}.apply()
}
val distributor = chooseUnifiedPushDistributor(context, usePreviousDistributor)
if (distributor == null) {
Timber.d("No UnifiedPush distributor installed, skipping UnifiedPush reconfiguration")
UnifiedPush.safeRemoveDistributor(context)
return
}
Timber.d("Chose %s as UnifiedPush distributor", distributor)
NotificationConfig.unifiedPushAvailable = true
UnifiedPush.saveDistributor(context, distributor)
accountsWithPushScope.forEach {
Timber.d("Registering %s with %s", it.fullName, distributor)
UnifiedPush.registerApp(context, it.unifiedPushInstance, messageForDistributor = it.fullName)
}
}
/**
* Choose the [UnifiedPush] distributor to register with.
*
* If no distributor is installed on the device, returns null.
*
* If one distributor is installed on the device, returns that.
*
* If multiple distributors are installed on the device, and the user has previously chosen
* a distributor, and that distributor is still installed on the device, and
* [usePreviousDistributor] is true, return that.
*
* Otherwise, show the user list of distributors and allows them to choose one. Returns
* their choice, unless they cancelled the dialog, in which case return null.
*
* @param context
* @param usePreviousDistributor
*/
suspend fun chooseUnifiedPushDistributor(context: Context, usePreviousDistributor: Boolean = true): String? {
val distributors = UnifiedPush.getDistributors(context)
Timber.d("Available distributors:")
distributors.forEach {
Timber.d(" %s", it)
}
return when (distributors.size) {
0 -> null
1 -> distributors.first()
else -> {
val distributor = UnifiedPush.getSavedDistributor(context)
if (usePreviousDistributor && distributors.contains(distributor)) {
Timber.d("Re-using user's previous distributor choice, %s", distributor)
return distributor
}
val distributorLabels = distributors.mapNotNull { getApplicationLabel(context, it) }
val result = AlertDialog.Builder(context)
.setTitle("Choose UnifiedPush distributor")
.awaitSingleChoiceItem(
distributorLabels,
-1,
android.R.string.ok,
)
if (result.button == AlertDialog.BUTTON_POSITIVE && result.index != -1) {
distributors[result.index]
} else {
null
}
}
}
}
/**
* @return The application label of [packageName], or null if the package name
* is not among installed packages.
*/
fun getApplicationLabel(context: Context, packageName: String): String? {
return try {
val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.getApplicationInfo(
packageName,
PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong()),
)
} else {
setMessage(R.string.dialog_push_notification_migration_other_accounts)
context.packageManager.getApplicationInfo(packageName, 0)
}
setNegativeButton(R.string.action_dismiss) { dialog, _ ->
sharedPreferencesRepository.edit().putBoolean(KEY_MIGRATION_NOTICE_DISMISSED, true).apply()
dialog.dismiss()
}
show()
}
context.packageManager.getApplicationLabel(info)
} catch (e: PackageManager.NameNotFoundException) {
null
} as String?
}
private suspend fun enableUnifiedPushNotificationsForAccount(context: Context, api: MastodonApi, accountManager: AccountManager, account: AccountEntity) {
if (isUnifiedPushNotificationEnabledForAccount(account)) {
// Already registered, update the subscription to match notification settings
val result = updateUnifiedPushSubscription(context, api, accountManager, account)
NotificationConfig.notificationMethodAccount[account.fullName] = when (result) {
is Err -> NotificationConfig.Method.PushError(result.error)
is Ok -> NotificationConfig.Method.Push
}
} else {
UnifiedPush.registerAppWithDialog(context, account.id.toString(), features = arrayListOf(UnifiedPush.FEATURE_BYTES_MESSAGE))
NotificationConfig.notificationMethodAccount[account.fullName] = NotificationConfig.Method.Push
}
}
fun disableUnifiedPushNotificationsForAccount(context: Context, account: AccountEntity) {
if (!isUnifiedPushNotificationEnabledForAccount(account)) {
// Not registered
return
}
UnifiedPush.unregisterApp(context, account.id.toString())
}
fun isUnifiedPushNotificationEnabledForAccount(account: AccountEntity): Boolean =
account.unifiedPushUrl.isNotEmpty()
/** True if one or more UnifiedPush distributors are available */
private fun isUnifiedPushAvailable(context: Context): Boolean =
UnifiedPush.getDistributors(context).isNotEmpty()
fun canEnablePushNotifications(context: Context, accountManager: AccountManager): Boolean {
val unifiedPushAvailable = isUnifiedPushAvailable(context)
val anyAccountNeedsMigration = anyAccountNeedsMigration(accountManager)
NotificationConfig.unifiedPushAvailable = unifiedPushAvailable
NotificationConfig.anyAccountNeedsMigration = anyAccountNeedsMigration
return unifiedPushAvailable && !anyAccountNeedsMigration
}
suspend fun enablePushNotificationsWithFallback(context: Context, api: MastodonApi, accountManager: AccountManager) {
Timber.d("Enabling push notifications with fallback")
if (!canEnablePushNotifications(context, accountManager)) {
Timber.d("Cannot enable push notifications, switching to pull")
NotificationConfig.notificationMethod = NotificationConfig.Method.Pull
accountManager.accounts.map {
NotificationConfig.notificationMethodAccount[it.fullName] = NotificationConfig.Method.Pull
}
// No UP distributors
enablePullNotifications(context)
return
}
NotificationConfig.notificationMethod = NotificationConfig.Method.Push
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
accountManager.accounts.forEach {
val notificationGroupEnabled = Build.VERSION.SDK_INT < 28 ||
nm.getNotificationChannelGroup(it.identifier)?.isBlocked == false
val shouldEnable = it.notificationsEnabled && notificationGroupEnabled
if (shouldEnable) {
enableUnifiedPushNotificationsForAccount(context, api, accountManager, it)
} else {
disableUnifiedPushNotificationsForAccount(context, it)
}
}
}
private fun disablePushNotifications(context: Context, accountManager: AccountManager) {
accountManager.accounts.forEach {
disableUnifiedPushNotificationsForAccount(context, it)
}
}
fun disableAllNotifications(context: Context, accountManager: AccountManager) {
/**
* Disables all notifications.
*
* - Cancels notification workers
* - Unregisters instances from the UnifiedPush distributor
*/
suspend fun disableAllNotifications(context: Context, api: MastodonApi, accountManager: AccountManager) {
Timber.d("Disabling all notifications")
disablePushNotifications(context, accountManager)
disablePushNotifications(context, api, accountManager)
disablePullNotifications(context)
}
private fun buildSubscriptionData(context: Context, account: AccountEntity): Map<String, Boolean> =
buildMap {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
Notification.Type.visibleTypes.forEach {
put(
"data[alerts][${it.presentation}]",
filterNotification(notificationManager, account, it),
)
/**
* Disables all push notifications.
*
* Disables push notifications for each account, and clears the relevant UnifiedPush preferences
* to work around a bug.
*/
private suspend fun disablePushNotifications(context: Context, api: MastodonApi, accountManager: AccountManager) {
accountManager.accounts.forEach { disablePushNotificationsForAccount(context, api, accountManager, it) }
// Clear UnifiedPush preferences, to work around
// https://github.com/UnifiedPush/android-connector/issues/85
val prefs = context.getSharedPreferences(PREF_MASTER, Context.MODE_PRIVATE)
prefs.edit().apply {
// Remove the set of instances.
remove(PREF_MASTER_INSTANCE)
// Remove the entry for each instance that points to the instance's token.
prefs.all.filter { it.key.endsWith("/$PREF_MASTER_TOKEN") }.forEach { remove(it.key) }
}.apply()
}
/**
* Disables UnifiedPush notifications for [account].
*
* - Clears UnifiedPush related data from the account's data
* - Calls the server to disable push notifications
* - Unregisters from the UnifiedPush provider
*/
suspend fun disablePushNotificationsForAccount(context: Context, api: MastodonApi, accountManager: AccountManager, account: AccountEntity) {
if (account.notificationMethod != AccountNotificationMethod.PUSH) return
// Clear the push notification from the account.
account.unifiedPushUrl = ""
account.pushServerKey = ""
account.pushAuth = ""
account.pushPrivKey = ""
account.pushPubKey = ""
accountManager.saveAccount(account)
NotificationConfig.notificationMethodAccount[account.fullName] = NotificationConfig.Method.Pull
// Try and unregister the endpoint from the server. Nothing we can do if this fails, and no
// need to wait for it to complete.
withContext(Dispatchers.IO) {
launch {
api.unsubscribePushNotifications("Bearer ${account.accessToken}", account.domain)
}
}
// Called by UnifiedPush callback
// Unregister from the UnifiedPush provider.
//
// UnifiedPush.unregisterApp will try and remove the user's distributor choice (including
// whether or not instances had acked it). Work around this bug by saving the values, and
// restoring them afterwards.
val prefs = context.getSharedPreferences(PREF_MASTER, Context.MODE_PRIVATE)
val savedDistributor = UnifiedPush.getSavedDistributor(context)
val savedDistributorAck = prefs.getBoolean(PREF_MASTER_DISTRIBUTOR_ACK, false)
UnifiedPush.unregisterApp(context, account.unifiedPushInstance)
prefs.edit().apply {
putString(PREF_MASTER_DISTRIBUTOR, savedDistributor)
putBoolean(PREF_MASTER_DISTRIBUTOR_ACK, savedDistributorAck)
}.apply()
}
/**
* Subscription data for [MastodonApi.subscribePushNotifications]. Fetches all user visible
* notifications.
*/
val subscriptionData = buildMap {
Notification.Type.visibleTypes.forEach {
put("data[alerts][${it.presentation}]", true)
}
}
/**
* Finishes Unified Push distributor registration
*
* Called from [app.pachli.receiver.UnifiedPushBroadcastReceiver.onNewEndpoint] after
* the distributor has set the endpoint.
*/
suspend fun registerUnifiedPushEndpoint(
context: Context,
api: MastodonApi,
@ -218,56 +350,21 @@ suspend fun registerUnifiedPushEndpoint(
endpoint,
keyPair.pubkey,
auth,
buildSubscriptionData(context, account),
).onFailure { throwable ->
Timber.w(throwable, "Error setting push endpoint for account %d", account.id)
disableUnifiedPushNotificationsForAccount(context, account)
subscriptionData,
).onFailure { error ->
Timber.w("Error setting push endpoint for account %s %d: %s", account, account.id, error.fmt(context))
NotificationConfig.notificationMethodAccount[account.fullName] = NotificationConfig.Method.PushError(error.throwable)
disablePushNotificationsForAccount(context, api, accountManager, account)
}.onSuccess {
Timber.d("UnifiedPush registration succeeded for account %d", account.id)
account.pushPubKey = keyPair.pubkey
account.pushPrivKey = keyPair.privKey
account.pushAuth = auth
account.pushServerKey = it.serverKey
account.pushServerKey = it.body.serverKey
account.unifiedPushUrl = endpoint
accountManager.saveAccount(account)
}
}
// Synchronize the enabled / disabled state of notifications with server-side subscription
suspend fun updateUnifiedPushSubscription(context: Context, api: MastodonApi, accountManager: AccountManager, account: AccountEntity): Result<Unit, Throwable> {
return withContext(Dispatchers.IO) {
return@withContext api.updatePushNotificationSubscription(
"Bearer ${account.accessToken}",
account.domain,
buildSubscriptionData(context, account),
).fold({
Timber.d("UnifiedPush subscription updated for account %d", account.id)
account.pushServerKey = it.serverKey
accountManager.saveAccount(account)
Ok(Unit)
}, {
Timber.e(it, "Could not enable UnifiedPush subscription for account %d", account.id)
Err(it)
})
}
}
suspend fun unregisterUnifiedPushEndpoint(api: MastodonApi, accountManager: AccountManager, account: AccountEntity) {
withContext(Dispatchers.IO) {
api.unsubscribePushNotifications("Bearer ${account.accessToken}", account.domain)
.onFailure { throwable ->
Timber.w(throwable, "Error unregistering push endpoint for account %d", account.id)
}
.onSuccess {
Timber.d("UnifiedPush unregistration succeeded for account %d", account.id)
// Clear the URL in database
account.unifiedPushUrl = ""
account.pushServerKey = ""
account.pushAuth = ""
account.pushPrivKey = ""
account.pushPubKey = ""
accountManager.saveAccount(account)
}
NotificationConfig.notificationMethodAccount[account.fullName] = NotificationConfig.Method.Push
}
}

View File

@ -29,7 +29,7 @@ import androidx.preference.PreferenceFragmentCompat
import app.pachli.BuildConfig
import app.pachli.R
import app.pachli.appstore.EventHub
import app.pachli.components.notifications.currentAccountNeedsMigration
import app.pachli.components.notifications.activeAccountNeedsPushScope
import app.pachli.core.accounts.AccountManager
import app.pachli.core.activity.extensions.TransitionKind
import app.pachli.core.activity.extensions.startActivityWithTransition
@ -175,7 +175,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
}
}
if (currentAccountNeedsMigration(accountManager)) {
if (activeAccountNeedsPushScope(accountManager)) {
preference {
setTitle(R.string.title_migration_relogin)
setIcon(R.drawable.ic_logout)

View File

@ -16,26 +16,49 @@
package app.pachli.components.preference
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.DividerItemDecoration
import app.pachli.R
import app.pachli.components.notifications.AccountNotificationMethod
import app.pachli.components.notifications.AppNotificationMethod
import app.pachli.components.notifications.getApplicationLabel
import app.pachli.components.notifications.hasPushScope
import app.pachli.components.notifications.notificationMethod
import app.pachli.core.accounts.AccountManager
import app.pachli.core.activity.NotificationConfig
import app.pachli.core.common.extensions.hide
import app.pachli.core.common.extensions.show
import app.pachli.core.common.util.unsafeLazy
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.designsystem.R as DR
import app.pachli.core.network.model.Notification
import app.pachli.core.preferences.AppTheme
import app.pachli.core.preferences.AppTheme.Companion.APP_THEME_DEFAULT
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository
import app.pachli.core.ui.extensions.await
import app.pachli.core.ui.makeIcon
import app.pachli.databinding.AccountNotificationDetailsListItemBinding
import app.pachli.feature.about.asDdHhMmSs
import app.pachli.feature.about.instantFormatter
import app.pachli.settings.emojiPreference
import app.pachli.settings.listPreference
import app.pachli.settings.makePreferenceScreen
@ -50,12 +73,17 @@ import app.pachli.util.LocaleManager
import app.pachli.util.deserialize
import app.pachli.util.serialize
import app.pachli.view.FontFamilyDialogFragment
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import dagger.hilt.android.AndroidEntryPoint
import de.c1710.filemojicompat_ui.views.picker.preference.EmojiPickerPreference
import java.time.Duration
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.launch
import org.unifiedpush.android.connector.UnifiedPush
@AndroidEntryPoint
class PreferencesFragment : PreferenceFragmentCompat() {
@ -72,6 +100,9 @@ class PreferencesFragment : PreferenceFragmentCompat() {
@Inject
lateinit var sharedPreferencesRepository: SharedPreferencesRepository
@Inject
lateinit var powerManager: PowerManager
private val iconSize by unsafeLazy { resources.getDimensionPixelSize(DR.dimen.preference_icon_size) }
override fun onCreateView(
@ -103,6 +134,7 @@ class PreferencesFragment : PreferenceFragmentCompat() {
return super.onCreateView(inflater, container, savedInstanceState)
}
@SuppressLint("BatteryLife", "ApplySharedPref")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
makePreferenceScreen {
preferenceCategory(R.string.pref_title_appearance_settings) {
@ -270,6 +302,131 @@ class PreferencesFragment : PreferenceFragmentCompat() {
}
}
preferenceCategory(R.string.pref_title_edit_notification_settings) {
val method = notificationMethod(context, accountManager)
preference {
setTitle(R.string.pref_title_notification_up_distributor)
val distributorPkg = UnifiedPush.getAckDistributor(context)
val distributorLabel = distributorPkg?.let { getApplicationLabel(context, it) }
setSummaryProvider {
distributorLabel?.let {
context.getString(R.string.pref_notification_up_distributor_name_fmt, it)
} ?: context.getString(R.string.pref_notification_up_distributor_none)
}
setOnPreferenceClickListener {
distributorPkg?.let { pkg ->
context.packageManager.getLaunchIntentForPackage(pkg)?.also {
startActivity(it)
}
}
return@setOnPreferenceClickListener true
}
}
if (UnifiedPush.getDistributors(context).size > 1) {
preference {
setTitle(R.string.pref_title_change_unified_push_distributor)
setOnPreferenceClickListener {
viewLifecycleOwner.lifecycleScope.launch {
val button = AlertDialog.Builder(context)
.setMessage(R.string.pref_change_unified_push_distributor_msg)
.setCancelable(true)
.create()
.await(R.string.restart, android.R.string.cancel)
if (button != AlertDialog.BUTTON_POSITIVE) return@launch
// Ideally UnifiedPush.forceRemoveDistributor would be used here
// and then the restart would force a new choice. However,
// forceRemoveDistributor triggers ConcurrentModificationException
// and crashes.
//
// So work around that by setting a preference to indicate that
// the chosen distributor should be ignored. This is then used
// in MainActivity and passed to chooseUnifiedPushDistributor.
sharedPreferencesRepository.edit().apply {
putBoolean(PrefKeys.USE_PREVIOUS_UNIFIED_PUSH_DISTRIBUTOR, false)
}.commit()
val packageManager = context.packageManager
val intent = packageManager.getLaunchIntentForPackage(context.packageName)!!
val componentName = intent.component
val mainIntent = Intent.makeRestartActivityTask(componentName)
mainIntent.setPackage(context.packageName)
context.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
}
return@setOnPreferenceClickListener true
}
}
}
preference {
setTitle(R.string.pref_title_notification_method)
setSummaryProvider {
val string = when (method) {
AppNotificationMethod.ALL_PUSH -> R.string.pref_notification_method_all_push
AppNotificationMethod.MIXED -> R.string.pref_notification_method_mixed
AppNotificationMethod.ALL_PULL -> R.string.pref_notification_method_all_pull
}
context.getString(string)
}
setOnPreferenceClickListener {
val adapter = AccountNotificationDetailsAdapter(context, accountManager.accounts.sortedBy { it.fullName })
viewLifecycleOwner.lifecycleScope.launch {
val dialog = AlertDialog.Builder(requireContext())
.setAdapter(adapter) { _, _ -> }
.create()
dialog.setOnShowListener {
dialog.listView.divider = DividerItemDecoration(context, DividerItemDecoration.VERTICAL).drawable
// Prevent a divider from appearing after the last item by disabling footer
// dividers and adding an empty footer.
dialog.listView.setFooterDividersEnabled(false)
dialog.listView.addFooterView(View(context))
}
dialog.await(positiveText = null)
}
return@setOnPreferenceClickListener true
}
}
preference {
setTitle(R.string.pref_title_notification_battery_optimisation)
val needsPull = method != AppNotificationMethod.ALL_PUSH
val isIgnoringBatteryOptimisations = powerManager.isIgnoringBatteryOptimizations(context.packageName)
val shouldIgnore = needsPull && !isIgnoringBatteryOptimisations
setSummaryProvider {
when {
shouldIgnore -> context.getString(R.string.pref_notification_battery_optimisation_should_ignore)
isIgnoringBatteryOptimisations -> context.getString(R.string.pref_notification_battery_optimisation_remove)
else -> context.getString(R.string.pref_notification_battery_optimisation_ok)
}
}
setOnPreferenceClickListener {
if (shouldIgnore) {
val intent = Intent().apply {
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
data = Uri.parse("package:${context.packageName}")
}
context.startActivity(intent)
}
return@setOnPreferenceClickListener true
}
}
}
preferenceCategory(R.string.pref_title_browser_settings) {
switchPreference {
setDefaultValue(false)
@ -404,3 +561,101 @@ class PreferencesFragment : PreferenceFragmentCompat() {
}
}
}
/**
* Displays notification configuration information for each [AccountEntity].
*
* Shows:
*
* - Account's full name.
* - The notification method (push or pull).
* - If pull, an explanation for why it's not push.
* - The last time notifications were fetched for the account, and the result.
*/
class AccountNotificationDetailsAdapter(context: Context, accounts: List<AccountEntity>) : ArrayAdapter<AccountEntity>(
context,
R.layout.account_notification_details_list_item,
R.id.accountName,
accounts,
) {
/** String resource for the account's notification method. */
@get:StringRes
private val AccountNotificationMethod.stringRes: Int
get() = when (this) {
AccountNotificationMethod.PUSH -> R.string.pref_notification_method_push
AccountNotificationMethod.PULL -> R.string.pref_notification_method_pull
}
/**
* String to show as the "extra" for the notification method.
*
* If the notification method is [PUSH][AccountNotificationMethod.PUSH] this should be the
* URL notifications are delivered to.
*
* Otherwise this should explain why the method is [PULL][AccountNotificationMethod.PULL]
* (either the error when registering, or the lack of the `push` oauth scope).
*/
private fun AccountEntity.notificationMethodExtra(): String {
return when (notificationMethod) {
AccountNotificationMethod.PUSH -> unifiedPushUrl
AccountNotificationMethod.PULL -> if (hasPushScope) {
context.getString(R.string.pref_notification_fetch_server_rejected, domain)
} else {
context.getString(R.string.pref_notification_fetch_needs_push)
}
}
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = if (convertView == null) {
AccountNotificationDetailsListItemBinding.inflate(LayoutInflater.from(context), parent, false)
} else {
AccountNotificationDetailsListItemBinding.bind(convertView)
}
val account = getItem(position) ?: return binding.root
with(binding) {
accountName.text = account.fullName
notificationMethod.text = context.getString(account.notificationMethod.stringRes)
notificationMethodExtra.text = account.notificationMethodExtra()
accountName.show()
notificationMethod.show()
notificationMethodExtra.show()
val lastFetch = NotificationConfig.lastFetchNewNotifications[account.fullName]
if (lastFetch == null) {
lastFetchTime.hide()
lastFetchError.hide()
return@with
}
val now = Instant.now()
val instant = lastFetch.first
val result = lastFetch.second
val (resTimestamp, error) = when (result) {
is Ok -> Pair(R.string.pref_notification_fetch_ok_timestamp_fmt, null)
is Err -> Pair(R.string.pref_notification_fetch_err_timestamp_fmt, result.error)
}
lastFetchTime.text = context.getString(
resTimestamp,
Duration.between(instant, now).asDdHhMmSs(),
instantFormatter.format(instant),
)
lastFetchTime.show()
if (error != null) {
lastFetchError.text = error
lastFetchError.show()
} else {
lastFetchError.hide()
}
}
return binding.root
}
}

View File

@ -1,68 +0,0 @@
/* Copyright 2022 Tusky contributors
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.receiver
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import app.pachli.components.notifications.canEnablePushNotifications
import app.pachli.components.notifications.isUnifiedPushNotificationEnabledForAccount
import app.pachli.components.notifications.updateUnifiedPushSubscription
import app.pachli.core.accounts.AccountManager
import app.pachli.core.network.retrofit.MastodonApi
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@DelicateCoroutinesApi
@AndroidEntryPoint
class NotificationBlockStateBroadcastReceiver : BroadcastReceiver() {
@Inject
lateinit var mastodonApi: MastodonApi
@Inject
lateinit var accountManager: AccountManager
override fun onReceive(context: Context, intent: Intent) {
if (Build.VERSION.SDK_INT < 28) return
if (!canEnablePushNotifications(context, accountManager)) return
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val gid = when (intent.action) {
NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED -> {
val channelId = intent.getStringExtra(NotificationManager.EXTRA_NOTIFICATION_CHANNEL_ID)
nm.getNotificationChannel(channelId).group
}
NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED -> {
intent.getStringExtra(NotificationManager.EXTRA_NOTIFICATION_CHANNEL_GROUP_ID)
}
else -> null
} ?: return
accountManager.getAccountByIdentifier(gid)?.let { account ->
if (isUnifiedPushNotificationEnabledForAccount(account)) {
// Update UnifiedPush notification subscription
GlobalScope.launch { updateUnifiedPushSubscription(context, mastodonApi, accountManager, account) }
}
}
}
}

View File

@ -22,8 +22,8 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import app.pachli.components.notifications.disablePushNotificationsForAccount
import app.pachli.components.notifications.registerUnifiedPushEndpoint
import app.pachli.components.notifications.unregisterUnifiedPushEndpoint
import app.pachli.core.accounts.AccountManager
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.worker.NotificationWorker
@ -35,7 +35,6 @@ import kotlinx.coroutines.launch
import org.unifiedpush.android.connector.MessagingReceiver
import timber.log.Timber
@DelicateCoroutinesApi
@AndroidEntryPoint
class UnifiedPushBroadcastReceiver : MessagingReceiver() {
@Inject
@ -45,31 +44,42 @@ class UnifiedPushBroadcastReceiver : MessagingReceiver() {
lateinit var mastodonApi: MastodonApi
override fun onMessage(context: Context, message: ByteArray, instance: String) {
Timber.d("onMessage")
Timber.d("New message received for account %s", instance)
val workManager = WorkManager.getInstance(context)
val request = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
// Start a worker just for this account
.setInputData(NotificationWorker.data(instance.toLong()))
.build()
workManager.enqueue(request)
}
@OptIn(DelicateCoroutinesApi::class)
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
Timber.d("Endpoint available for account %s: %s", instance, endpoint)
accountManager.getAccountById(instance.toLong())?.let {
Timber.d("onNewEndpoint for instance $instance")
accountManager.getAccountById(instance.toLong())?.let { account ->
Timber.d("Endpoint available for account %s: %s", account, instance)
// Launch the coroutine in global scope -- it is short and we don't want to lose the registration event
// and there is no saner way to use structured concurrency in a receiver
GlobalScope.launch { registerUnifiedPushEndpoint(context, mastodonApi, accountManager, it, endpoint) }
GlobalScope.launch { registerUnifiedPushEndpoint(context, mastodonApi, accountManager, account, endpoint) }
}
}
override fun onRegistrationFailed(context: Context, instance: String) = Unit
override fun onRegistrationFailed(context: Context, instance: String) {
Timber.d("onRegistrationFailed")
accountManager.getAccountById(instance.toLong())?.let { account ->
Timber.d("Could not register ${account.displayName}")
}
}
override fun onUnregistered(context: Context, instance: String) {
Timber.d("Endpoint unregistered for account %s", instance)
accountManager.getAccountById(instance.toLong())?.let {
// It's fine if the account does not exist anymore -- that means it has been logged out
GlobalScope.launch { unregisterUnifiedPushEndpoint(mastodonApi, accountManager, it) }
Timber.d("onUnregistered with instance $instance")
accountManager.getAccountById(instance.toLong())?.let { account ->
GlobalScope.launch {
disablePushNotificationsForAccount(context, mastodonApi, accountManager, account)
}
}
}
}

View File

@ -2,10 +2,8 @@ package app.pachli.usecase
import android.content.Context
import app.pachli.components.drafts.DraftHelper
import app.pachli.components.notifications.androidNotificationsAreEnabled
import app.pachli.components.notifications.deleteNotificationChannelsForAccount
import app.pachli.components.notifications.disablePullNotifications
import app.pachli.components.notifications.disableUnifiedPushNotificationsForAccount
import app.pachli.components.notifications.disablePushNotificationsForAccount
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.dao.ConversationsDao
import app.pachli.core.database.dao.RemoteKeyDao
@ -50,12 +48,7 @@ class LogoutUsecase @Inject constructor(
}
// disable push notifications
disableUnifiedPushNotificationsForAccount(context, activeAccount)
// disable pull notifications
if (!androidNotificationsAreEnabled(context, accountManager)) {
disablePullNotifications(context)
}
disablePushNotificationsForAccount(context, api, accountManager, activeAccount)
// clear notification channels
deleteNotificationChannelsForAccount(activeAccount, context)

View File

@ -21,6 +21,7 @@ import android.app.Notification
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import app.pachli.R
@ -42,9 +43,20 @@ class NotificationWorker @AssistedInject constructor(
override suspend fun doWork(): Result {
Timber.d("NotificationWorker.doWork() started")
notificationsFetcher.fetchAndShow()
val accountId = inputData.getAccountId()
notificationsFetcher.fetchAndShow(accountId)
return Result.success()
}
override suspend fun getForegroundInfo() = ForegroundInfo(NOTIFICATION_ID_FETCH_NOTIFICATION, notification)
companion object {
private const val ACCOUNT_ID = "accountId"
const val ALL_ACCOUNTS = -1L
fun data(accountId: Long) = Data.Builder().putLong(ACCOUNT_ID, accountId).build()
fun Data.getAccountId() = getLong(ACCOUNT_ID, ALL_ACCOUNTS)
}
}

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2024 Pachli Association
~
~ This file is a part of Pachli.
~
~ This program is free software; you can redistribute it and/or modify it under the terms of the
~ GNU General Public License as published by the Free Software Foundation; either version 3 of the
~ License, or (at your option) any later version.
~
~ Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
~ Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with Pachli; if not,
~ see <http://www.gnu.org/licenses>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="?attr/listPreferredItemHeight"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
<TextView android:id="@+id/accountName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
tools:ignore="SelectableText" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/pref_notification_fetch_method_title"
style="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?attr/colorAccent"
tools:ignore="SelectableText" />
<TextView android:id="@+id/notificationMethod"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItemSecondary"
tools:ignore="SelectableText" />
<TextView android:id="@+id/notificationMethodExtra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItemSecondary"
tools:ignore="SelectableText" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/notification_details_last_fetch_label"
style="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?attr/colorAccent"
tools:ignore="SelectableText" />
<TextView android:id="@+id/lastFetchTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItemSecondary"
tools:ignore="SelectableText" />
<TextView android:id="@+id/lastFetchError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorError"
android:textAppearance="?attr/textAppearanceListItemSecondary"
tools:ignore="SelectableText" />
</LinearLayout>

View File

@ -523,7 +523,6 @@
<item quantity="other">لا يمكنك رفع أكثر من %1$d مرفقات.</item>
</plurals>
<string name="saving_draft">جار حفظ المسودة …</string>
<string name="dialog_push_notification_migration_other_accounts">لقد قمت بإعادة تسجيل الدخول إلى حسابك الجاري لمنح إذن الاشعارات لـ Pachli. ومع ذلك، لا يزال لديك حسابات أخرى لم يتم ترحيلها بهذه الطريقة. قم بالتبديل إليهم وإعادة تسجيل الدخول واحدًا تلو الآخر لتمكين دعم إشعارات UnifiedPush.</string>
<string name="language_display_name_format">%s (%s)</string>
<string name="pref_default_post_language">لغة النشر الافتراضية</string>
<string name="status_count_one_plus">1+</string>
@ -544,7 +543,6 @@
<string name="failed_to_pin">فشل التثبيت</string>
<string name="description_post_language">لغة المنشور</string>
<string name="delete_scheduled_post_warning">هل تريد حذف هذا المنشور المُبَرمَج؟</string>
<string name="tips_push_notification_migration">أعد تسجيل الدخول إلى جميع الحسابات لتمكين دعم الإشعارات.</string>
<string name="action_set_focus">ضبط نقطة التركيز</string>
<string name="action_edit_image">تعديل الصورة</string>
<string name="action_add_reaction">إضافة رد فعل</string>
@ -562,12 +560,9 @@
<string name="title_followed_hashtags">الوسوم المتابَعة</string>
<string name="error_status_source_load">فشل تحميل مصدر المنشور من الخادم.</string>
<string name="report_category_violation">انتهاك الشروط</string>
<string name="dialog_push_notification_migration">من أجل استخدام الإشعارات عبر UnifiedPush ، يحتاج Pachli إلى إذن للإشتراك في الإشعارات على خادم Mastodon الخاص بك. يتطلب هذا إعادة تسجيل الدخول لتغيير نطاقات OAuth الممنوحة لـ Pachli. سيؤدي استخدام خيار إعادة تسجيل الدخول هنا أو في اعدادات الحساب إلى الاحتفاظ بجميع المسودات المحلية وذاكرة التخزين المؤقت.</string>
<string name="pref_title_wellbeing_mode">الرفاهية</string>
<string name="account_date_joined">%1$s انضم في</string>
<string name="title_migration_relogin">أعد تسجيل الدخول لاستلام الاشعارات</string>
<string name="action_dismiss">تجاهل</string>
<string name="action_details">تفاصيل</string>
<string name="pref_title_notification_filter_updates">المنشور الذي تفاعلت معه تم تعديله</string>
<string name="action_post_failed">فشل التحميل</string>
<string name="action_post_failed_show_drafts">إظهار المسودات</string>

View File

@ -82,8 +82,6 @@
<string name="action_open_reblogged_by">Паказаць пашырэнні</string>
<string name="action_open_faved_by">Паказаць абраныя</string>
<string name="title_mentions_dialog">Згадкі</string>
<string name="action_dismiss">Адхіліць</string>
<string name="action_details">Дэталі</string>
<string name="action_open_media_n">Адкрыць медыя #%d</string>
<string name="action_add_reaction">дадаць рэакцыю</string>
<string name="download_image">Спампоўка %1$s</string>
@ -486,13 +484,11 @@
\n- Статыстыка ў профілях пра Падпісчыкаў/Допісы
\n
\nНа Push-паведамленні гэта не ўплывае, але Вы можаце праглядзець налады апавяшчэнняў уручную.</string>
<string name="dialog_push_notification_migration">Каб выкарыстоўваць push-апавяшчэнні праз UnifiedPush, Pachli патрэбен дазвол, каб падпісацца на апавяшчэнні з Вашага сервера Mastodon. Для гэтага патрэбна выйсці і зайсці ва ўліковы запіс зноў, каб аднавіць вобласці дазволу OAuth, дадзеныя Pachli. Выкарыстоўванне магчымасці паўторнага ўваходу тут ці ў наладах уліковага запісу захоўвае ўсе Вашыя лакальныя чарнавікі і кэш.</string>
<string name="poll_allow_multiple_choices">Некалькі варыянтаў</string>
<string name="add_poll_choice">Дадаць варыянт</string>
<string name="edit_poll">Змяніць</string>
<string name="account_date_joined">Далучыўся(-лася) %1$s</string>
<string name="account_note_saved">Захавана!</string>
<string name="dialog_push_notification_migration_other_accounts">Вы паўторна зайшлі ў бягучы ўліковы запіс, каб дазволіць Pachli падпісацца на push-апавяшчэнні. Але ў Вас яшчэ засталіся ўліковыя запісы, якія не мігрыравалі такім чынам. Пераключыцеся на іх і зайдзіце паўторна, каб уключыць падтрымку апавяшчэнняў праз UnifiedPush.</string>
<string name="status_edit_info">%1$s адрэдагаваў(-ла)</string>
<string name="status_created_info">%1$s стварыў(-ла)</string>
<string name="pref_title_hide_top_toolbar">Схаваць загаловак верхняй панэлі інструментаў</string>
@ -531,7 +527,6 @@
<string name="delete_scheduled_post_warning">Выдаліць гэты запланаваны допіс\?</string>
<string name="poll_new_choice_hint">Варыянт %d</string>
<string name="drafts_post_failed_to_send">Не атрымалася даслаць гэты допіс!</string>
<string name="tips_push_notification_migration">Увайдзіце зноў на ўсіх уліковых запісах, каб push-апавяшчэнні запрацавалі.</string>
<string name="mute_notifications_switch">Бязгучныя апавяшчэнні</string>
<string name="warning_scheduling_interval">Найменшы час планавання ў Mastodon складае 5 хвілін.</string>
<string name="pref_summary_http_proxy_disabled">Адключана</string>

View File

@ -426,7 +426,6 @@
<string name="filter_expiration_format">%s (%s)</string>
<string name="dialog_delete_conversation_warning">Vols suprimir aquesta conversa\?</string>
<string name="error_image_edit_failed">La imatge no s\'ha pogut editar.</string>
<string name="action_dismiss">Descartar</string>
<string name="pref_title_http_proxy_port_message">El port hauria d\'estar entre %d i %d</string>
<string name="status_count_one_plus">+1</string>
<string name="title_edits">Edicions</string>
@ -471,7 +470,6 @@
<string name="action_add_reaction">afegir reacció</string>
<string name="action_share_account_link">Comparteix l\'enllaç al compte</string>
<string name="action_share_account_username">Comparteix el nom d\'usuari del compte</string>
<string name="action_details">Detalls</string>
<string name="send_account_username_to">Comparteix el nom d\'usuari del compte a…</string>
<string name="account_username_copied">S\'ha copiat el nom d\'usuari</string>
<string name="confirmation_hashtag_unfollowed">#%s deixat de seguir</string>
@ -486,7 +484,6 @@
<string name="title_migration_relogin">Torneu a iniciar sessió per rebre notificacions push</string>
<string name="a11y_label_loading_thread">S\'està carregant el fil</string>
<string name="compose_save_draft_loses_media">Guardar l\'esborrany\? (Els fitxers adjunts es tornaran a penjar quan recupereu l\'esborrany.)</string>
<string name="dialog_push_notification_migration_other_accounts">Heu tornat a iniciar sessió al vostre compte actual per concedir permís de subscripció push a Pachli. Tanmateix, encara teniu altres comptes que no s\'han migrat d\'aquesta manera. Canvieu-los i torneu a iniciar sessió un per un per activar el suport de notificacions UnifiedPush.</string>
<string name="action_edit_image">Edita la imatge</string>
<string name="failed_to_pin">No s\'ha pogut fixar</string>
<string name="failed_to_unpin">No s\'ha pogut desfixar</string>
@ -522,8 +519,6 @@
\n Les notificacions push no es veuran afectades, però podeu revisar les vostres preferències de notificació manualment.</string>
<string name="account_date_joined">S\'ha unit el %1$s</string>
<string name="saving_draft">Desant l\'esborrany…</string>
<string name="dialog_push_notification_migration">Per utilitzar les notificacions push mitjançant UnifiedPush, Pachli necessita permís per subscriure\'s a les notificacions al vostre servidor Mastodon. Això requereix un nou inici de sessió per canviar els àmbits d\'OAuth concedits a Pachli. Si feu servir l\'opció de tornar a iniciar sessió aquí o a les preferències del compte, es conservaran tots els esborranys locals i la memòria cau.</string>
<string name="tips_push_notification_migration">Torneu a iniciar sessió a tots els comptes per activar el suport de notificacions push.</string>
<string name="description_post_edited">Editat</string>
<string name="status_edit_info">%1$s ha editat</string>
<string name="action_subscribe_account">Subscriu-te</string>

View File

@ -432,7 +432,6 @@
\n - Statistiky sledujících a příspěvků na profilech
\n
\nPush oznámení nebudou ovlivněna, ale můžete si zkontrolovat jejich nastavení manuálně.</string>
<string name="dialog_push_notification_migration_other_accounts">Znovu jste se přihlásili ke svému aktuálnímu účtu, abyste aplikaci Pachli udělili oprávnění k odběru push. Stále však máte další účty, které tímto způsobem migrovány nebyly. Přepněte se na ně a znovu se přihlaste na jednom po druhém, abyste povolili podporu oznámení UnifiedPush.</string>
<string name="compose_save_draft_loses_media">Uložit koncept\? (Přílohy budou znovu nahrány, když obnovíte koncept.)</string>
<string name="set_focus_description">Klepnutím nebo přetažením kruhu vyberte ohnisko, které bude vždy viditelné v miniaturách.</string>
<string name="pref_title_confirm_favourites">Před oblíbením zobrazit dialog pro potvrzení</string>
@ -453,8 +452,6 @@
<string name="duration_30_days">30 dní</string>
<string name="drafts_post_reply_removed">Příspěvek, na který jste připravili odpověď, byl odstraněn</string>
<string name="action_set_focus">Nastavit bod zaostření</string>
<string name="tips_push_notification_migration">Znovu se přihlaste ke všem účtům, abyste povolili podporu push oznámení.</string>
<string name="dialog_push_notification_migration">Aby bylo možné používat push oznámení prostřednictvím UnifiedPush, Pachli potřebuje oprávnění k odběru oznámení na vašem serveru Mastodon. To vyžaduje opětovné přihlášení ke změně rozsahů OAuth udělených aplikaci Pachli. Použitím možnosti opětovného přihlášení zde nebo v předvolbách účtu zachováte všechny vaše místní koncepty a mezipaměť.</string>
<string name="action_add_reaction">přidat reakci</string>
<string name="pref_title_notification_filter_updates">příspěvek, se kterým jsem interagoval/a, je upraven</string>
<string name="pref_title_notification_filter_sign_ups">někdo se zaregistroval</string>
@ -491,9 +488,7 @@
<string name="notification_update_format">%s upravil/a svůj příspěvek</string>
<string name="action_unbookmark">Odebrat záložku</string>
<string name="action_delete_conversation">Smazat konverzaci</string>
<string name="action_dismiss">Zavřít</string>
<string name="action_details">Podrobnosti</string>
<string name="dialog_delete_conversation_warning">Smazat tuto konverzaci\?</string>
<string name="pref_title_notification_filter_follow_requests">Požádáno o sledování</string>
<string name="pref_title_animate_custom_emojis">Animovat vlastní emotikony</string>
</resources>
</resources>

View File

@ -309,8 +309,6 @@
<string name="error_image_edit_failed">Methu golygu\'r ddelwedd.</string>
<string name="description_post_favourited">Hoffwyd</string>
<string name="saving_draft">Yn cadw drafft…</string>
<string name="action_dismiss">Diystyru</string>
<string name="action_details">Manylion</string>
<string name="title_mentions_dialog">Crybwylliadau</string>
<string name="action_open_media_n">Agor cyfryngau #%d</string>
<string name="action_share_as">Rhannu fel …</string>
@ -393,8 +391,6 @@
<string name="compose_shortcut_long_label">Ysgrifennu neges</string>
<string name="drafts_post_failed_to_send">Methodd anfon y neges hon!</string>
<string name="pachli_compose_post_quicksetting_label">Ysgrifennu Neges</string>
<string name="tips_push_notification_migration">Ailfewngofnodwch i\'ch cyfrifon er mwyn galluogi hysbysiadau i\'ch ffôn.</string>
<string name="dialog_push_notification_migration">Er mwyn derbyn hysbysiadau i\'ch ffôn drwy UnifiedPush, mae angen caniatâd ar Pachli i danysgrifio i hysbysiadau ar eich gweinydd Mastodon. Bydd rhaid i chi fewngofnodi eto i newid y sgôp OAuth sy\'n cael ei roi i Pachli. Bydd defnyddio\'r opsiwn ailfewngofnodi yma neu yn newisiadau Cyfrif yn cadw\'ch holl ddrafftiau a\'ch storfa leol.</string>
<string name="mute_domain_warning">Ydych chi\'n siŵr yr hoffech chi flocio %s gyfan\? Fyddwch chi ddim yn gweld dim cynnwys o\'r parth hwnnw mewn unrhyw ffrwd gyhoeddus na chwaith yn eich hysbysiadau. Bydd eich dilynwyr o\'r parth hwnnw yn cael eu dileu.</string>
<string name="duration_indefinite">Dim diwedd</string>
<plurals name="favs">
@ -408,7 +404,6 @@
<string name="description_visibility_direct">Uniongyrchol</string>
<string name="compose_save_draft_loses_media">Cadw drafft\? (Bydd atodiadau\'n cael eu lanlwytho eto pan fyddwch chi\'n adfer y drafft.)</string>
<string name="description_post_reblogged">Ailflogiwyd</string>
<string name="dialog_push_notification_migration_other_accounts">Rydych wedi ail-fewngofnodi i\'ch cyfrif cyfredol i roi caniatâd tanysgrifio gwthio i Pachli. Fodd bynnag, mae gennych gyfrifon eraill o hyd nad ydyn nhw wedi\'u mudo fel hyn. Newidiwch atyn nhw ac ail-fewngofnodi fesul un er mwyn galluogi cefnogaeth hysbysiadau UnifiedPush.</string>
<string name="poll_info_format"><!-- 15 votes • 1 hour left -->%1$s • %2$s</string>
<string name="hint_media_description_missing">Dylai fod gan y cyfryngau ddisgrifiad.</string>
<string name="description_post_cw">Rhybudd cynnwys: %s</string>

View File

@ -462,15 +462,10 @@
<string name="notification_update_description">Benachrichtigungen, wenn Beiträge bearbeitet werden, mit denen du interagiert hast</string>
<string name="notification_update_name">Beitragsbearbeitungen</string>
<string name="title_migration_relogin">Neuanmeldung für Push-Benachrichtigungen</string>
<string name="action_dismiss">Ablehnen</string>
<string name="dialog_push_notification_migration_other_accounts">Du hast dich erneut in dein aktuelles Konto angemeldet, um Pachli die Genehmigung für Push-Abonnements zu erteilen. Du hast jedoch noch andere Konten, die nicht auf diese Weise migriert wurden. Wechsel zu diesen Konten und melde dich nacheinander neu an, um die Unterstützung für UnifiedPush-Benachrichtigungen zu aktivieren.</string>
<string name="dialog_push_notification_migration">Um Push-Benachrichtigungen über UnifiedPush verwenden zu können, benötigt Pachli die Erlaubnis, Benachrichtigungen auf dem Mastodon-Server zu abonnieren. Dies erfordert eine erneute Anmeldung, um die Pachli gewährten OAuth-Bereiche zu ändern. Wenn du die Option zum erneuten Anmelden hier oder in den Profileinstellungen verwendest, bleiben alle deine lokalen Entwürfe und der Cache erhalten.</string>
<string name="tips_push_notification_migration">Melde alle Konten neu an, um die Unterstützung für Push-Benachrichtigungen zu aktivieren.</string>
<string name="account_date_joined">%1$s beigetreten</string>
<string name="status_count_one_plus">1+</string>
<string name="status_created_at_now">Jetzt</string>
<string name="action_edit_image">Bild bearbeiten</string>
<string name="action_details">Details</string>
<string name="error_image_edit_failed">Das Bild konnte nicht bearbeitet werden.</string>
<string name="saving_draft">Entwurf wird gespeichert </string>
<string name="error_following_hashtag_format">Fehler beim Folgen von #%s</string>

View File

@ -459,8 +459,6 @@
<string name="error_unfollowing_hashtag_format">Okazos eraro dum la malsekvado de #%s</string>
<string name="title_migration_relogin">Ensaluti denove por ricevi sciigojn</string>
<string name="notification_update_format">%s redaktis sian mesaĝon</string>
<string name="action_dismiss">Fermi</string>
<string name="action_details">Detaloj</string>
<string name="notification_update_name">Redaktitaj mesaĝoj</string>
<string name="notification_update_description">Sciigoj, kiam mesaĝoj, kun kiuj vi interagis, estas redaktitaj</string>
<string name="action_edit_image">Redakti la bildon</string>
@ -473,7 +471,6 @@
<string name="pachli_compose_post_quicksetting_label">Ekverki mesaĝon</string>
<string name="account_date_joined">Aliĝis je %1$s</string>
<string name="saving_draft">Konservado de la malneto…</string>
<string name="tips_push_notification_migration">Ensalutu denove al ĉiuj kontoj por ŝalti sciigojn.</string>
<string name="delete_scheduled_post_warning">Ĉu forigi tiun planitan mesaĝon\?</string>
<string name="duration_no_change">(Neniu ŝanĝo)</string>
<string name="filter_expiration_format">%s (%s)</string>
@ -482,6 +479,4 @@
<string name="pref_show_self_username_never">Neniam</string>
<string name="pref_title_show_self_username">Montri uzantnomon en ilobreto</string>
<string name="description_post_language">Mesaĝolingvo</string>
<string name="dialog_push_notification_migration">Por ricevi sciigoj per UnifiedPush, Pachli bezonas taŭgan permeson el Mastodon-servilo. Tio postulas re-ensaluton por ŝanĝi OAuth-rajtoj donitaj al Pachli. Se vi uzas la opcion re-ensaluti ĉi tie aŭ en la agordoj de la konto, viaj malnetoj kaj kaŝmemoroj estos konservitaj.</string>
<string name="dialog_push_notification_migration_other_accounts">Vi re-ensalutis en tiu konto por doni sciigo-permeson al Pachli. Vi havas tamen aliajn kontojn, ĉe kiuj vi devas re-sensaluti. Iru al ili, kaj re-ensalutu por ebligi ricevon de sciigoj per UnifiedPush.</string>
</resources>
</resources>

View File

@ -462,8 +462,6 @@
<string name="title_migration_relogin">Reingresa para activar notificaciones push</string>
<string name="notification_sign_up_format">%s se registró</string>
<string name="notification_update_format">%s editó su publicación</string>
<string name="action_dismiss">Descartar</string>
<string name="action_details">Detalles</string>
<string name="delete_scheduled_post_warning">¿Eliminar publicación programada\?</string>
<string name="set_focus_description">Toca o arrastra el círculo para centrar el foco de la imagen, que será visible en las miniaturas.</string>
<string name="compose_save_draft_loses_media">¿Guardar este borrador\? (Los adjuntos se subirán de nuevo cuando vuelvas a él.)</string>
@ -471,8 +469,6 @@
<string name="account_date_joined">Se unió %1$s</string>
<string name="duration_14_days">14 días</string>
<string name="duration_365_days">365 días</string>
<string name="tips_push_notification_migration">Inicia sesión de nuevo en todas las cuentas para activar las notificaciones push.</string>
<string name="dialog_push_notification_migration">Para poder usar las notificaciones push con UnifiedPush, Pachli necesita permiso para suscribirse a las notificaciones de tu servidor de Mastodon. Es necesario volver a acceder para cambiar los parámetros OAuth concedidos a Pachli. Usar aquí, o en las Preferencias de la cuenta, la opción de volver a acceder conservará los borradores y la caché.</string>
<string name="failed_to_pin">Fallo al fijar</string>
<string name="failed_to_unpin">Fallo al quitarlo</string>
<string name="pref_show_self_username_disambiguate">Cuando hay varias cuentas ingresadas</string>
@ -490,7 +486,6 @@
<string name="duration_no_change">(Sin cambios)</string>
<string name="pachli_compose_post_quicksetting_label">Escribir publicación</string>
<string name="saving_draft">Guardando borrador…</string>
<string name="dialog_push_notification_migration_other_accounts">Has vuelto a iniciar sesión en esta cuenta para dar permiso de notificaciones push a Pachli. Sin embargo, aún hay otras cuentas que no tienen este permiso. Cambia a estas cuentas y vuelve a iniciar sesión, una a una, para activar el soporte de notificaciones de UnifiedPush.</string>
<string name="notification_sign_up_name">Creación de cuentas</string>
<string name="action_edit_image">Editar imagen</string>
<string name="hint_media_description_missing">El contenido debería tener una descripción.</string>
@ -785,4 +780,4 @@
<string name="pref_labs_reverse_home_timeline_title">Orden inverso en línea de tiempo</string>
<string name="pref_labs_reverse_home_timeline_off_summary">Publicaciones más recientes primero</string>
<string name="pref_labs_reverse_home_timeline_on_summary">Publicaciones más antiguas primero</string>
</resources>
</resources>

View File

@ -441,10 +441,8 @@
<string name="notification_sign_up_format">%1$s ثبت‌نام کرد</string>
<string name="pref_title_confirm_favourites">نمایش تأیید پیش از برگزیدن</string>
<string name="pachli_compose_post_quicksetting_label">ایجاد فرسته</string>
<string name="tips_push_notification_migration">ورود دوباره به تمامی حساب‌ها برای به کار انداختن پشتیبانی آگاهی‌های ارسالی.</string>
<string name="notification_update_description">آگاهی‌ها هنگام ویرایش فرسته‌هایی که با آن‌ها تعامل داشته‌اید</string>
<string name="action_unbookmark">برداشن نشانک</string>
<string name="dialog_push_notification_migration_other_accounts">برای اعطای اجازهٔ اشتراک آگاهی‌های ارسالی به تاسکی، دوباره به حسابتان وارد شدید. با این حال هنوز حساب‌هایی دیگر دارید که این‌گونه مهاجرت داده نشده‌اند. به آن‌ها رفته و برای به کار انداختن پشتیبانی آگاهی‌های UnifiedPush یکی‌یکی دوباره وارد شوید.</string>
<string name="action_logout_confirm">%1$s مطمئنید که می‌خواهید از خارج شوید؟ این کار تمامی داده‌های محلی از جمله پیش‌نویس‌ها و ترجیحات را حذف خواهد کرد.</string>
<string name="duration_14_days">۱۴ روز</string>
<string name="duration_30_days">۳۰ روز</string>
@ -453,7 +451,6 @@
<string name="duration_365_days">۳۶۵ روز</string>
<string name="duration_180_days">۱۸۰ روز</string>
<string name="status_count_one_plus">۱+</string>
<string name="dialog_push_notification_migration">تاسکی برای استفاده از آگاهی‌های ارسالی با UnifiedPush نیاز به اجازهٔ اشتراک آگاهی‌ها روی کارساز ماستودنتان دارد. این کار نیازمند ورود دوباره برای تغییر حوزه‌های OAuth اعطایی به تاسکی است. استفاده از گزینهٔ ورود دوباره در این‌جا یا در ترجیحات حساب، تمامی انباره‌ها و پیش‌نویس‌های محلیتان را نگه خواهد داشت.</string>
<string name="pref_title_notification_filter_sign_ups">کسی ثبت‌نام کرد</string>
<string name="notification_update_name">ویرایش‌های فرسته</string>
<string name="action_edit_image">ویرایش تصویر</string>
@ -462,8 +459,6 @@
<string name="notification_sign_up_name">ثبت‌نام‌ها</string>
<string name="notification_sign_up_description">آگاهی‌ها دربارهٔ کاربران جدید</string>
<string name="title_migration_relogin">ورود دوباره برای آگاهی‌های ارسالی</string>
<string name="action_dismiss">رد کردن</string>
<string name="action_details">جزییات</string>
<string name="saving_draft">ذخیرهٔ پیش‌نویس…</string>
<string name="error_following_hashtag_format">خطا در پی‌گیری #%s</string>
<string name="error_unfollowing_hashtag_format">خطا در ناپی‌گیری #%s</string>

View File

@ -225,7 +225,6 @@
<string name="notification_update_format">%s muokkasi julkaisua</string>
<string name="notification_update_name">Julkaisujen muokkaukset</string>
<string name="title_migration_relogin">Kirjaudu uudestaan sisään ottaaksesi vastaan push-ilmoituksia</string>
<string name="action_details">Yksityiskohdat</string>
<string name="error_image_edit_failed">Kuvaa ei voitu muokata.</string>
<string name="description_visibility_unlisted">Listaamaton</string>
<string name="dialog_delete_conversation_warning">Poista tämä keskustelu\?</string>
@ -298,7 +297,6 @@
<string name="filter_dialog_whole_word_description">Kun avainsana tai lauseke sisältää ainoastaan kirjaimia ja numeroita, sitä sovelletaan vain jos se vastaa koko sanaa</string>
<string name="abbreviated_years_ago">%dv</string>
<string name="title_public_trending_statuses">Nousussa olevat julkaisut</string>
<string name="action_dismiss">Hylkää</string>
<string name="compose_active_account_description">Julkaistaan käyttäjänä %1$s</string>
<string name="dialog_redraft_post_warning">Julkaisu poistetaan ja kirjoitetaan uudestaan?</string>
<string name="error_following_hashtags_unsupported">Tämä instanssi ei tue aihetunnisteita.</string>
@ -449,7 +447,6 @@
<string name="error_media_playback">Toisto epäonnistui: %s</string>
<string name="drafts_failed_loading_reply">Vastaustietojn lataaminen epäonnistui</string>
<string name="warning_scheduling_interval">Lyhin ajastusväli Mastodonissa on 5 minuuttia.</string>
<string name="dialog_push_notification_migration_other_accounts">Olet kirjautunut uudelleen nykyiseen tiliisi myöntääksesi Pachlille tilausluvan. Sinulla on kuitenkin muitakin tilejä, joita ei ole siirretty tällä tavalla. Vaihda niihin ja kirjaudu uudelleen yksi kerrallaan, jotta UnifiedPush-ilmoitusten tuki voidaan ottaa käyttöön.</string>
<string name="mute_notifications_switch">Hiljennä ilmoitukset</string>
<string name="accessibility_talking_about_tag">%1$d käyttäjää keskustelee aihetunnisteesta %2$s</string>
<string name="translation_provider_fmt">%1$s</string>
@ -482,7 +479,6 @@
<string name="account_date_joined">Liittyi %1$s</string>
<string name="ui_error_filter_v1_load_fmt">Suodattimien lataaminen epäonnistui: %1$s</string>
<string name="announcement_date_updated">(Päivitetty: %1$s)</string>
<string name="tips_push_notification_migration">Kirjaudu uudelleen kaikille tileille push-ilmoitusten ottamiseksi käyttöön.</string>
<string name="pref_update_notification_frequency_once_per_version">Kerran jokaisesta versiosta</string>
<string name="filter_description_hide">Piilota täysin</string>
<string name="filter_description_format">%s: %s</string>
@ -503,7 +499,6 @@
<string name="label_filter_context">Suodattimen kontekstit</string>
<string name="pref_title_confirm_reblogs">Pyydä vahvistus ennen tehostamista</string>
<string name="account_note_hint">Yksityinen muistiinpano tästä tilistä</string>
<string name="dialog_push_notification_migration">Push-ilmoitusten käyttämiseksi UnifiedPushin kautta Pachli tarvitsee luvan tilata Mastodon-palvelimellesi ilmoitukset. Tämä edellyttää Pachlille myönnettyjen OAuthin laajuuksien muuttamista. Uudelleenkirjautumisvaihtoehdon käyttäminen täällä tai tilin asetuksissa tallentaa kaikki paikalliset luonnokset ja välimuistin.</string>
<string name="label_filter_action">Suodattimen toiminta</string>
<string name="update_dialog_title">Päivitys on saatavilla</string>
<string name="filter_edit_keyword_title">Muokkaa avainsanaa</string>
@ -766,4 +761,4 @@
<string name="pref_labs_reverse_home_timeline_title">Aikajana käänteisessä järjestyksessä</string>
<string name="pref_labs_reverse_home_timeline_off_summary">Uusimmat julkaisut ensin</string>
<string name="pref_labs_reverse_home_timeline_on_summary">Vanhimmat julkaisut ensin</string>
</resources>
</resources>

View File

@ -469,15 +469,10 @@
<string name="notification_update_name">Messages modifiés</string>
<string name="notification_update_description">Notifications quand un message avec lequel vous avez interagi est modifié</string>
<string name="account_date_joined">Ici depuis %1$s</string>
<string name="action_details">Détails</string>
<string name="saving_draft">Sauvegarde du brouillon …</string>
<string name="status_count_one_plus">&gt;1</string>
<string name="dialog_push_notification_migration_other_accounts">Pachli peut maintenant recevoir les notifications instantanées de ce compte. Cependant, d\'autres de vos comptes n\'ont pas encore accès aux notifications instantanées. Basculez sur chacun de vos comptes et reconnectez les afin de recevoir les notifications avec UnifiedPush.</string>
<string name="action_edit_image">Retoucher limage</string>
<string name="action_dismiss">Fermer</string>
<string name="title_migration_relogin">Se reconnecter pour recevoir les notifications instantanées</string>
<string name="dialog_push_notification_migration">Afin de recevoir les notifications via UnifiedPush, Pachli doit demander à votre serveur Mastodon la permission de sinscrire aux notifications. Ceci nécessite une reconnexion de vos comptes afin de changer les droits OAuth accordés a Pachli. En utilisant loption de reconnexion ici ou dans les préférences de compte, vos brouillons et le cache seront préservés.</string>
<string name="tips_push_notification_migration">Reconnectez tous vos comptes pour activer les notifications instantanées.</string>
<string name="error_image_edit_failed">L\'image na pas pu être retouchée.</string>
<string name="delete_scheduled_post_warning">Supprimer ce message planifié \?</string>
<string name="filter_expiration_format">%s (%s)</string>

View File

@ -430,7 +430,5 @@
<string name="notification_sign_up_format">Chláraigh %s</string>
<string name="action_unbookmark">Bain leabharmharc</string>
<string name="action_delete_conversation">Scrios comhrá</string>
<string name="action_dismiss">Dún</string>
<string name="action_details">Sonraí</string>
<string name="dialog_delete_conversation_warning">Scrios an comhrá seo\?</string>
</resources>
</resources>

View File

@ -471,18 +471,13 @@
<string name="notification_update_description">Brathan nuair a thèid postaichean a rinn thu conaltradh leotha a dheasachadh</string>
<string name="pref_title_notification_filter_updates">chaidh post a rinn mi conaltradh leis a deasachadh</string>
<string name="saving_draft">A sàbhaladh na dreuchd…</string>
<string name="action_dismiss">Leig seachad</string>
<string name="action_details">Fiosrachadh</string>
<string name="account_date_joined">Air ballrachd fhaighinn %1$s</string>
<string name="tips_push_notification_migration">Clàraich a-steach às ùr leis a h-uile cunntas a chur na taice ri brathan putaidh an comas.</string>
<string name="title_migration_relogin">Clàraich a-steach às ùr airson brathan putaidh</string>
<string name="status_count_one_plus">1+</string>
<string name="action_edit_image">Deasaich an dealbh</string>
<string name="error_following_hashtag_format">Mearachd a leantainn air #%s</string>
<string name="error_unfollowing_hashtag_format">Mearachd a sgur de leantainn air #%s</string>
<string name="error_image_edit_failed">Cha b urrainn dhuinn an dealbh a dheasachadh.</string>
<string name="dialog_push_notification_migration">Airson brathan putaidh slighe UnifiedPush a chleachdadh, feumaidh Pachli cead airson fo-sgrìobhadh air brathan air an fhrithealaiche Mastodon agad fhaighinn. Bidh feum air clàradh a-steach às ùr airson na sgòpaichean OAuth a chaidh a cheadachadh dha Pachli atharrachadh. Ma nì thu clàradh a-steach às ùr an-seo no ann an “Roghainnean a chunntais”, cumaidh sinn na dreachdan is an tasgadan ionadail agad.</string>
<string name="dialog_push_notification_migration_other_accounts">Rinn thu clàradh a-steach às ùr dhan chunntas làithreach agad airson cead fo-sgrìobhadh putaidh a thoirt dha Pachli. Gidheadh, cha cunntasan eile agad fhathast nach deach imrich air an dòigh sin. Geàrr leum thuca is dèan clàradh a-steach às ùr do gach fear dhiubh airson taic do bhrathan UnifiedPush a chur an comas dhaibh.</string>
<string name="duration_no_change">(Gun atharrachadh)</string>
<string name="pref_title_show_self_username">Seall an t-ainm-cleachdaiche air na bàraichean-inneal</string>
<string name="set_focus_description">Thoir gogag no slaod an cearcall a thaghadh puing an fhòcais a chithear air na dealbhagan an-còmhnaidh.</string>

View File

@ -447,14 +447,9 @@
<string name="pref_title_notification_filter_updates">Foi editada unha publicación coa que interactuei</string>
<string name="notification_update_name">Edicións da publicación</string>
<string name="account_date_joined">Creada %1$s</string>
<string name="tips_push_notification_migration">Volve a acceder con tódalas contas para activar as notificacións push.</string>
<string name="notification_update_description">Notificacións cando son editadas publicacións coas que interactuaches</string>
<string name="dialog_push_notification_migration">Para poder usar as notificacións push vía UnifiedPush, Pachli require o permiso para subscribirse ás notificacións do teu servidor Mastodon. É necesario volver a acceder para cambiar os ámbitos OAuth concedidos a Pachli. Usando aquí, ou nas preferencias da Conta, a opción de volver a acceder conservarás os borradores locais e caché.</string>
<string name="dialog_push_notification_migration_other_accounts">Volveches a acceder para obter as notificacións push en Pachli. Aínda así tes algunha outra conta que non foi migrada a este modo. Cambia a esas contas e volve a conectar unha a unha para activar o soporte para notificacións de UnifiedPush.</string>
<string name="title_migration_relogin">Volve a acceder para ter notificacións push</string>
<string name="notification_update_format">%s editou a publicación</string>
<string name="action_dismiss">Desbotar</string>
<string name="action_details">Detalles</string>
<string name="saving_draft">Gardando borrador…</string>
<string name="status_count_one_plus">1+</string>
<string name="action_edit_image">Editar imaxe</string>

View File

@ -456,14 +456,9 @@
<string name="notification_update_description">Értesítések olyan bejegyzések szerkesztéséről, melyekkel már dolgod volt</string>
<string name="pachli_compose_post_quicksetting_label">Bejegyzés Létrehozása</string>
<string name="title_migration_relogin">Bejelentkezés újra a leküldési értesítések érdekében</string>
<string name="action_dismiss">Elvetés</string>
<string name="action_details">Részletek</string>
<string name="account_date_joined">Csatlakozva %1$s</string>
<string name="tips_push_notification_migration">Bejelentkezés újra minden fiókkal a leküldéses értesítések engedélyezése érdekében.</string>
<string name="status_count_one_plus">1+</string>
<string name="saving_draft">Vázlat mentése…</string>
<string name="dialog_push_notification_migration">Ahhoz, hogy használhass leküldési értesítéseket a UnifiedPush szolgáltatás révén, a Pachli-nak fel kell iratkoznia az értesítésekre a Mastodon kiszolgálódon. Ehhez új bejelentkezésre van szükség, hogy a Pachli számára kiosztott OAuth jogosultságok megváltozzanak. Az újbóli bejelentkezés funkció használata itt vagy a Fiókbeállításoknál meg fogja őrizni a helyi piszkozataidat és a cache tartalmát.</string>
<string name="dialog_push_notification_migration_other_accounts">Újra bejelentkeztél a fiókodba, hogy feliratkoztasd a Pachli-t a leküldési értesítések használatára. Ugyanakkor vannak még fiókjaid, melyek még nem lettek így migrálva. Válts át rájuk és jelentkezz be újra mindegyikben, hogy ezekben is engedélyezd a UnifiedPush értesítések támogatását.</string>
<string name="action_edit_image">Kép szerkesztése</string>
<string name="error_image_edit_failed">A kép nem szerkeszthető.</string>
<string name="pref_title_show_self_username">Felhasználónév mutatása az eszköztáron</string>

View File

@ -66,7 +66,6 @@
<string name="action_search">Cari</string>
<string name="action_access_drafts">Draf</string>
<string name="action_reset_schedule">Setel Ulang</string>
<string name="action_details">Detail</string>
<string name="action_copy_link">Salin tautan</string>
<string name="download_image">Mengunduh %1$s</string>
<string name="download_media">Unduh media</string>
@ -180,7 +179,6 @@
<string name="action_open_reblogger">Buka penulis boost</string>
<string name="action_open_reblogged_by">Tampilkan boost</string>
<string name="action_open_faved_by">Tampilkan favorit</string>
<string name="action_dismiss">Menolak</string>
<string name="action_open_media_n">Buka media #%d</string>
<string name="send_media_to">Bagikan media kepada…</string>
<string name="confirmation_unblocked">Pengguna ter-unblock</string>
@ -264,4 +262,4 @@
<string name="confirmation_hashtag_muted">#%s tersembunyi</string>
<string name="confirmation_hashtag_unmuted">#%s tidak tersembunyi</string>
<string name="confirmation_hashtag_unfollowed">#%s berhenti mengikuti</string>
</resources>
</resources>

View File

@ -448,16 +448,11 @@
<string name="notification_sign_up_description">Tilkynningar um nýja notendur</string>
<string name="notification_update_name">Breytingar á færslum</string>
<string name="notification_update_description">Tilkynningar þegar færslum sem þú hefur átt við er breytt</string>
<string name="dialog_push_notification_migration_other_accounts">Þú hefur skráð þig aftur inn í fyrirliggjandi aðganginn þinn til þess að veita heimild fyrir áskrift að ýti-tilkynningum í Pachli. Aftur á móti ertu með aðra aðganga sem ekki hafa verið yfirfærðir á þennan hátt. Skiptu yfir í þá og skráðu þig þar inn aftur til að virkja stuðning við tilkynningar í gegnum UnifiedPush.</string>
<string name="account_date_joined">Skráði sig %1$s</string>
<string name="tips_push_notification_migration">Skrá aftur inn alla aðganga til að virkja stuðning við ýti-tilkynningar.</string>
<string name="dialog_push_notification_migration">Til þess að geta sent ýti-tilkynningar í gegnum UnifiedPush, þarf Pachli heimild til að gerast áskrifandi að tilkynningum á Mastodon-netþjóninum þínum. Þetta krefst þess að skráð sé inn aftur til að breyta vægi OAuth-heimilda sem Pachli er úthlutað. Notaðu endurinnskráninguna hérna eða í kjörstillingum aðgangsins þíns til að varðveita öll drögin þín og skyndiminni á tækinu.</string>
<string name="status_count_one_plus">1+</string>
<string name="action_edit_image">Breyta mynd</string>
<string name="saving_draft">Vista drög…</string>
<string name="title_migration_relogin">Skráðu aftur inn fyrir ýti-tilkynningar</string>
<string name="action_dismiss">Hunsa</string>
<string name="action_details">Nánar</string>
<string name="filter_expiration_format">%s (%s)</string>
<string name="description_post_language">Tungumál færslu</string>
<string name="duration_no_change">(engin breyting)</string>

View File

@ -489,12 +489,7 @@
<string name="announcement_date_updated">(Aggiornato: %1$s)</string>
<string name="announcement_date">%1$s %2$s</string>
<string name="saving_draft">Salvataggio bozza…</string>
<string name="action_dismiss">Scartare</string>
<string name="action_details">Dettagli</string>
<string name="tips_push_notification_migration">Riaccedi a tutte le utenze per attivare il supporto delle notifiche.</string>
<string name="dialog_push_notification_migration">Al fine di utilizzare le notifiche tramite UnifiedPush, Pachli ha bisogno del permesso di sottoscrivere alle notifiche nella tua istanza Mastodon. Questo richiede un nuovo accesso per cambiare l\'OAuth precedentemente concesso a Pachli. Usare questa opzione qui o nelle preferenze dell\'account preserva tutte le tue bozze locali e la memoria temporanea (cache).</string>
<string name="filter_expiration_format">%s (%s)</string>
<string name="dialog_push_notification_migration_other_accounts">Nuovo accesso eseguito per l\'utenza corrente al fine di garantire il permesso delle notifiche a Pachli. Però hai altre utenze che non sono state migrate in questo modo. Cambia utenza e riaccedi una alla volta per abilitare il supporto alle notifiche UnifiedPush.</string>
<string name="account_date_joined">Registrato da %1$s</string>
<string name="error_image_edit_failed">L\'immagine non può essere modificata.</string>
<string name="title_migration_relogin">Riaccedi per le notifiche</string>

View File

@ -392,7 +392,6 @@
<string name="delete_scheduled_post_warning">この予約投稿を削除しますか?</string>
<string name="action_unbookmark">ブックマークを削除</string>
<string name="action_delete_conversation">会話を削除</string>
<string name="action_details">詳細情報</string>
<string name="notification_sign_up_format">%sさんがサインアップしました</string>
<string name="description_post_language">投稿言語</string>
<string name="description_post_favourited">お気に入りに追加しました</string>
@ -429,7 +428,6 @@
<string name="error_following_hashtags_unsupported">このインスタンスはハッシュタグのフォローに対応していません。</string>
<string name="title_followed_hashtags">フォローしたハッシュタグ</string>
<string name="confirmation_domain_unmuted">%s のミュートが解除されました</string>
<string name="dialog_push_notification_migration_other_accounts">プッシュ通知の購読許可を Pachli に与えるための現在のアカウントへの再ログインが完了しました。しかし、まだ他のアカウントはマイグレーションができていません。UnifiedPush 通知w有効にするには、他のアカウントにも切り替えて、それぞれ再ログインをしてください。</string>
<string name="action_unfollow_hashtag_format">#%s をフォロー解除しますか?</string>
<string name="notification_sign_up_description">新規ユーザーに関する通知</string>
<string name="notification_update_name">投稿の編集</string>
@ -437,7 +435,6 @@
<string name="notification_update_format">%sさんが投稿を編集しました</string>
<string name="post_edited">%s を編集しました</string>
<string name="notification_header_report_format">%sさんが %s を報告しました</string>
<string name="action_dismiss">キャンセル</string>
<string name="confirmation_hashtag_unfollowed">#%s をフォロー解除しました</string>
<string name="pref_title_animate_custom_emojis">カスタム絵文字のアニメーションを有効化</string>
<string name="dialog_delete_conversation_warning">この会話を削除しますか?</string>
@ -474,12 +471,10 @@
\n プッシュ通知には影響ありませんが、通知設定は手動で確認できます。</string>
<string name="review_notifications">通知の確認</string>
<string name="action_unsubscribe_account">購読の解除</string>
<string name="tips_push_notification_migration">プッシュ通知のサポートを有効にするにはすべてのアカウントで再ログインしてください。</string>
<string name="report_category_violation">ルール違反</string>
<string name="report_category_spam">スパム</string>
<string name="report_category_other">その他</string>
<string name="pachli_compose_post_quicksetting_label">投稿する</string>
<string name="dialog_push_notification_migration">UnifiedPush 経由でプッシュ通知を使用するには、Pachli に Mastodon サーバー上での購読許可が必要です。そのため、Pachli に与えられた OAuth スコープを変更するための再ログインが必要になります。ここかアカウント設定で再ログインのオプションを選んでも、ローカルの下書きとキャッシュは保存されます。</string>
<string name="error_following_hashtag_format">#%s のフォローエラー</string>
<string name="title_migration_relogin">プッシュ通知受け取るには再ログインしてください</string>
<string name="error_unfollowing_hashtag_format">#%s のフォロー解除エラー</string>

View File

@ -65,7 +65,6 @@
<string name="action_reset_schedule">Atiestatīt</string>
<string name="download_image">Lejupielādē %1$s</string>
<string name="title_mentions_dialog">Pieminējumi</string>
<string name="action_details">Detaļas</string>
<string name="confirmation_reported">Nosūtīts!</string>
<string name="action_copy_link">Nokopēt saiti</string>
<string name="confirmation_unblocked">Lietotājs atbloķēts</string>
@ -378,7 +377,6 @@
<string name="wellbeing_hide_stats_posts">Slēpt ierakstu kvantitatīvo statistiku</string>
<string name="drafts_failed_loading_reply">Neizdevās ielādēt atbildes informāciju</string>
<string name="action_delete_and_redraft">Dzēst un sākt no jauna</string>
<string name="action_dismiss">Aizvākt</string>
<string name="dialog_message_cancel_follow_request">Vai atcelt sekošanas pieprasījumu\?</string>
<string name="dialog_redraft_post_warning">Vai dzēst šo ierakstu un sākt no jauna\?</string>
<string name="send_post_notification_error_title">Sūtot ziņu, radās kļūda</string>
@ -522,4 +520,4 @@
<string name="filter_description_warn">Paslēpt ar brīdinājumu</string>
<string name="filter_edit_keyword_title">Labot atslēgvārdu</string>
<string name="filter_keyword_addition_title">Pievienot atslēgvārdu</string>
</resources>
</resources>

View File

@ -451,12 +451,7 @@
<string name="notification_update_name">Redigerte innlegg</string>
<string name="notification_update_description">Varslinger når et innlegg du har interagert med er redigert</string>
<string name="title_migration_relogin">Logg inn på nytt for pushvarsler</string>
<string name="action_dismiss">Avvis</string>
<string name="action_details">Detaljer</string>
<string name="account_date_joined">Ble med %1$s</string>
<string name="tips_push_notification_migration">Logg inn all konti på nytt for å skru på pushvarsler.</string>
<string name="dialog_push_notification_migration">For å kunne sende pushvarsler via UnifiedPush trenger Pachli tillatelse til å abonnere på varsler på Mastodon-serveren. Dette krever at du logger inn på nytt. Ved å bruke muligheten til å logge inn på nytt her eller i kontoinstillinger vil alle lokale utkast være tilgjengelig også etter at du har logget inn på nytt.</string>
<string name="dialog_push_notification_migration_other_accounts">Du har logget inn på nytt for å tillate Pachli til å sende pushvarsler, men du har fortsatt andre konti som ikke har fått den nødvendige tillatelsen. Bytt til dem og logg inn på nytt på samme måte for å skru på støtte for pushvarsler via UnifiedPush.</string>
<string name="saving_draft">Lagrer utkast…</string>
<string name="status_count_one_plus">1+</string>
<string name="action_edit_image">Rediger bilde</string>

View File

@ -439,13 +439,10 @@
\n
\nPushmeldingen worden hierdoor niet beïnvloed, maar je kunt de voorkeuren voor meldingen handmatig wijzigen.</string>
<string name="notification_sign_up_format">%s heeft zich geregistreerd</string>
<string name="tips_push_notification_migration">Alle accounts opnieuw inloggen i.v.m. ondersteuning pushmeldingen.</string>
<string name="error_image_edit_failed">Deze afbeelding kon niet worden bewerkt.</string>
<string name="title_migration_relogin">Opnieuw inloggen i.v.m. pushmeldingen</string>
<string name="notification_update_format">%s heeft diens bericht bewerkt</string>
<string name="action_unbookmark">Bladwijzer verwijderen</string>
<string name="action_dismiss">Afwijzen</string>
<string name="action_details">Details</string>
<string name="pref_title_notification_filter_sign_ups">iemand heeft zich geregistreerd</string>
<string name="pref_title_notification_filter_updates">een bericht waarmee ik interactie had is bewerkt</string>
<string name="notification_sign_up_name">Registraties</string>
@ -468,8 +465,6 @@
<string name="duration_no_change">(geen verandering)</string>
<string name="pref_title_show_self_username">Gebruikersnaam op werkbalken tonen</string>
<string name="set_focus_description">Tik of sleep de cirkel naar een centraal focuspunt dat op elke thumbnail zichtbaar moet blijven.</string>
<string name="dialog_push_notification_migration">Om pushmeldingen via UnifiedPush te kunnen gebruiken, moet Pachli zich op meldingen van jouw Mastodon-server abonneren. Dit betekent dat je opnieuw moet inloggen om de OAuth-toestemmingen voor Pachli te wijzigen. Het hier of onder accountvoorkeuren opnieuw inloggen behoudt jouw lokale concepten en buffer.</string>
<string name="dialog_push_notification_migration_other_accounts">Je hebt opnieuw op jouw huidige account ingelogd om toestemming voor pushmeldingen aan Pachli te verlenen. Je hebt echter nog andere accounts die nog niet op deze manier zijn overgezet. Ga naar deze accounts en log één voor één opnieuw in om UnifiedPush-meldingen ook daar in te schakelen.</string>
<string name="pref_show_self_username_always">Altijd</string>
<string name="pref_show_self_username_disambiguate">Wanneer meerdere accounts zijn ingelogd</string>
<string name="pref_show_self_username_never">Nooit</string>

View File

@ -444,8 +444,6 @@
<string name="error_unfollowing_hashtag_format">Error en quitant de seguir #%s</string>
<string name="notification_sign_up_format">%s a creat un compte</string>
<string name="notification_update_format">%s a modificat sa publicacion</string>
<string name="action_dismiss">Ignorar</string>
<string name="action_details">Detalhs</string>
<string name="action_edit_image">Modificar limatge</string>
<string name="pachli_compose_post_quicksetting_label">Compausar una publicacion</string>
<string name="account_date_joined">Arribada del %1$s</string>
@ -481,11 +479,8 @@
<string name="notification_sign_up_description">Notificacions quand qualquun crèa un compte novèl</string>
<string name="notification_report_description">Notificacions a prepaus dels senhalament a la moderacion</string>
<string name="notification_update_description">Notificacions quand una publicacion ont avètz reagit es modificada</string>
<string name="tips_push_notification_migration">Reconnectatz-vos a vòstres comptes per activar las notificacions instantanèas.</string>
<string name="follow_requests_info">Malgrat que vòstre compte siá pas verrolhat, l\'equipa de %1$s a pensat que volriatz validar manualament las demandas de d\'abonament provenent d\'aqueles comptes.</string>
<string name="dialog_push_notification_migration">Per tal de recebre las notificacions per UnifiedPush, Pachli deu demandar a vòstre servidor Mastodon la permission de s\'inscriure a las notificacions. Aquò necessita una reconnexion de vòstres comptes per tal de cambiar los dreches OAuth acordats a Pachli. En utilizant l\'opcion de reconnexion aicí o dins las preferéncias de compte, vòstres borrolhons e l\'escondon seràn preservats.</string>
<string name="pref_title_enable_swipe_for_tabs">Activar lo limpament per se desplaçar demest los onglets</string>
<string name="dialog_push_notification_migration_other_accounts">Pachli pòt ara recebre las notificacions instantanèas d\'aquel compte. Pasmens, d\'autres de vòstres comptes an pas encara accèsses a las notificacions instantanèas. Basculatz sus cadun de vòstres comptes e reconnectatz-los per tal de recebre las notificacions amb UnifiedPush.</string>
<string name="compose_save_draft_loses_media">Enregistrar lo borrolhon\? (Las pèças juntas seràn enviadas tornamai quand restauretz lo borrolhon.)</string>
<string name="pref_default_post_language">Lenga de publicacion per defaut</string>
<string name="report_category_spam">Messatge indesirable</string>

View File

@ -11,5 +11,4 @@
<string name="action_search">ସନ୍ଧାନ</string>
<string name="action_edit">ସମ୍ପାଦନା</string>
<string name="action_view_media">ମିଡ଼ିଆ</string>
<string name="action_details">ଵିଵରଣୀ</string>
</resources>

View File

@ -481,16 +481,11 @@
<string name="notification_update_format">%s edytował/a swój wpis</string>
<string name="notification_update_name">Edycje wpisów</string>
<string name="saving_draft">Zapisywanie szkicu…</string>
<string name="dialog_push_notification_migration_other_accounts">Zalogowałeś/-aś się ponownie na swoje konto, aby przyzwolić Pachli na wysyłanie powiadomień push. Masz jednak inne konta które nie zostały zmigrowane. Przełącz się na nie i zaloguj się ponownie aby włączyć wsparcie dla powiadomień UnifiedPush.</string>
<string name="status_count_one_plus">1+</string>
<string name="account_date_joined">Dołączył/-a %1$s</string>
<string name="tips_push_notification_migration">Zaloguj się ponownie na wszystkie konta aby włączyć wsparcie dla powiadomień push.</string>
<string name="dialog_push_notification_migration">Aby użyć powiadomień push przez UnifiedPush, Pachli wymaga pozwolenia na subskrybowanie powiadomień na twoim serwerze Mastodon. Wymaga to ponownego zalogowania aby zmienić zakresy OAuth przyznane Pachli. Użycie opcji ponownego zalogowania tutaj lub w ustawieniach konta zachowa wszystkie szkice i pamięć podręczną.</string>
<string name="action_edit_image">Edytuj obraz</string>
<string name="error_image_edit_failed">Obrazek nie mógł być zmodyfikowany.</string>
<string name="title_migration_relogin">Zaloguj się ponownie aby włączyć powiadomienia push</string>
<string name="action_dismiss">Odrzuć</string>
<string name="action_details">Detale</string>
<string name="filter_expiration_format">%s (%s)</string>
<string name="description_post_language">Język wpisu</string>
<string name="duration_no_change">(bez zmian)</string>
@ -591,4 +586,4 @@
<string name="filter_edit_keyword_title">Edytuj słowo kluczowe</string>
<string name="label_image">Obraz</string>
<string name="action_add">Dodaj</string>
</resources>
</resources>

View File

@ -468,18 +468,14 @@
<string name="error_muting_hashtag_format">Erro ao silenciar #%s</string>
<string name="account_username_copied">Nome de usuário copiado</string>
<string name="title_followed_hashtags">Hashtags seguidas</string>
<string name="action_details">Detalhes</string>
<string name="error_following_hashtag_format">Erro ao seguir #%s</string>
<string name="error_unfollowing_hashtag_format">Erro ao deixar de seguir #%s</string>
<string name="a11y_label_loading_thread">Carregando fio</string>
<string name="set_focus_description">Toque ou arraste o círculo para escolher o ponto focal que estará sempre visível nas miniaturas.</string>
<string name="filter_expiration_format">%s (%s)</string>
<string name="dialog_push_notification_migration_other_accounts">Você se conectou novamente à tua conta atual para conceder permissão de assinatura Push à Pachli. No entanto, ainda tem outras contas que não foram migradas dessa forma. Alterne para elas e autentique novamente uma por uma para habilitar o suporte às notificações UnifiedPush.</string>
<string name="dialog_push_notification_migration">Para usar notificações Push via UnifiedPush, Pachli precisa de permissão para assinar notificações em teu servidor Mastodon. Isso requer uma nova autenticação para alterar os escopos OAuth concedidos à Pachli. Usar a opção de reautenticar aqui ou nas preferências da conta preservará todos os teus rascunhos e cache locais.</string>
<string name="failed_to_pin">Falha ao fixar</string>
<string name="failed_to_unpin">Falha ao desafixar</string>
<string name="compose_save_draft_loses_media">Salvar rascunho\? (Os anexos serão reenviados assim que você restaurar o rascunho.)</string>
<string name="tips_push_notification_migration">Reautentique todas as contas para habilitar o suporte de notificação Push.</string>
<string name="status_created_info">%1$s publicou</string>
<string name="title_edits">Edições</string>
<string name="pref_default_post_language">Idioma padrão dos Toots</string>
@ -562,7 +558,6 @@
<string name="title_public_trending_statuses">Toots em alta</string>
<string name="announcement_date">%1$s %2$s</string>
<string name="socket_timeout_exception">A comunicação com tua instância demorou muito</string>
<string name="action_dismiss">Dispensar</string>
<string name="select_list_manage">Gerenciar listas</string>
<string name="load_newest_statuses">Carregar Toots mais recentes</string>
<string name="notification_report_description">Notificações sobre denúncias da moderação</string>

View File

@ -462,17 +462,12 @@
<string name="action_undo">Desfazer</string>
<string name="action_accept">Aceitar</string>
<string name="action_reject">Rejeitar</string>
<string name="tips_push_notification_migration">Faz novamente login em todas as contas para ativar as notificações push.</string>
<string name="account_date_joined">Criada em %1$s</string>
<string name="dialog_push_notification_migration">Para ativar as notificações push através de UnifiedPush, o Pachli necessita de permissão para subscrever as notificações da tua instância Mastodon. Isto obriga a fazer login novamente, por forma a alterar o escopo das permissões fornecidas ao Pachli pelo OAuth. Usar a opção de novo login, aqui ou nas Configurações da Conta, preservará todos os teus rascunhos e cache locais.</string>
<string name="status_count_one_plus">1+</string>
<string name="dialog_push_notification_migration_other_accounts">Fizeste novo login na tua conta para dar permissão para a subscrição das notificações push no Pachli. Contudo, ainda tens outras contas sem esta permissão. Podes atribuir essa permissão fazendo novo login em cada uma delas e ativar o suporte para UnifiedPush.</string>
<string name="action_edit_image">Editar imagem</string>
<string name="error_image_edit_failed">Não foi possível editar a imagem.</string>
<string name="saving_draft">A guardar rascunho…</string>
<string name="title_migration_relogin">Faz novamente login para as notificações push</string>
<string name="action_dismiss">Descartar</string>
<string name="action_details">Detalhes</string>
<string name="delete_scheduled_post_warning">Apagar esta publicação agendada\?</string>
<string name="set_focus_description">Toca ou arrasta o círculo para escolher o ponto de focagem que estará sempre visível nas pré-visualizações.</string>
<string name="filter_expiration_format">%s(%s)</string>
@ -518,4 +513,4 @@
<string name="post_edited">Editado %s</string>
<string name="title_followed_hashtags">Hashtags seguidas</string>
<string name="title_public_trending_hashtags">Hashtags populares</string>
</resources>
</resources>

View File

@ -462,8 +462,6 @@
<string name="description_post_language">दौत्यस्य भाषा</string>
<string name="error_following_hashtag_format">#%s-अनुसरणे दोषो जातः</string>
<string name="error_unfollowing_hashtag_format">#%s-अनुसरण-अपाकरणे दोषो जातः</string>
<string name="action_details">विवरणानि</string>
<string name="action_dismiss">उत्सृज्यताम्</string>
<string name="post_media_attachments">सम्योजितानि</string>
<string name="status_count_one_plus">१+</string>
<string name="account_note_saved">रक्षितम् !</string>
@ -490,4 +488,4 @@
<string name="send_account_username_to">लेखायाः उपभोक्तृनाम्नः संविभागं कुरुताम् अस्मै…</string>
<string name="confirmation_hashtag_unfollowed">#%s अनुसरणम् अपाकृतम्</string>
<string name="send_account_link_to">लेखायाः निरपेक्ष-सार्वत्रिक-वस्तुसङ्केतस्य संविभागं कुरुताम् अस्मै…</string>
</resources>
</resources>

View File

@ -434,11 +434,9 @@
<string name="error_unfollowing_hashtag_format">Kunde inte avfölja #%s</string>
<string name="dialog_delete_conversation_warning">Radera denna konversation\?</string>
<string name="pref_show_self_username_always">Alltid</string>
<string name="dialog_push_notification_migration">För att använda pushnotiser via UnifiedPush behöver Pachli din tillåtelse att prenumerera på notiser på din Mastodon-server. Detta kräver att du loggar in igen för att ändra vilka OAuth-scopes Pachli har tillgång till. Genom använda alternativet logga in igen här eller i Kontoinställningarna behåller du alla dina lokala utkast och data i cache.</string>
<string name="set_focus_description">Tryck eller dra cirkeln för att välja fokuspunkten som alltid kommer synas i miniatyrbilder.</string>
<string name="label_duration">Varaktighet</string>
<string name="duration_indefinite">Oändligt</string>
<string name="dialog_push_notification_migration_other_accounts">Du har loggat in igen på ditt konto för att ge Pachli tillgång till push-prenumeration. Dock har du andra konton som inte har migrerats såhär ännu. Växla till dem och logga in igen för att aktivera stöd för UnifiedPush-notiser.</string>
<string name="action_unsubscribe_account">Sluta prenumerera</string>
<string name="description_post_language">Inläggsspråk</string>
<string name="pref_show_self_username_disambiguate">När flera konton är inloggade</string>
@ -465,7 +463,6 @@
<string name="pachli_compose_post_quicksetting_label">Skriv inlägg</string>
<string name="account_date_joined">Gick med i %1$s</string>
<string name="saving_draft">Sparar utkast…</string>
<string name="tips_push_notification_migration">Logga in igen på alla konton för att tillåta pushnotiser.</string>
<string name="error_image_edit_failed">Bilden kunde inte redigeras.</string>
<string name="duration_365_days">365 dagar</string>
<string name="duration_180_days">180 dagar</string>
@ -482,8 +479,6 @@
<string name="post_media_attachments">Bilagor</string>
<string name="status_count_one_plus">1+</string>
<string name="title_migration_relogin">Logga in igen för pushnotiser</string>
<string name="action_dismiss">Avvisa</string>
<string name="action_details">Detaljer</string>
<string name="compose_save_draft_loses_media">Spara utkast\? (Bilagor kommer att laddas upp igen när du återställer utkastet.)</string>
<string name="failed_to_pin">Kunde inte fästa</string>
<string name="failed_to_unpin">Kunde inte lossa</string>

View File

@ -477,14 +477,11 @@
<string name="language_display_name_format">%s (%s)</string>
<string name="report_category_violation">Kural ihlali</string>
<string name="description_post_language">Gönderi dili</string>
<string name="dialog_push_notification_migration">UnifiedPush aracılığıyla push bildirimlerini kullanmak için Pachli\'nin Mastodon sunucundaki bildirimlere abone olma iznine ihtiyacı var. Bu, Pachli\'ye verilen OAuth kapsamlarını değiştirmek için yeniden oturum açmayı gerektirir. Burada veya Hesap tercihleri bölümünde yeniden giriş yapma seçeneğini kullanman tüm yerel taslaklarını ve önbelleğini koruyacaktır.</string>
<string name="confirmation_hashtag_unfollowed">#%s takip edilmeyenler</string>
<string name="action_subscribe_account">Abone Ol</string>
<string name="follow_requests_info">Hesabınız kilitli olmasa da, %1$s kadro bu hesaplardan gelen takip isteklerini elle gözden geçirmek isteyebileceğinizi düşündü.</string>
<string name="notification_report_format">Hakkında yeni rapor %s</string>
<string name="action_add_reaction">tepki ekle</string>
<string name="action_dismiss">Yok Say</string>
<string name="action_details">Ayrıntılar</string>
<string name="notification_subscription_name">Yeni yayınlar</string>
<string name="notification_subscription_description">abone olduğunuz birisi yeni gönderi yayınladığında gelen bildirimler</string>
<string name="notification_sign_up_name">Kayıt Ol</string>
@ -494,7 +491,6 @@
<string name="duration_indefinite">Tanımsız</string>
<string name="description_post_edited">Düzenlendi</string>
<string name="action_unsubscribe_account">Abonelikten Çık</string>
<string name="dialog_push_notification_migration_other_accounts">Pachli\'ye bildirim aboneliği izni vermek için mevcut hesabınıza yeniden giriş yaptınız. Ancak, yine de bu şekilde geçirilmemiş başka hesaplarınız var. UnifiedPush bildirimleri desteğini etkinleştirmek için bunlara geçin ve tek tek yeniden giriş yapın.</string>
<string name="error_following_hashtags_unsupported">Bu sunucu aşağıdaki etiketleri desteklemez.</string>
<string name="notification_subscription_format">%s az önce paylaşım yaptı</string>
<string name="notification_update_format">%s gönderilerini düzenledi</string>
@ -502,7 +498,6 @@
<string name="error_unfollowing_hashtag_format">Takip etmeyi bırakırken Hata #%s</string>
<string name="status_count_one_plus">1+</string>
<string name="label_duration">Süre</string>
<string name="tips_push_notification_migration">Bildirim besleme desteğini etkinleştirmek için tüm hesaplara yeniden giriş yapın.</string>
<string name="error_status_source_load">Sunucudan kaynak durumu yüklenemedi.</string>
<string name="pref_title_http_proxy_port_message">Bağlantı noktası %d ile %d arasında olmalıdır</string>
<string name="post_media_alt">Alternatif metin</string>

View File

@ -471,12 +471,7 @@
<string name="notification_update_description">Сповіщення, коли редагується повідомлення, з яким ви взаємодіяли</string>
<string name="notification_update_name">Редакції допису</string>
<string name="saving_draft">Збереження чернетки…</string>
<string name="action_dismiss">Відхилити</string>
<string name="action_details">Подробиці</string>
<string name="title_migration_relogin">Увійдіть повторно, щоб отримувати push-сповіщення</string>
<string name="tips_push_notification_migration">Увійдіть повторно до всіх облікових записів, щоб увімкнути підтримку push-сповіщень.</string>
<string name="dialog_push_notification_migration">Щоб використовувати push-сповіщення через UnifiedPush, Pachli потребує дозволу стежити за сповіщеннями на вашому сервері Mastodon. Це вимагає повторного входу, щоб змінити області OAuth, надані Pachli. Використання параметра повторного входу тут або в налаштуваннях облікового запису збереже всі ваші локальні чернетки та кеш.</string>
<string name="dialog_push_notification_migration_other_accounts">Ви повторно увійшли до свого поточного облікового запису, щоб надати дозвіл на стеження Pachli. Однак у вас все ще є інші облікові записи, які не мігрували таким чином. Перейдіть до них і повторно увійдіть до них по одному, щоб забезпечити підтримку UnifiedPush сповіщень.</string>
<string name="account_date_joined">Приєднується %1$s</string>
<string name="action_edit_image">Редагувати зображення</string>
<string name="status_count_one_plus">1+</string>

View File

@ -439,13 +439,8 @@
<string name="notification_update_name">Sửa tút</string>
<string name="notification_update_description">Thông báo khi tút mà tôi tương tác bị sửa</string>
<string name="saving_draft">Đang lưu nháp…</string>
<string name="action_dismiss">Bỏ qua</string>
<string name="title_migration_relogin">Đăng nhập lại để hiện thông báo đẩy</string>
<string name="action_details">Chi tiết</string>
<string name="account_date_joined">Đã tham gia %1$s</string>
<string name="tips_push_notification_migration">Đăng nhập lại tất cả tài khoản để kích hoạt thông báo đẩy.</string>
<string name="dialog_push_notification_migration_other_accounts">Bạn đã đăng nhập lại vào tài khoản hiện tại của mình để cấp quyền thông báo đẩy cho Pachli. Tuy nhiên, bạn vẫn có các tài khoản khác chưa kích hoạt thông báo đẩy theo cách này. Chuyển sang chúng và đăng nhập từng cái một để cho phép hỗ trợ thông báo UnifiedPush.</string>
<string name="dialog_push_notification_migration">Để sử dụng thông báo đẩy qua UnifiedPush, Pachli cần có quyền đăng ký thông báo trên máy chủ Mastodon của bạn. Bạn hãy thoát ra rồi đăng nhập lại để thay đổi phạm vi OAuth được cấp cho Pachli. Sử dụng đăng nhập lại ở đây hoặc trong cài đặt Tài khoản sẽ bảo toàn tất cả các tút nháp và bộ nhớ đệm trên điện thoại của bạn.</string>
<string name="status_count_one_plus">1+</string>
<string name="action_edit_image">Sửa ảnh</string>
<string name="error_image_edit_failed">Hình ảnh này không thể sửa.</string>

View File

@ -454,12 +454,7 @@
<string name="notification_update_description">当你进行过互动的嘟文被编辑时发出通知</string>
<string name="saving_draft">正在保存草稿…</string>
<string name="title_migration_relogin">重新登陆以启用通知推送</string>
<string name="action_dismiss">不理会</string>
<string name="action_details">详情</string>
<string name="dialog_push_notification_migration_other_accounts">你已重新登录当前账户,向 Pachli 授予推送订阅权限。但是,你仍然有其他没有以这种方式迁移的账户。切换到它们,逐个重新登录,以启用 UnifiedPush 通知支持。</string>
<string name="account_date_joined">加入于%1$s</string>
<string name="tips_push_notification_migration">重新登录所有账户来启用推送通知支持。</string>
<string name="dialog_push_notification_migration">为了通过 UnifiedPush 使用推送通知Pachli 需要订阅你 Mastodon 服务器通知的权限。这需要重新登录来更改授予 Pachli 的 OAuth 作用域。使用此处或账户首选项中“重新登录”选项将保留你所有的本地草稿和缓存。</string>
<string name="status_count_one_plus">1+</string>
<string name="action_edit_image">编辑图片</string>
<string name="error_image_edit_failed">无法编辑图片。</string>

View File

@ -463,7 +463,6 @@
<string name="action_subscribe_account">訂閱</string>
<string name="action_unsubscribe_account">取消訂閱</string>
<string name="saving_draft">正在儲存草稿…</string>
<string name="tips_push_notification_migration">重新登入所有帳號以啟用推播功能。</string>
<string name="action_set_focus">設置關注點</string>
<string name="title_migration_relogin">重新登入以啟用推播功能</string>
<string name="status_count_one_plus">1+</string>
@ -480,13 +479,9 @@
<string name="error_unfollowing_hashtag_format">取消追蹤 #%s 時發生錯誤</string>
<string name="action_unbookmark">移除書籤</string>
<string name="action_delete_conversation">刪除對話</string>
<string name="action_dismiss">撤銷</string>
<string name="action_details">詳情</string>
<string name="pref_title_notification_filter_updates">我互動過的嘟文被編輯了</string>
<string name="duration_14_days">14 天</string>
<string name="dialog_push_notification_migration">為了透過 UnifiedPush使用推播功能Pachli 需要獲得訂閱您 Mastodon 服務器上的通知之權限。這會需要重新登入才能更改授予 Pachli 的 OAuth 範疇。在此頁面或帳戶設定頁面中使用重新登入選項將會保留您所有的本機草稿和快取。</string>
<string name="dialog_push_notification_migration_other_accounts">您已重新登入當前帳號並授予 Pachli 推送訂閱的權限。 然而,您仍擁有其他帳號未以此種方式遷移。 請切換到該帳號,並且逐一重新登入,以啟用 UnifiedPush 的通知支援。</string>
<string name="notification_sign_up_format">%s 已註冊</string>
<string name="notification_update_format">%s 編輯了他們的嘟文</string>
<string name="error_image_edit_failed">這張圖片不能編輯。</string>
</resources>
</resources>

View File

@ -14,7 +14,9 @@
~
~ You should have received a copy of the GNU General Public License along with Pachli; if not,
~ see <http://www.gnu.org/licenses>.
--><resources>
-->
<resources>
<string name="error_empty">This cannot be empty.</string>
<string name="error_no_web_browser_found">Couldn\'t find a web browser to use.</string>
<string name="error_compose_character_limit">The post is too long!</string>
@ -177,8 +179,6 @@
<string name="action_open_reblogger">Open boost author</string>
<string name="action_open_reblogged_by">Show boosts</string>
<string name="action_open_faved_by">Show favorites</string>
<string name="action_dismiss">Dismiss</string>
<string name="action_details">Details</string>
<string name="action_add_reaction">add reaction</string>
<string name="action_suggestions">Suggested accounts</string>
<string name="action_translate">Translate</string>
@ -610,9 +610,6 @@
<string name="announcement_date_updated">(Updated: %1$s)</string>
<string name="announcement_date">%1$s %2$s</string>
<string name="saving_draft">Saving draft…</string>
<string name="tips_push_notification_migration">Re-login all accounts to enable push notification support.</string>
<string name="dialog_push_notification_migration">In order to use push notifications via UnifiedPush, Pachli needs permission to subscribe to notifications on your Mastodon server. This requires a re-login to change the OAuth scopes granted to Pachli. Using the re-login option here or in "Account preferences" will preserve all of your local drafts and cache.</string>
<string name="dialog_push_notification_migration_other_accounts">You have re-logged into your current account to grant push subscription permission to Pachli. However, you still have other accounts that have not been migrated this way. Switch to them and re-login one by one in order to enable UnifiedPush notifications support.</string>
<string name="delete_scheduled_post_warning">Delete this scheduled post?</string>
<string name="language_display_name_format">%s (%s)</string>
<string name="report_category_violation">Rule violation</string>
@ -685,6 +682,33 @@
<string name="translating">Translating…</string>
<string name="translation_provider_fmt">%1$s</string>
<string name="pref_title_notification_up_distributor">Unified push distributor</string>
<string name="pref_notification_up_distributor_name_fmt">%1$s. Tap to open</string>
<string name="pref_notification_up_distributor_none">None</string>
<string name="pref_title_notification_method">Notification method</string>
<string name="pref_notification_method_all_pull">Fetched approximately once every 15 minutes. Tap for details.</string>
<string name="pref_notification_method_all_push">All accounts use UnifiedPush. Tap for details.</string>
<string name="pref_notification_method_mixed">Some accounts use UnifiedPush, some pull every 15 minutes. Tap for details</string>
<string name="pref_title_notification_battery_optimisation">Battery optimization</string>
<string name="pref_notification_battery_optimisation_should_ignore">Battery optimisation should be disabled. Tap to open.</string>
<string name="pref_notification_battery_optimisation_remove">Battery optimisation is </string>
<string name="pref_notification_battery_optimisation_ok">Battery optimisation does not need to be disabled.</string>
<string name="pref_notification_method_push">Push</string>
<string name="pref_notification_method_pull">Pull</string>
<string name="pref_notification_fetch_method_title">Notification method</string>
<string name="pref_notification_fetch_ok_timestamp_fmt">✔ %1$s ago @ %2$s</string>
<string name="pref_notification_fetch_err_timestamp_fmt">✖ %1$s ago @ %2$s</string>
<string name="pref_notification_fetch_needs_push">Account does not have \"push\" scope. Logging out and back in may fix this.</string>
<string name="pref_notification_fetch_server_rejected">%1$s rejected push registration</string>
<string name="pref_title_change_unified_push_distributor">Change Unified Push distributor</string>
<string name="pref_change_unified_push_distributor_msg">This will clear the Unified Push distributor choice and restart Pachli.\n
\nIf multiple Unified Push distributors are installed you will be asked to choose one.</string>
<string name="server_repository_error">Could not fetch server info for %1$s: %2$s</string>
<string name="pref_title_update_check_now">Check for update now</string>
<string name="pref_update_check_no_updates">There are no updates available</string>

View File

@ -45,6 +45,7 @@ data class AccountEntity(
// nullable for backward compatibility
var clientSecret: String?,
var isActive: Boolean,
/** Account's remote (server) ID. */
var accountId: String = "",
/** User's local name, without the leading `@` or the `@domain` portion */
var username: String = "",
@ -80,7 +81,7 @@ data class AccountEntity(
*/
var mediaPreviewEnabled: Boolean = true,
/**
* ID of the last notification the user read on the Notification, list, and should be restored
* ID of the last notification the user read on the Notification list, and should be restored
* to view when the user returns to the list.
*
* May not be the ID of the most recent notification if the user has scrolled down the list.
@ -122,6 +123,10 @@ data class AccountEntity(
val fullName: String
get() = "@$username@$domain"
/** UnifiedPush "instance" identifier for this account. */
val unifiedPushInstance: String
get() = id.toString()
fun logout() {
// deleting credentials so they cannot be used again
accessToken = ""

View File

@ -764,21 +764,13 @@ interface MastodonApi {
// Should be generated dynamically from all the available notification
// types defined in [app.pachli.entities.Notification.Types]
@FieldMap data: Map<String, Boolean>,
): NetworkResult<NotificationSubscribeResult>
@FormUrlEncoded
@PUT("api/v1/push/subscription")
suspend fun updatePushNotificationSubscription(
@Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String,
@FieldMap data: Map<String, Boolean>,
): NetworkResult<NotificationSubscribeResult>
): ApiResult<NotificationSubscribeResult>
@DELETE("api/v1/push/subscription")
suspend fun unsubscribePushNotifications(
@Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String,
): NetworkResult<ResponseBody>
): ApiResult<Unit>
@GET("api/v1/tags/{name}")
suspend fun tag(@Path("name") name: String): NetworkResult<HashTag>

View File

@ -129,6 +129,14 @@ object PrefKeys {
const val LAB_REVERSE_TIMELINE = "labReverseTimeline"
/**
* True if the user's previous choice of UnifiedPush distributor should be
* used by default.
*
* Default is `true`.
*/
const val USE_PREVIOUS_UNIFIED_PUSH_DISTRIBUTOR = "usePreviousUnifiedPushDistributor"
/** Keys that are no longer used (e.g., the preference has been removed */
object Deprecated {
// Empty at this time

View File

@ -30,12 +30,15 @@ import kotlinx.coroutines.suspendCancellableCoroutine
* [AlertDialog.BUTTON_POSITIVE], [AlertDialog.BUTTON_NEGATIVE], or
* [AlertDialog.BUTTON_NEUTRAL].
*
* @param positiveText Text to show on the positive button
* @param negativeText Optional text to show on the negative button
* @param neutralText Optional text to show on the neutral button
* @param positiveText Optional text to show on the positive button. If null the button isn't
* shown.
* @param negativeText Optional text to show on the negative button. If null the button isn't
* shown.
* @param neutralText Optional text to show on the neutral button If null the button isn't
* shown.
*/
suspend fun AlertDialog.await(
positiveText: String,
positiveText: String?,
negativeText: String? = null,
neutralText: String? = null,
) = suspendCancellableCoroutine { cont ->
@ -43,7 +46,7 @@ suspend fun AlertDialog.await(
cont.resume(which) { dismiss() }
}
setButton(AlertDialog.BUTTON_POSITIVE, positiveText, listener)
positiveText?.let { setButton(AlertDialog.BUTTON_POSITIVE, positiveText, listener) }
negativeText?.let { setButton(AlertDialog.BUTTON_NEGATIVE, it, listener) }
neutralText?.let { setButton(AlertDialog.BUTTON_NEUTRAL, it, listener) }
@ -56,11 +59,11 @@ suspend fun AlertDialog.await(
* @see [AlertDialog.await]
*/
suspend fun AlertDialog.await(
@StringRes positiveTextResource: Int,
@StringRes positiveTextResource: Int?,
@StringRes negativeTextResource: Int? = null,
@StringRes neutralTextResource: Int? = null,
) = await(
context.getString(positiveTextResource),
positiveTextResource?.let { context.getString(it) },
negativeTextResource?.let { context.getString(it) },
neutralTextResource?.let { context.getString(it) },
)

View File

@ -79,7 +79,7 @@ timber = "5.0.1"
touchimageview = "3.6"
truth = "1.4.4"
turbine = "1.1.0"
unified-push = "2.1.1"
unified-push = "2.4.0"
xmlwriter = "1.0.4"
# Tool dependencies