Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,21 @@
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait"
tools:ignore="LockedOrientationActivity,DiscouragedApi">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="${applicationId}" />
</intent-filter>

<meta-data
android:name="android.app.lib_name"
android:value="" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import com.shopify.checkoutkit.androiddemo.common.navigation.BottomAppBarWithNav
import com.shopify.checkoutkit.androiddemo.common.navigation.CheckoutKitNavHost
import com.shopify.checkoutkit.androiddemo.common.navigation.Screen
import com.shopify.checkoutkit.androiddemo.common.ui.theme.CheckoutKitSampleTheme
import com.shopify.checkoutkit.androiddemo.e2e.E2ENavigationEffect
import com.shopify.checkoutkit.androiddemo.e2e.E2ETestIds
import com.shopify.checkoutkit.androiddemo.logs.LogsViewModel
import com.shopify.checkoutkit.androiddemo.settings.SettingsUiState
Expand Down Expand Up @@ -96,6 +97,9 @@ fun CheckoutKitAppRoot(
.testTag(E2ETestIds.APP_READY),
) {
val navController = rememberNavController()

E2ENavigationEffect(navController)

var currentScreen by remember { mutableStateOf<Screen>(Screen.Product) }
var presentedCheckoutUrl by remember { mutableStateOf<String?>(null) }
val snackbarHostState = remember { SnackbarHostState() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.shopify.checkoutkit.androiddemo

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
Expand All @@ -14,6 +15,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import com.shopify.checkoutkit.androiddemo.e2e.E2EControlLinkHandler
import timber.log.Timber
import timber.log.Timber.DebugTree

Expand Down Expand Up @@ -45,6 +47,8 @@ class MainActivity : ComponentActivity() {
CheckoutKitApp()
}

E2EControlLinkHandler.handle(this, intent)

requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
val fileChooserParams = this.fileChooserParams
if (isGranted && fileChooserParams != null) {
Expand All @@ -67,6 +71,12 @@ class MainActivity : ComponentActivity() {
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)

E2EControlLinkHandler.handle(this, intent)
}

fun onShowFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
this.filePathCallback = filePathCallback
if (permissionAlreadyGranted(Manifest.permission.CAMERA)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
Expand All @@ -49,6 +50,7 @@ import com.shopify.checkoutkit.androiddemo.common.components.MoneyText
import com.shopify.checkoutkit.androiddemo.common.components.ProgressIndicator
import com.shopify.checkoutkit.androiddemo.common.ui.theme.horizontalPadding
import com.shopify.checkoutkit.androiddemo.common.ui.theme.verticalPadding
import com.shopify.checkoutkit.androiddemo.e2e.E2ETestIds
import com.shopify.checkoutkit.androiddemo.settings.data.CheckoutPresentationMode

@Composable
Expand Down Expand Up @@ -242,6 +244,7 @@ private fun CheckoutButton(
Button(
shape = RectangleShape,
onClick = onClick,
modifier = Modifier.testTag(E2ETestIds.Cart.CHECKOUT_BUTTON),
) {
Column {
Text(
Expand All @@ -252,6 +255,7 @@ private fun CheckoutButton(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.testTag(E2ETestIds.Cart.CHECKOUT_READY)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.shopify.checkoutkit.androiddemo.e2e

import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch

object E2EControlLinkHandler {
fun handle(activity: ComponentActivity, intent: Intent) {
val url = intent.data?.toString() ?: return

activity.lifecycleScope.launch {
E2EController(E2ESampleAppTarget()).handle(url)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.shopify.checkoutkit.androiddemo.e2e

interface E2ECommandTarget {
suspend fun selectBuyerIdentityMode(mode: E2EBuyerIdentityMode)

suspend fun resetCart()

suspend fun variantId(atProductIndex: Int): String

suspend fun addCartLine(variantId: String, quantity: Int)

suspend fun showCart()

suspend fun report(failure: String)
}

class E2EController(private val target: E2ECommandTarget) {

suspend fun handle(url: String): Boolean {
val link = try {
E2EControlLink.parse(url)
} catch (error: IllegalArgumentException) {
target.report(message(error))
return true
}

if (link == null) {
return false
}

perform(link)

return true
}

private suspend fun perform(link: E2EControlLink) {
try {
when (link) {
is E2EControlLink.Reset -> target.resetCart()
is E2EControlLink.Cart -> seedCart(link)
is E2EControlLink.SignIn -> throw UnsupportedOperationException("signIn is not implemented yet")
}
} catch (error: Exception) {
target.report(message(error))
}
}

private suspend fun seedCart(command: E2EControlLink.Cart) {
command.buyerIdentityMode?.let { target.selectBuyerIdentityMode(it) }

target.resetCart()

val variantId = command.variantId ?: target.variantId(command.productIndex ?: 0)

target.addCartLine(variantId, command.quantity)
target.showCart()
}

private fun message(error: Throwable) = error.message ?: error.toString()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.shopify.checkoutkit.androiddemo.e2e

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavController
import com.shopify.checkoutkit.androiddemo.common.navigation.Screen
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow

object E2ENavigation {
val destinations = MutableSharedFlow<Screen>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)

suspend fun go(screen: Screen) = destinations.emit(screen)
}

@Composable
fun E2ENavigationEffect(navController: NavController) {
LaunchedEffect(navController) {
E2ENavigation.destinations.collect { screen ->
navController.navigate(screen.route)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.shopify.checkoutkit.androiddemo.e2e

import com.shopify.checkoutkit.androiddemo.cart.CartViewModel
import com.shopify.checkoutkit.androiddemo.common.ID
import com.shopify.checkoutkit.androiddemo.common.navigation.Screen
import com.shopify.checkoutkit.androiddemo.products.product.data.ProductRepository
import com.shopify.checkoutkit.androiddemo.settings.PreferencesManager
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.suspendCancellableCoroutine
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

class E2ESampleAppTarget : E2ECommandTarget, KoinComponent {
private val cartViewModel: CartViewModel by inject()
private val preferencesManager: PreferencesManager by inject()
private val productRepository: ProductRepository by inject()

override suspend fun selectBuyerIdentityMode(mode: E2EBuyerIdentityMode) {
val enabled = mode == E2EBuyerIdentityMode.HARDCODED

preferencesManager.setBuyerIdentityDemoEnabled(enabled)
preferencesManager.userPreferencesFlow.first { it.buyerIdentityDemoEnabled == enabled }
}

override suspend fun resetCart() {
cartViewModel.clearCart()
}

override suspend fun variantId(atProductIndex: Int): String {
val products = productRepository
.getProducts(numProducts = atProductIndex + 1, numVariants = 1, cursor = null)
.products

val variantId = products.getOrNull(atProductIndex)?.variants?.firstOrNull()?.id

return variantId?.id ?: throw IllegalStateException("No product at index $atProductIndex")
}

override suspend fun addCartLine(variantId: String, quantity: Int) {
suspendCancellableCoroutine { continuation ->
cartViewModel.addToCart(ID(variantId), quantity) { result ->
result
.onSuccess { continuation.resume(Unit) }
.onFailure { continuation.resumeWithException(it) }
}
}
}

override suspend fun showCart() {
E2ENavigation.go(Screen.Cart)
}

override suspend fun report(failure: String) {
Timber.e("[E2E] $failure")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ package com.shopify.checkoutkit.androiddemo.e2e

object E2ETestIds {
const val APP_READY = "checkout-kit-sample-ready"

object Cart {
const val CHECKOUT_READY = "cart-checkout-ready"
const val CHECKOUT_BUTTON = "checkout-button"
}
}
Loading
Loading