Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LinkActivity, ViewModel and Navigation #9334

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions link/api/link.api
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ public final class com/stripe/android/link/BuildConfig {
public fun <init> ()V
}

public final class com/stripe/android/link/ComposableSingletons$LinkActivityKt {
public static final field INSTANCE Lcom/stripe/android/link/ComposableSingletons$LinkActivityKt;
public static field lambda-1 Lkotlin/jvm/functions/Function4;
public static field lambda-2 Lkotlin/jvm/functions/Function4;
public static field lambda-3 Lkotlin/jvm/functions/Function4;
public static field lambda-4 Lkotlin/jvm/functions/Function4;
public static field lambda-5 Lkotlin/jvm/functions/Function4;
public fun <init> ()V
public final fun getLambda-1$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-2$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-3$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-4$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-5$link_release ()Lkotlin/jvm/functions/Function4;
}

public final class com/stripe/android/link/LinkActivityContract$Companion {
}

public final class com/stripe/android/link/LinkActivityResult$Canceled$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/link/LinkActivityResult$Canceled;
Expand Down
7 changes: 7 additions & 0 deletions link/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity
android:name="com.stripe.android.link.LinkActivity"
android:autoRemoveFromRecents="true"
android:configChanges="orientation|keyboard|keyboardHidden|screenLayout|screenSize|smallestScreenSize"
android:launchMode="singleTop"
android:theme="@style/StripeTransparentTheme" />

<activity
android:name="com.stripe.android.link.LinkForegroundActivity"
android:autoRemoveFromRecents="true"
Expand Down
5 changes: 5 additions & 0 deletions link/src/main/java/com/stripe/android/link/LinkAction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.stripe.android.link

internal sealed interface LinkAction {
data object BackPressed : LinkAction
}
84 changes: 84 additions & 0 deletions link/src/main/java/com/stripe/android/link/LinkActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.stripe.android.link

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.LaunchedEffect
import androidx.core.os.bundleOf
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.stripe.android.link.ui.cardedit.CardEditScreen
import com.stripe.android.link.ui.paymentmenthod.PaymentMethodScreen
import com.stripe.android.link.ui.signup.SignUpScreen
import com.stripe.android.link.ui.verification.VerificationScreen
import com.stripe.android.link.ui.wallet.WalletScreen

internal class LinkActivity : AppCompatActivity() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will navigation between PaymentSheet and LinkActivity look like? Since this is its own activity with its own navigation, I'm assuming this will be totally separate but I'm curious what the transition will look like

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yh, it'll be separate from PaymentSheet. PaymentSheet will launch this with an activityResultCaller.

@VisibleForTesting
internal var viewModelFactory: ViewModelProvider.Factory = LinkActivityViewModel.Factory()
private val viewModel: LinkActivityViewModel by viewModels { viewModelFactory }

@VisibleForTesting
internal lateinit var navController: NavHostController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
navController = rememberNavController()

LaunchedEffect(Unit) {
viewModel.navController = navController
viewModel.dismissWithResult = ::dismissWithResult
}

NavHost(
navController = navController,
startDestination = LinkScreen.SignUp.route
) {
composable(LinkScreen.SignUp.route) {
SignUpScreen()
}

composable(LinkScreen.Verification.route) {
VerificationScreen()
}

composable(LinkScreen.Wallet.route) {
WalletScreen()
}

composable(LinkScreen.CardEdit.route) {
CardEditScreen()
}

composable(LinkScreen.PaymentMethod.route) {
PaymentMethodScreen()
}
}
}
}

private fun dismissWithResult(result: LinkActivityResult) {
val bundle = bundleOf(
LinkActivityContract.EXTRA_RESULT to LinkActivityContract.Result(result)
)
this@LinkActivity.setResult(
Activity.RESULT_OK,
Intent().putExtras(bundle)
)
this@LinkActivity.finish()
}

override fun onDestroy() {
super.onDestroy()
viewModel.unregisterActivity()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ class LinkActivityContract @Inject internal constructor(
data class Result(
val linkResult: LinkActivityResult
)

companion object {
internal const val EXTRA_RESULT =
"com.stripe.android.link.LinkActivityContract.extra_result"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.stripe.android.link

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavHostController

internal class LinkActivityViewModel : ViewModel() {
var navController: NavHostController? = null
var dismissWithResult: ((LinkActivityResult) -> Unit)? = null

fun handleViewAction(action: LinkAction) {
when (action) {
LinkAction.BackPressed -> handleBackPressed()
}
}

private fun handleBackPressed() {
navController?.let { navController ->
if (!navController.popBackStack()) {
dismissWithResult?.invoke(LinkActivityResult.Canceled())
}
}
}

fun unregisterActivity() {
navController = null
dismissWithResult = null
}

internal class Factory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LinkActivityViewModel() as T
}
}
}
12 changes: 6 additions & 6 deletions link/src/main/java/com/stripe/android/link/LinkScreen.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.stripe.android.link

internal sealed interface LinkScreen {
data object Verification : LinkScreen
data object Wallet : LinkScreen
data object PaymentMethod : LinkScreen
data object CardEdit : LinkScreen
data object SignUp : LinkScreen
internal sealed class LinkScreen(val route: String) {
data object Verification : LinkScreen("verification")
data object Wallet : LinkScreen("wallet")
data object PaymentMethod : LinkScreen("paymentMethod")
data object CardEdit : LinkScreen("cardEdit")
data object SignUp : LinkScreen("signUp")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.stripe.android.link

import androidx.navigation.NavHostController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
internal class LinkActivityViewModelTest {
private val dispatcher = UnconfinedTestDispatcher()
private val vm = LinkActivityViewModel()
private val navController: NavHostController = mock()
private val dismissWithResult: (LinkActivityResult) -> Unit = mock()

@Before
fun setUp() {
Dispatchers.setMain(dispatcher)
vm.dismissWithResult = dismissWithResult
vm.navController = navController
}

@After
fun tearDown() {
Dispatchers.resetMain()
}

@Test
fun `test that cancel result is called on back pressed with empty stack`() = runTest(dispatcher) {
whenever(navController.popBackStack()).thenReturn(false)

vm.handleViewAction(LinkAction.BackPressed)

verify(navController).popBackStack()
verify(dismissWithResult).invoke(LinkActivityResult.Canceled())
}

@Test
fun `test that cancel result is called on back pressed with non-empty stack`() = runTest(dispatcher) {
whenever(navController.popBackStack()).thenReturn(true)

vm.handleViewAction(LinkAction.BackPressed)

verify(navController).popBackStack()
verify(dismissWithResult, times(0)).invoke(LinkActivityResult.Canceled())
}

@Test
fun `test that activity unregister removes dismissWithResult and nav controller`() = runTest(dispatcher) {
vm.unregisterActivity()

assertThat(vm.dismissWithResult).isEqualTo(null)
assertThat(vm.navController).isEqualTo(null)
}
}
Loading