Navigate to the Follow Requests page from notification (#2757)

* Navigate to the Follow Requests page from notification

Fixes #2655

* Fix lock status
This commit is contained in:
Eva Tatarka 2022-11-07 14:04:07 -05:00 committed by GitHub
parent c52ecc24ce
commit b39cb06748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 145 additions and 2 deletions

View File

@ -146,6 +146,7 @@ dependencies {
testImplementation libs.mockwebserver testImplementation libs.mockwebserver
testImplementation libs.androidx.core.testing testImplementation libs.androidx.core.testing
testImplementation libs.kotlinx.coroutines.test testImplementation libs.kotlinx.coroutines.test
testImplementation libs.androidx.work.testing
androidTestImplementation libs.espresso.core androidTestImplementation libs.espresso.core
androidTestImplementation libs.androidx.room.testing androidTestImplementation libs.androidx.room.testing

View File

@ -77,6 +77,7 @@ import com.keylesspalace.tusky.components.search.SearchActivity
import com.keylesspalace.tusky.databinding.ActivityMainBinding import com.keylesspalace.tusky.databinding.ActivityMainBinding
import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.interfaces.AccountSelectionListener import com.keylesspalace.tusky.interfaces.AccountSelectionListener
import com.keylesspalace.tusky.interfaces.ActionButtonActivity import com.keylesspalace.tusky.interfaces.ActionButtonActivity
import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.interfaces.ReselectableFragment
@ -211,10 +212,16 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
val intent = DraftsActivity.newIntent(this) val intent = DraftsActivity.newIntent(this)
startActivity(intent) startActivity(intent)
} else if (accountRequested && savedInstanceState == null) { } else if (accountRequested && savedInstanceState == null) {
// user clicked a notification, show notification tab // 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 showNotificationTab = true
} }
} }
}
window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own
setContentView(binding.root) setContentView(binding.root)

View File

@ -84,6 +84,8 @@ public class NotificationHelper {
*/ */
public static final String ACCOUNT_ID = "account_id"; public static final String ACCOUNT_ID = "account_id";
public static final String TYPE = "type";
private static final String TAG = "NotificationHelper"; private static final String TAG = "NotificationHelper";
public static final String REPLY_ACTION = "REPLY_ACTION"; 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) { private static NotificationCompat.Builder newNotification(Context context, Notification body, AccountEntity account, boolean summary) {
Intent summaryResultIntent = new Intent(context, MainActivity.class); Intent summaryResultIntent = new Intent(context, MainActivity.class);
summaryResultIntent.putExtra(ACCOUNT_ID, account.getId()); summaryResultIntent.putExtra(ACCOUNT_ID, account.getId());
summaryResultIntent.putExtra(TYPE, body.getType().name());
TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context); TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context);
summaryStackBuilder.addParentStack(MainActivity.class); summaryStackBuilder.addParentStack(MainActivity.class);
summaryStackBuilder.addNextIntent(summaryResultIntent); summaryStackBuilder.addNextIntent(summaryResultIntent);
@ -278,6 +281,7 @@ public class NotificationHelper {
// we have to switch account here // we have to switch account here
Intent eventResultIntent = new Intent(context, MainActivity.class); Intent eventResultIntent = new Intent(context, MainActivity.class);
eventResultIntent.putExtra(ACCOUNT_ID, account.getId()); eventResultIntent.putExtra(ACCOUNT_ID, account.getId());
eventResultIntent.putExtra(TYPE, body.getType().name());
TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context); TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context);
eventStackBuilder.addParentStack(MainActivity.class); eventStackBuilder.addParentStack(MainActivity.class);
eventStackBuilder.addNextIntent(eventResultIntent); eventStackBuilder.addNextIntent(eventResultIntent);

View File

@ -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<ViewPager2>(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
}
}

View File

@ -82,6 +82,7 @@ androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefre
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" } androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "androidx-viewpager2" } androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "androidx-viewpager2" }
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "androidx-work" } 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-android-lifecycle = { module = "com.uber.autodispose2:autodispose-androidx-lifecycle", version.ref = "autodispose" }
autodispose-core = { module = "com.uber.autodispose2:autodispose", version.ref = "autodispose" } autodispose-core = { module = "com.uber.autodispose2:autodispose", version.ref = "autodispose" }
bouncycastle = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bouncycastle" } bouncycastle = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bouncycastle" }