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