diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index e04277884..eef856004 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -36,7 +36,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -47,7 +47,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -58,7 +58,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -69,7 +69,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -694,17 +694,6 @@
column="86"/>
-
-
-
-
@@ -723,7 +712,7 @@
errorLine2=" ^">
@@ -756,7 +745,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -800,7 +789,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -811,7 +800,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -822,7 +811,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -833,7 +822,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -844,7 +833,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -855,7 +844,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -866,7 +855,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -877,7 +866,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -888,7 +877,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -899,7 +888,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -910,7 +899,7 @@
errorLine2=" ~~~~~">
@@ -1207,7 +1196,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1218,7 +1207,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1229,7 +1218,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1240,7 +1229,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1251,7 +1240,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -1262,7 +1251,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1273,7 +1262,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1284,7 +1273,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1409,17 +1398,6 @@
column="13"/>
-
-
-
-
@@ -1570,7 +1548,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3050f5843..d5c85a387 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
+
-
-
-
-
-
-
-
diff --git a/app/src/main/java/app/pachli/MainActivity.kt b/app/src/main/java/app/pachli/MainActivity.kt
index 7926dbd06..4f8b53a4b 100644
--- a/app/src/main/java/app/pachli/MainActivity.kt
+++ b/app/src/main/java/app/pachli/MainActivity.kt
@@ -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()
diff --git a/app/src/main/java/app/pachli/components/notifications/NotificationFetcher.kt b/app/src/main/java/app/pachli/components/notifications/NotificationFetcher.kt
index fc9b5cc17..f068be3c8 100644
--- a/app/src/main/java/app/pachli/components/notifications/NotificationFetcher.kt
+++ b/app/src/main/java/app/pachli/components/notifications/NotificationFetcher.kt
@@ -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()
diff --git a/app/src/main/java/app/pachli/components/notifications/NotificationHelper.kt b/app/src/main/java/app/pachli/components/notifications/NotificationHelper.kt
index df2b89777..89fa73de3 100644
--- a/app/src/main/java/app/pachli/components/notifications/NotificationHelper.kt
+++ b/app/src/main/java/app/pachli/components/notifications/NotificationHelper.kt
@@ -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
}
}
diff --git a/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt b/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt
index 3194bd758..2a0c3f253 100644
--- a/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt
+++ b/app/src/main/java/app/pachli/components/notifications/NotificationsFragment.kt
@@ -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) {
diff --git a/app/src/main/java/app/pachli/components/notifications/PushNotificationHelper.kt b/app/src/main/java/app/pachli/components/notifications/PushNotificationHelper.kt
index 02eba9c87..c933bf334 100644
--- a/app/src/main/java/app/pachli/components/notifications/PushNotificationHelper.kt
+++ b/app/src/main/java/app/pachli/components/notifications/PushNotificationHelper.kt
@@ -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 =
- 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 {
- 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
}
}
diff --git a/app/src/main/java/app/pachli/components/preference/AccountPreferencesFragment.kt b/app/src/main/java/app/pachli/components/preference/AccountPreferencesFragment.kt
index bfa800add..ce3cdab3c 100644
--- a/app/src/main/java/app/pachli/components/preference/AccountPreferencesFragment.kt
+++ b/app/src/main/java/app/pachli/components/preference/AccountPreferencesFragment.kt
@@ -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)
diff --git a/app/src/main/java/app/pachli/components/preference/PreferencesFragment.kt b/app/src/main/java/app/pachli/components/preference/PreferencesFragment.kt
index ce879b3f5..3fc0540b7 100644
--- a/app/src/main/java/app/pachli/components/preference/PreferencesFragment.kt
+++ b/app/src/main/java/app/pachli/components/preference/PreferencesFragment.kt
@@ -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) : ArrayAdapter(
+ 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
+ }
+}
diff --git a/app/src/main/java/app/pachli/receiver/NotificationBlockStateBroadcastReceiver.kt b/app/src/main/java/app/pachli/receiver/NotificationBlockStateBroadcastReceiver.kt
deleted file mode 100644
index d190b52dc..000000000
--- a/app/src/main/java/app/pachli/receiver/NotificationBlockStateBroadcastReceiver.kt
+++ /dev/null
@@ -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 .
- */
-
-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) }
- }
- }
- }
-}
diff --git a/app/src/main/java/app/pachli/receiver/UnifiedPushBroadcastReceiver.kt b/app/src/main/java/app/pachli/receiver/UnifiedPushBroadcastReceiver.kt
index 8de0d4e19..dd8be520f 100644
--- a/app/src/main/java/app/pachli/receiver/UnifiedPushBroadcastReceiver.kt
+++ b/app/src/main/java/app/pachli/receiver/UnifiedPushBroadcastReceiver.kt
@@ -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)
+ }
}
}
}
diff --git a/app/src/main/java/app/pachli/usecase/LogoutUsecase.kt b/app/src/main/java/app/pachli/usecase/LogoutUsecase.kt
index fc0dae630..165e50a05 100644
--- a/app/src/main/java/app/pachli/usecase/LogoutUsecase.kt
+++ b/app/src/main/java/app/pachli/usecase/LogoutUsecase.kt
@@ -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)
diff --git a/app/src/main/java/app/pachli/worker/NotificationWorker.kt b/app/src/main/java/app/pachli/worker/NotificationWorker.kt
index 0b49938ba..d8f9f0ebb 100644
--- a/app/src/main/java/app/pachli/worker/NotificationWorker.kt
+++ b/app/src/main/java/app/pachli/worker/NotificationWorker.kt
@@ -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)
+ }
}
diff --git a/app/src/main/res/layout/account_notification_details_list_item.xml b/app/src/main/res/layout/account_notification_details_list_item.xml
new file mode 100644
index 000000000..83e1a22aa
--- /dev/null
+++ b/app/src/main/res/layout/account_notification_details_list_item.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index c7d0ff6bf..786d42b4e 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -523,7 +523,6 @@
- لا يمكنك رفع أكثر من %1$d مرفقات.
جار حفظ المسودة …
- لقد قمت بإعادة تسجيل الدخول إلى حسابك الجاري لمنح إذن الاشعارات لـ Pachli. ومع ذلك، لا يزال لديك حسابات أخرى لم يتم ترحيلها بهذه الطريقة. قم بالتبديل إليهم وإعادة تسجيل الدخول واحدًا تلو الآخر لتمكين دعم إشعارات UnifiedPush.
%s (%s)
لغة النشر الافتراضية
1+
@@ -544,7 +543,6 @@
فشل التثبيت
لغة المنشور
هل تريد حذف هذا المنشور المُبَرمَج؟
- أعد تسجيل الدخول إلى جميع الحسابات لتمكين دعم الإشعارات.
ضبط نقطة التركيز
تعديل الصورة
إضافة رد فعل
@@ -562,12 +560,9 @@
الوسوم المتابَعة
فشل تحميل مصدر المنشور من الخادم.
انتهاك الشروط
- من أجل استخدام الإشعارات عبر UnifiedPush ، يحتاج Pachli إلى إذن للإشتراك في الإشعارات على خادم Mastodon الخاص بك. يتطلب هذا إعادة تسجيل الدخول لتغيير نطاقات OAuth الممنوحة لـ Pachli. سيؤدي استخدام خيار إعادة تسجيل الدخول هنا أو في اعدادات الحساب إلى الاحتفاظ بجميع المسودات المحلية وذاكرة التخزين المؤقت.
الرفاهية
%1$s انضم في
أعد تسجيل الدخول لاستلام الاشعارات
- تجاهل
- تفاصيل
المنشور الذي تفاعلت معه تم تعديله
فشل التحميل
إظهار المسودات
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index 8b2c0fffa..ad9510fdd 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -82,8 +82,6 @@
Паказаць пашырэнні
Паказаць абраныя
Згадкі
- Адхіліць
- Дэталі
Адкрыць медыя #%d
дадаць рэакцыю
Спампоўка %1$s
@@ -486,13 +484,11 @@
\n- Статыстыка ў профілях пра Падпісчыкаў/Допісы
\n
\nНа Push-паведамленні гэта не ўплывае, але Вы можаце праглядзець налады апавяшчэнняў уручную.
- Каб выкарыстоўваць push-апавяшчэнні праз UnifiedPush, Pachli патрэбен дазвол, каб падпісацца на апавяшчэнні з Вашага сервера Mastodon. Для гэтага патрэбна выйсці і зайсці ва ўліковы запіс зноў, каб аднавіць вобласці дазволу OAuth, дадзеныя Pachli. Выкарыстоўванне магчымасці паўторнага ўваходу тут ці ў наладах уліковага запісу захоўвае ўсе Вашыя лакальныя чарнавікі і кэш.
Некалькі варыянтаў
Дадаць варыянт
Змяніць
Далучыўся(-лася) %1$s
Захавана!
- Вы паўторна зайшлі ў бягучы ўліковы запіс, каб дазволіць Pachli падпісацца на push-апавяшчэнні. Але ў Вас яшчэ засталіся ўліковыя запісы, якія не мігрыравалі такім чынам. Пераключыцеся на іх і зайдзіце паўторна, каб уключыць падтрымку апавяшчэнняў праз UnifiedPush.
%1$s адрэдагаваў(-ла)
%1$s стварыў(-ла)
Схаваць загаловак верхняй панэлі інструментаў
@@ -531,7 +527,6 @@
Выдаліць гэты запланаваны допіс\?
Варыянт %d
Не атрымалася даслаць гэты допіс!
- Увайдзіце зноў на ўсіх уліковых запісах, каб push-апавяшчэнні запрацавалі.
Бязгучныя апавяшчэнні
Найменшы час планавання ў Mastodon складае 5 хвілін.
Адключана
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index ec4e52872..6846b57d0 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -426,7 +426,6 @@
%s (%s)
Vols suprimir aquesta conversa\?
La imatge no s\'ha pogut editar.
- Descartar
El port hauria d\'estar entre %d i %d
+1
Edicions
@@ -471,7 +470,6 @@
afegir reacció
Comparteix l\'enllaç al compte
Comparteix el nom d\'usuari del compte
- Detalls
Comparteix el nom d\'usuari del compte a…
S\'ha copiat el nom d\'usuari
#%s deixat de seguir
@@ -486,7 +484,6 @@
Torneu a iniciar sessió per rebre notificacions push
S\'està carregant el fil
Guardar l\'esborrany\? (Els fitxers adjunts es tornaran a penjar quan recupereu l\'esborrany.)
- 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.
Edita la imatge
No s\'ha pogut fixar
No s\'ha pogut desfixar
@@ -522,8 +519,6 @@
\n Les notificacions push no es veuran afectades, però podeu revisar les vostres preferències de notificació manualment.
S\'ha unit el %1$s
Desant l\'esborrany…
- 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.
- Torneu a iniciar sessió a tots els comptes per activar el suport de notificacions push.
Editat
%1$s ha editat
Subscriu-te
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index a324fd158..0baee5825 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -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ě.
- 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.
Uložit koncept\? (Přílohy budou znovu nahrány, když obnovíte koncept.)
Klepnutím nebo přetažením kruhu vyberte ohnisko, které bude vždy viditelné v miniaturách.
Před oblíbením zobrazit dialog pro potvrzení
@@ -453,8 +452,6 @@
30 dní
Příspěvek, na který jste připravili odpověď, byl odstraněn
Nastavit bod zaostření
- Znovu se přihlaste ke všem účtům, abyste povolili podporu push oznámení.
- 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ěť.
přidat reakci
příspěvek, se kterým jsem interagoval/a, je upraven
někdo se zaregistroval
@@ -491,9 +488,7 @@
%s upravil/a svůj příspěvek
Odebrat záložku
Smazat konverzaci
- Zavřít
- Podrobnosti
Smazat tuto konverzaci\?
Požádáno o sledování
Animovat vlastní emotikony
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml
index 619d63f26..289f8e0af 100644
--- a/app/src/main/res/values-cy/strings.xml
+++ b/app/src/main/res/values-cy/strings.xml
@@ -309,8 +309,6 @@
Methu golygu\'r ddelwedd.
Hoffwyd
Yn cadw drafft…
- Diystyru
- Manylion
Crybwylliadau
Agor cyfryngau #%d
Rhannu fel …
@@ -393,8 +391,6 @@
Ysgrifennu neges
Methodd anfon y neges hon!
Ysgrifennu Neges
- Ailfewngofnodwch i\'ch cyfrifon er mwyn galluogi hysbysiadau i\'ch ffôn.
- 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.
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.
Dim diwedd
@@ -408,7 +404,6 @@
Uniongyrchol
Cadw drafft\? (Bydd atodiadau\'n cael eu lanlwytho eto pan fyddwch chi\'n adfer y drafft.)
Ailflogiwyd
- 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.
%1$s • %2$s
Dylai fod gan y cyfryngau ddisgrifiad.
Rhybudd cynnwys: %s
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index e60a2d96d..b56536f3a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -462,15 +462,10 @@
Benachrichtigungen, wenn Beiträge bearbeitet werden, mit denen du interagiert hast
Beitragsbearbeitungen
Neuanmeldung für Push-Benachrichtigungen
- Ablehnen
- 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.
- 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.
- Melde alle Konten neu an, um die Unterstützung für Push-Benachrichtigungen zu aktivieren.
%1$s beigetreten
1+
Jetzt
Bild bearbeiten
- Details
Das Bild konnte nicht bearbeitet werden.
Entwurf wird gespeichert …
Fehler beim Folgen von #%s
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 39b04a8e1..95656349e 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -459,8 +459,6 @@
Okazos eraro dum la malsekvado de #%s
Ensaluti denove por ricevi sciigojn
%s redaktis sian mesaĝon
- Fermi
- Detaloj
Redaktitaj mesaĝoj
Sciigoj, kiam mesaĝoj, kun kiuj vi interagis, estas redaktitaj
Redakti la bildon
@@ -473,7 +471,6 @@
Ekverki mesaĝon
Aliĝis je %1$s
Konservado de la malneto…
- Ensalutu denove al ĉiuj kontoj por ŝalti sciigojn.
Ĉu forigi tiun planitan mesaĝon\?
(Neniu ŝanĝo)
%s (%s)
@@ -482,6 +479,4 @@
Neniam
Montri uzantnomon en ilobreto
Mesaĝolingvo
- 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.
- 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.
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index bbb8851a5..e0dfc2c30 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -462,8 +462,6 @@
Reingresa para activar notificaciones push
%s se registró
%s editó su publicación
- Descartar
- Detalles
¿Eliminar publicación programada\?
Toca o arrastra el círculo para centrar el foco de la imagen, que será visible en las miniaturas.
¿Guardar este borrador\? (Los adjuntos se subirán de nuevo cuando vuelvas a él.)
@@ -471,8 +469,6 @@
Se unió %1$s
14 días
365 días
- Inicia sesión de nuevo en todas las cuentas para activar las notificaciones push.
- 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é.
Fallo al fijar
Fallo al quitarlo
Cuando hay varias cuentas ingresadas
@@ -490,7 +486,6 @@
(Sin cambios)
Escribir publicación
Guardando borrador…
- 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.
Creación de cuentas
Editar imagen
El contenido debería tener una descripción.
@@ -785,4 +780,4 @@
Orden inverso en línea de tiempo
Publicaciones más recientes primero
Publicaciones más antiguas primero
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index ce2874a28..fbf805c54 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -441,10 +441,8 @@
%1$s ثبتنام کرد
نمایش تأیید پیش از برگزیدن
ایجاد فرسته
- ورود دوباره به تمامی حسابها برای به کار انداختن پشتیبانی آگاهیهای ارسالی.
آگاهیها هنگام ویرایش فرستههایی که با آنها تعامل داشتهاید
برداشن نشانک
- برای اعطای اجازهٔ اشتراک آگاهیهای ارسالی به تاسکی، دوباره به حسابتان وارد شدید. با این حال هنوز حسابهایی دیگر دارید که اینگونه مهاجرت داده نشدهاند. به آنها رفته و برای به کار انداختن پشتیبانی آگاهیهای UnifiedPush یکییکی دوباره وارد شوید.
%1$s مطمئنید که میخواهید از خارج شوید؟ این کار تمامی دادههای محلی از جمله پیشنویسها و ترجیحات را حذف خواهد کرد.
۱۴ روز
۳۰ روز
@@ -453,7 +451,6 @@
۳۶۵ روز
۱۸۰ روز
۱+
- تاسکی برای استفاده از آگاهیهای ارسالی با UnifiedPush نیاز به اجازهٔ اشتراک آگاهیها روی کارساز ماستودنتان دارد. این کار نیازمند ورود دوباره برای تغییر حوزههای OAuth اعطایی به تاسکی است. استفاده از گزینهٔ ورود دوباره در اینجا یا در ترجیحات حساب، تمامی انبارهها و پیشنویسهای محلیتان را نگه خواهد داشت.
کسی ثبتنام کرد
ویرایشهای فرسته
ویرایش تصویر
@@ -462,8 +459,6 @@
ثبتنامها
آگاهیها دربارهٔ کاربران جدید
ورود دوباره برای آگاهیهای ارسالی
- رد کردن
- جزییات
ذخیرهٔ پیشنویس…
خطا در پیگیری #%s
خطا در ناپیگیری #%s
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 6cebcafb2..3435f5cee 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -225,7 +225,6 @@
%s muokkasi julkaisua
Julkaisujen muokkaukset
Kirjaudu uudestaan sisään ottaaksesi vastaan push-ilmoituksia
- Yksityiskohdat
Kuvaa ei voitu muokata.
Listaamaton
Poista tämä keskustelu\?
@@ -298,7 +297,6 @@
Kun avainsana tai lauseke sisältää ainoastaan kirjaimia ja numeroita, sitä sovelletaan vain jos se vastaa koko sanaa
%dv
Nousussa olevat julkaisut
- Hylkää
Julkaistaan käyttäjänä %1$s
Julkaisu poistetaan ja kirjoitetaan uudestaan?
Tämä instanssi ei tue aihetunnisteita.
@@ -449,7 +447,6 @@
Toisto epäonnistui: %s
Vastaustietojn lataaminen epäonnistui
Lyhin ajastusväli Mastodonissa on 5 minuuttia.
- 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.
Hiljennä ilmoitukset
%1$d käyttäjää keskustelee aihetunnisteesta %2$s
%1$s
@@ -482,7 +479,6 @@
Liittyi %1$s
Suodattimien lataaminen epäonnistui: %1$s
(Päivitetty: %1$s)
- Kirjaudu uudelleen kaikille tileille push-ilmoitusten ottamiseksi käyttöön.
Kerran jokaisesta versiosta
Piilota täysin
%s: %s
@@ -503,7 +499,6 @@
Suodattimen kontekstit
Pyydä vahvistus ennen tehostamista
Yksityinen muistiinpano tästä tilistä
- 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.
Suodattimen toiminta
Päivitys on saatavilla
Muokkaa avainsanaa
@@ -766,4 +761,4 @@
Aikajana käänteisessä järjestyksessä
Uusimmat julkaisut ensin
Vanhimmat julkaisut ensin
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index f27be81d6..544bd39f8 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -469,15 +469,10 @@
Messages modifiés
Notifications quand un message avec lequel vous avez interagi est modifié
Ici depuis %1$s
- Détails
Sauvegarde du brouillon …
>1
- 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.
Retoucher l’image
- Fermer
Se reconnecter pour recevoir les notifications instantanées
- Afin de recevoir les notifications via UnifiedPush, Pachli doit demander à votre serveur Mastodon la permission de s’inscrire aux notifications. Ceci nécessite une reconnexion de vos comptes afin de changer les droits OAuth accordés a Pachli. En utilisant l’option de reconnexion ici ou dans les préférences de compte, vos brouillons et le cache seront préservés.
- Reconnectez tous vos comptes pour activer les notifications instantanées.
L\'image n’a pas pu être retouchée.
Supprimer ce message planifié \?
%s (%s)
diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml
index 08b36019f..93d9ddb4c 100644
--- a/app/src/main/res/values-ga/strings.xml
+++ b/app/src/main/res/values-ga/strings.xml
@@ -430,7 +430,5 @@
Chláraigh %s
Bain leabharmharc
Scrios comhrá
- Dún
- Sonraí
Scrios an comhrá seo\?
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index df98f7a68..9a5c03efa 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -471,18 +471,13 @@
Brathan nuair a thèid postaichean a rinn thu conaltradh leotha a dheasachadh
chaidh post a rinn mi conaltradh leis a deasachadh
A’ sàbhaladh na dreuchd…
- Leig seachad
- Fiosrachadh
Air ballrachd fhaighinn %1$s
- Clàraich a-steach às ùr leis a h-uile cunntas a chur na taice ri brathan putaidh an comas.
Clàraich a-steach às ùr airson brathan putaidh
1+
Deasaich an dealbh
Mearachd a’ leantainn air #%s
Mearachd a’ sgur de leantainn air #%s
Cha b’ urrainn dhuinn an dealbh a dheasachadh.
- 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.
- 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.
(Gun atharrachadh)
Seall an t-ainm-cleachdaiche air na bàraichean-inneal
Thoir gogag no slaod an cearcall a thaghadh puing an fhòcais a chithear air na dealbhagan an-còmhnaidh.
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 3914c2e26..35a1fc52d 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -447,14 +447,9 @@
Foi editada unha publicación coa que interactuei
Edicións da publicación
Creada %1$s
- Volve a acceder con tódalas contas para activar as notificacións push.
Notificacións cando son editadas publicacións coas que interactuaches
- 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é.
- 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.
Volve a acceder para ter notificacións push
%s editou a publicación
- Desbotar
- Detalles
Gardando borrador…
1+
Editar imaxe
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index f53c76997..cfa9eeed1 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -456,14 +456,9 @@
Értesítések olyan bejegyzések szerkesztéséről, melyekkel már dolgod volt
Bejegyzés Létrehozása
Bejelentkezés újra a leküldési értesítések érdekében
- Elvetés
- Részletek
Csatlakozva %1$s
- Bejelentkezés újra minden fiókkal a leküldéses értesítések engedélyezése érdekében.
1+
Vázlat mentése…
- 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.
- Ú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.
Kép szerkesztése
A kép nem szerkeszthető.
Felhasználónév mutatása az eszköztáron
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 13ac2aea9..806c07e29 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -66,7 +66,6 @@
Cari
Draf
Setel Ulang
- Detail
Salin tautan
Mengunduh %1$s
Unduh media
@@ -180,7 +179,6 @@
Buka penulis boost
Tampilkan boost
Tampilkan favorit
- Menolak
Buka media #%d
Bagikan media kepada…
Pengguna ter-unblock
@@ -264,4 +262,4 @@
#%s tersembunyi
#%s tidak tersembunyi
#%s berhenti mengikuti
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index f936ba270..d80802bb7 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -448,16 +448,11 @@
Tilkynningar um nýja notendur
Breytingar á færslum
Tilkynningar þegar færslum sem þú hefur átt við er breytt
- Þú 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.
Skráði sig %1$s
- Skrá aftur inn alla aðganga til að virkja stuðning við ýti-tilkynningar.
- 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.
1+
Breyta mynd
Vista drög…
Skráðu aftur inn fyrir ýti-tilkynningar
- Hunsa
- Nánar
%s (%s)
Tungumál færslu
(engin breyting)
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 06c87569c..d85bd4269 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -489,12 +489,7 @@
(Aggiornato: %1$s)
%1$s %2$s
Salvataggio bozza…
- Scartare
- Dettagli
- Riaccedi a tutte le utenze per attivare il supporto delle notifiche.
- 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).
%s (%s)
- 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.
Registrato da %1$s
L\'immagine non può essere modificata.
Riaccedi per le notifiche
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index c9ad857fd..7aa69d16a 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -392,7 +392,6 @@
この予約投稿を削除しますか?
ブックマークを削除
会話を削除
- 詳細情報
%sさんがサインアップしました
投稿言語
お気に入りに追加しました
@@ -429,7 +428,6 @@
このインスタンスはハッシュタグのフォローに対応していません。
フォローしたハッシュタグ
%s のミュートが解除されました
- プッシュ通知の購読許可を Pachli に与えるための現在のアカウントへの再ログインが完了しました。しかし、まだ他のアカウントはマイグレーションができていません。UnifiedPush 通知w有効にするには、他のアカウントにも切り替えて、それぞれ再ログインをしてください。
#%s をフォロー解除しますか?
新規ユーザーに関する通知
投稿の編集
@@ -437,7 +435,6 @@
%sさんが投稿を編集しました
%s を編集しました
%sさんが %s を報告しました
- キャンセル
#%s をフォロー解除しました
カスタム絵文字のアニメーションを有効化
この会話を削除しますか?
@@ -474,12 +471,10 @@
\n プッシュ通知には影響ありませんが、通知設定は手動で確認できます。
通知の確認
購読の解除
- プッシュ通知のサポートを有効にするにはすべてのアカウントで再ログインしてください。
ルール違反
スパム
その他
投稿する
- UnifiedPush 経由でプッシュ通知を使用するには、Pachli に Mastodon サーバー上での購読許可が必要です。そのため、Pachli に与えられた OAuth スコープを変更するための再ログインが必要になります。ここかアカウント設定で再ログインのオプションを選んでも、ローカルの下書きとキャッシュは保存されます。
#%s のフォローエラー
プッシュ通知受け取るには再ログインしてください
#%s のフォロー解除エラー
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index 0ec46e011..3047d4af3 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -65,7 +65,6 @@
Atiestatīt
Lejupielādē %1$s
Pieminējumi
- Detaļas
Nosūtīts!
Nokopēt saiti
Lietotājs atbloķēts
@@ -378,7 +377,6 @@
Slēpt ierakstu kvantitatīvo statistiku
Neizdevās ielādēt atbildes informāciju
Dzēst un sākt no jauna
- Aizvākt
Vai atcelt sekošanas pieprasījumu\?
Vai dzēst šo ierakstu un sākt no jauna\?
Sūtot ziņu, radās kļūda
@@ -522,4 +520,4 @@
Paslēpt ar brīdinājumu
Labot atslēgvārdu
Pievienot atslēgvārdu
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 1ca038888..799eb4d5f 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -451,12 +451,7 @@
Redigerte innlegg
Varslinger når et innlegg du har interagert med er redigert
Logg inn på nytt for pushvarsler
- Avvis
- Detaljer
Ble med %1$s
- Logg inn all konti på nytt for å skru på pushvarsler.
- 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.
- 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.
Lagrer utkast…
1+
Rediger bilde
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 8e64ecf17..9fe4fb304 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -439,13 +439,10 @@
\n
\nPushmeldingen worden hierdoor niet beïnvloed, maar je kunt de voorkeuren voor meldingen handmatig wijzigen.
%s heeft zich geregistreerd
- Alle accounts opnieuw inloggen i.v.m. ondersteuning pushmeldingen.
Deze afbeelding kon niet worden bewerkt.
Opnieuw inloggen i.v.m. pushmeldingen
%s heeft diens bericht bewerkt
Bladwijzer verwijderen
- Afwijzen
- Details
iemand heeft zich geregistreerd
een bericht waarmee ik interactie had is bewerkt
Registraties
@@ -468,8 +465,6 @@
(geen verandering)
Gebruikersnaam op werkbalken tonen
Tik of sleep de cirkel naar een centraal focuspunt dat op elke thumbnail zichtbaar moet blijven.
- 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.
- 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.
Altijd
Wanneer meerdere accounts zijn ingelogd
Nooit
diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml
index ac66e8f87..337bf7018 100644
--- a/app/src/main/res/values-oc/strings.xml
+++ b/app/src/main/res/values-oc/strings.xml
@@ -444,8 +444,6 @@
Error en quitant de seguir #%s
%s a creat un compte
%s a modificat sa publicacion
- Ignorar
- Detalhs
Modificar l’imatge
Compausar una publicacion
Arribada del %1$s
@@ -481,11 +479,8 @@
Notificacions quand qualqu’un crèa un compte novèl
Notificacions a prepaus dels senhalament a la moderacion
Notificacions quand una publicacion ont avètz reagit es modificada
- Reconnectatz-vos a vòstres comptes per activar las notificacions instantanèas.
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.
- 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.
Activar lo limpament per se desplaçar demest los onglets
- 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.
Enregistrar lo borrolhon \? (Las pèças juntas seràn enviadas tornamai quand restauretz lo borrolhon.)
Lenga de publicacion per defaut
Messatge indesirable
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index 02accdc90..3b13ffc43 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -11,5 +11,4 @@
ସନ୍ଧାନ
ସମ୍ପାଦନା
ମିଡ଼ିଆ
- ଵିଵରଣୀ
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 4e08fca6e..af2f26506 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -481,16 +481,11 @@
%s edytował/a swój wpis
Edycje wpisów
Zapisywanie szkicu…
- 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.
1+
Dołączył/-a %1$s
- Zaloguj się ponownie na wszystkie konta aby włączyć wsparcie dla powiadomień push.
- 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ą.
Edytuj obraz
Obrazek nie mógł być zmodyfikowany.
Zaloguj się ponownie aby włączyć powiadomienia push
- Odrzuć
- Detale
%s (%s)
Język wpisu
(bez zmian)
@@ -591,4 +586,4 @@
Edytuj słowo kluczowe
Obraz
Dodaj
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 00a99dc89..7f479cb15 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -468,18 +468,14 @@
Erro ao silenciar #%s
Nome de usuário copiado
Hashtags seguidas
- Detalhes
Erro ao seguir #%s
Erro ao deixar de seguir #%s
Carregando fio
Toque ou arraste o círculo para escolher o ponto focal que estará sempre visível nas miniaturas.
%s (%s)
- 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.
- 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.
Falha ao fixar
Falha ao desafixar
Salvar rascunho\? (Os anexos serão reenviados assim que você restaurar o rascunho.)
- Reautentique todas as contas para habilitar o suporte de notificação Push.
%1$s publicou
Edições
Idioma padrão dos Toots
@@ -562,7 +558,6 @@
Toots em alta
%1$s %2$s
A comunicação com tua instância demorou muito
- Dispensar
Gerenciar listas
Carregar Toots mais recentes
Notificações sobre denúncias da moderação
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 3e083765e..87f7adbdb 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -462,17 +462,12 @@
Desfazer
Aceitar
Rejeitar
- Faz novamente login em todas as contas para ativar as notificações push.
Criada em %1$s
- 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.
1+
- 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.
Editar imagem
Não foi possível editar a imagem.
A guardar rascunho…
Faz novamente login para as notificações push
- Descartar
- Detalhes
Apagar esta publicação agendada\?
Toca ou arrasta o círculo para escolher o ponto de focagem que estará sempre visível nas pré-visualizações.
%s(%s)
@@ -518,4 +513,4 @@
Editado %s
Hashtags seguidas
Hashtags populares
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-sa/strings.xml b/app/src/main/res/values-sa/strings.xml
index 1d60af051..d8a652ef2 100644
--- a/app/src/main/res/values-sa/strings.xml
+++ b/app/src/main/res/values-sa/strings.xml
@@ -462,8 +462,6 @@
दौत्यस्य भाषा
#%s-अनुसरणे दोषो जातः
#%s-अनुसरण-अपाकरणे दोषो जातः
- विवरणानि
- उत्सृज्यताम्
सम्योजितानि
१+
रक्षितम् !
@@ -490,4 +488,4 @@
लेखायाः उपभोक्तृनाम्नः संविभागं कुरुताम् अस्मै…
#%s अनुसरणम् अपाकृतम्
लेखायाः निरपेक्ष-सार्वत्रिक-वस्तुसङ्केतस्य संविभागं कुरुताम् अस्मै…
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index a2309ffea..7102d67b4 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -434,11 +434,9 @@
Kunde inte avfölja #%s
Radera denna konversation\?
Alltid
- 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.
Tryck eller dra cirkeln för att välja fokuspunkten som alltid kommer synas i miniatyrbilder.
Varaktighet
Oändligt
- 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.
Sluta prenumerera
Inläggsspråk
När flera konton är inloggade
@@ -465,7 +463,6 @@
Skriv inlägg
Gick med i %1$s
Sparar utkast…
- Logga in igen på alla konton för att tillåta pushnotiser.
Bilden kunde inte redigeras.
365 dagar
180 dagar
@@ -482,8 +479,6 @@
Bilagor
1+
Logga in igen för pushnotiser
- Avvisa
- Detaljer
Spara utkast\? (Bilagor kommer att laddas upp igen när du återställer utkastet.)
Kunde inte fästa
Kunde inte lossa
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 0fccbe65a..326340d4a 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -477,14 +477,11 @@
%s (%s)
Kural ihlali
Gönderi dili
- 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.
#%s takip edilmeyenler
Abone Ol
Hesabınız kilitli olmasa da, %1$s kadro bu hesaplardan gelen takip isteklerini elle gözden geçirmek isteyebileceğinizi düşündü.
Hakkında yeni rapor %s
tepki ekle
- Yok Say
- Ayrıntılar
Yeni yayınlar
abone olduğunuz birisi yeni gönderi yayınladığında gelen bildirimler
Kayıt Ol
@@ -494,7 +491,6 @@
Tanımsız
Düzenlendi
Abonelikten Çık
- 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.
Bu sunucu aşağıdaki etiketleri desteklemez.
%s az önce paylaşım yaptı
%s gönderilerini düzenledi
@@ -502,7 +498,6 @@
Takip etmeyi bırakırken Hata #%s
1+
Süre
- Bildirim besleme desteğini etkinleştirmek için tüm hesaplara yeniden giriş yapın.
Sunucudan kaynak durumu yüklenemedi.
Bağlantı noktası %d ile %d arasında olmalıdır
Alternatif metin
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index dc6077da3..a3cac1349 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -471,12 +471,7 @@
Сповіщення, коли редагується повідомлення, з яким ви взаємодіяли
Редакції допису
Збереження чернетки…
- Відхилити
- Подробиці
Увійдіть повторно, щоб отримувати push-сповіщення
- Увійдіть повторно до всіх облікових записів, щоб увімкнути підтримку push-сповіщень.
- Щоб використовувати push-сповіщення через UnifiedPush, Pachli потребує дозволу стежити за сповіщеннями на вашому сервері Mastodon. Це вимагає повторного входу, щоб змінити області OAuth, надані Pachli. Використання параметра повторного входу тут або в налаштуваннях облікового запису збереже всі ваші локальні чернетки та кеш.
- Ви повторно увійшли до свого поточного облікового запису, щоб надати дозвіл на стеження Pachli. Однак у вас все ще є інші облікові записи, які не мігрували таким чином. Перейдіть до них і повторно увійдіть до них по одному, щоб забезпечити підтримку UnifiedPush сповіщень.
Приєднується %1$s
Редагувати зображення
1+
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 18e5ac351..cf5402a3b 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -439,13 +439,8 @@
Sửa tút
Thông báo khi tút mà tôi tương tác bị sửa
Đang lưu nháp…
- Bỏ qua
Đăng nhập lại để hiện thông báo đẩy
- Chi tiết
Đã tham gia %1$s
- Đăng nhập lại tất cả tài khoản để kích hoạt thông báo đẩy.
- 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.
- Để 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.
1+
Sửa ảnh
Hình ảnh này không thể sửa.
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 6b14d762c..06daded59 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -454,12 +454,7 @@
当你进行过互动的嘟文被编辑时发出通知
正在保存草稿…
重新登陆以启用通知推送
- 不理会
- 详情
- 你已重新登录当前账户,向 Pachli 授予推送订阅权限。但是,你仍然有其他没有以这种方式迁移的账户。切换到它们,逐个重新登录,以启用 UnifiedPush 通知支持。
加入于%1$s
- 重新登录所有账户来启用推送通知支持。
- 为了通过 UnifiedPush 使用推送通知,Pachli 需要订阅你 Mastodon 服务器通知的权限。这需要重新登录来更改授予 Pachli 的 OAuth 作用域。使用此处或账户首选项中“重新登录”选项将保留你所有的本地草稿和缓存。
1+
编辑图片
无法编辑图片。
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 8aa4f2441..fd198a2c9 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -463,7 +463,6 @@
訂閱
取消訂閱
正在儲存草稿…
- 重新登入所有帳號以啟用推播功能。
設置關注點
重新登入以啟用推播功能
1+
@@ -480,13 +479,9 @@
取消追蹤 #%s 時發生錯誤
移除書籤
刪除對話
- 撤銷
- 詳情
我互動過的嘟文被編輯了
14 天
- 為了透過 UnifiedPush使用推播功能,Pachli 需要獲得訂閱您 Mastodon 服務器上的通知之權限。這會需要重新登入才能更改授予 Pachli 的 OAuth 範疇。在此頁面或帳戶設定頁面中使用重新登入選項將會保留您所有的本機草稿和快取。
- 您已重新登入當前帳號並授予 Pachli 推送訂閱的權限。 然而,您仍擁有其他帳號未以此種方式遷移。 請切換到該帳號,並且逐一重新登入,以啟用 UnifiedPush 的通知支援。
%s 已註冊
%s 編輯了他們的嘟文
這張圖片不能編輯。
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 677e9f7c0..680bcdd17 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -14,7 +14,9 @@
~
~ You should have received a copy of the GNU General Public License along with Pachli; if not,
~ see .
- -->
+ -->
+
+
This cannot be empty.
Couldn\'t find a web browser to use.
The post is too long!
@@ -177,8 +179,6 @@
Open boost author
Show boosts
Show favorites
- Dismiss
- Details
add reaction
Suggested accounts
Translate
@@ -610,9 +610,6 @@
(Updated: %1$s)
%1$s %2$s
Saving draft…
- Re-login all accounts to enable push notification support.
- 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.
- 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.
Delete this scheduled post?
%s (%s)
Rule violation
@@ -685,6 +682,33 @@
Translating…
%1$s
+ Unified push distributor
+ %1$s. Tap to open
+ None
+
+ Notification method
+ Fetched approximately once every 15 minutes. Tap for details.
+ All accounts use UnifiedPush. Tap for details.
+ Some accounts use UnifiedPush, some pull every 15 minutes. Tap for details
+
+ Battery optimization
+ Battery optimisation should be disabled. Tap to open.
+ Battery optimisation is
+ Battery optimisation does not need to be disabled.
+
+ Push
+ Pull
+ Notification method
+ ✔ %1$s ago @ %2$s
+ ✖ %1$s ago @ %2$s
+
+ Account does not have \"push\" scope. Logging out and back in may fix this.
+ %1$s rejected push registration
+
+ Change Unified Push distributor
+ 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.
+
Could not fetch server info for %1$s: %2$s
Check for update now
There are no updates available
diff --git a/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt b/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt
index 8657354bb..5883b7b3f 100644
--- a/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt
+++ b/core/database/src/main/kotlin/app/pachli/core/database/model/AccountEntity.kt
@@ -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 = ""
diff --git a/core/network/src/main/kotlin/app/pachli/core/network/retrofit/MastodonApi.kt b/core/network/src/main/kotlin/app/pachli/core/network/retrofit/MastodonApi.kt
index 2da9cfc16..12ae4b94e 100644
--- a/core/network/src/main/kotlin/app/pachli/core/network/retrofit/MastodonApi.kt
+++ b/core/network/src/main/kotlin/app/pachli/core/network/retrofit/MastodonApi.kt
@@ -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,
- ): NetworkResult
-
- @FormUrlEncoded
- @PUT("api/v1/push/subscription")
- suspend fun updatePushNotificationSubscription(
- @Header("Authorization") auth: String,
- @Header(DOMAIN_HEADER) domain: String,
- @FieldMap data: Map,
- ): NetworkResult
+ ): ApiResult
@DELETE("api/v1/push/subscription")
suspend fun unsubscribePushNotifications(
@Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String,
- ): NetworkResult
+ ): ApiResult
@GET("api/v1/tags/{name}")
suspend fun tag(@Path("name") name: String): NetworkResult
diff --git a/core/preferences/src/main/kotlin/app/pachli/core/preferences/SettingsConstants.kt b/core/preferences/src/main/kotlin/app/pachli/core/preferences/SettingsConstants.kt
index 37cd91eca..87d0e11c4 100644
--- a/core/preferences/src/main/kotlin/app/pachli/core/preferences/SettingsConstants.kt
+++ b/core/preferences/src/main/kotlin/app/pachli/core/preferences/SettingsConstants.kt
@@ -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
diff --git a/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/AlertDialogExtensions.kt b/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/AlertDialogExtensions.kt
index 73b88c29e..654bf5e8e 100644
--- a/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/AlertDialogExtensions.kt
+++ b/core/ui/src/main/kotlin/app/pachli/core/ui/extensions/AlertDialogExtensions.kt
@@ -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) },
)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7d1f988dd..6daf221fb 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -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