From b39cb06748ece6eb123918eb84eb7116bacd6b9c Mon Sep 17 00:00:00 2001 From: Eva Tatarka Date: Mon, 7 Nov 2022 14:04:07 -0500 Subject: [PATCH] Navigate to the Follow Requests page from notification (#2757) * Navigate to the Follow Requests page from notification Fixes #2655 * Fix lock status --- app/build.gradle | 1 + .../com/keylesspalace/tusky/MainActivity.kt | 11 +- .../notifications/NotificationHelper.java | 4 + .../keylesspalace/tusky/MainActivityTest.kt | 130 ++++++++++++++++++ gradle/libs.versions.toml | 1 + 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt diff --git a/app/build.gradle b/app/build.gradle index bc60e6533..12699fb1d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -146,6 +146,7 @@ dependencies { testImplementation libs.mockwebserver testImplementation libs.androidx.core.testing testImplementation libs.kotlinx.coroutines.test + testImplementation libs.androidx.work.testing androidTestImplementation libs.espresso.core androidTestImplementation libs.androidx.room.testing diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 80bcfde33..c00387b5f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -77,6 +77,7 @@ import com.keylesspalace.tusky.components.search.SearchActivity import com.keylesspalace.tusky.databinding.ActivityMainBinding import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.interfaces.AccountSelectionListener import com.keylesspalace.tusky.interfaces.ActionButtonActivity import com.keylesspalace.tusky.interfaces.ReselectableFragment @@ -211,8 +212,14 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val intent = DraftsActivity.newIntent(this) startActivity(intent) } else if (accountRequested && savedInstanceState == null) { - // user clicked a notification, show notification tab - showNotificationTab = true + // 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) { + val intent = AccountListActivity.newIntent(this, AccountListActivity.Type.FOLLOW_REQUESTS, accountLocked = true) + startActivityWithSlideInAnimation(intent) + } else { + showNotificationTab = true + } } } window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own 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 615411384..beac22aad 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 @@ -84,6 +84,8 @@ public class NotificationHelper { */ public static final String ACCOUNT_ID = "account_id"; + public static final String TYPE = "type"; + private static final String TAG = "NotificationHelper"; public static final String REPLY_ACTION = "REPLY_ACTION"; @@ -268,6 +270,7 @@ public class NotificationHelper { private static NotificationCompat.Builder newNotification(Context context, Notification body, AccountEntity account, boolean summary) { Intent summaryResultIntent = new Intent(context, MainActivity.class); summaryResultIntent.putExtra(ACCOUNT_ID, account.getId()); + summaryResultIntent.putExtra(TYPE, body.getType().name()); TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context); summaryStackBuilder.addParentStack(MainActivity.class); summaryStackBuilder.addNextIntent(summaryResultIntent); @@ -278,6 +281,7 @@ public class NotificationHelper { // 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()); TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context); eventStackBuilder.addParentStack(MainActivity.class); eventStackBuilder.addNextIntent(eventResultIntent); diff --git a/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt new file mode 100644 index 000000000..141424555 --- /dev/null +++ b/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt @@ -0,0 +1,130 @@ +package com.keylesspalace.tusky + +import android.app.Activity +import android.app.NotificationManager +import android.content.ComponentName +import android.content.Intent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.viewpager2.widget.ViewPager2 +import androidx.work.testing.WorkManagerTestInitHelper +import at.connyduck.calladapter.networkresult.NetworkResult +import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.components.notifications.NotificationHelper +import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.entity.Notification +import com.keylesspalace.tusky.entity.TimelineAccount +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.robolectric.Robolectric +import org.robolectric.Shadows.shadowOf +import org.robolectric.android.util.concurrent.BackgroundExecutor.runInBackground +import org.robolectric.annotation.Config +import java.util.Date + +@Config(sdk = [28]) +@RunWith(AndroidJUnit4::class) +class MainActivityTest { + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val account = Account( + id = "1", + localUsername = "", + username = "", + displayName = "", + createdAt = Date(), + note = "", + url = "", + avatar = "", + header = "", + ) + private val accountEntity = AccountEntity( + id = 1, + domain = "test.domain", + accessToken = "fakeToken", + clientId = "fakeId", + clientSecret = "fakeSecret", + isActive = true + ) + + @Before + fun setup() { + WorkManagerTestInitHelper.initializeTestWorkManager(context) + } + + @Test + fun `clicking notification of type FOLLOW shows notification tab`() { + val intent = showNotification(Notification.Type.FOLLOW) + + val activity = startMainActivity(intent) + val currentTab = activity.findViewById(R.id.viewPager).currentItem + + val notificationTab = defaultTabs().indexOfFirst { it.id == NOTIFICATIONS } + + assertEquals(currentTab, notificationTab) + } + + @Test + fun `clicking notification of type FOLLOW_REQUEST shows follow requests`() { + val intent = showNotification(Notification.Type.FOLLOW_REQUEST) + + val activity = startMainActivity(intent) + val nextActivity = shadowOf(activity).peekNextStartedActivity() + + assertNotNull(nextActivity) + assertEquals(ComponentName(context, AccountListActivity::class.java.name), nextActivity.component) + assertEquals(AccountListActivity.Type.FOLLOW_REQUESTS, nextActivity.getSerializableExtra("type")) + } + + private fun showNotification(type: Notification.Type): Intent { + val notificationManager = context.getSystemService(NotificationManager::class.java) + val shadowNotificationManager = shadowOf(notificationManager) + + NotificationHelper.createNotificationChannelsForAccount(accountEntity, context) + + runInBackground { + NotificationHelper.make( + context, + Notification( + type = type, + id = "id", + account = TimelineAccount( + id = "1", + localUsername = "connyduck", + username = "connyduck@mastodon.example", + displayName = "Conny Duck", + url = "https://mastodon.example/@ConnyDuck", + avatar = "https://mastodon.example/system/accounts/avatars/000/150/486/original/ab27d7ddd18a10ea.jpg" + ), + status = null + ), + accountEntity, + true + ) + } + + val notification = shadowNotificationManager.allNotifications.first() + return shadowOf(notification.contentIntent).savedIntent + } + + private fun startMainActivity(intent: Intent): Activity { + val controller = Robolectric.buildActivity(MainActivity::class.java, intent) + val activity = controller.get() + activity.eventHub = EventHub() + activity.accountManager = mock { + on { activeAccount } doReturn accountEntity + } + activity.mastodonApi = mock { + onBlocking { accountVerifyCredentials() } doReturn NetworkResult.success(account) + onBlocking { listAnnouncements(false) } doReturn NetworkResult.success(emptyList()) + } + controller.create().start() + return activity + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db6f5cbba..f671e3058 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -82,6 +82,7 @@ androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefre androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" } androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "androidx-viewpager2" } androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "androidx-work" } +androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" } autodispose-android-lifecycle = { module = "com.uber.autodispose2:autodispose-androidx-lifecycle", version.ref = "autodispose" } autodispose-core = { module = "com.uber.autodispose2:autodispose", version.ref = "autodispose" } bouncycastle = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bouncycastle" }