From dc9e9f2aebfd3feb057fee4c3ac72b05cbc75a12 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sat, 19 Aug 2023 12:31:47 +0200 Subject: [PATCH 1/4] Improve account switching intents (#3732) Before, intent creation was scattered across multiple sites, with account switching logic in both `ComposeActivity` and `MainActivity`. Now, intents are only created in `MainActivity` Companion, and account switching only occurs in `MainActivity` Fixes #3695 --- app/lint-baseline.xml | 542 +++++++++--------- .../com/keylesspalace/tusky/BaseActivity.java | 5 +- .../com/keylesspalace/tusky/MainActivity.kt | 129 ++++- .../components/compose/ComposeActivity.kt | 35 +- .../notifications/NotificationHelper.java | 27 +- .../tusky/service/SendStatusService.kt | 4 +- .../tusky/service/TuskyTileService.kt | 8 +- .../tusky/util/ShareShortcutHelper.kt | 3 +- 8 files changed, 394 insertions(+), 359 deletions(-) diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index a09fe5dae..4cf240803 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -19,7 +19,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -30,7 +30,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -41,7 +41,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -84,6 +84,17 @@ column="21"/> + + + + - - - - @@ -333,7 +333,7 @@ errorLine2=" ^"> @@ -355,7 +355,7 @@ errorLine2=" ^"> @@ -366,7 +366,7 @@ errorLine2=" ^"> @@ -388,7 +388,7 @@ errorLine2=" ^"> @@ -410,7 +410,7 @@ errorLine2=" ^"> @@ -421,7 +421,7 @@ errorLine2=" ^"> @@ -432,7 +432,7 @@ errorLine2=" ^"> @@ -443,7 +443,7 @@ errorLine2=" ^"> @@ -454,7 +454,7 @@ errorLine2=" ^"> @@ -465,7 +465,7 @@ errorLine2=" ^"> @@ -476,7 +476,7 @@ errorLine2=" ^"> @@ -487,7 +487,7 @@ errorLine2=" ^"> @@ -498,7 +498,7 @@ errorLine2=" ^"> @@ -509,7 +509,7 @@ errorLine2=" ^"> @@ -520,7 +520,7 @@ errorLine2=" ^"> @@ -531,7 +531,7 @@ errorLine2=" ^"> @@ -542,7 +542,7 @@ errorLine2=" ^"> @@ -553,7 +553,7 @@ errorLine2=" ^"> @@ -751,7 +751,7 @@ errorLine2=" ^"> @@ -762,7 +762,7 @@ errorLine2=" ^"> @@ -773,7 +773,7 @@ errorLine2=" ^"> @@ -784,7 +784,7 @@ errorLine2=" ^"> @@ -795,7 +795,7 @@ errorLine2=" ^"> @@ -817,7 +817,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -861,7 +861,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -872,7 +872,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1136,7 +1136,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1928,7 +1928,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1939,7 +1939,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -1950,7 +1950,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1961,7 +1961,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1972,7 +1972,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -1983,7 +1983,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -1994,7 +1994,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2005,7 +2005,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2016,7 +2016,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2027,7 +2027,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2038,7 +2038,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2049,7 +2049,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2060,7 +2060,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2071,7 +2071,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2082,7 +2082,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2093,7 +2093,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2104,7 +2104,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2115,7 +2115,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2126,7 +2126,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2137,7 +2137,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2148,7 +2148,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2159,7 +2159,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2181,7 +2181,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2192,7 +2192,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2203,7 +2203,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2533,7 +2533,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -2544,7 +2544,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2555,7 +2555,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2643,7 +2643,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2654,7 +2654,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2665,7 +2665,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2676,7 +2676,7 @@ errorLine2=" ~~~~~~~~~"> @@ -3122,12 +3122,12 @@ + message="Access to `private` method `forwardToComposeActivity` of class `MainActivity` requires synthetic accessor" + errorLine1=" forwardToComposeActivity(intent)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3138,7 +3138,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3149,7 +3149,7 @@ errorLine2=" ~~~~~~~"> @@ -3160,7 +3160,7 @@ errorLine2=" ~~~~~~~"> @@ -3171,7 +3171,7 @@ errorLine2=" ~~~~~~~"> @@ -3182,54 +3182,10 @@ errorLine2=" ~~~~~~~"> - - - - - - - - - - - - - - - - @@ -3281,7 +3237,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3292,7 +3248,7 @@ errorLine2=" ~~~~~~~"> @@ -3303,7 +3259,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3314,7 +3270,7 @@ errorLine2=" ~~~~~~~"> @@ -3325,7 +3281,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3336,7 +3292,7 @@ errorLine2=" ~~~~~~~"> @@ -3347,7 +3303,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3358,18 +3314,40 @@ errorLine2=" ~~~~~~~"> + message="Access to `private` method `primaryDrawerItem` of class `MainActivityKt` requires synthetic accessor" + errorLine1=" primaryDrawerItem {" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + + + + + + + + @@ -3378,31 +3356,9 @@ message="Access to `private` method `setOnClick` of class `MainActivityKt` requires synthetic accessor" errorLine1=" onClick = {" errorLine2=" ~~~~~~~"> - - - - - - - - @@ -3413,7 +3369,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -3424,7 +3380,7 @@ errorLine2=" ~~~~~~~"> @@ -3435,7 +3391,51 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + + + + + @@ -3446,7 +3446,7 @@ errorLine2=" ~~~~~~~"> @@ -3457,7 +3457,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3468,7 +3468,7 @@ errorLine2=" ~~~~~~~"> @@ -3479,7 +3479,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3490,7 +3490,7 @@ errorLine2=" ~~~~~~~"> @@ -3501,7 +3501,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -3512,7 +3512,7 @@ errorLine2=" ~~~~~~~"> @@ -3523,7 +3523,7 @@ errorLine2=" ~~~~~~~"> @@ -3534,7 +3534,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3545,7 +3545,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3556,7 +3556,7 @@ errorLine2=" ~~~~~~~"> @@ -3567,7 +3567,7 @@ errorLine2=" ~~~~~~~"> @@ -3578,7 +3578,7 @@ errorLine2=" ~~~~~~~"> @@ -3589,7 +3589,7 @@ errorLine2=" ~~~~~~~"> @@ -3600,7 +3600,7 @@ errorLine2=" ~~~~~~~"> @@ -3611,7 +3611,7 @@ errorLine2=" ~~~~~~~"> @@ -4062,7 +4062,7 @@ errorLine2=" ~~~~~~~"> @@ -4073,7 +4073,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -4084,7 +4084,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -4095,7 +4095,7 @@ errorLine2=" ~~~~~~~"> @@ -4106,10 +4106,21 @@ errorLine2=" ~~~~~~~"> + + + + - - - - + + + + - - - - @@ -4194,7 +4194,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -4238,7 +4238,7 @@ errorLine2=" ~~~~~~~~~~"> @@ -4916,7 +4916,7 @@ errorLine2=" ~~~~~~~~"> @@ -4927,7 +4927,7 @@ errorLine2=" ~~~~~~~~"> @@ -4938,7 +4938,7 @@ errorLine2=" ~~~~~~~~"> @@ -4949,7 +4949,7 @@ errorLine2=" ~~~~~~~~"> @@ -4960,7 +4960,7 @@ errorLine2=" ~~~~~~~~"> @@ -4971,7 +4971,7 @@ errorLine2=" ~~~~~~~~"> @@ -5136,7 +5136,7 @@ errorLine2=" ~~~~~~~~"> @@ -5147,7 +5147,7 @@ errorLine2=" ~~~~~~~~"> @@ -5158,7 +5158,7 @@ errorLine2=" ~~~~~~~~"> @@ -5169,7 +5169,7 @@ errorLine2=" ~~~~~~~~"> @@ -5180,7 +5180,7 @@ errorLine2=" ~~~~~~~~"> @@ -5191,7 +5191,7 @@ errorLine2=" ~~~~~~~~"> @@ -5202,7 +5202,7 @@ errorLine2=" ~~~~~~~~"> @@ -5554,7 +5554,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -5983,7 +5983,7 @@ errorLine2=" ~~~~~~~~"> @@ -5994,7 +5994,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -6005,7 +6005,7 @@ errorLine2=" ~~~~~~~"> @@ -6016,7 +6016,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -6027,7 +6027,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -6038,7 +6038,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -6049,7 +6049,7 @@ errorLine2=" ~~~~~~~"> @@ -6060,7 +6060,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -6071,7 +6071,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -6082,7 +6082,7 @@ errorLine2=" ~~~~~~~"> @@ -6093,7 +6093,7 @@ errorLine2=" ~~~~~~~"> @@ -6104,7 +6104,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -6115,7 +6115,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -6192,7 +6192,7 @@ errorLine2=" ~~~~"> @@ -6203,7 +6203,7 @@ errorLine2=" ~~~~~~"> @@ -6214,7 +6214,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -6225,7 +6225,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6236,7 +6236,7 @@ errorLine2=" ~~~~~~"> @@ -6247,7 +6247,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6258,7 +6258,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -6269,7 +6269,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6280,7 +6280,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6291,7 +6291,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -6302,7 +6302,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6313,7 +6313,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -6324,7 +6324,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6335,7 +6335,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6346,7 +6346,7 @@ errorLine2=" ~~~~~~"> @@ -6357,7 +6357,7 @@ errorLine2=" ~~~~~~"> @@ -6368,7 +6368,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6379,7 +6379,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -6390,7 +6390,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6401,7 +6401,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6412,7 +6412,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -6423,7 +6423,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -6434,7 +6434,7 @@ errorLine2=" ~~~~~~~"> @@ -6445,7 +6445,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -6456,7 +6456,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -6467,7 +6467,7 @@ errorLine2=" ~~~~~~~"> @@ -6478,7 +6478,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -6489,7 +6489,7 @@ errorLine2=" ~~~~~~~"> @@ -6500,7 +6500,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -6511,7 +6511,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -6522,7 +6522,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6533,7 +6533,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 62709d7c3..6cdc471a0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -256,9 +256,8 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab public void openAsAccount(@NonNull String url, @NonNull AccountEntity account) { accountManager.setActiveAccount(account.getId()); - Intent intent = new Intent(this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.putExtra(MainActivity.REDIRECT_URL, url); + Intent intent = MainActivity.redirectIntent(this, account.getId(), url); + startActivity(intent); finishWithoutSlideOutAnimation(); } diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index da060aa8a..ccd3cd954 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -16,6 +16,7 @@ package com.keylesspalace.tusky import android.Manifest +import android.app.NotificationManager import android.content.Context import android.content.DialogInterface import android.content.Intent @@ -41,6 +42,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.content.IntentCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.view.GravityCompat import androidx.core.view.MenuProvider @@ -182,30 +184,39 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje ?: return // will be redirected to LoginActivity by BaseActivity var showNotificationTab = false - if (intent != null) { + + // check for savedInstanceState in order to not handle intent events more than once + if (intent != null && savedInstanceState == null) { + val notificationId = intent.getIntExtra(NOTIFICATION_ID, -1) + if (notificationId != -1) { + // opened from a notification action, cancel the notification + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(intent.getStringExtra(NOTIFICATION_TAG), notificationId) + } + /** there are two possibilities the accountId can be passed to MainActivity: - * - from our code as long 'account_id' + * - from our code as Long Intent Extra TUSKY_ACCOUNT_ID * - from share shortcuts as String 'android.intent.extra.shortcut.ID' */ - var accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1) - if (accountId == -1L) { + var tuskyAccountId = intent.getLongExtra(TUSKY_ACCOUNT_ID, -1) + if (tuskyAccountId == -1L) { val accountIdString = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID) if (accountIdString != null) { - accountId = accountIdString.toLong() + tuskyAccountId = accountIdString.toLong() } } - val accountRequested = accountId != -1L - if (accountRequested && accountId != activeAccount.id) { - accountManager.setActiveAccount(accountId) + val accountRequested = tuskyAccountId != -1L + if (accountRequested && tuskyAccountId != activeAccount.id) { + accountManager.setActiveAccount(tuskyAccountId) } val openDrafts = intent.getBooleanExtra(OPEN_DRAFTS, false) - if (canHandleMimeType(intent.type)) { + if (canHandleMimeType(intent.type) || intent.hasExtra(COMPOSE_OPTIONS)) { // Sharing to Tusky from an external app if (accountRequested) { // The correct account is already active - forwardShare(intent) + forwardToComposeActivity(intent) } else { // No account was provided, show the chooser showAccountChooserDialog( @@ -216,10 +227,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val requestedId = account.id if (requestedId == activeAccount.id) { // The correct account is already active - forwardShare(intent) + forwardToComposeActivity(intent) } else { // A different account was requested, restart the activity - intent.putExtra(NotificationHelper.ACCOUNT_ID, requestedId) + intent.putExtra(TUSKY_ACCOUNT_ID, requestedId) changeAccount(requestedId, intent) } } @@ -229,10 +240,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } else if (openDrafts) { val intent = DraftsActivity.newIntent(this) startActivity(intent) - } else if (accountRequested && savedInstanceState == null) { + } else if (accountRequested && intent.hasExtra(NOTIFICATION_TYPE)) { // user clicked a notification, show follow requests for type FOLLOW_REQUEST, // otherwise show notification tab - if (intent.getStringExtra(NotificationHelper.TYPE) == Notification.Type.FOLLOW_REQUEST.name) { + if (intent.getSerializableExtra(NOTIFICATION_TYPE) == Notification.Type.FOLLOW_REQUEST) { val intent = AccountListActivity.newIntent(this, AccountListActivity.Type.FOLLOW_REQUESTS) startActivityWithSlideInAnimation(intent) } else { @@ -422,12 +433,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } } - private fun forwardShare(intent: Intent) { - val composeIntent = Intent(this, ComposeActivity::class.java) - composeIntent.action = intent.action - composeIntent.type = intent.type - composeIntent.putExtras(intent) - composeIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + private fun forwardToComposeActivity(intent: Intent) { + val composeOptions = IntentCompat.getParcelableExtra(intent, COMPOSE_OPTIONS, ComposeActivity.ComposeOptions::class.java) + + val composeIntent = if (composeOptions != null) { + ComposeActivity.startIntent(this, composeOptions) + } else { + Intent(this, ComposeActivity::class.java).apply { + action = intent.action + type = intent.type + putExtras(intent) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + } startActivity(composeIntent) finish() } @@ -1043,8 +1061,75 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje private const val TAG = "MainActivity" // logging tag private const val DRAWER_ITEM_ADD_ACCOUNT: Long = -13 private const val DRAWER_ITEM_ANNOUNCEMENTS: Long = 14 - const val REDIRECT_URL = "redirectUrl" - const val OPEN_DRAFTS = "draft" + private const val REDIRECT_URL = "redirectUrl" + private const val OPEN_DRAFTS = "draft" + private const val TUSKY_ACCOUNT_ID = "tuskyAccountId" + private const val COMPOSE_OPTIONS = "composeOptions" + private const val NOTIFICATION_TYPE = "notificationType" + private const val NOTIFICATION_TAG = "notificationTag" + private const val NOTIFICATION_ID = "notificationId" + + /** + * Switches the active account to the provided accountId and then stays on MainActivity + */ + @JvmStatic + fun accountSwitchIntent(context: Context, tuskyAccountId: Long): Intent { + return Intent(context, MainActivity::class.java).apply { + putExtra(TUSKY_ACCOUNT_ID, tuskyAccountId) + } + } + + /** + * Switches the active account to the accountId and takes the user to the correct place according to the notification they clicked + */ + @JvmStatic + fun openNotificationIntent(context: Context, tuskyAccountId: Long, type: Notification.Type): Intent { + return accountSwitchIntent(context, tuskyAccountId).apply { + putExtra(NOTIFICATION_TYPE, type) + } + } + + /** + * Switches the active account to the accountId and then opens ComposeActivity with the provided options + * @param tuskyAccountId the id of the Tusky account to open the screen with. Set to -1 for current account. + * @param notificationId optional id of the notification that should be cancelled when this intent is opened + * @param notificationTag optional tag of the notification that should be cancelled when this intent is opened + */ + @JvmStatic + fun composeIntent( + context: Context, + options: ComposeActivity.ComposeOptions, + tuskyAccountId: Long = -1, + notificationTag: String? = null, + notificationId: Int = -1 + ): Intent { + return accountSwitchIntent(context, tuskyAccountId).apply { + action = Intent.ACTION_SEND // so it can be opened via shortcuts + putExtra(COMPOSE_OPTIONS, options) + putExtra(NOTIFICATION_TAG, notificationTag) + putExtra(NOTIFICATION_ID, notificationId) + } + } + + /** + * switches the active account to the accountId and then tries to resolve and show the provided url + */ + @JvmStatic + fun redirectIntent(context: Context, tuskyAccountId: Long, url: String): Intent { + return accountSwitchIntent(context, tuskyAccountId).apply { + putExtra(REDIRECT_URL, url) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + } + + /** + * switches the active account to the provided accountId and then opens drafts + */ + fun draftIntent(context: Context, tuskyAccountId: Long): Intent { + return accountSwitchIntent(context, tuskyAccountId).apply { + putExtra(OPEN_DRAFTS, true) + } + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index f26279712..a2828f3a3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -16,7 +16,6 @@ package com.keylesspalace.tusky.components.compose import android.Manifest -import android.app.NotificationManager import android.app.ProgressDialog import android.content.ClipData import android.content.Context @@ -207,22 +206,7 @@ class ComposeActivity : public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val notificationId = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1) - if (notificationId != -1) { - // ComposeActivity was opened from a notification, delete the notification - val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - notificationManager.cancel(notificationId) - } - - // If started from an intent then compose as the account ID from the intent. - // Otherwise use the active account. If null then the user is not logged in, - // and return from the activity. - val intentAccountId = intent.getLongExtra(ACCOUNT_ID_EXTRA, -1) - activeAccount = if (intentAccountId != -1L) { - accountManager.getAccountById(intentAccountId) - } else { - accountManager.activeAccount - } ?: return + activeAccount = accountManager.activeAccount ?: return val theme = preferences.getString("appTheme", APP_THEME_DEFAULT) if (theme == "black") { @@ -280,7 +264,7 @@ class ComposeActivity : binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt) } - setupLanguageSpinner(getInitialLanguages(composeOptions?.language, accountManager.activeAccount)) + setupLanguageSpinner(getInitialLanguages(composeOptions?.language, activeAccount)) setupComposeField(preferences, viewModel.startingText) setupContentWarningField(composeOptions?.contentWarning) setupPollView() @@ -1355,8 +1339,6 @@ class ComposeActivity : private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1 internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS" - private const val NOTIFICATION_ID_EXTRA = "NOTIFICATION_ID" - private const val ACCOUNT_ID_EXTRA = "ACCOUNT_ID" private const val PHOTO_UPLOAD_URI_KEY = "PHOTO_UPLOAD_URI" private const val VISIBILITY_KEY = "VISIBILITY" private const val SCHEDULED_TIME_KEY = "SCHEDULE" @@ -1364,26 +1346,15 @@ class ComposeActivity : /** * @param options ComposeOptions to configure the ComposeActivity - * @param notificationId the id of the notification that starts the Activity - * @param accountId the id of the account to compose with, null for the current account * @return an Intent to start the ComposeActivity */ @JvmStatic - @JvmOverloads fun startIntent( context: Context, - options: ComposeOptions, - notificationId: Int? = null, - accountId: Long? = null + options: ComposeOptions ): Intent { return Intent(context, ComposeActivity::class.java).apply { putExtra(COMPOSE_OPTIONS_EXTRA, options) - if (notificationId != null) { - putExtra(NOTIFICATION_ID_EXTRA, notificationId) - } - if (accountId != null) { - putExtra(ACCOUNT_ID_EXTRA, accountId) - } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java index 481098934..a0c5a8ed5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java @@ -85,13 +85,6 @@ public class NotificationHelper { /** Dynamic notification IDs start here */ private static int notificationId = NOTIFICATION_ID_PRUNE_CACHE + 1; - /** - * constants used in Intents - */ - public static final String ACCOUNT_ID = "account_id"; - - public static final String TYPE = APPLICATION_ID + ".notification.type"; - private static final String TAG = "NotificationHelper"; public static final String REPLY_ACTION = "REPLY_ACTION"; @@ -325,11 +318,10 @@ public class NotificationHelper { // Create a notification that summarises the other notifications in this group // All notifications in this group have the same type, so get it from the first. - String notificationType = members.get(0).getNotification().extras.getString(EXTRA_NOTIFICATION_TYPE); + Notification.Type notificationType = (Notification.Type)members.get(0).getNotification().extras.getSerializable(EXTRA_NOTIFICATION_TYPE); + + Intent summaryResultIntent = MainActivity.openNotificationIntent(context, accountId, notificationType); - Intent summaryResultIntent = new Intent(context, MainActivity.class); - summaryResultIntent.putExtra(ACCOUNT_ID, (long) accountId); - summaryResultIntent.putExtra(TYPE, notificationType); TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context); summaryStackBuilder.addParentStack(MainActivity.class); summaryStackBuilder.addNextIntent(summaryResultIntent); @@ -373,10 +365,8 @@ public class NotificationHelper { private static NotificationCompat.Builder newAndroidNotification(Context context, Notification body, AccountEntity account) { - // we have to switch account here - Intent eventResultIntent = new Intent(context, MainActivity.class); - eventResultIntent.putExtra(ACCOUNT_ID, account.getId()); - eventResultIntent.putExtra(TYPE, body.getType().name()); + Intent eventResultIntent = MainActivity.openNotificationIntent(context, account.getId(), body.getType()); + TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context); eventStackBuilder.addParentStack(MainActivity.class); eventStackBuilder.addNextIntent(eventResultIntent); @@ -464,12 +454,7 @@ public class NotificationHelper { composeOptions.setLanguage(actionableStatus.getLanguage()); composeOptions.setKind(ComposeActivity.ComposeKind.NEW); - Intent composeIntent = ComposeActivity.startIntent( - context, - composeOptions, - notificationId, - account.getId() - ); + Intent composeIntent = MainActivity.composeIntent(context, composeOptions, account.getId(), body.getId(), (int)account.getId()); composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt index cf03115b3..3aaad1b70 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt @@ -379,9 +379,7 @@ class SendStatusService : Service(), Injectable { accountId: Long, statusId: Int ): Notification { - val intent = Intent(this, MainActivity::class.java) - intent.putExtra(NotificationHelper.ACCOUNT_ID, accountId) - intent.putExtra(MainActivity.OPEN_DRAFTS, true) + val intent = MainActivity.draftIntent(this, accountId) val pendingIntent = PendingIntent.getActivity( this, diff --git a/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt index 1e170da84..2bf761f9f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt @@ -19,6 +19,7 @@ import android.annotation.TargetApi import android.content.Intent import android.service.quicksettings.TileService import com.keylesspalace.tusky.MainActivity +import com.keylesspalace.tusky.components.compose.ComposeActivity /** * Small Addition that adds in a QuickSettings tile @@ -29,11 +30,8 @@ import com.keylesspalace.tusky.MainActivity class TuskyTileService : TileService() { override fun onClick() { - val intent = Intent(this, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - action = Intent.ACTION_SEND - type = "text/plain" - } + val intent = MainActivity.composeIntent(this, ComposeActivity.ComposeOptions()) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivityAndCollapse(intent) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt index 9d8e4b238..800671ead 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt @@ -29,7 +29,6 @@ import androidx.core.graphics.drawable.IconCompat import com.bumptech.glide.Glide import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountEntity import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers @@ -72,7 +71,7 @@ fun updateShortcut(context: Context, account: AccountEntity) { val intent = Intent(context, MainActivity::class.java).apply { action = Intent.ACTION_SEND type = "text/plain" - putExtra(NotificationHelper.ACCOUNT_ID, account.id) + putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString()) } val shortcutInfo = ShortcutInfoCompat.Builder(context, account.id.toString()) From b6102a755abed62ca39332f4be8dc1802c2fa296 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Sat, 19 Aug 2023 12:54:35 +0200 Subject: [PATCH 2/4] Rename "Trending" to "TrendingTags" or similar where necessary (#3906) The "trending" functionality will expand to include trending links and posts. But at the moment the "Trending" references in the code are exclusively to hashtags. Rename "Trending" to "TrendingTags" or similar everywhere necessary in order to prepare for this. This includes a database migration, as the identifier for the "Trending tags" tab in the account preferences was changed from "Trending" to "TrendingTags". The migration updates the stored value if necessary. --- app/lint-baseline.xml | 10 +- .../53.json | 1009 +++++++++++++++++ .../com/keylesspalace/tusky/MainActivity.kt | 12 +- .../java/com/keylesspalace/tusky/TabData.kt | 10 +- .../tusky/TabPreferenceActivity.kt | 6 +- .../components/trending/TrendingActivity.kt | 2 +- ...ndingAdapter.kt => TrendingTagsAdapter.kt} | 2 +- ...ingFragment.kt => TrendingTagsFragment.kt} | 36 +- ...gViewModel.kt => TrendingTagsViewModel.kt} | 20 +- .../keylesspalace/tusky/db/AppDatabase.java | 15 +- .../com/keylesspalace/tusky/di/AppModule.kt | 2 +- .../tusky/di/FragmentBuildersModule.kt | 4 +- .../tusky/di/ViewModelFactory.kt | 6 +- ...rending.xml => fragment_trending_tags.xml} | 0 14 files changed, 1077 insertions(+), 57 deletions(-) create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/53.json rename app/src/main/java/com/keylesspalace/tusky/components/trending/{TrendingAdapter.kt => TrendingTagsAdapter.kt} (99%) rename app/src/main/java/com/keylesspalace/tusky/components/trending/{TrendingFragment.kt => TrendingTagsFragment.kt} (86%) rename app/src/main/java/com/keylesspalace/tusky/components/trending/viewmodel/{TrendingViewModel.kt => TrendingTagsViewModel.kt} (84%) rename app/src/main/res/layout/{fragment_trending.xml => fragment_trending_tags.xml} (100%) diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index 4cf240803..8dd9f6cf7 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -1531,7 +1531,7 @@ errorLine1=" android:background="?android:attr/colorBackground">" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3991,22 +3991,22 @@ diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/53.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/53.json new file mode 100644 index 000000000..824768cb5 --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/53.json @@ -0,0 +1,1009 @@ +{ + "formatVersion": 1, + "database": { + "version": 53, + "identityHash": "233a8680f540e9a89950da21532ce85d", + "entities": [ + { + "tableName": "DraftEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountId` INTEGER NOT NULL, `inReplyToId` TEXT, `content` TEXT, `contentWarning` TEXT, `sensitive` INTEGER NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT NOT NULL, `poll` TEXT, `failedToSend` INTEGER NOT NULL, `failedToSendNew` INTEGER NOT NULL, `scheduledAt` TEXT, `language` TEXT, `statusId` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentWarning", + "columnName": "contentWarning", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "failedToSend", + "columnName": "failedToSend", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "failedToSendNew", + "columnName": "failedToSendNew", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scheduledAt", + "columnName": "scheduledAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "statusId", + "columnName": "statusId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, `clientId` TEXT, `clientSecret` TEXT, `isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `profilePictureUrl` TEXT NOT NULL, `notificationsEnabled` INTEGER NOT NULL, `notificationsMentioned` INTEGER NOT NULL, `notificationsFollowed` INTEGER NOT NULL, `notificationsFollowRequested` INTEGER NOT NULL, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsSubscriptions` INTEGER NOT NULL, `notificationsSignUps` INTEGER NOT NULL, `notificationsUpdates` INTEGER NOT NULL, `notificationsReports` INTEGER NOT NULL, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `defaultPostLanguage` TEXT NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `notificationMarkerId` TEXT NOT NULL DEFAULT '0', `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL, `oauthScopes` TEXT NOT NULL, `unifiedPushUrl` TEXT NOT NULL, `pushPubKey` TEXT NOT NULL, `pushPrivKey` TEXT NOT NULL, `pushAuth` TEXT NOT NULL, `pushServerKey` TEXT NOT NULL, `lastVisibleHomeTimelineStatusId` TEXT, `locked` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domain", + "columnName": "domain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "accessToken", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "clientId", + "columnName": "clientId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "clientSecret", + "columnName": "clientSecret", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isActive", + "columnName": "isActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profilePictureUrl", + "columnName": "profilePictureUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsEnabled", + "columnName": "notificationsEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsMentioned", + "columnName": "notificationsMentioned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFollowed", + "columnName": "notificationsFollowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFollowRequested", + "columnName": "notificationsFollowRequested", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsReblogged", + "columnName": "notificationsReblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFavorited", + "columnName": "notificationsFavorited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsPolls", + "columnName": "notificationsPolls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsSubscriptions", + "columnName": "notificationsSubscriptions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsSignUps", + "columnName": "notificationsSignUps", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsUpdates", + "columnName": "notificationsUpdates", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsReports", + "columnName": "notificationsReports", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationSound", + "columnName": "notificationSound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationVibration", + "columnName": "notificationVibration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationLight", + "columnName": "notificationLight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultPostPrivacy", + "columnName": "defaultPostPrivacy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultMediaSensitivity", + "columnName": "defaultMediaSensitivity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultPostLanguage", + "columnName": "defaultPostLanguage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "alwaysShowSensitiveMedia", + "columnName": "alwaysShowSensitiveMedia", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alwaysOpenSpoiler", + "columnName": "alwaysOpenSpoiler", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mediaPreviewEnabled", + "columnName": "mediaPreviewEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationId", + "columnName": "lastNotificationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationMarkerId", + "columnName": "notificationMarkerId", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'0'" + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tabPreferences", + "columnName": "tabPreferences", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsFilter", + "columnName": "notificationsFilter", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "oauthScopes", + "columnName": "oauthScopes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unifiedPushUrl", + "columnName": "unifiedPushUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushPubKey", + "columnName": "pushPubKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushPrivKey", + "columnName": "pushPrivKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushAuth", + "columnName": "pushAuth", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushServerKey", + "columnName": "pushServerKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastVisibleHomeTimelineStatusId", + "columnName": "lastVisibleHomeTimelineStatusId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "locked", + "columnName": "locked", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_AccountEntity_domain_accountId", + "unique": true, + "columnNames": [ + "domain", + "accountId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_AccountEntity_domain_accountId` ON `${TABLE_NAME}` (`domain`, `accountId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "InstanceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, `maxPollOptions` INTEGER, `maxPollOptionLength` INTEGER, `minPollDuration` INTEGER, `maxPollDuration` INTEGER, `charactersReservedPerUrl` INTEGER, `version` TEXT, `videoSizeLimit` INTEGER, `imageSizeLimit` INTEGER, `imageMatrixLimit` INTEGER, `maxMediaAttachments` INTEGER, `maxFields` INTEGER, `maxFieldNameLength` INTEGER, `maxFieldValueLength` INTEGER, PRIMARY KEY(`instance`))", + "fields": [ + { + "fieldPath": "instance", + "columnName": "instance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojiList", + "columnName": "emojiList", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maximumTootCharacters", + "columnName": "maximumTootCharacters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptions", + "columnName": "maxPollOptions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptionLength", + "columnName": "maxPollOptionLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "minPollDuration", + "columnName": "minPollDuration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollDuration", + "columnName": "maxPollDuration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "charactersReservedPerUrl", + "columnName": "charactersReservedPerUrl", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videoSizeLimit", + "columnName": "videoSizeLimit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "imageSizeLimit", + "columnName": "imageSizeLimit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "imageMatrixLimit", + "columnName": "imageMatrixLimit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxMediaAttachments", + "columnName": "maxMediaAttachments", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxFields", + "columnName": "maxFields", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxFieldNameLength", + "columnName": "maxFieldNameLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxFieldValueLength", + "columnName": "maxFieldValueLength", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "instance" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimelineStatusEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `editedAt` INTEGER, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `repliesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT, `mentions` TEXT, `tags` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, `muted` INTEGER, `expanded` INTEGER NOT NULL, `contentCollapsed` INTEGER NOT NULL, `contentShowing` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `card` TEXT, `language` TEXT, `filtered` TEXT, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorServerId", + "columnName": "authorServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToAccountId", + "columnName": "inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "editedAt", + "columnName": "editedAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogsCount", + "columnName": "reblogsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favouritesCount", + "columnName": "favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repliesCount", + "columnName": "repliesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reblogged", + "columnName": "reblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarked", + "columnName": "bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favourited", + "columnName": "favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spoilerText", + "columnName": "spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "application", + "columnName": "application", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogServerId", + "columnName": "reblogServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogAccountId", + "columnName": "reblogAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "muted", + "columnName": "muted", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "expanded", + "columnName": "expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contentCollapsed", + "columnName": "contentCollapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contentShowing", + "columnName": "contentShowing", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pinned", + "columnName": "pinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "card", + "columnName": "card", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "filtered", + "columnName": "filtered", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serverId", + "timelineUserId" + ] + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_timelineUserId` ON `${TABLE_NAME}` (`authorServerId`, `timelineUserId`)" + } + ], + "foreignKeys": [ + { + "table": "TimelineAccountEntity", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "authorServerId", + "timelineUserId" + ], + "referencedColumns": [ + "serverId", + "timelineUserId" + ] + } + ] + }, + { + "tableName": "TimelineAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `timelineUserId` INTEGER NOT NULL, `localUsername` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `avatar` TEXT NOT NULL, `emojis` TEXT NOT NULL, `bot` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `timelineUserId`))", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUsername", + "columnName": "localUsername", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bot", + "columnName": "bot", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serverId", + "timelineUserId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `order` INTEGER NOT NULL, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_editedAt` INTEGER, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_repliesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_tags` TEXT, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_muted` INTEGER NOT NULL, `s_poll` TEXT, `s_language` TEXT, PRIMARY KEY(`id`, `accountId`))", + "fields": [ + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.id", + "columnName": "s_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.url", + "columnName": "s_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToId", + "columnName": "s_inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToAccountId", + "columnName": "s_inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.account", + "columnName": "s_account", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.content", + "columnName": "s_content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.createdAt", + "columnName": "s_createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.editedAt", + "columnName": "s_editedAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastStatus.emojis", + "columnName": "s_emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.favouritesCount", + "columnName": "s_favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.repliesCount", + "columnName": "s_repliesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.favourited", + "columnName": "s_favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.bookmarked", + "columnName": "s_bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.sensitive", + "columnName": "s_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.spoilerText", + "columnName": "s_spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.attachments", + "columnName": "s_attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.mentions", + "columnName": "s_mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.tags", + "columnName": "s_tags", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.showingHiddenContent", + "columnName": "s_showingHiddenContent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.expanded", + "columnName": "s_expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsed", + "columnName": "s_collapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.muted", + "columnName": "s_muted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.poll", + "columnName": "s_poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.language", + "columnName": "s_language", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "accountId" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '233a8680f540e9a89950da21532ce85d')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index ccd3cd954..bf79ef615 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -274,7 +274,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje setupDrawer( savedInstanceState, addSearchButton = hideTopToolbar, - addTrendingButton = !accountManager.activeAccount!!.tabPreferences.hasTab(TRENDING) + addTrendingTagsButton = !accountManager.activeAccount!!.tabPreferences.hasTab(TRENDING_TAGS) ) /* Fetch user info while we're doing other things. This has to be done after setting up the @@ -299,7 +299,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje is MainTabsChangedEvent -> { refreshMainDrawerItems( addSearchButton = hideTopToolbar, - addTrendingButton = !event.newTabs.hasTab(TRENDING) + addTrendingTagsButton = !event.newTabs.hasTab(TRENDING_TAGS) ) setupTabs(false) @@ -453,7 +453,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje private fun setupDrawer( savedInstanceState: Bundle?, addSearchButton: Boolean, - addTrendingButton: Boolean + addTrendingTagsButton: Boolean ) { val drawerOpenClickListener = View.OnClickListener { binding.mainDrawerLayout.open() } @@ -514,12 +514,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje }) binding.mainDrawer.apply { - refreshMainDrawerItems(addSearchButton, addTrendingButton) + refreshMainDrawerItems(addSearchButton, addTrendingTagsButton) setSavedInstance(savedInstanceState) } } - private fun refreshMainDrawerItems(addSearchButton: Boolean, addTrendingButton: Boolean) { + private fun refreshMainDrawerItems(addSearchButton: Boolean, addTrendingTagsButton: Boolean) { binding.mainDrawer.apply { itemAdapter.clear() tintStatusBar = true @@ -636,7 +636,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje ) } - if (addTrendingButton) { + if (addTrendingTagsButton) { binding.mainDrawer.addItemsAtPosition( 5, primaryDrawerItem { diff --git a/app/src/main/java/com/keylesspalace/tusky/TabData.kt b/app/src/main/java/com/keylesspalace/tusky/TabData.kt index 6bff4a82b..58969292f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabData.kt @@ -23,7 +23,7 @@ import com.keylesspalace.tusky.components.conversation.ConversationsFragment import com.keylesspalace.tusky.components.notifications.NotificationsFragment import com.keylesspalace.tusky.components.timeline.TimelineFragment import com.keylesspalace.tusky.components.timeline.viewmodel.TimelineViewModel -import com.keylesspalace.tusky.components.trending.TrendingFragment +import com.keylesspalace.tusky.components.trending.TrendingTagsFragment import java.util.Objects /** this would be a good case for a sealed class, but that does not work nice with Room */ @@ -33,7 +33,7 @@ const val NOTIFICATIONS = "Notifications" const val LOCAL = "Local" const val FEDERATED = "Federated" const val DIRECT = "Direct" -const val TRENDING = "Trending" +const val TRENDING_TAGS = "TrendingTags" const val HASHTAG = "Hashtag" const val LIST = "List" @@ -92,11 +92,11 @@ fun createTabDataFromId(id: String, arguments: List = emptyList()): TabD icon = R.drawable.ic_reblog_direct_24dp, fragment = { ConversationsFragment.newInstance() } ) - TRENDING -> TabData( - id = TRENDING, + TRENDING_TAGS -> TabData( + id = TRENDING_TAGS, text = R.string.title_public_trending_hashtags, icon = R.drawable.ic_trending_up_24px, - fragment = { TrendingFragment.newInstance() } + fragment = { TrendingTagsFragment.newInstance() } ) HASHTAG -> TabData( id = HASHTAG, diff --git a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt index 096fe3363..5f85f7375 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt @@ -378,9 +378,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene if (!currentTabs.contains(directMessagesTab)) { addableTabs.add(directMessagesTab) } - val trendingTab = createTabDataFromId(TRENDING) - if (!currentTabs.contains(trendingTab)) { - addableTabs.add(trendingTab) + val trendingTagsTab = createTabDataFromId(TRENDING_TAGS) + if (!currentTabs.contains(trendingTagsTab)) { + addableTabs.add(trendingTagsTab) } addableTabs.add(createTabDataFromId(HASHTAG)) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingActivity.kt index 3270e9c26..bdf2cdc79 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingActivity.kt @@ -48,7 +48,7 @@ class TrendingActivity : BaseActivity(), HasAndroidInjector { if (supportFragmentManager.findFragmentById(R.id.fragmentContainer) == null) { supportFragmentManager.commit { - val fragment = TrendingFragment.newInstance() + val fragment = TrendingTagsFragment.newInstance() replace(R.id.fragmentContainer, fragment) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsAdapter.kt similarity index 99% rename from app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingAdapter.kt rename to app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsAdapter.kt index b40d67670..4137d200e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsAdapter.kt @@ -24,7 +24,7 @@ import com.keylesspalace.tusky.databinding.ItemTrendingCellBinding import com.keylesspalace.tusky.databinding.ItemTrendingDateBinding import com.keylesspalace.tusky.viewdata.TrendingViewData -class TrendingAdapter( +class TrendingTagsAdapter( private val onViewTag: (String) -> Unit ) : ListAdapter(TrendingDifferCallback) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsFragment.kt similarity index 86% rename from app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingFragment.kt rename to app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsFragment.kt index 3d12d74e4..c04caee6d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsFragment.kt @@ -33,8 +33,8 @@ import at.connyduck.sparkbutton.helpers.Utils import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.StatusListActivity -import com.keylesspalace.tusky.components.trending.viewmodel.TrendingViewModel -import com.keylesspalace.tusky.databinding.FragmentTrendingBinding +import com.keylesspalace.tusky.components.trending.viewmodel.TrendingTagsViewModel +import com.keylesspalace.tusky.databinding.FragmentTrendingTagsBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.interfaces.ActionButtonActivity @@ -48,8 +48,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject -class TrendingFragment : - Fragment(R.layout.fragment_trending), +class TrendingTagsFragment : + Fragment(R.layout.fragment_trending_tags), OnRefreshListener, Injectable, ReselectableFragment, @@ -58,11 +58,11 @@ class TrendingFragment : @Inject lateinit var viewModelFactory: ViewModelFactory - private val viewModel: TrendingViewModel by viewModels { viewModelFactory } + private val viewModel: TrendingTagsViewModel by viewModels { viewModelFactory } - private val binding by viewBinding(FragmentTrendingBinding::bind) + private val binding by viewBinding(FragmentTrendingTagsBinding::bind) - private val adapter = TrendingAdapter(::onViewTag) + private val adapter = TrendingTagsAdapter(::onViewTag) override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) @@ -111,8 +111,8 @@ class TrendingFragment : spanSizeLookup = object : SpanSizeLookup() { override fun getSpanSize(position: Int): Int { return when (adapter.getItemViewType(position)) { - TrendingAdapter.VIEW_TYPE_HEADER -> columnCount - TrendingAdapter.VIEW_TYPE_TAG -> 1 + TrendingTagsAdapter.VIEW_TYPE_HEADER -> columnCount + TrendingTagsAdapter.VIEW_TYPE_TAG -> 1 else -> -1 } } @@ -139,15 +139,15 @@ class TrendingFragment : (requireActivity() as BaseActivity).startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag)) } - private fun processViewState(uiState: TrendingViewModel.TrendingUiState) { + private fun processViewState(uiState: TrendingTagsViewModel.TrendingTagsUiState) { Log.d(TAG, uiState.loadingState.name) when (uiState.loadingState) { - TrendingViewModel.LoadingState.INITIAL -> clearLoadingState() - TrendingViewModel.LoadingState.LOADING -> applyLoadingState() - TrendingViewModel.LoadingState.REFRESHING -> applyRefreshingState() - TrendingViewModel.LoadingState.LOADED -> applyLoadedState(uiState.trendingViewData) - TrendingViewModel.LoadingState.ERROR_NETWORK -> networkError() - TrendingViewModel.LoadingState.ERROR_OTHER -> otherError() + TrendingTagsViewModel.LoadingState.INITIAL -> clearLoadingState() + TrendingTagsViewModel.LoadingState.LOADING -> applyLoadingState() + TrendingTagsViewModel.LoadingState.REFRESHING -> applyRefreshingState() + TrendingTagsViewModel.LoadingState.LOADED -> applyLoadedState(uiState.trendingViewData) + TrendingTagsViewModel.LoadingState.ERROR_NETWORK -> networkError() + TrendingTagsViewModel.LoadingState.ERROR_OTHER -> otherError() } } @@ -247,8 +247,8 @@ class TrendingFragment : } companion object { - private const val TAG = "TrendingFragment" + private const val TAG = "TrendingTagsFragment" - fun newInstance() = TrendingFragment() + fun newInstance() = TrendingTagsFragment() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/trending/viewmodel/TrendingViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/trending/viewmodel/TrendingTagsViewModel.kt similarity index 84% rename from app/src/main/java/com/keylesspalace/tusky/components/trending/viewmodel/TrendingViewModel.kt rename to app/src/main/java/com/keylesspalace/tusky/components/trending/viewmodel/TrendingTagsViewModel.kt index 67447b308..92f0a8c46 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/trending/viewmodel/TrendingViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/trending/viewmodel/TrendingTagsViewModel.kt @@ -35,7 +35,7 @@ import kotlinx.coroutines.launch import java.io.IOException import javax.inject.Inject -class TrendingViewModel @Inject constructor( +class TrendingTagsViewModel @Inject constructor( private val mastodonApi: MastodonApi, private val eventHub: EventHub ) : ViewModel() { @@ -43,13 +43,13 @@ class TrendingViewModel @Inject constructor( INITIAL, LOADING, REFRESHING, LOADED, ERROR_NETWORK, ERROR_OTHER } - data class TrendingUiState( + data class TrendingTagsUiState( val trendingViewData: List, val loadingState: LoadingState ) - val uiState: Flow get() = _uiState - private val _uiState = MutableStateFlow(TrendingUiState(listOf(), LoadingState.INITIAL)) + val uiState: Flow get() = _uiState + private val _uiState = MutableStateFlow(TrendingTagsUiState(listOf(), LoadingState.INITIAL)) init { invalidate() @@ -73,9 +73,9 @@ class TrendingViewModel @Inject constructor( */ fun invalidate(refresh: Boolean = false) = viewModelScope.launch { if (refresh) { - _uiState.value = TrendingUiState(emptyList(), LoadingState.REFRESHING) + _uiState.value = TrendingTagsUiState(emptyList(), LoadingState.REFRESHING) } else { - _uiState.value = TrendingUiState(emptyList(), LoadingState.LOADING) + _uiState.value = TrendingTagsUiState(emptyList(), LoadingState.LOADING) } val deferredFilters = async { mastodonApi.getFilters() } @@ -85,7 +85,7 @@ class TrendingViewModel @Inject constructor( val firstTag = tagResponse.firstOrNull() _uiState.value = if (firstTag == null) { - TrendingUiState(emptyList(), LoadingState.LOADED) + TrendingTagsUiState(emptyList(), LoadingState.LOADED) } else { val homeFilters = deferredFilters.await().getOrNull()?.filter { filter -> filter.context.contains(Filter.Kind.HOME.kind) @@ -100,15 +100,15 @@ class TrendingViewModel @Inject constructor( .toViewData() val header = TrendingViewData.Header(firstTag.start(), firstTag.end()) - TrendingUiState(listOf(header) + tags, LoadingState.LOADED) + TrendingTagsUiState(listOf(header) + tags, LoadingState.LOADED) } }, { error -> Log.w(TAG, "failed loading trending tags", error) if (error is IOException) { - _uiState.value = TrendingUiState(emptyList(), LoadingState.ERROR_NETWORK) + _uiState.value = TrendingTagsUiState(emptyList(), LoadingState.ERROR_NETWORK) } else { - _uiState.value = TrendingUiState(emptyList(), LoadingState.ERROR_OTHER) + _uiState.value = TrendingTagsUiState(emptyList(), LoadingState.ERROR_OTHER) } } ) diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java index 86ce13cb1..d5e8c3e99 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -42,12 +42,12 @@ import java.io.File; TimelineAccountEntity.class, ConversationEntity.class }, - version = 52, + version = 53, autoMigrations = { @AutoMigration(from = 48, to = 49), @AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class), @AutoMigration(from = 50, to = 51), - @AutoMigration(from = 51, to = 52) + @AutoMigration(from = 51, to = 52), } ) public abstract class AppDatabase extends RoomDatabase { @@ -674,4 +674,15 @@ public abstract class AppDatabase extends RoomDatabase { @DeleteColumn(tableName = "AccountEntity", columnName = "activeNotifications") static class MIGRATION_49_50 implements AutoMigrationSpec { } + + /** + * TabData.TRENDING was renamed to TabData.TRENDING_TAGS, and the text + * representation was changed from "Trending" to "TrendingTags". + */ + public static final Migration MIGRATION_52_53 = new Migration(52, 53) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("UPDATE `AccountEntity` SET `tabpreferences` = REPLACE(tabpreferences, 'Trending:', 'TrendingTags:')"); + } + }; } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt index bc2c7d753..0f65d6c25 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -68,7 +68,7 @@ class AppModule { AppDatabase.MIGRATION_38_39, AppDatabase.MIGRATION_39_40, AppDatabase.MIGRATION_40_41, AppDatabase.MIGRATION_41_42, AppDatabase.MIGRATION_42_43, AppDatabase.MIGRATION_43_44, AppDatabase.MIGRATION_44_45, AppDatabase.MIGRATION_45_46, AppDatabase.MIGRATION_46_47, - AppDatabase.MIGRATION_47_48 + AppDatabase.MIGRATION_47_48, AppDatabase.MIGRATION_52_53 ) .build() } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt index a5c24456c..7629cff9f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt @@ -32,7 +32,7 @@ import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragmen import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment import com.keylesspalace.tusky.components.timeline.TimelineFragment -import com.keylesspalace.tusky.components.trending.TrendingFragment +import com.keylesspalace.tusky.components.trending.TrendingTagsFragment import com.keylesspalace.tusky.components.viewthread.ViewThreadFragment import com.keylesspalace.tusky.components.viewthread.edits.ViewEditsFragment import com.keylesspalace.tusky.fragment.ViewVideoFragment @@ -99,7 +99,7 @@ abstract class FragmentBuildersModule { abstract fun listsForAccountFragment(): ListsForAccountFragment @ContributesAndroidInjector - abstract fun trendingFragment(): TrendingFragment + abstract fun trendingTagsFragment(): TrendingTagsFragment @ContributesAndroidInjector abstract fun viewVideoFragment(): ViewVideoFragment diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index af1972d5f..ae69e1dd9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -38,7 +38,7 @@ import com.keylesspalace.tusky.components.scheduled.ScheduledStatusViewModel import com.keylesspalace.tusky.components.search.SearchViewModel import com.keylesspalace.tusky.components.timeline.viewmodel.CachedTimelineViewModel import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel -import com.keylesspalace.tusky.components.trending.viewmodel.TrendingViewModel +import com.keylesspalace.tusky.components.trending.viewmodel.TrendingTagsViewModel import com.keylesspalace.tusky.components.viewthread.ViewThreadViewModel import com.keylesspalace.tusky.components.viewthread.edits.ViewEditsViewModel import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel @@ -172,8 +172,8 @@ abstract class ViewModelModule { @Binds @IntoMap - @ViewModelKey(TrendingViewModel::class) - internal abstract fun trendingViewModel(viewModel: TrendingViewModel): ViewModel + @ViewModelKey(TrendingTagsViewModel::class) + internal abstract fun trendingTagsViewModel(viewModel: TrendingTagsViewModel): ViewModel @Binds @IntoMap diff --git a/app/src/main/res/layout/fragment_trending.xml b/app/src/main/res/layout/fragment_trending_tags.xml similarity index 100% rename from app/src/main/res/layout/fragment_trending.xml rename to app/src/main/res/layout/fragment_trending_tags.xml From c7ffc6ad935a1300f795513d16822bdf3c2615df Mon Sep 17 00:00:00 2001 From: Angelo Suzuki <1063155+tinsukE@users.noreply.github.com> Date: Sat, 19 Aug 2023 14:17:04 +0200 Subject: [PATCH 3/4] Hide option to mute own domain from account profile page (it is a no-op). (#3937) Muting your own server domain is a no-op. Check for this case, and remove the "mute" menu item if true. Fixes #3798 --- .../tusky/components/account/AccountActivity.kt | 13 ++++++++----- .../tusky/components/account/AccountViewModel.kt | 12 ++++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt index ea9075210..f5e962d8e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt @@ -772,13 +772,16 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide loadedAccount?.let { loadedAccount -> val muteDomain = menu.findItem(R.id.action_mute_domain) domain = getDomain(loadedAccount.url) - if (domain.isEmpty()) { + when { // If we can't get the domain, there's no way we can mute it anyway... - menu.removeItem(R.id.action_mute_domain) - } else { - if (blockingDomain) { + // If the account is from our own domain, muting it is no-op + domain.isEmpty() || viewModel.isFromOwnDomain -> { + menu.removeItem(R.id.action_mute_domain) + } + blockingDomain -> { muteDomain.title = getString(R.string.action_unmute_domain, domain) - } else { + } + else -> { muteDomain.title = getString(R.string.action_mute_domain, domain) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt index 5b32e3404..b12e3923d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt @@ -19,6 +19,7 @@ import com.keylesspalace.tusky.util.Error import com.keylesspalace.tusky.util.Loading import com.keylesspalace.tusky.util.Resource import com.keylesspalace.tusky.util.Success +import com.keylesspalace.tusky.util.getDomain import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -27,7 +28,7 @@ import javax.inject.Inject class AccountViewModel @Inject constructor( private val mastodonApi: MastodonApi, private val eventHub: EventHub, - private val accountManager: AccountManager + accountManager: AccountManager ) : ViewModel() { val accountData = MutableLiveData>() @@ -41,8 +42,13 @@ class AccountViewModel @Inject constructor( lateinit var accountId: String var isSelf = false + /** True if the viewed account has the same domain as the active account */ + var isFromOwnDomain = false + private var noteUpdateJob: Job? = null + private val activeAccount = accountManager.activeAccount!! + init { viewModelScope.launch { eventHub.events.collect { event -> @@ -65,6 +71,8 @@ class AccountViewModel @Inject constructor( accountData.postValue(Success(account)) isDataLoading = false isRefreshing.postValue(false) + + isFromOwnDomain = getDomain(account.url) == activeAccount.domain }, { t -> Log.w(TAG, "failed obtaining account", t) @@ -298,7 +306,7 @@ class AccountViewModel @Inject constructor( fun setAccountInfo(accountId: String) { this.accountId = accountId - this.isSelf = accountManager.activeAccount?.accountId == accountId + this.isSelf = activeAccount.accountId == accountId reload(false) } From 059352f4718c7faa8a35a0a240a897a33a70038c Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Sat, 19 Aug 2023 14:41:10 +0200 Subject: [PATCH 4/4] Display notification filter/clear actions as menu items (#3877) Previously the notification filter and clear actions were shown as buttons in the UI, with a preference that determined whether they were displayed. Remove this preference, and display them as menu items. - "Filter notifications" is shown as an icon, if possible - "Clear notifications" is only ever shown as a menu item, to reduce the chance the user inadvertently selects it To ensure that the options menu appears correctly, remove the code that creates a "fake" action bar, and adjust the layouts so that there are three toolbars; - mainToolbar -- displays the icons, and the current "location" (Home, Notifications, etc) - topNav -- displays the row of tabs at the top - bottomNav -- displays the row of tabs at the bottom Only one of them is set as the support action bar (depending on the user's preferences). This provides the "show a logo" and "show the options menu" functionality as standard, without needing to re-implement as the previous code did. --- .../com/keylesspalace/tusky/MainActivity.kt | 177 ++++++++---------- .../keylesspalace/tusky/TuskyApplication.kt | 6 + .../notifications/NotificationsFragment.kt | 45 ++--- .../notifications/NotificationsViewModel.kt | 13 +- .../preference/PreferencesFragment.kt | 7 - .../tusky/settings/SettingsConstants.kt | 8 +- app/src/main/res/layout/activity_main.xml | 55 ++---- .../fragment_timeline_notifications.xml | 43 ----- .../main/res/menu/fragment_notifications.xml | 10 + app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-bg/strings.xml | 1 - app/src/main/res/values-bn-rBD/strings.xml | 1 - app/src/main/res/values-bn-rIN/strings.xml | 1 - app/src/main/res/values-ca/strings.xml | 1 - app/src/main/res/values-ckb/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-cy/strings.xml | 3 +- app/src/main/res/values-de/strings.xml | 3 +- app/src/main/res/values-eo/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fa/strings.xml | 3 +- app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-ga/strings.xml | 1 - app/src/main/res/values-gd/strings.xml | 3 +- app/src/main/res/values-gl/strings.xml | 1 - app/src/main/res/values-hi/strings.xml | 1 - app/src/main/res/values-hu/strings.xml | 1 - app/src/main/res/values-is/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-lv/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 3 +- app/src/main/res/values-oc/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt-rPT/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sa/strings.xml | 1 - app/src/main/res/values-si/strings.xml | 3 +- app/src/main/res/values-sl/strings.xml | 1 - app/src/main/res/values-sv/strings.xml | 1 - app/src/main/res/values-th/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 3 +- app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 3 +- app/src/main/res/values-zh-rCN/strings.xml | 3 +- app/src/main/res/values-zh-rHK/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 5 +- app/src/main/res/values/styles.xml | 5 - .../NotificationsViewModelTestBase.kt | 1 - .../NotificationsViewModelTestUiState.kt | 20 -- 56 files changed, 152 insertions(+), 304 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index bf79ef615..e79fab251 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -16,6 +16,7 @@ package com.keylesspalace.tusky import android.Manifest +import android.annotation.SuppressLint import android.app.NotificationManager import android.content.Context import android.content.DialogInterface @@ -34,6 +35,7 @@ import android.view.KeyEvent import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.MenuItem.SHOW_AS_ACTION_NEVER import android.view.View import android.widget.ImageView import androidx.activity.OnBackPressedCallback @@ -46,6 +48,8 @@ import androidx.core.content.IntentCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.view.GravityCompat import androidx.core.view.MenuProvider +import androidx.core.view.forEach +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import androidx.viewpager2.widget.MarginPageTransformer @@ -102,7 +106,6 @@ import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.unsafeLazy import com.keylesspalace.tusky.util.updateShortcut import com.keylesspalace.tusky.util.viewBinding -import com.keylesspalace.tusky.util.visible import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.utils.colorInt @@ -177,6 +180,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje /** Adapter for the different timeline tabs */ private lateinit var tabAdapter: MainPagerAdapter + @SuppressLint("RestrictedApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -253,7 +257,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own setContentView(binding.root) - setSupportActionBar(binding.mainToolbar) glide = Glide.with(this) @@ -262,8 +265,21 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje startActivity(composeIntent) } + // Determine which of the three toolbars should be the supportActionBar (which hosts + // the options menu). val hideTopToolbar = preferences.getBoolean(PrefKeys.HIDE_TOP_TOOLBAR, false) - binding.mainToolbar.visible(!hideTopToolbar) + if (hideTopToolbar) { + when (preferences.getString(PrefKeys.MAIN_NAV_POSITION, "top")) { + "top" -> setSupportActionBar(binding.topNav) + "bottom" -> setSupportActionBar(binding.bottomNav) + } + binding.mainToolbar.hide() + // There's not enough space in the top/bottom bars to show the title as well. + supportActionBar?.setDisplayShowTitleEnabled(false) + } else { + setSupportActionBar(binding.mainToolbar) + binding.mainToolbar.show() + } loadDrawerAvatar(activeAccount.profilePictureUrl, true) @@ -361,6 +377,14 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } } + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + + // If the main toolbar is hidden then there's no space in the top/bottomNav to show + // the menu items as icons, so forceably disable them + if (!binding.mainToolbar.isVisible) menu.forEach { it.setShowAsAction(SHOW_AS_ACTION_NEVER) } + } + override fun onMenuItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_search -> { @@ -458,8 +482,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val drawerOpenClickListener = View.OnClickListener { binding.mainDrawerLayout.open() } binding.mainToolbar.setNavigationOnClickListener(drawerOpenClickListener) - binding.topNavAvatar.setOnClickListener(drawerOpenClickListener) - binding.bottomNavAvatar.setOnClickListener(drawerOpenClickListener) + binding.topNav.setNavigationOnClickListener(drawerOpenClickListener) + binding.bottomNav.setNavigationOnClickListener(drawerOpenClickListener) header = AccountHeaderView(this).apply { headerBackgroundScaleType = ImageView.ScaleType.CENTER_CROP @@ -894,112 +918,75 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val hideTopToolbar = preferences.getBoolean(PrefKeys.HIDE_TOP_TOOLBAR, false) val animateAvatars = preferences.getBoolean("animateGifAvatars", false) - if (hideTopToolbar) { + val activeToolbar = if (hideTopToolbar) { val navOnBottom = preferences.getString("mainNavPosition", "top") == "bottom" - - val avatarView = if (navOnBottom) { - binding.bottomNavAvatar.show() - binding.bottomNavAvatar + if (navOnBottom) { + binding.bottomNav } else { - binding.topNavAvatar.show() - binding.topNavAvatar - } - - if (animateAvatars) { - Glide.with(this) - .load(avatarUrl) - .placeholder(R.drawable.avatar_default) - .into(avatarView) - } else { - Glide.with(this) - .asBitmap() - .load(avatarUrl) - .placeholder(R.drawable.avatar_default) - .into(avatarView) + binding.topNav } } else { - binding.bottomNavAvatar.hide() - binding.topNavAvatar.hide() + binding.mainToolbar + } - val navIconSize = resources.getDimensionPixelSize(R.dimen.avatar_toolbar_nav_icon_size) + val navIconSize = resources.getDimensionPixelSize(R.dimen.avatar_toolbar_nav_icon_size) - if (animateAvatars) { - glide.asDrawable() - .load(avatarUrl) - .transform( - RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp)) - ) - .apply { - if (showPlaceholder) { - placeholder(R.drawable.avatar_default) + if (animateAvatars) { + glide.asDrawable().load(avatarUrl).transform(RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp))) + .apply { + if (showPlaceholder) placeholder(R.drawable.avatar_default) + } + .into(object : CustomTarget(navIconSize, navIconSize) { + + override fun onLoadStarted(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } } - .into(object : CustomTarget(navIconSize, navIconSize) { - override fun onLoadStarted(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } - } + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + if (resource is Animatable) resource.start() + activeToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize) + } - override fun onResourceReady( - resource: Drawable, - transition: Transition? - ) { - if (resource is Animatable) { - resource.start() - } - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(resource, navIconSize, navIconSize) - } - - override fun onLoadCleared(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } - } - }) - } else { - glide.asBitmap() - .load(avatarUrl) - .transform( - RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp)) - ) - .apply { - if (showPlaceholder) { - placeholder(R.drawable.avatar_default) + override fun onLoadCleared(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } } - .into(object : CustomTarget(navIconSize, navIconSize) { - - override fun onLoadStarted(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } + }) + } else { + glide.asBitmap().load(avatarUrl).transform(RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp))) + .apply { + if (showPlaceholder) placeholder(R.drawable.avatar_default) + } + .into(object : CustomTarget(navIconSize, navIconSize) { + override fun onLoadStarted(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } + } - override fun onResourceReady( - resource: Bitmap, - transition: Transition? - ) { - binding.mainToolbar.navigationIcon = FixedSizeDrawable( - BitmapDrawable(resources, resource), - navIconSize, - navIconSize - ) - } + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + activeToolbar.navigationIcon = FixedSizeDrawable( + BitmapDrawable(resources, resource), + navIconSize, + navIconSize + ) + } - override fun onLoadCleared(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } + override fun onLoadCleared(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } - }) - } + } + }) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt index e7c646991..3c943863d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt @@ -130,6 +130,12 @@ class TuskyApplication : Application(), HasAndroidInjector { editor.remove(PrefKeys.MEDIA_PREVIEW_ENABLED) } + if (oldVersion < 2023072401) { + // The notifications filter / clear options are shown on a menu, not a separate bar, + // the preference to display them is not needed. + editor.remove(PrefKeys.Deprecated.SHOW_NOTIFICATIONS_FILTER) + } + editor.putInt(PrefKeys.SCHEMA_VERSION, newVersion) editor.apply() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt index f26a46528..13d11eeb6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt @@ -28,7 +28,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.MenuProvider import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment @@ -46,7 +45,6 @@ import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE import androidx.recyclerview.widget.SimpleItemAnimator import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import at.connyduck.sparkbutton.helpers.Utils -import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior import com.google.android.material.color.MaterialColors import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R @@ -123,21 +121,6 @@ class NotificationsFragment : return inflater.inflate(R.layout.fragment_timeline_notifications, container, false) } - private fun updateFilterVisibility(showFilter: Boolean) { - val params = binding.swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams - if (showFilter) { - binding.appBarOptions.setExpanded(true, false) - binding.appBarOptions.visibility = View.VISIBLE - // Set content behaviour to hide filter on scroll - params.behavior = ScrollingViewBehavior() - } else { - binding.appBarOptions.setExpanded(false, false) - binding.appBarOptions.visibility = View.GONE - // Clear behaviour to hide app bar - params.behavior = null - } - } - private fun confirmClearNotifications() { AlertDialog.Builder(requireContext()) .setMessage(R.string.notification_clear_text) @@ -215,8 +198,6 @@ class NotificationsFragment : footer = NotificationsLoadStateAdapter { adapter.retry() } ) - binding.buttonClear.setOnClickListener { confirmClearNotifications() } - binding.buttonFilter.setOnClickListener { showFilterDialog() } (binding.recyclerView.itemAnimator as SimpleItemAnimator?)!!.supportsChangeAnimations = false @@ -369,10 +350,10 @@ class NotificationsFragment : } } - // Update filter option visibility from uiState - launch { - viewModel.uiState.collectLatest { updateFilterVisibility(it.showFilterOptions) } - } + // Collect the uiState. Nothing is done with it, but if you don't collect it then + // accessing viewModel.uiState.value (e.g., when the filter dialog is created) + // returns an empty object. + launch { viewModel.uiState.collect() } // Update status display from statusDisplayOptions. If the new options request // relative time display collect the flow to periodically update the timestamp in the list gui elements. @@ -439,10 +420,17 @@ class NotificationsFragment : override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.fragment_notifications, menu) + val iconColor = MaterialColors.getColor(binding.root, android.R.attr.textColorPrimary) menu.findItem(R.id.action_refresh)?.apply { icon = IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_refresh).apply { sizeDp = 20 - colorInt = MaterialColors.getColor(binding.root, android.R.attr.textColorPrimary) + colorInt = iconColor + } + } + menu.findItem(R.id.action_edit_notification_filter)?.apply { + icon = IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_tune).apply { + sizeDp = 20 + colorInt = iconColor } } } @@ -458,6 +446,14 @@ class NotificationsFragment : viewModel.accept(InfallibleUiAction.LoadNewest) true } + R.id.action_edit_notification_filter -> { + showFilterDialog() + true + } + R.id.action_clear_notifications -> { + confirmClearNotifications() + true + } else -> false } } @@ -625,7 +621,6 @@ class NotificationsFragment : override fun onReselect() { if (isAdded) { - binding.appBarOptions.setExpanded(true, false) layoutManager.scrollToPosition(0) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt index 1f3f982b9..d06a8bbcf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt @@ -74,23 +74,18 @@ data class UiState( /** Filtered notification types */ val activeFilter: Set = emptySet(), - /** True if the UI to filter and clear notifications should be shown */ - val showFilterOptions: Boolean = false, - /** True if the FAB should be shown while scrolling */ val showFabWhileScrolling: Boolean = true ) /** Preferences the UI reacts to */ data class UiPrefs( - val showFabWhileScrolling: Boolean, - val showFilter: Boolean + val showFabWhileScrolling: Boolean ) { companion object { /** Relevant preference keys. Changes to any of these trigger a display update */ val prefKeys = setOf( - PrefKeys.FAB_HIDE, - PrefKeys.SHOW_NOTIFICATIONS_FILTER + PrefKeys.FAB_HIDE ) } } @@ -495,7 +490,6 @@ class NotificationsViewModel @Inject constructor( uiState = combine(notificationFilter, getUiPrefs()) { filter, prefs -> UiState( activeFilter = filter.filter, - showFilterOptions = prefs.showFilter, showFabWhileScrolling = prefs.showFabWhileScrolling ) }.stateIn( @@ -544,8 +538,7 @@ class NotificationsViewModel @Inject constructor( .onStart { emit(toPrefs()) } private fun toPrefs() = UiPrefs( - showFabWhileScrolling = !preferences.getBoolean(PrefKeys.FAB_HIDE, false), - showFilter = preferences.getBoolean(PrefKeys.SHOW_NOTIFICATIONS_FILTER, true) + showFabWhileScrolling = !preferences.getBoolean(PrefKeys.FAB_HIDE, false) ) companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index e2d29d495..f6541f1fd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -208,13 +208,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { isSingleLineTitle = false } - switchPreference { - setDefaultValue(true) - key = PrefKeys.SHOW_NOTIFICATIONS_FILTER - setTitle(R.string.pref_title_show_notifications_filter) - isSingleLineTitle = false - } - switchPreference { setDefaultValue(true) key = PrefKeys.CONFIRM_REBLOGS diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt index 1a64f69b0..636c1fc69 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -41,7 +41,7 @@ enum class AppTheme(val value: String) { * * - Adding a new preference that does not change the interpretation of an existing preference */ -const val SCHEMA_VERSION = 2023022701 +const val SCHEMA_VERSION = 2023072401 object PrefKeys { // Note: not all of these keys are actually used as SharedPreferences keys but we must give @@ -61,7 +61,6 @@ object PrefKeys { const val ANIMATE_GIF_AVATARS = "animateGifAvatars" const val USE_BLURHASH = "useBlurhash" const val SHOW_SELF_USERNAME = "showSelfUsername" - const val SHOW_NOTIFICATIONS_FILTER = "showNotificationsFilter" const val SHOW_CARDS_IN_TIMELINES = "showCardsInTimelines" const val CONFIRM_REBLOGS = "confirmReblogs" const val CONFIRM_FAVOURITES = "confirmFavourites" @@ -104,4 +103,9 @@ object PrefKeys { /** UI text scaling factor, stored as float, 100 = 100% = no scaling */ const val UI_TEXT_SCALE_RATIO = "uiTextScaleRatio" + + /** Keys that are no longer used (e.g., the preference has been removed */ + object Deprecated { + const val SHOW_NOTIFICATIONS_FILTER = "showNotificationsFilter" + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index dd22e156a..8082e0ce6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -29,22 +29,14 @@ app:layout_scrollFlags="scroll|enterAlways" app:navigationContentDescription="@string/action_open_drawer" /> - - - + android:layout_height="48dp" + android:orientation="horizontal" + app:contentInsetStart="0dp" + app:contentInsetStartWithNavigation="0dp" + app:navigationContentDescription="@string/action_open_drawer"> - - + @@ -73,33 +64,16 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" app:contentInsetStart="0dp" + app:contentInsetStartWithNavigation="0dp" app:fabAlignmentMode="end"> - - - - - - - + android:layout_height="?attr/actionBarSize" + app:tabGravity="fill" + app:tabIndicatorGravity="top" + app:tabMode="fixed" /> @@ -132,4 +106,3 @@ android:fitsSystemWindows="true" /> - diff --git a/app/src/main/res/layout/fragment_timeline_notifications.xml b/app/src/main/res/layout/fragment_timeline_notifications.xml index 39be8fa8b..4d139b155 100644 --- a/app/src/main/res/layout/fragment_timeline_notifications.xml +++ b/app/src/main/res/layout/fragment_timeline_notifications.xml @@ -18,53 +18,10 @@ - - - - -